github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/internal/operator/orbiter/kinds/clusters/kubernetes/initialize.go (about)

     1  package kubernetes
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/caos/orbos/pkg/kubernetes"
     9  
    10  	macherrs "k8s.io/apimachinery/pkg/api/errors"
    11  
    12  	"github.com/caos/orbos/internal/operator/common"
    13  	"github.com/caos/orbos/internal/operator/orbiter/kinds/clusters/core/infra"
    14  	"github.com/caos/orbos/mntr"
    15  	core "k8s.io/api/core/v1"
    16  	v1 "k8s.io/api/core/v1"
    17  )
    18  
    19  type initializedPool struct {
    20  	upscaling   int
    21  	downscaling []*initializedMachine
    22  	infra       infra.Pool
    23  	tier        Tier
    24  	desired     Pool
    25  	machines    func() ([]*initializedMachine, error)
    26  }
    27  
    28  func (i *initializedMachines) forEach(baseMonitor mntr.Monitor, do func(machine *initializedMachine, machineMonitor mntr.Monitor) (goon bool)) {
    29  	if i == nil {
    30  		return
    31  	}
    32  	for _, machine := range *i {
    33  		if !do(machine, baseMonitor.WithField("machine", machine.infra.ID())) {
    34  			break
    35  		}
    36  	}
    37  }
    38  
    39  type initializeFunc func(initializedPool, []*initializedMachine) error
    40  type uninitializeMachineFunc func(id string)
    41  type initializeMachineFunc func(machine infra.Machine, pool *initializedPool) *initializedMachine
    42  
    43  func (i *initializedPool) enhance(initialize initializeFunc) {
    44  	original := i.machines
    45  	i.machines = func() ([]*initializedMachine, error) {
    46  		machines, err := original()
    47  		if err != nil {
    48  			return nil, err
    49  		}
    50  		if err := initialize(*i, machines); err != nil {
    51  			return nil, err
    52  		}
    53  		return machines, nil
    54  	}
    55  }
    56  
    57  type initializedMachine struct {
    58  	infra            infra.Machine
    59  	reconcile        func() error
    60  	currentNodeagent *common.NodeAgentCurrent
    61  	desiredNodeagent *common.NodeAgentSpec
    62  	currentMachine   *Machine
    63  	pool             *initializedPool
    64  	node             *v1.Node
    65  }
    66  
    67  func initialize(
    68  	monitor mntr.Monitor,
    69  	curr *CurrentCluster,
    70  	desired DesiredV0,
    71  	nodeAgentsCurrent *common.CurrentNodeAgents,
    72  	nodeAgentsDesired *common.DesiredNodeAgents,
    73  	providerPools map[string]map[string]infra.Pool,
    74  	k8s *kubernetes.Client,
    75  	postInit func(machine *initializedMachine)) (
    76  	controlplane *initializedPool,
    77  	controlplaneMachines []*initializedMachine,
    78  	workers []*initializedPool,
    79  	workerMachines []*initializedMachine,
    80  	initializeMachine initializeMachineFunc,
    81  	uninitializeMachine uninitializeMachineFunc,
    82  	err error) {
    83  
    84  	curr.Status = "running"
    85  
    86  	initializePool := func(infraPool infra.Pool, desired Pool, tier Tier) (*initializedPool, error) {
    87  		pool := &initializedPool{
    88  			infra:   infraPool,
    89  			tier:    tier,
    90  			desired: desired,
    91  		}
    92  		pool.machines = func() ([]*initializedMachine, error) {
    93  			infraMachines, err := infraPool.GetMachines()
    94  			if err != nil {
    95  				return nil, err
    96  			}
    97  			machines := make([]*initializedMachine, len(infraMachines))
    98  			for i, infraMachine := range infraMachines {
    99  				machines[i] = initializeMachine(infraMachine, pool)
   100  				if machines[i].currentMachine.Updating || machines[i].currentMachine.Rebooting {
   101  					curr.Status = "maintaining"
   102  				}
   103  			}
   104  			sort.Sort(initializedMachines(machines))
   105  			return machines, nil
   106  		}
   107  
   108  		machines, err := pool.machines()
   109  		if err != nil {
   110  			return pool, err
   111  		}
   112  
   113  		var replace initializedMachines
   114  		var backReplacement int
   115  		for _, machine := range machines {
   116  			if req, _, _ := machine.infra.ReplacementRequired(); req {
   117  				replace = append(replace, machine)
   118  				continue
   119  			}
   120  			if machine.currentMachine.Joined {
   121  				backReplacement++
   122  			}
   123  		}
   124  
   125  		machinesPerDesired := 1
   126  		if desired.Nodes > 0 {
   127  			machinesPerDesired = pool.infra.DesiredMembers(desired.Nodes) / desired.Nodes
   128  		}
   129  
   130  		upscale := (desired.Nodes * machinesPerDesired) + len(replace) - len(machines)
   131  		if upscale > 0 {
   132  			pool.upscaling = upscale
   133  			return pool, nil
   134  		}
   135  
   136  		if len(replace) > 0 {
   137  			for backReplacement >= (desired.Nodes*machinesPerDesired) && len(replace) > 0 {
   138  				backReplacement--
   139  				pool.downscaling = append(pool.downscaling, replace[0])
   140  				replace = replace[1:]
   141  			}
   142  			return pool, nil
   143  		}
   144  
   145  		pool.downscaling = machines[(desired.Nodes * machinesPerDesired):]
   146  
   147  		return pool, nil
   148  	}
   149  
   150  	initializeMachine = func(machine infra.Machine, pool *initializedPool) *initializedMachine {
   151  
   152  		current := &Machine{
   153  			Metadata: MachineMetadata{
   154  				Tier:     pool.tier,
   155  				Provider: pool.desired.Provider,
   156  				Pool:     pool.desired.Pool,
   157  			},
   158  		}
   159  
   160  		var node *v1.Node
   161  		if k8s != nil {
   162  			var k8sNodeErr error
   163  			node, k8sNodeErr = k8s.GetNode(machine.ID())
   164  			if k8sNodeErr != nil {
   165  				if macherrs.IsNotFound(k8sNodeErr) {
   166  					node = nil
   167  				} else {
   168  					current.Unknown = true
   169  				}
   170  			}
   171  		}
   172  
   173  		naSpec, _ := nodeAgentsDesired.Get(machine.ID())
   174  		naCurr, _ := nodeAgentsCurrent.Get(machine.ID())
   175  
   176  		// Retry if kubeapi returns other error than "NotFound"
   177  
   178  		reconcile := func() error { return nil }
   179  		if node != nil && !current.Unknown {
   180  			reconcile = reconcileNodeFunc(*node, monitor, pool.desired, k8s, pool.tier, naSpec, naCurr)
   181  			current.Joined = true
   182  			for _, cond := range node.Status.Conditions {
   183  				if cond.Type == v1.NodeReady {
   184  					current.Ready = true
   185  					current.Updating = k8s.Tainted(node, kubernetes.Updating)
   186  					current.Rebooting = k8s.Tainted(node, kubernetes.Rebooting)
   187  				}
   188  			}
   189  		}
   190  
   191  		curr.Machines.Set(machine.ID(), current)
   192  
   193  		machineMonitor := monitor.WithField("machine", machine.ID())
   194  
   195  		naSpec.ChangesAllowed = !pool.desired.UpdatesDisabled
   196  		k8sSoftware := ParseString(desired.Spec.Versions.Kubernetes).DefineSoftware()
   197  
   198  		if !softwareDefines(*naSpec.Software, k8sSoftware) {
   199  			k8sSoftware.Merge(KubernetesSoftware(naCurr.Software), false)
   200  			if !softwareContains(*naSpec.Software, k8sSoftware) {
   201  				naSpec.Software.Merge(k8sSoftware, false)
   202  				machineMonitor.Debug("Kubernetes software desired")
   203  			}
   204  		}
   205  
   206  		initMachine := &initializedMachine{
   207  			infra:            machine,
   208  			currentNodeagent: naCurr,
   209  			desiredNodeagent: naSpec,
   210  			reconcile:        reconcile,
   211  			currentMachine:   current,
   212  			pool:             pool,
   213  			node:             node,
   214  		}
   215  
   216  		postInit(initMachine)
   217  
   218  		return initMachine
   219  	}
   220  
   221  	for providerName, provider := range providerPools {
   222  	pools:
   223  		for poolName, pool := range provider {
   224  			if desired.Spec.ControlPlane.Provider == providerName && desired.Spec.ControlPlane.Pool == poolName {
   225  				controlplane, err = initializePool(pool, desired.Spec.ControlPlane, Controlplane)
   226  				if err != nil {
   227  					return controlplane,
   228  						controlplaneMachines,
   229  						workers,
   230  						workerMachines,
   231  						initializeMachine,
   232  						uninitializeMachine,
   233  						err
   234  				}
   235  				controlplaneMachines, err = controlplane.machines()
   236  				if err != nil {
   237  					return controlplane,
   238  						controlplaneMachines,
   239  						workers,
   240  						workerMachines,
   241  						initializeMachine,
   242  						uninitializeMachine,
   243  						err
   244  				}
   245  				continue
   246  			}
   247  
   248  			for _, desiredPool := range desired.Spec.Workers {
   249  				if providerName == desiredPool.Provider && poolName == desiredPool.Pool {
   250  					workerPool, err := initializePool(pool, *desiredPool, Workers)
   251  					if err != nil {
   252  						return controlplane,
   253  							controlplaneMachines,
   254  							workers,
   255  							workerMachines,
   256  							initializeMachine,
   257  							uninitializeMachine,
   258  							err
   259  					}
   260  					workers = append(workers, workerPool)
   261  					initializedWorkerMachines, err := workerPool.machines()
   262  					if err != nil {
   263  						return controlplane,
   264  							controlplaneMachines,
   265  							workers,
   266  							workerMachines,
   267  							initializeMachine,
   268  							uninitializeMachine,
   269  							err
   270  					}
   271  					workerMachines = append(workerMachines, initializedWorkerMachines...)
   272  					continue pools
   273  				}
   274  			}
   275  		}
   276  	}
   277  
   278  	for _, machine := range append(controlplaneMachines, workerMachines...) {
   279  		if !machine.currentMachine.Ready {
   280  			curr.Status = "degraded"
   281  		}
   282  		if machine.currentMachine.Updating || !machine.currentMachine.Joined || !machine.currentMachine.FirewallIsReady {
   283  			curr.Status = "maintaining"
   284  			break
   285  		}
   286  	}
   287  
   288  	return controlplane,
   289  		controlplaneMachines,
   290  		workers,
   291  		workerMachines,
   292  		initializeMachine, func(id string) {
   293  			nodeAgentsDesired.Delete(id)
   294  			curr.Machines.Delete(id)
   295  		}, nil
   296  }
   297  
   298  func reconcileNodeFunc(node v1.Node, monitor mntr.Monitor, pool Pool, k8s *kubernetes.Client, tier Tier, naSpec *common.NodeAgentSpec, naCurr *common.NodeAgentCurrent) func() error {
   299  	n := &node
   300  	reconcileNode := false
   301  	reconcileMonitor := monitor.WithField("node", n.Name)
   302  	handleMaybe := func(maybeMonitorFields map[string]interface{}) {
   303  		if maybeMonitorFields != nil {
   304  			reconcileNode = true
   305  			reconcileMonitor = monitor.WithFields(maybeMonitorFields)
   306  		}
   307  	}
   308  
   309  	handleMaybe(reconcileLabel(n, "orbos.ch/pool", pool.Pool))
   310  	handleMaybe(reconcileLabel(n, "orbos.ch/tier", string(tier)))
   311  	handleMaybe(reconcileTaints(n, pool, k8s, naSpec, naCurr))
   312  
   313  	if !reconcileNode {
   314  		return func() error { return nil }
   315  	}
   316  	return func() error {
   317  		reconcileMonitor.Info("Reconciling node")
   318  		return k8s.UpdateNode(n)
   319  	}
   320  }
   321  
   322  func reconcileTaints(node *v1.Node, pool Pool, k8s *kubernetes.Client, naSpec *common.NodeAgentSpec, naCurr *common.NodeAgentCurrent) map[string]interface{} {
   323  	desiredTaints := pool.Taints.ToK8sTaints()
   324  	newTaints := append([]core.Taint{}, desiredTaints...)
   325  	updateTaints := false
   326  
   327  	// user defined taints
   328  outer:
   329  	for _, existing := range node.Spec.Taints {
   330  		if strings.HasPrefix(existing.Key, "node.kubernetes.io/") || strings.HasPrefix(existing.Key, kubernetes.TaintKeyPrefix) {
   331  			newTaints = append(newTaints, existing)
   332  			continue
   333  		}
   334  		for _, des := range desiredTaints {
   335  			if existing.Key == des.Key &&
   336  				existing.Effect == des.Effect &&
   337  				existing.Value == des.Value {
   338  				continue outer
   339  			}
   340  		}
   341  		updateTaints = true
   342  		break
   343  	}
   344  	// internal taints
   345  	if k8s.Tainted(node, kubernetes.Updating) && node.Labels["orbos.ch/updating"] == node.Status.NodeInfo.KubeletVersion {
   346  		newTaints = k8s.RemoveFromTaints(newTaints, kubernetes.Updating)
   347  		updateTaints = true
   348  	}
   349  
   350  	if k8s.Tainted(node, kubernetes.Rebooting) && naCurr.Booted.After(naSpec.RebootRequired) {
   351  		newTaints = k8s.RemoveFromTaints(newTaints, kubernetes.Rebooting)
   352  		updateTaints = true
   353  	}
   354  
   355  	if !updateTaints && len(node.Spec.Taints) == len(newTaints) {
   356  		return nil
   357  	}
   358  	node.Spec.Taints = newTaints
   359  	return map[string]interface{}{"taints": desiredTaints}
   360  }
   361  
   362  func reconcileLabel(node *v1.Node, key, value string) map[string]interface{} {
   363  	if node.Labels[key] == value {
   364  		return nil
   365  	}
   366  	if node.Labels == nil {
   367  		node.Labels = make(map[string]string)
   368  	}
   369  	node.Labels[key] = value
   370  	return map[string]interface{}{
   371  		fmt.Sprintf("label.%s", key): value,
   372  	}
   373  }