github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/registry/local.go (about) 1 // Copyright © 2022 Alibaba Group Holding Ltd. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package registry 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/json" 21 "fmt" 22 "net" 23 "os" 24 "path" 25 "path/filepath" 26 "sort" 27 "strconv" 28 "strings" 29 30 "github.com/sealerio/sealer/common" 31 containerruntime "github.com/sealerio/sealer/pkg/container-runtime" 32 "github.com/sealerio/sealer/pkg/imagedistributor" 33 "github.com/sealerio/sealer/pkg/infradriver" 34 "github.com/sealerio/sealer/pkg/ipvs" 35 v2 "github.com/sealerio/sealer/types/api/v2" 36 netutils "github.com/sealerio/sealer/utils/net" 37 osutils "github.com/sealerio/sealer/utils/os" 38 "github.com/sealerio/sealer/utils/shellcommand" 39 40 "github.com/containers/common/pkg/auth" 41 "github.com/pelletier/go-toml" 42 "github.com/sirupsen/logrus" 43 "golang.org/x/sync/errgroup" 44 k8snet "k8s.io/utils/net" 45 ) 46 47 const ( 48 LvscarePodFileName = "reg-lvscare.yaml" 49 ) 50 51 type localConfigurator struct { 52 *v2.LocalRegistry 53 deployHosts []net.IP 54 containerRuntimeInfo containerruntime.Info 55 infraDriver infradriver.InfraDriver 56 distributor imagedistributor.Distributor 57 } 58 59 func (c *localConfigurator) GetRegistryInfo() RegistryInfo { 60 registryInfo := RegistryInfo{Local: LocalRegistryInfo{LocalRegistry: c.LocalRegistry}} 61 if *c.LocalRegistry.HA { 62 registryInfo.Local.Vip = GetRegistryVIP(c.infraDriver) 63 registryInfo.Local.DeployHosts = c.deployHosts 64 } else { 65 registryInfo.Local.DeployHosts = append(registryInfo.Local.DeployHosts, c.deployHosts[0]) 66 } 67 return registryInfo 68 } 69 70 func (c *localConfigurator) GetDriver() (Driver, error) { 71 endpoint := net.JoinHostPort(c.Domain, strconv.Itoa(c.Port)) 72 dataDir := filepath.Join(c.infraDriver.GetClusterRootfsPath(), "registry") 73 return newLocalRegistryDriver(endpoint, dataDir, c.deployHosts, c.distributor), nil 74 } 75 76 func (c *localConfigurator) UninstallFrom(deletedMasters, deletedNodes []net.IP) error { 77 // remove all registry configs on /etc/hosts 78 all := append(deletedMasters, deletedNodes...) 79 if err := c.removeRegistryConfig(all); err != nil { 80 return err 81 } 82 if !*c.HA { 83 return nil 84 } 85 // if current deployHosts is null,means clean all, just return. 86 if len(c.deployHosts) == 0 { 87 return nil 88 } 89 // if deletedMasters is nil, means no need to flush workers, just return 90 if len(deletedMasters) == 0 { 91 return nil 92 } 93 // flush ipvs policy on remain nodes 94 return c.configureLvs(c.deployHosts, netutils.RemoveIPs(c.infraDriver.GetHostIPListByRole(common.NODE), deletedNodes)) 95 } 96 97 func (c *localConfigurator) removeRegistryConfig(hosts []net.IP) error { 98 var uninstallCmd []string 99 if c.RegistryConfig.Username != "" && c.RegistryConfig.Password != "" { 100 //todo use sdk to logout instead of shell cmd 101 logoutCmd := fmt.Sprintf("docker logout %s ", net.JoinHostPort(c.Domain, strconv.Itoa(c.Port))) 102 if c.containerRuntimeInfo.Type != common.Docker { 103 logoutCmd = fmt.Sprintf("nerdctl logout %s ", net.JoinHostPort(c.Domain, strconv.Itoa(c.Port))) 104 } 105 uninstallCmd = append(uninstallCmd, logoutCmd) 106 } 107 108 f := func(host net.IP) error { 109 err := c.infraDriver.CmdAsync(host, nil, strings.Join(uninstallCmd, "&&")) 110 if err != nil { 111 return fmt.Errorf("failed to delete registry configuration: %v", err) 112 } 113 return nil 114 } 115 116 return c.infraDriver.Execute(hosts, f) 117 } 118 119 func (c *localConfigurator) InstallOn(masters, nodes []net.IP) error { 120 hosts := append(masters, nodes...) 121 logrus.Infof("will install local private registry configuration on %+v\n", hosts) 122 if err := c.configureRegistryNetwork(masters, nodes); err != nil { 123 return err 124 } 125 126 if err := c.configureRegistryCert(hosts); err != nil { 127 return err 128 } 129 130 if err := c.configureDaemonService(hosts); err != nil { 131 return err 132 } 133 134 if err := c.configureAccessCredential(hosts); err != nil { 135 return err 136 } 137 138 return nil 139 } 140 141 // add registry domain and ip to "/etc/hosts" 142 // add registry ip to ipvs policy 143 func (c *localConfigurator) configureRegistryNetwork(masters, nodes []net.IP) error { 144 if !*c.HA { 145 return c.configureSingletonHostsFile(append(masters, nodes...)) 146 } 147 148 eg, _ := errgroup.WithContext(context.Background()) 149 150 for i := range masters { 151 master := masters[i] 152 eg.Go(func() error { 153 cmd := shellcommand.CommandSetHostAlias(c.Domain, master.String()) 154 if err := c.infraDriver.CmdAsync(master, nil, cmd); err != nil { 155 return fmt.Errorf("failed to config masters hosts file: %v", err) 156 } 157 return nil 158 }) 159 } 160 161 if err := eg.Wait(); err != nil { 162 return err 163 } 164 165 // if masters is nil, means no need to flush old nodes 166 if len(masters) == 0 { 167 return c.configureLvs(c.deployHosts, nodes) 168 } 169 return c.configureLvs(c.deployHosts, c.infraDriver.GetHostIPListByRole(common.NODE)) 170 } 171 172 func (c *localConfigurator) configureLvs(registryHosts, clientHosts []net.IP) error { 173 var rs []string 174 var realEndpoints []string 175 hosts := netutils.IPsToIPStrs(registryHosts) 176 sort.Strings(hosts) 177 for _, m := range hosts { 178 ep := net.JoinHostPort(m, strconv.Itoa(c.Port)) 179 rs = append(rs, fmt.Sprintf("--rs %s", ep)) 180 realEndpoints = append(realEndpoints, ep) 181 } 182 183 //todo should make lvs image name as const value in sealer repo. 184 lvsImageURL := path.Join(net.JoinHostPort(c.Domain, strconv.Itoa(c.Port)), common.LvsCareRepoAndTag) 185 186 vip := GetRegistryVIP(c.infraDriver) 187 188 vs := net.JoinHostPort(vip, strconv.Itoa(c.Port)) 189 // due to registry server do not have health path to check, choose "/" as default. 190 healthPath := "/" 191 healthSchem := "https" 192 if *c.Insecure { 193 healthSchem = "http" 194 } 195 196 y, err := ipvs.LvsStaticPodYaml(common.RegLvsCareStaticPodName, vs, realEndpoints, lvsImageURL, healthPath, healthSchem) 197 if err != nil { 198 return err 199 } 200 201 lvscareStaticCmd := ipvs.GetCreateLvscareStaticPodCmd(y, LvscarePodFileName) 202 203 ipvsCmd := fmt.Sprintf("seautil ipvs --vs %s %s --health-path %s --health-schem %s --run-once", 204 vs, strings.Join(rs, " "), healthPath, healthSchem) 205 // flush all cluster nodes as latest ipvs policy. 206 eg, _ := errgroup.WithContext(context.Background()) 207 208 for i := range clientHosts { 209 n := clientHosts[i] 210 eg.Go(func() error { 211 err := c.infraDriver.CmdAsync(n, nil, ipvsCmd, lvscareStaticCmd) 212 if err != nil { 213 return fmt.Errorf("failed to config nodes lvs policy: %s: %v", ipvsCmd, err) 214 } 215 216 err = c.infraDriver.CmdAsync(n, nil, shellcommand.CommandSetHostAlias(c.Domain, vip)) 217 if err != nil { 218 return fmt.Errorf("failed to config nodes hosts file cmd: %v", err) 219 } 220 return nil 221 }) 222 } 223 return eg.Wait() 224 } 225 226 func (c *localConfigurator) configureSingletonHostsFile(hosts []net.IP) error { 227 // add registry ip to "/etc/hosts" 228 f := func(host net.IP) error { 229 err := c.infraDriver.CmdAsync(host, nil, shellcommand.CommandSetHostAlias(c.Domain, c.deployHosts[0].String())) 230 if err != nil { 231 return fmt.Errorf("failed to config cluster hosts file cmd: %v", err) 232 } 233 return nil 234 } 235 236 return c.infraDriver.Execute(hosts, f) 237 } 238 239 func (c *localConfigurator) configureRegistryCert(hosts []net.IP) error { 240 // if deploy registry as InsecureMode ,skip to configure cert. 241 if *c.Insecure { 242 return nil 243 } 244 245 var ( 246 endpoint = net.JoinHostPort(c.Domain, strconv.Itoa(c.Port)) 247 caFile = c.Domain + ".crt" 248 src = filepath.Join(c.infraDriver.GetClusterRootfsPath(), "certs", caFile) 249 dest = filepath.Join(c.containerRuntimeInfo.CertsDir, endpoint, caFile) 250 ) 251 252 return c.copy2RemoteHosts(src, dest, hosts) 253 } 254 255 func (c *localConfigurator) configureAccessCredential(hosts []net.IP) error { 256 var ( 257 username = c.RegistryConfig.Username 258 password = c.RegistryConfig.Password 259 endpoint = net.JoinHostPort(c.Domain, strconv.Itoa(c.Port)) 260 tmpAuthFilePath = "/tmp/config.json" 261 // todo we need this config file when kubelet pull images from registry. while, we could optimize the logic here. 262 remoteKubeletAuthFilePath = "/var/lib/kubelet/config.json" 263 ) 264 265 if username == "" || password == "" { 266 return nil 267 } 268 269 err := auth.Login(context.TODO(), 270 nil, 271 &auth.LoginOptions{ 272 AuthFile: tmpAuthFilePath, 273 Password: password, 274 Username: username, 275 Stdout: os.Stdout, 276 AcceptRepositories: true, 277 }, 278 []string{endpoint}) 279 280 if err != nil { 281 return err 282 } 283 284 defer func() { 285 err = os.Remove(tmpAuthFilePath) 286 if err != nil { 287 logrus.Debugf("failed to remove tmp registry auth file:%s", tmpAuthFilePath) 288 } 289 }() 290 291 err = c.copy2RemoteHosts(tmpAuthFilePath, c.containerRuntimeInfo.ConfigFilePath, hosts) 292 if err != nil { 293 return err 294 } 295 296 err = c.copy2RemoteHosts(tmpAuthFilePath, remoteKubeletAuthFilePath, hosts) 297 if err != nil { 298 return err 299 } 300 301 return nil 302 } 303 304 func (c *localConfigurator) copy2RemoteHosts(src, dest string, hosts []net.IP) error { 305 f := func(host net.IP) error { 306 err := c.infraDriver.Copy(host, src, dest) 307 if err != nil { 308 return fmt.Errorf("failed to copy local file %s to remote %s : %v", src, dest, err) 309 } 310 return nil 311 } 312 313 return c.infraDriver.Execute(hosts, f) 314 } 315 316 func (c *localConfigurator) configureDaemonService(hosts []net.IP) error { 317 var ( 318 src string 319 dest string 320 endpoint = net.JoinHostPort(c.Domain, strconv.Itoa(c.Port)) 321 ) 322 323 if endpoint == common.DefaultRegistryURL { 324 return nil 325 } 326 327 if c.containerRuntimeInfo.Type == common.Docker { 328 src = filepath.Join(c.infraDriver.GetClusterRootfsPath(), "etc", "daemon.json") 329 dest = "/etc/docker/daemon.json" 330 if err := c.configureDockerDaemonService(endpoint, src); err != nil { 331 return err 332 } 333 } 334 335 if c.containerRuntimeInfo.Type == common.Containerd { 336 src = filepath.Join(c.infraDriver.GetClusterRootfsPath(), "etc", "hosts.toml") 337 dest = filepath.Join(containerruntime.DefaultContainerdCertsDir, endpoint, "hosts.toml") 338 if err := c.configureContainerdDaemonService(endpoint, src); err != nil { 339 return err 340 } 341 } 342 343 eg, _ := errgroup.WithContext(context.Background()) 344 345 // for docker: copy daemon.json to "/etc/docker/daemon.json" 346 // for containerd : copy hosts.toml to "/etc/containerd/certs.d/${domain}:${port}/hosts.toml" 347 for i := range hosts { 348 ip := hosts[i] 349 eg.Go(func() error { 350 err := c.infraDriver.Copy(ip, src, dest) 351 if err != nil { 352 return err 353 } 354 355 err = c.infraDriver.CmdAsync(ip, nil, "systemctl daemon-reload") 356 if err != nil { 357 return err 358 } 359 return nil 360 }) 361 } 362 return eg.Wait() 363 } 364 365 func (c *localConfigurator) configureDockerDaemonService(endpoint, daemonFile string) error { 366 var daemonConf DaemonConfig 367 368 b, err := os.ReadFile(filepath.Clean(daemonFile)) 369 if err != nil { 370 return err 371 } 372 373 b = bytes.TrimSpace(b) 374 // if config file is empty, only add registry config. 375 if len(b) != 0 { 376 if err := json.Unmarshal(b, &daemonConf); err != nil { 377 return fmt.Errorf("failed to load %s to DaemonConfig: %v", daemonFile, err) 378 } 379 } 380 381 daemonConf.RegistryMirrors = append(daemonConf.RegistryMirrors, "https://"+endpoint) 382 383 content, err := json.MarshalIndent(daemonConf, "", " ") 384 385 if err != nil { 386 return fmt.Errorf("failed to marshal daemonFile: %v", err) 387 } 388 389 return osutils.NewCommonWriter(daemonFile).WriteFile(content) 390 } 391 392 func (c *localConfigurator) configureContainerdDaemonService(endpoint, hostTomlFile string) error { 393 var ( 394 caFile = c.Domain + ".crt" 395 registryCaCertPath = filepath.Join(c.containerRuntimeInfo.CertsDir, endpoint, caFile) 396 url = "https://" + endpoint 397 ) 398 399 cfg := Hosts{ 400 Server: url, 401 HostConfigs: map[string]HostFileConfig{ 402 url: {CACert: registryCaCertPath}, 403 }, 404 } 405 406 bs, err := toml.Marshal(cfg) 407 if err != nil { 408 return fmt.Errorf("failed to marshal containerd hosts.toml file: %v", err) 409 } 410 411 return osutils.NewCommonWriter(hostTomlFile).WriteFile(bs) 412 } 413 414 type Hosts struct { 415 // Server specifies the default server. When `host` is 416 // also specified, those hosts are tried first. 417 Server string `toml:"server"` 418 // HostConfigs store the per-host configuration 419 HostConfigs map[string]HostFileConfig `toml:"host"` 420 } 421 422 type HostFileConfig struct { 423 // CACert are the public key certificates for TLS 424 // Accepted types 425 // - string - Single file with certificate(s) 426 // - []string - Multiple files with certificates 427 CACert interface{} `toml:"ca"` 428 } 429 430 type DaemonConfig struct { 431 AllowNonDistributableArtifacts []string `json:"allow-nondistributable-artifacts,omitempty"` 432 APICorsHeader string `json:"api-cors-header,omitempty"` 433 AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` 434 Bip string `json:"bip,omitempty"` 435 Bridge string `json:"bridge,omitempty"` 436 CgroupParent string `json:"cgroup-parent,omitempty"` 437 ClusterAdvertise string `json:"cluster-advertise,omitempty"` 438 ClusterStore string `json:"cluster-store,omitempty"` 439 Containerd string `json:"containerd,omitempty"` 440 ContainerdNamespace string `json:"containerd-namespace,omitempty"` 441 ContainerdPluginNamespace string `json:"containerd-plugin-namespace,omitempty"` 442 DataRoot string `json:"data-root,omitempty"` 443 Debug bool `json:"debug,omitempty"` 444 DefaultCgroupnsMode string `json:"default-cgroupns-mode,omitempty"` 445 DefaultGateway string `json:"default-gateway,omitempty"` 446 DefaultGatewayV6 string `json:"default-gateway-v6,omitempty"` 447 DefaultRuntime string `json:"default-runtime,omitempty"` 448 DefaultShmSize string `json:"default-shm-size,omitempty"` 449 DNS []string `json:"dns,omitempty"` 450 DNSOpts []string `json:"dns-opts,omitempty"` 451 DNSSearch []string `json:"dns-search,omitempty"` 452 ExecOpts []string `json:"exec-opts,omitempty"` 453 ExecRoot string `json:"exec-root,omitempty"` 454 Experimental bool `json:"experimental,omitempty"` 455 FixedCidr string `json:"fixed-cidr,omitempty"` 456 FixedCidrV6 string `json:"fixed-cidr-v6,omitempty"` 457 Group string `json:"group,omitempty"` 458 Hosts []string `json:"hosts,omitempty"` 459 Icc bool `json:"icc,omitempty"` 460 Init bool `json:"init,omitempty"` 461 InitPath string `json:"init-path,omitempty"` 462 InsecureRegistries []string `json:"insecure-registries,omitempty"` 463 IP string `json:"ip,omitempty"` 464 IPForward bool `json:"ip-forward,omitempty"` 465 IPMasq bool `json:"ip-masq,omitempty"` 466 Iptables bool `json:"iptables,omitempty"` 467 IP6Tables bool `json:"ip6tables,omitempty"` 468 Ipv6 bool `json:"ipv6,omitempty"` 469 Labels []string `json:"labels,omitempty"` 470 LiveRestore bool `json:"live-restore,omitempty"` 471 LogDriver string `json:"log-driver,omitempty"` 472 LogLevel string `json:"log-level,omitempty"` 473 MaxConcurrentDownloads int `json:"max-concurrent-downloads,omitempty"` 474 MaxConcurrentUploads int `json:"max-concurrent-uploads,omitempty"` 475 MaxDownloadAttempts int `json:"max-download-attempts,omitempty"` 476 Mtu int `json:"mtu,omitempty"` 477 NoNewPrivileges bool `json:"no-new-privileges,omitempty"` 478 NodeGenericResources []string `json:"node-generic-resources,omitempty"` 479 OomScoreAdjust int `json:"oom-score-adjust,omitempty"` 480 Pidfile string `json:"pidfile,omitempty"` 481 RawLogs bool `json:"raw-logs,omitempty"` 482 RegistryMirrors []string `json:"registry-mirrors,omitempty"` 483 SeccompProfile string `json:"seccomp-profile,omitempty"` 484 SelinuxEnabled bool `json:"selinux-enabled,omitempty"` 485 ShutdownTimeout int `json:"shutdown-timeout,omitempty"` 486 StorageDriver string `json:"storage-driver,omitempty"` 487 StorageOpts []string `json:"storage-opts,omitempty"` 488 SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr,omitempty"` 489 TLS bool `json:"tls,omitempty"` 490 Tlscacert string `json:"tlscacert,omitempty"` 491 Tlscert string `json:"tlscert,omitempty"` 492 Tlskey string `json:"tlskey,omitempty"` 493 Tlsverify bool `json:"tlsverify,omitempty"` 494 UserlandProxy bool `json:"userland-proxy,omitempty"` 495 UserlandProxyPath string `json:"userland-proxy-path,omitempty"` 496 UsernsRemap string `json:"userns-remap,omitempty"` 497 ClusterStoreOpts map[string]string `json:"cluster-store-opts,omitempty"` 498 LogOpts *DaemonLogOpts `json:"log-opts,omitempty"` 499 } 500 501 type DaemonLogOpts struct { 502 CacheDisabled string `json:"cache-disabled,omitempty"` 503 CacheMaxFile string `json:"cache-max-file,omitempty"` 504 CacheMaxSize string `json:"cache-max-size,omitempty"` 505 CacheCompress string `json:"cache-compress,omitempty"` 506 Env string `json:"env,omitempty"` 507 Labels string `json:"labels,omitempty"` 508 MaxFile string `json:"max-file,omitempty"` 509 MaxSize string `json:"max-size,omitempty"` 510 } 511 512 func GetRegistryVIP(infraDriver infradriver.InfraDriver) string { 513 vip := common.DefaultVIP 514 if hosts := infraDriver.GetHostIPList(); len(hosts) > 0 && k8snet.IsIPv6(hosts[0]) { 515 vip = common.DefaultVIPForIPv6 516 } 517 518 if ipv4, ok := infraDriver.GetClusterEnv()[common.EnvIPvsVIPForIPv4]; ok { 519 vip = ipv4 520 } 521 522 if ipv6, ok := infraDriver.GetClusterEnv()[common.EnvIPvsVIPForIPv6]; ok { 523 vip = ipv6 524 } 525 return vip 526 }