Commit d6355d7b authored by Brad Davidson's avatar Brad Davidson Committed by Brad Davidson

Add embedded registry implementation

Signed-off-by: 's avatarBrad Davidson <brad.davidson@rancher.com> (cherry picked from commit 37e9b87f) Signed-off-by: 's avatarBrad Davidson <brad.davidson@rancher.com>
parent d15bd074
...@@ -16,6 +16,7 @@ import ( ...@@ -16,6 +16,7 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
...@@ -26,6 +27,7 @@ import ( ...@@ -26,6 +27,7 @@ import (
"github.com/k3s-io/k3s/pkg/clientaccess" "github.com/k3s-io/k3s/pkg/clientaccess"
"github.com/k3s-io/k3s/pkg/daemons/config" "github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/daemons/control/deps" "github.com/k3s-io/k3s/pkg/daemons/control/deps"
"github.com/k3s-io/k3s/pkg/spegel"
"github.com/k3s-io/k3s/pkg/util" "github.com/k3s-io/k3s/pkg/util"
"github.com/k3s-io/k3s/pkg/version" "github.com/k3s-io/k3s/pkg/version"
"github.com/k3s-io/k3s/pkg/vpn" "github.com/k3s-io/k3s/pkg/vpn"
...@@ -679,6 +681,29 @@ func get(ctx context.Context, envInfo *cmds.Agent, proxy proxy.Proxy) (*config.N ...@@ -679,6 +681,29 @@ func get(ctx context.Context, envInfo *cmds.Agent, proxy proxy.Proxy) (*config.N
} }
nodeConfig.AgentConfig.Registry = privRegistries.Registry nodeConfig.AgentConfig.Registry = privRegistries.Registry
if nodeConfig.EmbeddedRegistry {
psk, err := hex.DecodeString(controlConfig.IPSECPSK)
if err != nil {
return nil, err
}
if len(psk) < 32 {
return nil, errors.New("insufficient PSK bytes")
}
conf := spegel.DefaultRegistry
conf.ExternalAddress = nodeConfig.AgentConfig.NodeIP
conf.InternalAddress = controlConfig.Loopback(false)
conf.RegistryPort = strconv.Itoa(controlConfig.SupervisorPort)
conf.ClientCAFile = clientCAFile
conf.ClientCertFile = clientK3sControllerCert
conf.ClientKeyFile = clientK3sControllerKey
conf.ServerCAFile = serverCAFile
conf.ServerCertFile = servingKubeletCert
conf.ServerKeyFile = servingKubeletKey
conf.PSK = psk[:32]
conf.InjectMirror(nodeConfig)
}
if err := validateNetworkConfig(nodeConfig); err != nil { if err := validateNetworkConfig(nodeConfig); err != nil {
return nil, err return nil, err
} }
......
package containerd package containerd
import ( import (
"net"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
...@@ -10,6 +11,7 @@ import ( ...@@ -10,6 +11,7 @@ import (
"github.com/k3s-io/k3s/pkg/agent/templates" "github.com/k3s-io/k3s/pkg/agent/templates"
util2 "github.com/k3s-io/k3s/pkg/agent/util" util2 "github.com/k3s-io/k3s/pkg/agent/util"
"github.com/k3s-io/k3s/pkg/daemons/config" "github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/spegel"
"github.com/k3s-io/k3s/pkg/version" "github.com/k3s-io/k3s/pkg/version"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
...@@ -36,6 +38,7 @@ func writeContainerdConfig(cfg *config.Node, containerdConfig templates.Containe ...@@ -36,6 +38,7 @@ func writeContainerdConfig(cfg *config.Node, containerdConfig templates.Containe
// writeContainerdHosts merges registry mirrors/configs, and renders and saves hosts.toml from the filled template // writeContainerdHosts merges registry mirrors/configs, and renders and saves hosts.toml from the filled template
func writeContainerdHosts(cfg *config.Node, containerdConfig templates.ContainerdConfig) error { func writeContainerdHosts(cfg *config.Node, containerdConfig templates.ContainerdConfig) error {
mirrorAddr := net.JoinHostPort(spegel.DefaultRegistry.InternalAddress, spegel.DefaultRegistry.RegistryPort)
registry := containerdConfig.PrivateRegistryConfig registry := containerdConfig.PrivateRegistryConfig
hosts := map[string]templates.HostConfig{} hosts := map[string]templates.HostConfig{}
...@@ -57,12 +60,17 @@ func writeContainerdHosts(cfg *config.Node, containerdConfig templates.Container ...@@ -57,12 +60,17 @@ func writeContainerdHosts(cfg *config.Node, containerdConfig templates.Container
// structure, which is defined in rancher/wharfie. // structure, which is defined in rancher/wharfie.
for _, endpoint := range mirror.Endpoints { for _, endpoint := range mirror.Endpoints {
if endpointURL, err := url.Parse(endpoint); err == nil { if endpointURL, err := url.Parse(endpoint); err == nil {
config.Endpoints = append(config.Endpoints, templates.RegistryEndpoint{ re := templates.RegistryEndpoint{
OverridePath: endpointURL.Path != "" && endpointURL.Path != "/" && !strings.HasSuffix(endpointURL.Path, "/v2"), OverridePath: endpointURL.Path != "" && endpointURL.Path != "/" && !strings.HasSuffix(endpointURL.Path, "/v2"),
Config: registry.Configs[endpointURL.Host], Config: registry.Configs[endpointURL.Host],
Rewrites: mirror.Rewrites, Rewrites: mirror.Rewrites,
URI: endpoint, URI: endpoint,
}) }
// Do not apply rewrites to the embedded registry endpoint
if endpointURL.Host == mirrorAddr {
re.Rewrites = nil
}
config.Endpoints = append(config.Endpoints, re)
} }
} }
hosts[host] = config hosts[host] = config
......
...@@ -30,6 +30,7 @@ import ( ...@@ -30,6 +30,7 @@ import (
"github.com/k3s-io/k3s/pkg/daemons/executor" "github.com/k3s-io/k3s/pkg/daemons/executor"
"github.com/k3s-io/k3s/pkg/nodeconfig" "github.com/k3s-io/k3s/pkg/nodeconfig"
"github.com/k3s-io/k3s/pkg/rootless" "github.com/k3s-io/k3s/pkg/rootless"
"github.com/k3s-io/k3s/pkg/spegel"
"github.com/k3s-io/k3s/pkg/util" "github.com/k3s-io/k3s/pkg/util"
"github.com/k3s-io/k3s/pkg/version" "github.com/k3s-io/k3s/pkg/version"
"github.com/pkg/errors" "github.com/pkg/errors"
...@@ -102,6 +103,16 @@ func run(ctx context.Context, cfg cmds.Agent, proxy proxy.Proxy) error { ...@@ -102,6 +103,16 @@ func run(ctx context.Context, cfg cmds.Agent, proxy proxy.Proxy) error {
nodeConfig.AgentConfig.EnableIPv4 = enableIPv4 nodeConfig.AgentConfig.EnableIPv4 = enableIPv4
nodeConfig.AgentConfig.EnableIPv6 = enableIPv6 nodeConfig.AgentConfig.EnableIPv6 = enableIPv6
if nodeConfig.EmbeddedRegistry {
if nodeConfig.Docker || nodeConfig.ContainerRuntimeEndpoint != "" {
return errors.New("embedded registry mirror requires embedded containerd")
}
if err := spegel.DefaultRegistry.Start(ctx, nodeConfig); err != nil {
return errors.Wrap(err, "failed to start embedded registry")
}
}
if err := setupCriCtlConfig(cfg, nodeConfig); err != nil { if err := setupCriCtlConfig(cfg, nodeConfig); err != nil {
return err return err
} }
......
...@@ -2,21 +2,27 @@ package agent ...@@ -2,21 +2,27 @@ package agent
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"github.com/erikdubbelboer/gspt" "github.com/erikdubbelboer/gspt"
"github.com/gorilla/mux"
"github.com/k3s-io/k3s/pkg/agent" "github.com/k3s-io/k3s/pkg/agent"
"github.com/k3s-io/k3s/pkg/authenticator"
"github.com/k3s-io/k3s/pkg/cli/cmds" "github.com/k3s-io/k3s/pkg/cli/cmds"
"github.com/k3s-io/k3s/pkg/datadir" "github.com/k3s-io/k3s/pkg/datadir"
"github.com/k3s-io/k3s/pkg/spegel"
"github.com/k3s-io/k3s/pkg/util" "github.com/k3s-io/k3s/pkg/util"
"github.com/k3s-io/k3s/pkg/version" "github.com/k3s-io/k3s/pkg/version"
"github.com/k3s-io/k3s/pkg/vpn" "github.com/k3s-io/k3s/pkg/vpn"
"github.com/rancher/wrangler/pkg/signals" "github.com/rancher/wrangler/pkg/signals"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
apiauth "k8s.io/apiserver/pkg/authentication/authenticator"
) )
func Run(ctx *cli.Context) error { func Run(ctx *cli.Context) error {
...@@ -98,5 +104,37 @@ func Run(ctx *cli.Context) error { ...@@ -98,5 +104,37 @@ func Run(ctx *cli.Context) error {
} }
} }
// Until the agent is run and retrieves config from the server, we won't know
// if the embedded registry is enabled. If it is not enabled, these are not
// used as the registry is never started.
conf := spegel.DefaultRegistry
conf.Bootstrapper = spegel.NewAgentBootstrapper(cfg.ServerURL, cfg.Token, cfg.DataDir)
conf.HandlerFunc = func(conf *spegel.Config, router *mux.Router) error {
// Create and bind a new authenticator using the configured client CA
authArgs := []string{"--client-ca-file=" + conf.ClientCAFile}
auth, err := authenticator.FromArgs(authArgs)
if err != nil {
return err
}
conf.AuthFunc = func() apiauth.Request {
return auth
}
// Create a new server and listen on the configured port
server := &http.Server{
Handler: router,
Addr: ":" + conf.RegistryPort,
TLSConfig: &tls.Config{
ClientAuth: tls.RequestClientCert,
},
}
go func() {
if err := server.ListenAndServeTLS(conf.ServerCertFile, conf.ServerKeyFile); err != nil && !errors.Is(err, http.ErrServerClosed) {
logrus.Fatalf("registry server failed: %v", err)
}
}()
return nil
}
return agent.Run(contextCtx, cfg) return agent.Run(contextCtx, cfg)
} }
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
systemd "github.com/coreos/go-systemd/daemon" systemd "github.com/coreos/go-systemd/daemon"
"github.com/erikdubbelboer/gspt" "github.com/erikdubbelboer/gspt"
"github.com/gorilla/mux"
"github.com/k3s-io/k3s/pkg/agent" "github.com/k3s-io/k3s/pkg/agent"
"github.com/k3s-io/k3s/pkg/agent/loadbalancer" "github.com/k3s-io/k3s/pkg/agent/loadbalancer"
"github.com/k3s-io/k3s/pkg/cli/cmds" "github.com/k3s-io/k3s/pkg/cli/cmds"
...@@ -20,6 +21,7 @@ import ( ...@@ -20,6 +21,7 @@ import (
"github.com/k3s-io/k3s/pkg/etcd" "github.com/k3s-io/k3s/pkg/etcd"
"github.com/k3s-io/k3s/pkg/rootless" "github.com/k3s-io/k3s/pkg/rootless"
"github.com/k3s-io/k3s/pkg/server" "github.com/k3s-io/k3s/pkg/server"
"github.com/k3s-io/k3s/pkg/spegel"
"github.com/k3s-io/k3s/pkg/util" "github.com/k3s-io/k3s/pkg/util"
"github.com/k3s-io/k3s/pkg/version" "github.com/k3s-io/k3s/pkg/version"
"github.com/k3s-io/k3s/pkg/vpn" "github.com/k3s-io/k3s/pkg/vpn"
...@@ -28,6 +30,7 @@ import ( ...@@ -28,6 +30,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
utilnet "k8s.io/apimachinery/pkg/util/net" utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apiserver/pkg/authentication/authenticator"
kubeapiserverflag "k8s.io/component-base/cli/flag" kubeapiserverflag "k8s.io/component-base/cli/flag"
"k8s.io/kubernetes/pkg/controlplane" "k8s.io/kubernetes/pkg/controlplane"
utilsnet "k8s.io/utils/net" utilsnet "k8s.io/utils/net"
...@@ -548,6 +551,23 @@ func run(app *cli.Context, cfg *cmds.Server, leaderControllers server.CustomCont ...@@ -548,6 +551,23 @@ func run(app *cli.Context, cfg *cmds.Server, leaderControllers server.CustomCont
return agent.RunStandalone(ctx, agentConfig) return agent.RunStandalone(ctx, agentConfig)
} }
if cfg.EmbeddedRegistry {
conf := spegel.DefaultRegistry
conf.Bootstrapper = spegel.NewChainingBootstrapper(
spegel.NewServerBootstrapper(&serverConfig.ControlConfig),
spegel.NewAgentBootstrapper(cfg.ServerURL, token, agentConfig.DataDir),
spegel.NewSelfBootstrapper(),
)
conf.HandlerFunc = func(_ *spegel.Config, router *mux.Router) error {
router.NotFoundHandler = serverConfig.ControlConfig.Runtime.Handler
serverConfig.ControlConfig.Runtime.Handler = router
return nil
}
conf.AuthFunc = func() authenticator.Request {
return serverConfig.ControlConfig.Runtime.Authenticator
}
}
return agent.Run(ctx, agentConfig) return agent.Run(ctx, agentConfig)
} }
......
...@@ -152,6 +152,7 @@ type CriticalControlArgs struct { ...@@ -152,6 +152,7 @@ type CriticalControlArgs struct {
DisableServiceLB bool `cli:"disable-service-lb"` DisableServiceLB bool `cli:"disable-service-lb"`
EncryptSecrets bool `cli:"secrets-encryption"` EncryptSecrets bool `cli:"secrets-encryption"`
MultiClusterCIDR bool `cli:"multi-cluster-cidr"` MultiClusterCIDR bool `cli:"multi-cluster-cidr"`
EmbeddedRegistry bool `cli:"embedded-registry"`
FlannelBackend string `cli:"flannel-backend"` FlannelBackend string `cli:"flannel-backend"`
FlannelIPv6Masq bool `cli:"flannel-ipv6-masq"` FlannelIPv6Masq bool `cli:"flannel-ipv6-masq"`
FlannelExternalIP bool `cli:"flannel-external-ip"` FlannelExternalIP bool `cli:"flannel-external-ip"`
...@@ -186,7 +187,6 @@ type Control struct { ...@@ -186,7 +187,6 @@ type Control struct {
DisableKubeProxy bool DisableKubeProxy bool
DisableScheduler bool DisableScheduler bool
DisableServiceLB bool DisableServiceLB bool
EmbeddedRegistry bool
Rootless bool Rootless bool
ServiceLBNamespace string ServiceLBNamespace string
EnablePProf bool EnablePProf bool
......
package spegel
import (
"context"
"math/rand"
"os"
"path/filepath"
"strings"
"time"
"github.com/k3s-io/k3s/pkg/clientaccess"
"github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/version"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/pkg/errors"
"github.com/rancher/wrangler/pkg/merr"
"github.com/sirupsen/logrus"
"github.com/xenitab/spegel/pkg/routing"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/wait"
nodeutil "k8s.io/kubernetes/pkg/controller/util/node"
)
// explicit interface checks
var _ routing.Bootstrapper = &selfBootstrapper{}
var _ routing.Bootstrapper = &agentBootstrapper{}
var _ routing.Bootstrapper = &serverBootstrapper{}
var _ routing.Bootstrapper = &chainingBootstrapper{}
type selfBootstrapper struct {
id string
}
// NewSelfBootstrapper returns a stub p2p bootstrapper that just returns its own ID
func NewSelfBootstrapper() routing.Bootstrapper {
return &selfBootstrapper{}
}
func (s *selfBootstrapper) Run(_ context.Context, id string) error {
s.id = id
return nil
}
func (s *selfBootstrapper) GetAddress() (*peer.AddrInfo, error) {
return peer.AddrInfoFromString(s.id)
}
type agentBootstrapper struct {
server string
token string
clientCert string
clientKey string
}
// NewAgentBootstrapper returns a p2p bootstrapper that retrieves a peer address from its server
func NewAgentBootstrapper(server, token, dataDir string) routing.Bootstrapper {
return &agentBootstrapper{
clientCert: filepath.Join(dataDir, "agent", "client-kubelet.crt"),
clientKey: filepath.Join(dataDir, "agent", "client-kubelet.key"),
server: server,
token: token,
}
}
func (c *agentBootstrapper) Run(_ context.Context, _ string) error {
return nil
}
func (c *agentBootstrapper) GetAddress() (*peer.AddrInfo, error) {
if c.server == "" || c.token == "" {
return nil, errors.New("cannot get addresses without server and token")
}
withCert := clientaccess.WithClientCertificate(c.clientCert, c.clientKey)
info, err := clientaccess.ParseAndValidateToken(c.server, c.token, withCert)
if err != nil {
return nil, err
}
addr, err := info.Get("/v1-" + version.Program + "/p2p")
if err != nil {
return nil, err
}
addrInfo, err := peer.AddrInfoFromString(string(addr))
return addrInfo, err
}
type serverBootstrapper struct {
controlConfig *config.Control
}
// NewServerBootstrapper returns a p2p bootstrapper that returns an address from a random other cluster member.
func NewServerBootstrapper(controlConfig *config.Control) routing.Bootstrapper {
return &serverBootstrapper{
controlConfig: controlConfig,
}
}
func (s *serverBootstrapper) Run(_ context.Context, id string) error {
s.controlConfig.Runtime.ClusterControllerStarts["spegel-p2p"] = func(ctx context.Context) {
nodes := s.controlConfig.Runtime.Core.Core().V1().Node()
wait.PollImmediateUntilWithContext(ctx, 1*time.Second, func(ctx context.Context) (bool, error) {
nodeName := os.Getenv("NODE_NAME")
if nodeName == "" {
return false, nil
}
node, err := nodes.Get(nodeName, metav1.GetOptions{})
if err != nil {
return false, nil
}
if node.Annotations == nil {
node.Annotations = map[string]string{}
}
node.Annotations[P2pAddressAnnotation] = id
if node.Labels == nil {
node.Labels = map[string]string{}
}
node.Labels[P2pEnabledLabel] = "true"
if _, err = nodes.Update(node); err != nil {
return false, nil
}
logrus.Infof("Node P2P address annotations and labels added: %s", id)
return true, nil
})
}
return nil
}
func (s *serverBootstrapper) GetAddress() (addrInfo *peer.AddrInfo, err error) {
if s.controlConfig.Runtime.Core == nil {
return nil, errors.New("runtime core not ready")
}
nodeName := os.Getenv("NODE_NAME")
if nodeName == "" {
return nil, errors.New("node name not set")
}
nodes := s.controlConfig.Runtime.Core.Core().V1().Node()
labelSelector := labels.Set{P2pEnabledLabel: "true"}.String()
nodeList, err := nodes.List(metav1.ListOptions{LabelSelector: labelSelector})
if err != nil {
return nil, err
}
for _, i := range rand.Perm(len(nodeList.Items)) {
node := nodeList.Items[i]
if node.Name == nodeName {
// don't return our own address
continue
}
if find, condition := nodeutil.GetNodeCondition(&node.Status, v1.NodeReady); find == -1 || condition.Status != v1.ConditionTrue {
// don't return the address of a not-ready node
continue
}
if val, ok := node.Annotations[P2pAddressAnnotation]; ok {
for _, addr := range strings.Split(val, ",") {
if info, err := peer.AddrInfoFromString(addr); err == nil {
return info, nil
}
}
}
}
return nil, errors.New("no ready p2p peers found")
}
type chainingBootstrapper struct {
bootstrappers []routing.Bootstrapper
}
// NewChainingBootstrapper returns a p2p bootstrapper that passes through to a list of bootstrappers.
func NewChainingBootstrapper(bootstrappers ...routing.Bootstrapper) routing.Bootstrapper {
return &chainingBootstrapper{
bootstrappers: bootstrappers,
}
}
func (c *chainingBootstrapper) Run(ctx context.Context, id string) error {
errs := merr.Errors{}
for _, b := range c.bootstrappers {
if err := b.Run(ctx, id); err != nil {
errs = append(errs, err)
}
}
return merr.NewErrors(errs...)
}
func (c *chainingBootstrapper) GetAddress() (*peer.AddrInfo, error) {
errs := merr.Errors{}
for _, b := range c.bootstrappers {
addr, err := b.GetAddress()
if err == nil {
return addr, nil
}
errs = append(errs, err)
}
return nil, merr.NewErrors(errs...)
}
package spegel
import (
"net"
"github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/rancher/wharfie/pkg/registries"
)
// InjectMirror configures TLS for the registry mirror client, and adds the mirror address as an endpoint
// to all configured registries.
func (c *Config) InjectMirror(nodeConfig *config.Node) error {
mirrorAddr := net.JoinHostPort(c.InternalAddress, c.RegistryPort)
registry := nodeConfig.AgentConfig.Registry
if registry.Configs == nil {
registry.Configs = map[string]registries.RegistryConfig{}
}
registry.Configs[mirrorAddr] = registries.RegistryConfig{
TLS: &registries.TLSConfig{
CAFile: c.ServerCAFile,
CertFile: c.ClientCertFile,
KeyFile: c.ClientKeyFile,
},
}
if registry.Mirrors == nil {
registry.Mirrors = map[string]registries.Mirror{}
}
for host, mirror := range registry.Mirrors {
if host != "*" {
mirror.Endpoints = append([]string{"https://" + mirrorAddr}, mirror.Endpoints...)
registry.Mirrors[host] = mirror
}
}
return nil
}
package spegel
import (
"context"
"fmt"
"log"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"time"
"github.com/k3s-io/k3s/pkg/clientaccess"
"github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/version"
"github.com/rancher/dynamiclistener/cert"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/utils/pointer"
"github.com/go-logr/logr"
"github.com/go-logr/stdr"
"github.com/gorilla/mux"
leveldb "github.com/ipfs/go-ds-leveldb"
ipfslog "github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/xenitab/spegel/pkg/metrics"
"github.com/xenitab/spegel/pkg/oci"
"github.com/xenitab/spegel/pkg/registry"
"github.com/xenitab/spegel/pkg/routing"
"github.com/xenitab/spegel/pkg/state"
"k8s.io/component-base/metrics/legacyregistry"
)
// DefaultRegistry is the default instance of a Spegel distributed registry
var DefaultRegistry = &Config{
Bootstrapper: NewSelfBootstrapper(),
HandlerFunc: func(_ *Config, _ *mux.Router) error {
return errors.New("not implemented")
},
AuthFunc: func() authenticator.Request {
return union.New(nil)
},
}
var (
P2pAddressAnnotation = "p2p." + version.Program + ".cattle.io/node-address"
P2pEnabledLabel = "p2p." + version.Program + ".cattle.io/enabled"
P2pPortEnv = version.ProgramUpper + "_P2P_PORT"
)
type authFunc func() authenticator.Request
type handlerFunc func(config *Config, router *mux.Router) error
// Config holds fields for a distributed registry
type Config struct {
ClientCAFile string
ClientCertFile string
ClientKeyFile string
ServerCAFile string
ServerCertFile string
ServerKeyFile string
// ExternalAddress is the address for other nodes to connect to the registry API.
ExternalAddress string
// InternalAddress is the address for the local containerd instance to connect to the registry API.
InternalAddress string
// RegistryPort is the port for the registry API.
RegistryPort string
// PSK is the preshared key required to join the p2p network.
PSK []byte
// Bootstrapper is the bootstrapper that will be used to discover p2p peers.
Bootstrapper routing.Bootstrapper
// HandlerFunc will be called to add the registry API handler to an existing router.
HandlerFunc handlerFunc
// Authenticator will be called to retrieve an authenticator used to validate the request to the registry API.
AuthFunc authFunc
}
// These values are not currently configurable
const (
resolveLatestTag = false
resolveRetries = 0
resolveTimeout = time.Second * 5
registryScheme = "https"
registryNamespace = "k8s.io"
defaultRouterPort = "5001"
)
func init() {
// ensure that spegel exposes metrics through the same registry used by Kubernetes components
metrics.DefaultRegisterer = legacyregistry.Registerer()
metrics.DefaultGatherer = legacyregistry.DefaultGatherer
}
// Start starts the embedded p2p router, and binds the registry API to an existing HTTP router.
func (c *Config) Start(ctx context.Context, nodeConfig *config.Node) error {
// distribute images for all configured mirrors. there doesn't need to be a
// configured endpoint, just having a key for the registry will do.
urls := []url.URL{}
registries := []string{}
for host := range nodeConfig.AgentConfig.Registry.Mirrors {
if u, err := url.Parse("https://" + host); err != nil || host == "*" {
logrus.Errorf("Distributed registry mirror skipping unsupported registry: %s", host)
} else {
urls = append(urls, *u)
registries = append(registries, host)
}
}
if len(registries) == 0 {
logrus.Errorf("Not starting distributed registry mirror: no registries configured for distributed mirroring")
return nil
}
logrus.Infof("Starting distributed registry mirror at https://%s:%s/v2 for registries %v",
c.ExternalAddress, c.RegistryPort, registries)
// set up the various logging logging frameworks
level := ipfslog.LevelInfo
if logrus.IsLevelEnabled(logrus.DebugLevel) {
level = ipfslog.LevelDebug
stdlog := log.New(logrus.StandardLogger().Writer(), "spegel ", log.LstdFlags)
logger := stdr.NewWithOptions(stdlog, stdr.Options{Verbosity: pointer.Int(10)})
ctx = logr.NewContext(ctx, logger)
}
ipfslog.SetAllLoggers(level)
// Get containerd client
ociClient, err := oci.NewContainerd(nodeConfig.Containerd.Address, registryNamespace, nodeConfig.Containerd.Registry, urls)
if err != nil {
return errors.Wrap(err, "failed to create OCI client")
}
// create or load persistent private key
keyFile := filepath.Join(nodeConfig.Containerd.Opt, "peer.key")
keyBytes, _, err := cert.LoadOrGenerateKeyFile(keyFile, false)
if err != nil {
return errors.Wrap(err, "failed to load or generate p2p private key")
}
privKey, err := cert.ParsePrivateKeyPEM(keyBytes)
if err != nil {
return errors.Wrap(err, "failed to parse p2p private key")
}
p2pKey, _, err := crypto.KeyPairFromStdKey(privKey)
if err != nil {
return errors.Wrap(err, "failed to convert p2p private key")
}
// create a peerstore to allow persisting nodes across restarts
peerFile := filepath.Join(nodeConfig.Containerd.Opt, "peerstore.db")
ds, err := leveldb.NewDatastore(peerFile, nil)
if err != nil {
return errors.Wrap(err, "failed to create peerstore datastore")
}
ps, err := pstoreds.NewPeerstore(ctx, ds, pstoreds.DefaultOpts())
if err != nil {
return errors.Wrap(err, "failed to create peerstore")
}
// get port and start p2p router
routerPort := defaultRouterPort
if env := os.Getenv(P2pPortEnv); env != "" {
if i, err := strconv.Atoi(env); i == 0 || err != nil {
logrus.Warnf("Invalid P2P node port; using default")
} else {
routerPort = env
}
}
routerAddr := net.JoinHostPort(c.ExternalAddress, routerPort)
logrus.Infof("Starting distributed registry P2P node at %s", routerAddr)
opts := []libp2p.Option{
libp2p.Identity(p2pKey),
libp2p.Peerstore(ps),
libp2p.PrivateNetwork(c.PSK),
libp2p.PrometheusRegisterer(metrics.DefaultRegisterer),
}
router, err := routing.NewP2PRouter(ctx, routerAddr, c.Bootstrapper, c.RegistryPort, registryScheme, opts...)
if err != nil {
return errors.Wrap(err, "failed to create p2p router")
}
caCert, err := os.ReadFile(c.ServerCAFile)
if err != nil {
return errors.Wrap(err, "failed to read server CA")
}
localAddr := net.JoinHostPort(c.InternalAddress, c.RegistryPort)
client := clientaccess.GetHTTPClient(caCert, c.ClientCertFile, c.ClientKeyFile)
metrics.Register()
reg := registry.NewRegistry(ociClient, router, localAddr, resolveRetries, resolveTimeout, resolveLatestTag, client.Transport)
regSvr := reg.Server(":"+c.RegistryPort, logr.FromContextOrDiscard(ctx))
// Close router on shutdown
go func() {
<-ctx.Done()
router.Close()
}()
// Track images available in containerd and publish via p2p router
go state.Track(ctx, ociClient, router, resolveLatestTag)
mRouter := mux.NewRouter().SkipClean(true)
mRouter.Use(c.authMiddleware())
mRouter.PathPrefix("/v2").Handler(regSvr.Handler)
mRouter.PathPrefix("/v1-" + version.Program + "/p2p").Handler(c.peerInfo())
if err := c.HandlerFunc(c, mRouter); err != nil {
return err
}
// Wait up to 5 seconds for the p2p network to find peers. This will return
// immediately if the node is bootstrapping from itself.
wait.PollImmediateWithContext(ctx, time.Second, resolveTimeout, func(_ context.Context) (bool, error) {
return router.HasMirrors()
})
return nil
}
// peerInfo sends a peer address retrieved from the bootstrapper via HTTP
func (c *Config) peerInfo() http.HandlerFunc {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
client, _, _ := net.SplitHostPort(req.RemoteAddr)
info, err := c.Bootstrapper.GetAddress()
if err != nil {
http.Error(resp, "Internal Error", http.StatusInternalServerError)
return
}
logrus.Debugf("Serving p2p peer addr %s to client at %s", info, client)
resp.WriteHeader(http.StatusOK)
resp.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(resp, "%s/p2p/%s", info.Addrs[0].String(), info.ID.Pretty())
})
}
// authMiddleware calls the configured authenticator to gate access to the registry API
func (c *Config) authMiddleware() mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if _, ok, err := c.AuthFunc().AuthenticateRequest(req); !ok || err != nil {
http.Error(resp, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(resp, req)
})
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment