spegel/main.go

239 lines
9.6 KiB
Go
Raw Normal View History

2023-01-24 15:47:27 +01:00
package main
import (
"context"
2023-01-24 16:40:11 +01:00
"errors"
2023-01-24 15:47:27 +01:00
"fmt"
"net"
2023-01-24 16:40:11 +01:00
"net/http"
"net/http/pprof"
2023-01-24 15:47:27 +01:00
"net/url"
"os"
"os/signal"
"syscall"
2023-01-24 16:40:11 +01:00
"time"
2023-01-24 15:47:27 +01:00
2024-05-14 22:34:31 +02:00
"log/slog"
2023-01-24 15:47:27 +01:00
"github.com/alexflint/go-arg"
"github.com/go-logr/logr"
2023-01-24 16:40:11 +01:00
"github.com/prometheus/client_golang/prometheus/promhttp"
2023-01-24 15:47:27 +01:00
"github.com/spf13/afero"
"golang.org/x/sync/errgroup"
"k8s.io/klog/v2"
2023-01-24 15:47:27 +01:00
"github.com/spegel-org/spegel/internal/kubernetes"
"github.com/spegel-org/spegel/pkg/metrics"
"github.com/spegel-org/spegel/pkg/oci"
"github.com/spegel-org/spegel/pkg/registry"
"github.com/spegel-org/spegel/pkg/routing"
"github.com/spegel-org/spegel/pkg/state"
"github.com/spegel-org/spegel/pkg/throttle"
2023-01-24 15:47:27 +01:00
)
type ConfigurationCmd struct {
2024-03-21 15:10:43 +00:00
ContainerdRegistryConfigPath string `arg:"--containerd-registry-config-path,env:CONTAINERD_REGISTRY_CONFIG_PATH" default:"/etc/containerd/certs.d" help:"Directory where mirror configuration is written."`
Registries []url.URL `arg:"--registries,required,env:REGISTRIES" help:"registries that are configured to be mirrored."`
MirrorRegistries []url.URL `arg:"--mirror-registries,env:MIRROR_REGISTRIES,required" help:"registries that are configured to act as mirrors."`
ResolveTags bool `arg:"--resolve-tags,env:RESOLVE_TAGS" default:"true" help:"When true Spegel will resolve tags to digests."`
AppendMirrors bool `arg:"--append-mirrors,env:APPEND_MIRRORS" default:"false" help:"When true existing mirror configuration will be appended to instead of replaced."`
}
2024-02-01 11:43:43 +01:00
type BootstrapConfig struct {
2024-03-21 15:10:43 +00:00
BootstrapKind string `arg:"--bootstrap-kind,env:BOOTSTRAP_KIND" help:"Kind of bootsrapper to use."`
2024-04-27 15:19:26 -04:00
HTTPBootstrapAddr string `arg:"--http-bootstrap-addr,env:HTTP_BOOTSTRAP_ADDR" help:"Address to serve for HTTP bootstrap."`
2024-04-03 21:16:39 +02:00
HTTPBootstrapPeer string `arg:"--http-bootstrap-peer,env:HTTP_BOOTSTRAP_PEER" help:"Peer to HTTP bootstrap with."`
2024-03-21 15:10:43 +00:00
KubeconfigPath string `arg:"--kubeconfig-path,env:KUBECONFIG_PATH" help:"Path to the kubeconfig file."`
LeaderElectionName string `arg:"--leader-election-name,env:LEADER_ELECTION_NAME" default:"spegel-leader-election" help:"Name of leader election."`
LeaderElectionNamespace string `arg:"--leader-election-namespace,env:LEADER_ELECTION_NAMESPACE" default:"spegel" help:"Kubernetes namespace to write leader election data."`
2024-02-01 11:43:43 +01:00
}
type RegistryCmd struct {
2024-02-01 11:43:43 +01:00
BootstrapConfig
2024-03-21 15:10:43 +00:00
BlobSpeed *throttle.Byterate `arg:"--blob-speed,env:BLOB_SPEED" help:"Maximum write speed per request when serving blob layers. Should be an integer followed by unit Bps, KBps, MBps, GBps, or TBps."`
ContainerdRegistryConfigPath string `arg:"--containerd-registry-config-path,env:CONTAINERD_REGISTRY_CONFIG_PATH" default:"/etc/containerd/certs.d" help:"Directory where mirror configuration is written."`
MetricsAddr string `arg:"--metrics-addr,required,env:METRICS_ADDR" help:"address to serve metrics."`
LocalAddr string `arg:"--local-addr,required,env:LOCAL_ADDR" help:"Address that the local Spegel instance will be reached at."`
ContainerdSock string `arg:"--containerd-sock,env:CONTAINERD_SOCK" default:"/run/containerd/containerd.sock" help:"Endpoint of containerd service."`
ContainerdNamespace string `arg:"--containerd-namespace,env:CONTAINERD_NAMESPACE" default:"k8s.io" help:"Containerd namespace to fetch images from."`
ContainerdContentPath string `arg:"--containerd-content-path,env:CONTAINERD_CONTENT_PATH" default:"/var/lib/containerd/io.containerd.content.v1.content" help:"Path to Containerd content store"`
2024-03-21 15:10:43 +00:00
RouterAddr string `arg:"--router-addr,env:ROUTER_ADDR,required" help:"address to serve router."`
RegistryAddr string `arg:"--registry-addr,env:REGISTRY_ADDR,required" help:"address to server image registry."`
Registries []url.URL `arg:"--registries,env:REGISTRIES,required" help:"registries that are configured to be mirrored."`
MirrorResolveTimeout time.Duration `arg:"--mirror-resolve-timeout,env:MIRROR_RESOLVE_TIMEOUT" default:"5s" help:"Max duration spent finding a mirror."`
MirrorResolveRetries int `arg:"--mirror-resolve-retries,env:MIRROR_RESOLVE_RETRIES" default:"3" help:"Max amount of mirrors to attempt."`
ResolveLatestTag bool `arg:"--resolve-latest-tag,env:RESOLVE_LATEST_TAG" default:"true" help:"When true latest tags will be resolved to digests."`
}
2023-03-17 13:41:43 +01:00
type Arguments struct {
Configuration *ConfigurationCmd `arg:"subcommand:configuration"`
Registry *RegistryCmd `arg:"subcommand:registry"`
2024-04-20 11:23:49 +02:00
LogLevel slog.Level `arg:"--log-level,env:LOG_LEVEL" default:"INFO" help:"Minimum log level to output. Value should be DEBUG, INFO, WARN, or ERROR."`
2023-01-24 15:47:27 +01:00
}
func main() {
2023-03-17 13:41:43 +01:00
args := &Arguments{}
2023-01-24 15:47:27 +01:00
arg.MustParse(args)
2024-04-20 11:23:49 +02:00
opts := slog.HandlerOptions{
AddSource: true,
Level: args.LogLevel,
2023-01-24 15:47:27 +01:00
}
2024-04-20 11:23:49 +02:00
handler := slog.NewJSONHandler(os.Stderr, &opts)
log := logr.FromSlogHandler(handler)
klog.SetLogger(log)
ctx := logr.NewContext(context.Background(), log)
2023-01-24 15:47:27 +01:00
2024-04-20 11:23:49 +02:00
err := run(ctx, args)
2023-01-24 15:47:27 +01:00
if err != nil {
2024-04-20 11:23:49 +02:00
log.Error(err, "run exit with error")
2023-01-24 15:47:27 +01:00
os.Exit(1)
}
2023-01-31 23:17:06 +01:00
log.Info("gracefully shutdown")
}
2023-01-24 15:47:27 +01:00
2023-03-17 13:41:43 +01:00
func run(ctx context.Context, args *Arguments) error {
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGTERM)
defer cancel()
switch {
case args.Configuration != nil:
return configurationCommand(ctx, args.Configuration)
case args.Registry != nil:
return registryCommand(ctx, args.Registry)
default:
return fmt.Errorf("unknown subcommand")
}
}
2023-03-17 13:41:43 +01:00
func configurationCommand(ctx context.Context, args *ConfigurationCmd) error {
fs := afero.NewOsFs()
err := oci.AddMirrorConfiguration(ctx, fs, args.ContainerdRegistryConfigPath, args.Registries, args.MirrorRegistries, args.ResolveTags, args.AppendMirrors)
if err != nil {
return err
}
return nil
}
2023-03-17 13:41:43 +01:00
func registryCommand(ctx context.Context, args *RegistryCmd) (err error) {
log := logr.FromContextOrDiscard(ctx)
g, ctx := errgroup.WithContext(ctx)
2024-02-01 11:43:43 +01:00
// OCI Client
ociClient, err := oci.NewContainerd(args.ContainerdSock, args.ContainerdNamespace, args.ContainerdRegistryConfigPath, args.Registries, oci.WithContentPath(args.ContainerdContentPath))
if err != nil {
return err
}
err = ociClient.Verify(ctx)
2023-01-31 23:17:06 +01:00
if err != nil {
2023-05-16 09:10:41 +02:00
return err
2023-01-26 18:48:02 +01:00
}
2023-01-31 23:17:06 +01:00
2024-01-19 18:25:48 +01:00
// Metrics
metrics.Register()
2023-01-24 16:40:11 +01:00
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.HandlerFor(metrics.DefaultGatherer, promhttp.HandlerOpts{}))
mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
mux.Handle("/debug/pprof/block", pprof.Handler("block"))
mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))
metricsSrv := &http.Server{
2023-03-17 13:41:43 +01:00
Addr: args.MetricsAddr,
2023-01-24 16:40:11 +01:00
Handler: mux,
}
g.Go(func() error {
if err := metricsSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
2023-01-24 16:40:11 +01:00
return err
}
return nil
})
g.Go(func() error {
<-ctx.Done()
2023-01-25 23:52:50 +01:00
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
2023-01-24 16:40:11 +01:00
defer cancel()
return metricsSrv.Shutdown(shutdownCtx)
2023-01-24 16:40:11 +01:00
})
2024-01-19 18:25:48 +01:00
// Router
_, registryPort, err := net.SplitHostPort(args.RegistryAddr)
if err != nil {
return err
}
2024-02-01 11:43:43 +01:00
bootstrapper, err := getBootstrapper(args.BootstrapConfig)
if err != nil {
return err
}
router, err := routing.NewP2PRouter(ctx, args.RouterAddr, bootstrapper, registryPort)
2023-01-24 15:47:27 +01:00
if err != nil {
2023-01-31 23:17:06 +01:00
return err
2023-01-24 15:47:27 +01:00
}
2024-01-19 18:25:48 +01:00
g.Go(func() error {
return router.Run(ctx)
})
2023-01-25 19:08:33 +01:00
g.Go(func() error {
<-ctx.Done()
2023-01-26 18:48:02 +01:00
return router.Close()
2023-01-25 19:08:33 +01:00
})
2024-01-19 18:25:48 +01:00
// State tracking
2023-01-24 15:47:27 +01:00
g.Go(func() error {
2024-02-29 11:41:53 +01:00
err := state.Track(ctx, ociClient, router, args.ResolveLatestTag)
if err != nil {
return err
}
return nil
2023-01-24 15:47:27 +01:00
})
2024-01-19 18:25:48 +01:00
// Registry
registryOpts := []registry.Option{
registry.WithResolveLatestTag(args.ResolveLatestTag),
registry.WithResolveRetries(args.MirrorResolveRetries),
registry.WithResolveTimeout(args.MirrorResolveTimeout),
registry.WithLocalAddress(args.LocalAddr),
2024-03-15 10:21:00 +01:00
registry.WithLogger(log),
}
2024-02-20 09:46:33 +01:00
if args.BlobSpeed != nil {
registryOpts = append(registryOpts, registry.WithBlobSpeed(*args.BlobSpeed))
}
reg := registry.NewRegistry(ociClient, router, registryOpts...)
2024-03-15 10:21:00 +01:00
regSrv := reg.Server(args.RegistryAddr)
2023-01-24 15:47:27 +01:00
g.Go(func() error {
if err := regSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
2023-01-24 15:47:27 +01:00
})
g.Go(func() error {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return regSrv.Shutdown(shutdownCtx)
2023-01-24 15:47:27 +01:00
})
2024-01-19 18:25:48 +01:00
log.Info("running Spegel", "registry", args.RegistryAddr, "router", args.RouterAddr)
2023-01-24 15:47:27 +01:00
err = g.Wait()
if err != nil {
2023-01-31 23:17:06 +01:00
return err
2023-01-24 15:47:27 +01:00
}
2023-01-31 23:17:06 +01:00
return nil
2023-01-24 15:47:27 +01:00
}
2024-02-01 11:43:43 +01:00
2024-05-14 22:52:54 +02:00
func getBootstrapper(cfg BootstrapConfig) (routing.Bootstrapper, error) { //nolint: ireturn // Return type can be different structs.
2024-02-01 11:43:43 +01:00
switch cfg.BootstrapKind {
case "http":
return routing.NewHTTPBootstrapper(cfg.HTTPBootstrapAddr, cfg.HTTPBootstrapPeer), nil
case "kubernetes":
cs, err := kubernetes.GetClientset(cfg.KubeconfigPath)
2024-02-01 11:43:43 +01:00
if err != nil {
return nil, err
}
return routing.NewKubernetesBootstrapper(cs, cfg.LeaderElectionNamespace, cfg.LeaderElectionName), nil
default:
return nil, fmt.Errorf("unknown bootstrap kind %s", cfg.BootstrapKind)
}
}