github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/infra/container/container.go (about) 1 // Copyright © 2021 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 container 16 17 import ( 18 "fmt" 19 "net" 20 "os" 21 "strconv" 22 "time" 23 24 "github.com/sealerio/sealer/pkg/infra/container/client" 25 "github.com/sealerio/sealer/pkg/infra/container/client/docker" 26 v1 "github.com/sealerio/sealer/types/api/v1" 27 osi "github.com/sealerio/sealer/utils/os" 28 "github.com/sealerio/sealer/utils/ssh" 29 "github.com/sirupsen/logrus" 30 ) 31 32 const ( 33 CONTAINER = "CONTAINER" 34 DockerHost = "/var/run/docker.sock" 35 DefaultPassword = "Seadent123" 36 MASTER = "master" 37 NODE = "node" 38 ChangePasswordCmd = "echo root:%s | chpasswd" // #nosec 39 RoleLabel = "sealer-io-role" 40 RoleLabelMaster = "sealer-io-role-is-master" 41 NetworkName = "sealer-network" 42 ImageName = "sealerio/sealer-base-image:v1" 43 SealerImageRootPath = "/var/lib/sealer" 44 // for check rootless docker in info.SecurityOptions 45 RootlessDocker = "name=rootless" 46 // for check overlay2 StorageDriver in info.StorageDriver 47 Overlay2 = "overlay2" 48 ) 49 50 type ApplyProvider struct { 51 Cluster *v1.Cluster 52 Provider client.ProviderService 53 } 54 55 type ApplyResult struct { 56 ToJoinNumber int 57 ToDeleteIPList []net.IP 58 Role string 59 } 60 61 func (a *ApplyProvider) Apply() error { 62 // delete apply 63 if a.Cluster.DeletionTimestamp != nil { 64 logrus.Info("deletion timestamp not nil, will clear infra") 65 return a.CleanUp() 66 } 67 // new apply 68 if a.Cluster.Annotations == nil { 69 err := a.CheckServerInfo() 70 if err != nil { 71 return err 72 } 73 a.Cluster.Annotations = make(map[string]string) 74 } 75 // change apply: scale up or scale down,count!=len(iplist) 76 if a.Cluster.Spec.Masters.Count != strconv.Itoa(len(a.Cluster.Spec.Masters.IPList)) || 77 a.Cluster.Spec.Nodes.Count != strconv.Itoa(len(a.Cluster.Spec.Nodes.IPList)) { 78 return a.ReconcileContainer() 79 } 80 return nil 81 } 82 83 func (a *ApplyProvider) CheckServerInfo() error { 84 /* 85 1,rootless docker:do not support rootless docker currently.if support, CgroupVersion must = 2 86 2,StorageDriver:overlay2 87 3,cpu num >1 88 4,docker host : /var/run/docker.sock. set env DOCKER_HOST to override 89 */ 90 info, err := a.Provider.GetServerInfo() 91 if err != nil { 92 return fmt.Errorf("failed to get docker server, please check docker server running status") 93 } 94 95 for _, opt := range info.SecurityOptions { 96 if opt == RootlessDocker { 97 return fmt.Errorf("do not support rootless docker currently") 98 } 99 } 100 101 if info.StorageDriver != Overlay2 { 102 return fmt.Errorf("only support storage driver overlay2 ,but current is :%s", info.StorageDriver) 103 } 104 105 if info.CPUNumber <= 1 { 106 return fmt.Errorf("cpu number of docker server must greater than 1 ,but current is :%d", info.CPUNumber) 107 } 108 109 if !info.MemoryLimit || !info.PidsLimit || !info.CPUShares { 110 return fmt.Errorf("requires setting systemd property \"Delegate=yes\"") 111 } 112 113 if !osi.IsFileExist(DockerHost) && os.Getenv("DOCKER_HOST") == "" { 114 return fmt.Errorf("sealer user default docker host /var/run/docker.sock, please set env DOCKER_HOST='' to override it") 115 } 116 117 return nil 118 } 119 120 func (a *ApplyProvider) ReconcileContainer() error { 121 // scale up: apply diff container, append ip list. 122 // scale down: delete diff container by id,delete ip list. if no container,need do cleanup 123 currentMasterNum := len(a.Cluster.Spec.Masters.IPList) 124 num, list, _ := getDiff(a.Cluster.Spec.Masters) 125 masterApplyResult := &ApplyResult{ 126 ToJoinNumber: num, 127 ToDeleteIPList: list, 128 Role: MASTER, 129 } 130 num, list, _ = getDiff(a.Cluster.Spec.Nodes) 131 nodeApplyResult := &ApplyResult{ 132 ToJoinNumber: num, 133 ToDeleteIPList: list, 134 Role: NODE, 135 } 136 //Abnormal scene :master number must > 0 137 if currentMasterNum+masterApplyResult.ToJoinNumber-len(masterApplyResult.ToDeleteIPList) <= 0 { 138 return fmt.Errorf("master number can not be 0") 139 } 140 logrus.Infof("master apply result: ToJoinNumber %d, ToDeleteIpList : %s", 141 masterApplyResult.ToJoinNumber, masterApplyResult.ToDeleteIPList) 142 143 logrus.Infof("node apply result: ToJoinNumber %d, ToDeleteIpList : %s", 144 nodeApplyResult.ToJoinNumber, nodeApplyResult.ToDeleteIPList) 145 146 if err := a.applyResult(masterApplyResult); err != nil { 147 return err 148 } 149 return a.applyResult(nodeApplyResult) 150 } 151 152 func (a *ApplyProvider) applyResult(result *ApplyResult) error { 153 // create or delete an update iplist 154 switch result.Role { 155 case MASTER: 156 if result.ToJoinNumber > 0 { 157 joinIPList, err := a.applyToJoin(result.ToJoinNumber, result.Role) 158 if err != nil { 159 return err 160 } 161 a.Cluster.Spec.Masters.IPList = append(a.Cluster.Spec.Masters.IPList, joinIPList...) 162 } 163 if len(result.ToDeleteIPList) > 0 { 164 err := a.applyToDelete(result.ToDeleteIPList) 165 if err != nil { 166 return err 167 } 168 a.Cluster.Spec.Masters.IPList = a.Cluster.Spec.Masters.IPList[:len(a.Cluster.Spec.Masters.IPList)- 169 len(result.ToDeleteIPList)] 170 } 171 case NODE: 172 if result.ToJoinNumber > 0 { 173 joinIPList, err := a.applyToJoin(result.ToJoinNumber, result.Role) 174 if err != nil { 175 return err 176 } 177 a.Cluster.Spec.Nodes.IPList = append(a.Cluster.Spec.Nodes.IPList, joinIPList...) 178 } 179 if len(result.ToDeleteIPList) > 0 { 180 err := a.applyToDelete(result.ToDeleteIPList) 181 if err != nil { 182 return err 183 } 184 a.Cluster.Spec.Nodes.IPList = a.Cluster.Spec.Nodes.IPList[:len(a.Cluster.Spec.Nodes.IPList)- 185 len(result.ToDeleteIPList)] 186 } 187 default: 188 return fmt.Errorf("unknown node role: %q", result.Role) 189 } 190 return nil 191 } 192 193 func (a *ApplyProvider) applyToJoin(toJoinNumber int, role string) ([]net.IP, error) { 194 // run container and return append ip list 195 var toJoinIPList []net.IP 196 for i := 0; i < toJoinNumber; i++ { 197 name := fmt.Sprintf("sealer-%s-%s", role, GenUniqueID(10)) 198 opts := &client.CreateOptsForContainer{ 199 ImageName: ImageName, 200 NetworkName: NetworkName, 201 ContainerHostName: name, 202 ContainerName: name, 203 ContainerLabel: map[string]string{ 204 RoleLabel: role, 205 }, 206 } 207 if len(a.Cluster.Spec.Masters.IPList) == 0 && i == 0 { 208 opts.ContainerLabel[RoleLabelMaster] = "true" 209 } 210 211 containerID, err := a.Provider.RunContainer(opts) 212 if err != nil { 213 return toJoinIPList, fmt.Errorf("failed to create container %s,error is %v", opts.ContainerName, err) 214 } 215 time.Sleep(3 * time.Second) 216 info, err := a.Provider.GetContainerInfo(containerID, NetworkName) 217 if err != nil { 218 return toJoinIPList, fmt.Errorf("failed to get container info of %s,error is %v", containerID, err) 219 } 220 221 err = a.changeDefaultPasswd(net.ParseIP(info.ContainerIP)) 222 if err != nil { 223 return nil, fmt.Errorf("failed to change container password of %s,error is %v", containerID, err) 224 } 225 226 a.Cluster.Annotations[info.ContainerIP] = containerID 227 toJoinIPList = append(toJoinIPList, net.ParseIP(info.ContainerIP)) 228 } 229 return toJoinIPList, nil 230 } 231 232 func (a *ApplyProvider) changeDefaultPasswd(containerIP net.IP) error { 233 if a.Cluster.Spec.SSH.Passwd == "" { 234 return nil 235 } 236 237 if a.Cluster.Spec.SSH.Passwd == DefaultPassword { 238 return nil 239 } 240 241 user := "root" 242 if a.Cluster.Spec.SSH.User != "" { 243 user = a.Cluster.Spec.SSH.User 244 } 245 sshClient := &ssh.SSH{ 246 User: user, 247 Password: DefaultPassword, 248 } 249 250 cmd := fmt.Sprintf(ChangePasswordCmd, a.Cluster.Spec.SSH.Passwd) 251 _, err := sshClient.Cmd(containerIP, nil, cmd) 252 return err 253 } 254 255 func (a *ApplyProvider) applyToDelete(deleteIPList []net.IP) error { 256 // delete container and return deleted ip list 257 for _, ip := range deleteIPList { 258 id, ok := a.Cluster.Annotations[ip.String()] 259 if !ok { 260 logrus.Warnf("failed to delete container %s", ip) 261 continue 262 } 263 err := a.Provider.RmContainer(id) 264 if err != nil { 265 return fmt.Errorf("failed to delete container:%s", id) 266 } 267 delete(a.Cluster.Annotations, ip.String()) 268 } 269 return nil 270 } 271 272 func (a *ApplyProvider) CleanUp() error { 273 //clean up container,cleanup image,clean up network 274 var iplist []net.IP 275 iplist = append(iplist, a.Cluster.Spec.Masters.IPList...) 276 iplist = append(iplist, a.Cluster.Spec.Nodes.IPList...) 277 278 for _, ip := range iplist { 279 id, ok := a.Cluster.Annotations[ip.String()] 280 if !ok { 281 continue 282 } 283 err := a.Provider.RmContainer(id) 284 if err != nil { 285 // log it 286 logrus.Infof("failed to delete container:%s", id) 287 return err 288 } 289 } 290 291 return nil 292 } 293 294 func NewClientWithCluster(cluster *v1.Cluster) (*ApplyProvider, error) { 295 p, err := docker.NewDockerProvider() 296 if err != nil { 297 return nil, err 298 } 299 300 return &ApplyProvider{ 301 Cluster: cluster, 302 Provider: p, 303 }, nil 304 }