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"
2023-06-27 17:53:47 +02:00
"net"
2023-01-24 16:40:11 +01:00
"net/http"
2023-01-24 15:47:27 +01:00
"net/url"
"os"
"os/signal"
2023-01-24 16:56:17 +01:00
"syscall"
2023-01-24 16:40:11 +01:00
"time"
2023-01-24 15:47:27 +01:00
"github.com/alexflint/go-arg"
"github.com/go-logr/logr"
"github.com/go-logr/zapr"
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"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
2023-10-27 16:50:48 +02:00
"k8s.io/klog/v2"
2023-01-24 15:47:27 +01:00
2024-04-10 11:03:47 +02:00
"github.com/spegel-org/spegel/internal/kubernetes"
2024-04-01 18:59:26 +02:00
"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
)
2023-03-15 23:10:18 +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." `
2024-04-08 12:09:30 +02:00
AppendMirrors bool ` arg:"--append-mirrors,env:APPEND_MIRRORS" default:"false" help:"When true existing mirror configuration will be appended to instead of replaced." `
2023-03-15 23:10:18 +01:00
}
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." `
HTTPBootstrapAddr string ` arg:"--http-bootstrap-addr,env:HTTP_BOOTSTRAP_KIND" 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
}
2023-03-15 23:10:18 +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." `
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-15 23:10:18 +01:00
}
2023-03-17 13:41:43 +01:00
type Arguments struct {
2023-03-15 23:10:18 +01:00
Configuration * ConfigurationCmd ` arg:"subcommand:configuration" `
Registry * RegistryCmd ` arg:"subcommand:registry" `
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 )
zapLog , err := zap . NewProduction ( )
if err != nil {
panic ( fmt . Sprintf ( "who watches the watchmen (%v)?" , err ) )
}
log := zapr . NewLogger ( zapLog )
2023-10-27 16:50:48 +02:00
klog . SetLogger ( log )
2023-03-15 23:10:18 +01:00
ctx := logr . NewContext ( context . Background ( ) , log )
2023-01-24 15:47:27 +01:00
2023-03-15 23:10:18 +01:00
err = run ( ctx , args )
2023-01-24 15:47:27 +01:00
if err != nil {
2023-01-31 23:17:06 +01:00
log . Error ( err , "" )
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 {
2023-03-15 23:10:18 +01:00
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 {
2023-03-15 23:10:18 +01:00
fs := afero . NewOsFs ( )
2024-04-08 12:09:30 +02:00
err := oci . AddMirrorConfiguration ( ctx , fs , args . ContainerdRegistryConfigPath , args . Registries , args . MirrorRegistries , args . ResolveTags , args . AppendMirrors )
2023-03-15 23:10:18 +01:00
if err != nil {
return err
}
return nil
}
2023-03-17 13:41:43 +01:00
func registryCommand ( ctx context . Context , args * RegistryCmd ) ( err error ) {
2023-03-15 23:10:18 +01:00
log := logr . FromContextOrDiscard ( ctx )
g , ctx := errgroup . WithContext ( ctx )
2024-02-01 11:43:43 +01:00
// OCI Client
2023-07-31 22:33:56 +02:00
ociClient , err := oci . NewContainerd ( args . ContainerdSock , args . ContainerdNamespace , args . ContainerdRegistryConfigPath , args . Registries )
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
2024-01-18 11:33:11 +01:00
metrics . Register ( )
2023-01-24 16:40:11 +01:00
mux := http . NewServeMux ( )
2024-01-18 11:33:11 +01:00
mux . Handle ( "/metrics" , promhttp . HandlerFor ( metrics . DefaultGatherer , promhttp . HandlerOpts { } ) )
2023-06-28 21:52:39 +02:00
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 {
2023-06-28 21:52:39 +02:00
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 ( )
2023-06-28 21:52:39 +02:00
return metricsSrv . Shutdown ( shutdownCtx )
2023-01-24 16:40:11 +01:00
} )
2024-01-19 18:25:48 +01:00
// Router
2023-06-27 17:53:47 +02:00
_ , 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
}
2023-06-27 17:53:47 +02:00
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
}
2023-08-17 21:51:35 +02:00
return nil
2023-01-24 15:47:27 +01:00
} )
2024-01-19 18:25:48 +01:00
// Registry
2024-02-05 14:32:56 +01:00
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-05 14:32:56 +01:00
}
2024-02-20 09:46:33 +01:00
if args . BlobSpeed != nil {
registryOpts = append ( registryOpts , registry . WithBlobSpeed ( * args . BlobSpeed ) )
}
2024-02-05 14:32:56 +01:00
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 {
2023-06-28 21:52:39 +02:00
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 ( )
2023-06-28 21:52:39 +02:00
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
func getBootstrapper ( cfg BootstrapConfig ) ( routing . Bootstrapper , error ) {
switch cfg . BootstrapKind {
case "http" :
return routing . NewHTTPBootstrapper ( cfg . HTTPBootstrapAddr , cfg . HTTPBootstrapPeer ) , nil
case "kubernetes" :
2024-04-10 11:03:47 +02:00
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 )
}
}