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  }