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  }