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 }