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