734 lines
27 KiB
Go
734 lines
27 KiB
Go
/*
|
|
Copyright 2016 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package gce
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ghodss/yaml"
|
|
gce "google.golang.org/api/compute/v1"
|
|
apiv1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/klog/v2"
|
|
|
|
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
|
|
"k8s.io/autoscaler/cluster-autoscaler/utils/gpu"
|
|
"k8s.io/autoscaler/cluster-autoscaler/utils/units"
|
|
)
|
|
|
|
// GceTemplateBuilder builds templates for GCE nodes.
|
|
type GceTemplateBuilder struct{}
|
|
|
|
// LocalSSDDiskSizeInGiB is the size of each local SSD in GiB
|
|
// (cf. https://cloud.google.com/compute/docs/disks/local-ssd)
|
|
const LocalSSDDiskSizeInGiB = 375
|
|
|
|
// TODO: This should be imported from sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/common/constants.go
|
|
// This key is applicable to both GCE and GKE
|
|
const gceCSITopologyKeyZone = "topology.gke.io/zone"
|
|
|
|
func (t *GceTemplateBuilder) getAcceleratorCount(accelerators []*gce.AcceleratorConfig) int64 {
|
|
count := int64(0)
|
|
for _, accelerator := range accelerators {
|
|
if strings.HasPrefix(accelerator.AcceleratorType, "nvidia-") {
|
|
count += accelerator.AcceleratorCount
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
// BuildCapacity builds a list of resource capacities given list of hardware.
|
|
func (t *GceTemplateBuilder) BuildCapacity(cpu int64, mem int64, accelerators []*gce.AcceleratorConfig, os OperatingSystem, osDistribution OperatingSystemDistribution, arch SystemArchitecture,
|
|
ephemeralStorage int64, ephemeralStorageLocalSSDCount int64, pods *int64, version string, r OsReservedCalculator) (apiv1.ResourceList, error) {
|
|
capacity := apiv1.ResourceList{}
|
|
if pods == nil {
|
|
capacity[apiv1.ResourcePods] = *resource.NewQuantity(110, resource.DecimalSI)
|
|
} else {
|
|
capacity[apiv1.ResourcePods] = *resource.NewQuantity(*pods, resource.DecimalSI)
|
|
}
|
|
|
|
capacity[apiv1.ResourceCPU] = *resource.NewQuantity(cpu, resource.DecimalSI)
|
|
memTotal := mem - r.CalculateKernelReserved(mem, os, osDistribution, arch, version)
|
|
capacity[apiv1.ResourceMemory] = *resource.NewQuantity(memTotal, resource.DecimalSI)
|
|
|
|
if accelerators != nil && len(accelerators) > 0 {
|
|
capacity[gpu.ResourceNvidiaGPU] = *resource.NewQuantity(t.getAcceleratorCount(accelerators), resource.DecimalSI)
|
|
}
|
|
|
|
if ephemeralStorage > 0 {
|
|
var storageTotal int64
|
|
if ephemeralStorageLocalSSDCount > 0 {
|
|
storageTotal = ephemeralStorage - EphemeralStorageOnLocalSSDFilesystemOverheadInBytes(ephemeralStorageLocalSSDCount, osDistribution)
|
|
} else {
|
|
storageTotal = ephemeralStorage - r.CalculateOSReservedEphemeralStorage(ephemeralStorage, os, osDistribution, arch, version)
|
|
}
|
|
capacity[apiv1.ResourceEphemeralStorage] = *resource.NewQuantity(int64(math.Max(float64(storageTotal), 0)), resource.DecimalSI)
|
|
}
|
|
|
|
return capacity, nil
|
|
}
|
|
|
|
// BuildAllocatableFromKubeEnv builds node allocatable based on capacity of the node and
|
|
// value of kubeEnv.
|
|
// KubeEnv is a multi-line string containing entries in the form of
|
|
// <RESOURCE_NAME>:<string>. One of the resources it contains is a list of
|
|
// kubelet arguments from which we can extract the resources reserved by
|
|
// the kubelet for its operation. Allocated resources are capacity minus reserved.
|
|
// If we fail to extract the reserved resources from kubeEnv (e.g it is in a
|
|
// wrong format or does not contain kubelet arguments), we return an error.
|
|
func (t *GceTemplateBuilder) BuildAllocatableFromKubeEnv(capacity apiv1.ResourceList, kubeEnv string, evictionHard *EvictionHard) (apiv1.ResourceList, error) {
|
|
kubeReserved, err := extractKubeReservedFromKubeEnv(kubeEnv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
reserved, err := parseKubeReserved(kubeReserved)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return t.CalculateAllocatable(capacity, reserved, evictionHard), nil
|
|
}
|
|
|
|
// CalculateAllocatable computes allocatable resources subtracting kube reserved values
|
|
// and kubelet eviction memory buffer from corresponding capacity.
|
|
func (t *GceTemplateBuilder) CalculateAllocatable(capacity apiv1.ResourceList, kubeReserved apiv1.ResourceList, evictionHard *EvictionHard) apiv1.ResourceList {
|
|
allocatable := apiv1.ResourceList{}
|
|
for key, value := range capacity {
|
|
quantity := value.DeepCopy()
|
|
if reservedQuantity, found := kubeReserved[key]; found {
|
|
quantity.Sub(reservedQuantity)
|
|
}
|
|
if key == apiv1.ResourceMemory {
|
|
quantity = *resource.NewQuantity(quantity.Value()-GetKubeletEvictionHardForMemory(evictionHard), resource.BinarySI)
|
|
}
|
|
if key == apiv1.ResourceEphemeralStorage {
|
|
quantity = *resource.NewQuantity(quantity.Value()-int64(GetKubeletEvictionHardForEphemeralStorage(value.Value(), evictionHard)), resource.BinarySI)
|
|
}
|
|
allocatable[key] = quantity
|
|
}
|
|
return allocatable
|
|
}
|
|
|
|
func getKubeEnvValueFromTemplateMetadata(template *gce.InstanceTemplate) (string, error) {
|
|
if template.Properties.Metadata == nil {
|
|
return "", fmt.Errorf("instance template %s has no metadata", template.Name)
|
|
}
|
|
for _, item := range template.Properties.Metadata.Items {
|
|
if item.Key == "kube-env" {
|
|
if item.Value == nil {
|
|
return "", fmt.Errorf("no kube-env content in metadata")
|
|
}
|
|
return *item.Value, nil
|
|
}
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// BuildNodeFromTemplate builds node from provided GCE template.
|
|
func (t *GceTemplateBuilder) BuildNodeFromTemplate(mig Mig, template *gce.InstanceTemplate, cpu int64, mem int64, pods *int64, reserved OsReservedCalculator) (*apiv1.Node, error) {
|
|
|
|
if template.Properties == nil {
|
|
return nil, fmt.Errorf("instance template %s has no properties", template.Name)
|
|
}
|
|
|
|
node := apiv1.Node{}
|
|
nodeName := fmt.Sprintf("%s-template-%d", template.Name, rand.Int63())
|
|
|
|
kubeEnvValue, err := getKubeEnvValueFromTemplateMetadata(template)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not obtain kube-env from template metadata; %v", err)
|
|
}
|
|
|
|
node.ObjectMeta = metav1.ObjectMeta{
|
|
Name: nodeName,
|
|
SelfLink: fmt.Sprintf("/api/v1/nodes/%s", nodeName),
|
|
Labels: map[string]string{},
|
|
}
|
|
|
|
// This call is safe even if kubeEnvValue is empty
|
|
os := extractOperatingSystemFromKubeEnv(kubeEnvValue)
|
|
if os == OperatingSystemUnknown {
|
|
return nil, fmt.Errorf("could not obtain os from kube-env from template metadata")
|
|
}
|
|
|
|
osDistribution := extractOperatingSystemDistributionFromKubeEnv(kubeEnvValue)
|
|
if osDistribution == OperatingSystemDistributionUnknown {
|
|
return nil, fmt.Errorf("could not obtain os-distribution from kube-env from template metadata")
|
|
}
|
|
arch := extractSystemArchitectureFromKubeEnv(kubeEnvValue)
|
|
if arch == UnknownArch {
|
|
return nil, fmt.Errorf("could not obtain arch from kube-env from template metadata")
|
|
}
|
|
|
|
var ephemeralStorage int64 = -1
|
|
ssdCount := ephemeralStorageLocalSSDCount(kubeEnvValue)
|
|
if ssdCount > 0 {
|
|
ephemeralStorage, err = getLocalSSDEphemeralStorageFromInstanceTemplateProperties(template.Properties, ssdCount)
|
|
} else if !isBootDiskEphemeralStorageWithInstanceTemplateDisabled(kubeEnvValue) {
|
|
ephemeralStorage, err = getBootDiskEphemeralStorageFromInstanceTemplateProperties(template.Properties)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not fetch ephemeral storage from instance template: %v", err)
|
|
}
|
|
|
|
capacity, err := t.BuildCapacity(cpu, mem, template.Properties.GuestAccelerators, os, osDistribution, arch, ephemeralStorage, ssdCount, pods, mig.Version(), reserved)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
node.Status = apiv1.NodeStatus{
|
|
Capacity: capacity,
|
|
}
|
|
var nodeAllocatable apiv1.ResourceList
|
|
|
|
if kubeEnvValue != "" {
|
|
// Extract labels
|
|
kubeEnvLabels, err := extractLabelsFromKubeEnv(kubeEnvValue)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
node.Labels = cloudprovider.JoinStringMaps(node.Labels, kubeEnvLabels)
|
|
|
|
// Extract taints
|
|
kubeEnvTaints, err := extractTaintsFromKubeEnv(kubeEnvValue)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
node.Spec.Taints = append(node.Spec.Taints, kubeEnvTaints...)
|
|
|
|
// Extract Eviction Hard
|
|
evictionHardFromKubeEnv, err := extractEvictionHardFromKubeEnv(kubeEnvValue)
|
|
if err != nil || len(evictionHardFromKubeEnv) == 0 {
|
|
klog.Warning("unable to get evictionHardFromKubeEnv values, continuing without it.")
|
|
}
|
|
evictionHard := ParseEvictionHardOrGetDefault(evictionHardFromKubeEnv)
|
|
|
|
if allocatable, err := t.BuildAllocatableFromKubeEnv(node.Status.Capacity, kubeEnvValue, evictionHard); err == nil {
|
|
nodeAllocatable = allocatable
|
|
}
|
|
}
|
|
|
|
if nodeAllocatable == nil {
|
|
klog.Warningf("could not extract kube-reserved from kubeEnv for mig %q, setting allocatable to capacity.", mig.GceRef().Name)
|
|
node.Status.Allocatable = node.Status.Capacity
|
|
} else {
|
|
node.Status.Allocatable = nodeAllocatable
|
|
}
|
|
// GenericLabels
|
|
labels, err := BuildGenericLabels(mig.GceRef(), template.Properties.MachineType, nodeName, os)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
node.Labels = cloudprovider.JoinStringMaps(node.Labels, labels)
|
|
|
|
// Ready status
|
|
node.Status.Conditions = cloudprovider.BuildReadyConditions()
|
|
return &node, nil
|
|
}
|
|
|
|
func ephemeralStorageLocalSSDCount(kubeEnvValue string) int64 {
|
|
v, found, err := extractAutoscalerVarFromKubeEnv(kubeEnvValue, "ephemeral_storage_local_ssd_count")
|
|
if err != nil {
|
|
klog.Warningf("cannot extract ephemeral_storage_local_ssd_count from kube-env, default to 0: %v", err)
|
|
return 0
|
|
}
|
|
|
|
if !found {
|
|
return 0
|
|
}
|
|
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
klog.Warningf("cannot parse ephemeral_storage_local_ssd_count value, default to 0: %v", err)
|
|
return 0
|
|
}
|
|
|
|
return int64(n)
|
|
}
|
|
|
|
func getLocalSSDEphemeralStorageFromInstanceTemplateProperties(instanceProperties *gce.InstanceProperties, ssdCount int64) (ephemeralStorage int64, err error) {
|
|
if instanceProperties.Disks == nil {
|
|
return 0, fmt.Errorf("instance properties disks is nil")
|
|
}
|
|
|
|
var count int64
|
|
for _, disk := range instanceProperties.Disks {
|
|
if disk != nil && disk.InitializeParams != nil {
|
|
if disk.Type == "SCRATCH" && disk.InitializeParams.DiskType == "local-ssd" {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
|
|
if count < ssdCount {
|
|
return 0, fmt.Errorf("actual local SSD count is lower than ephemeral_storage_local_ssd_count")
|
|
}
|
|
|
|
return ssdCount * LocalSSDDiskSizeInGiB * units.GiB, nil
|
|
}
|
|
|
|
// isBootDiskEphemeralStorageWithInstanceTemplateDisabled will allow bypassing Disk Size of Boot Disk from being
|
|
// picked up from Instance Template and used as Ephemeral Storage, in case other type of storage are used
|
|
// as ephemeral storage
|
|
func isBootDiskEphemeralStorageWithInstanceTemplateDisabled(kubeEnvValue string) bool {
|
|
v, found, err := extractAutoscalerVarFromKubeEnv(kubeEnvValue, "BLOCK_EPH_STORAGE_BOOT_DISK")
|
|
if err == nil && found && v == "true" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func getBootDiskEphemeralStorageFromInstanceTemplateProperties(instanceProperties *gce.InstanceProperties) (ephemeralStorage int64, err error) {
|
|
if instanceProperties.Disks == nil {
|
|
return 0, fmt.Errorf("unable to get ephemeral storage because instance properties disks is nil")
|
|
}
|
|
|
|
for _, disk := range instanceProperties.Disks {
|
|
if disk != nil && disk.InitializeParams != nil {
|
|
if disk.Boot {
|
|
return disk.InitializeParams.DiskSizeGb * units.GiB, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0, fmt.Errorf("unable to get ephemeral storage, either no attached disks or no disk with boot=true")
|
|
}
|
|
|
|
// BuildGenericLabels builds basic labels that should be present on every GCE node,
|
|
// including hostname, zone etc.
|
|
func BuildGenericLabels(ref GceRef, machineType string, nodeName string, os OperatingSystem) (map[string]string, error) {
|
|
result := make(map[string]string)
|
|
|
|
if os == OperatingSystemUnknown {
|
|
return nil, fmt.Errorf("unknown operating system passed")
|
|
}
|
|
|
|
// TODO: extract it somehow
|
|
result[apiv1.LabelArchStable] = string(DefaultArch)
|
|
result[apiv1.LabelOSStable] = string(os)
|
|
|
|
result[apiv1.LabelInstanceTypeStable] = machineType
|
|
ix := strings.LastIndex(ref.Zone, "-")
|
|
if ix == -1 {
|
|
return nil, fmt.Errorf("unexpected zone: %s", ref.Zone)
|
|
}
|
|
result[apiv1.LabelTopologyRegion] = ref.Zone[:ix]
|
|
result[apiv1.LabelTopologyZone] = ref.Zone
|
|
result[gceCSITopologyKeyZone] = ref.Zone
|
|
result[apiv1.LabelHostname] = nodeName
|
|
return result, nil
|
|
}
|
|
|
|
func parseKubeReserved(kubeReserved string) (apiv1.ResourceList, error) {
|
|
resourcesMap, err := parseKeyValueListToMap(kubeReserved)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to extract kube-reserved from kube-env: %q", err)
|
|
}
|
|
reservedResources := apiv1.ResourceList{}
|
|
for name, quantity := range resourcesMap {
|
|
switch apiv1.ResourceName(name) {
|
|
case apiv1.ResourceCPU, apiv1.ResourceMemory, apiv1.ResourceEphemeralStorage:
|
|
if q, err := resource.ParseQuantity(quantity); err == nil && q.Sign() >= 0 {
|
|
reservedResources[apiv1.ResourceName(name)] = q
|
|
}
|
|
default:
|
|
klog.Warningf("ignoring resource from kube-reserved: %q", name)
|
|
}
|
|
}
|
|
return reservedResources, nil
|
|
}
|
|
|
|
// GetLabelsFromTemplate returns labels from instance template
|
|
func GetLabelsFromTemplate(template *gce.InstanceTemplate) (map[string]string, error) {
|
|
kubeEnv, err := getKubeEnvValueFromTemplateMetadata(template)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return extractLabelsFromKubeEnv(kubeEnv)
|
|
}
|
|
|
|
func extractLabelsFromKubeEnv(kubeEnv string) (map[string]string, error) {
|
|
// In v1.10+, labels are only exposed for the autoscaler via AUTOSCALER_ENV_VARS
|
|
// see kubernetes/kubernetes#61119. We try AUTOSCALER_ENV_VARS first, then
|
|
// fall back to the old way.
|
|
labels, found, err := extractAutoscalerVarFromKubeEnv(kubeEnv, "node_labels")
|
|
if err != nil {
|
|
klog.Errorf("error while trying to extract node_labels from AUTOSCALER_ENV_VARS: %v", err)
|
|
}
|
|
if !found {
|
|
labels, err = extractFromKubeEnv(kubeEnv, "NODE_LABELS")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return parseKeyValueListToMap(labels)
|
|
}
|
|
|
|
// GetTaintsFromTemplate returns labels from instance template
|
|
func GetTaintsFromTemplate(template *gce.InstanceTemplate) ([]apiv1.Taint, error) {
|
|
kubeEnv, err := getKubeEnvValueFromTemplateMetadata(template)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return extractTaintsFromKubeEnv(kubeEnv)
|
|
}
|
|
|
|
func extractTaintsFromKubeEnv(kubeEnv string) ([]apiv1.Taint, error) {
|
|
// In v1.10+, taints are only exposed for the autoscaler via AUTOSCALER_ENV_VARS
|
|
// see kubernetes/kubernetes#61119. We try AUTOSCALER_ENV_VARS first, then
|
|
// fall back to the old way.
|
|
taints, found, err := extractAutoscalerVarFromKubeEnv(kubeEnv, "node_taints")
|
|
if err != nil {
|
|
klog.Errorf("error while trying to extract node_taints from AUTOSCALER_ENV_VARS: %v", err)
|
|
}
|
|
if !found {
|
|
taints, err = extractFromKubeEnv(kubeEnv, "NODE_TAINTS")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
taintMap, err := parseKeyValueListToMap(taints)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buildTaints(taintMap)
|
|
}
|
|
|
|
func extractKubeReservedFromKubeEnv(kubeEnv string) (string, error) {
|
|
// In v1.10+, kube-reserved is only exposed for the autoscaler via AUTOSCALER_ENV_VARS
|
|
// see kubernetes/kubernetes#61119. We try AUTOSCALER_ENV_VARS first, then
|
|
// fall back to the old way.
|
|
kubeReserved, found, err := extractAutoscalerVarFromKubeEnv(kubeEnv, "kube_reserved")
|
|
if err != nil {
|
|
klog.Errorf("error while trying to extract kube_reserved from AUTOSCALER_ENV_VARS: %v", err)
|
|
}
|
|
if !found {
|
|
kubeletArgs, err := extractFromKubeEnv(kubeEnv, "KUBELET_TEST_ARGS")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
resourcesRegexp := regexp.MustCompile(`--kube-reserved=([^ ]+)`)
|
|
|
|
matches := resourcesRegexp.FindStringSubmatch(kubeletArgs)
|
|
if len(matches) > 1 {
|
|
return matches[1], nil
|
|
}
|
|
return "", fmt.Errorf("kube-reserved not in kubelet args in kube-env: %q", kubeletArgs)
|
|
}
|
|
return kubeReserved, nil
|
|
}
|
|
|
|
// OperatingSystem denotes operating system used by nodes coming from node group
|
|
type OperatingSystem string
|
|
|
|
const (
|
|
// OperatingSystemUnknown is used if operating system is unknown
|
|
OperatingSystemUnknown OperatingSystem = ""
|
|
// OperatingSystemLinux is used if operating system is Linux
|
|
OperatingSystemLinux OperatingSystem = "linux"
|
|
// OperatingSystemWindows is used if operating system is Windows
|
|
OperatingSystemWindows OperatingSystem = "windows"
|
|
|
|
// OperatingSystemDefault defines which operating system will be assumed if not explicitly passed via AUTOSCALER_ENV_VARS
|
|
OperatingSystemDefault = OperatingSystemLinux
|
|
)
|
|
|
|
func extractOperatingSystemFromKubeEnv(kubeEnv string) OperatingSystem {
|
|
osValue, found, err := extractAutoscalerVarFromKubeEnv(kubeEnv, "os")
|
|
if err != nil {
|
|
klog.Errorf("error while obtaining os from AUTOSCALER_ENV_VARS; %v", err)
|
|
return OperatingSystemUnknown
|
|
}
|
|
|
|
if !found {
|
|
klog.Warningf("no os defined in AUTOSCALER_ENV_VARS; using default %v", OperatingSystemDefault)
|
|
return OperatingSystemDefault
|
|
}
|
|
|
|
switch osValue {
|
|
case string(OperatingSystemLinux):
|
|
return OperatingSystemLinux
|
|
case string(OperatingSystemWindows):
|
|
return OperatingSystemWindows
|
|
default:
|
|
klog.Errorf("unexpected os=%v passed via AUTOSCALER_ENV_VARS", osValue)
|
|
return OperatingSystemUnknown
|
|
}
|
|
}
|
|
|
|
// OperatingSystemImage denotes image of the operating system used by nodes coming from node group
|
|
type OperatingSystemImage string
|
|
|
|
const (
|
|
// OperatingSystemImageUnknown is used if operating distribution system is unknown
|
|
OperatingSystemImageUnknown OperatingSystemImage = ""
|
|
// OperatingSystemImageUbuntu is used if operating distribution system is Ubuntu
|
|
OperatingSystemImageUbuntu OperatingSystemImage = "ubuntu"
|
|
// OperatingSystemImageWindowsLTSC is used if operating distribution system is Windows LTSC
|
|
OperatingSystemImageWindowsLTSC OperatingSystemImage = "windows_ltsc"
|
|
// OperatingSystemImageWindowsSAC is used if operating distribution system is Windows SAC
|
|
OperatingSystemImageWindowsSAC OperatingSystemImage = "windows_sac"
|
|
// OperatingSystemImageCOS is used if operating distribution system is COS
|
|
OperatingSystemImageCOS OperatingSystemImage = "cos"
|
|
// OperatingSystemImageCOSContainerd is used if operating distribution system is COS Containerd
|
|
OperatingSystemImageCOSContainerd OperatingSystemImage = "cos_containerd"
|
|
// OperatingSystemImageUbuntuContainerd is used if operating distribution system is Ubuntu Containerd
|
|
OperatingSystemImageUbuntuContainerd OperatingSystemImage = "ubuntu_containerd"
|
|
// OperatingSystemImageWindowsLTSCContainerd is used if operating distribution system is Windows LTSC Containerd
|
|
OperatingSystemImageWindowsLTSCContainerd OperatingSystemImage = "windows_ltsc_containerd"
|
|
// OperatingSystemImageWindowsSACContainerd is used if operating distribution system is Windows SAC Containerd
|
|
OperatingSystemImageWindowsSACContainerd OperatingSystemImage = "windows_sac_containerd"
|
|
|
|
// OperatingSystemImageDefault defines which operating system will be assumed as default.
|
|
OperatingSystemImageDefault = OperatingSystemImageCOSContainerd
|
|
)
|
|
|
|
// OperatingSystemDistribution denotes distribution of the operating system used by nodes coming from node group
|
|
type OperatingSystemDistribution string
|
|
|
|
const (
|
|
// OperatingSystemDistributionUnknown is used if operating distribution system is unknown
|
|
OperatingSystemDistributionUnknown OperatingSystemDistribution = ""
|
|
// OperatingSystemDistributionUbuntu is used if operating distribution system is Ubuntu
|
|
OperatingSystemDistributionUbuntu OperatingSystemDistribution = "ubuntu"
|
|
// OperatingSystemDistributionWindowsLTSC is used if operating distribution system is Windows LTSC
|
|
OperatingSystemDistributionWindowsLTSC OperatingSystemDistribution = "windows_ltsc"
|
|
// OperatingSystemDistributionWindowsSAC is used if operating distribution system is Windows SAC
|
|
OperatingSystemDistributionWindowsSAC OperatingSystemDistribution = "windows_sac"
|
|
// OperatingSystemDistributionCOS is used if operating distribution system is COS
|
|
OperatingSystemDistributionCOS OperatingSystemDistribution = "cos"
|
|
|
|
// OperatingSystemDistributionDefault defines which operating system will be assumed if not explicitly passed via AUTOSCALER_ENV_VARS
|
|
OperatingSystemDistributionDefault = OperatingSystemDistributionCOS
|
|
)
|
|
|
|
func extractOperatingSystemDistributionFromImageType(imageType string) OperatingSystemDistribution {
|
|
switch imageType {
|
|
case string(OperatingSystemImageUbuntu), string(OperatingSystemImageUbuntuContainerd):
|
|
return OperatingSystemDistributionUbuntu
|
|
case string(OperatingSystemImageWindowsLTSC), string(OperatingSystemImageWindowsLTSCContainerd):
|
|
return OperatingSystemDistributionWindowsLTSC
|
|
case string(OperatingSystemImageWindowsSAC), string(OperatingSystemImageWindowsSACContainerd):
|
|
return OperatingSystemDistributionWindowsSAC
|
|
case string(OperatingSystemImageCOS), string(OperatingSystemImageCOSContainerd):
|
|
return OperatingSystemDistributionCOS
|
|
default:
|
|
return OperatingSystemDistributionUnknown
|
|
}
|
|
}
|
|
|
|
// SystemArchitecture denotes distribution of the System Architecture used by nodes coming from node group
|
|
type SystemArchitecture string
|
|
|
|
const (
|
|
// UnknownArch is used if the Architecture is Unknown
|
|
UnknownArch SystemArchitecture = ""
|
|
// Amd64 is used if the Architecture is x86_64
|
|
Amd64 SystemArchitecture = "amd64"
|
|
// Arm64 is used if the Architecture is ARM
|
|
Arm64 SystemArchitecture = "arm64"
|
|
// DefaultArch is used if the Architecture is used as a fallback if not passed by AUTOSCALER_ENV_VARS
|
|
DefaultArch SystemArchitecture = Amd64
|
|
)
|
|
|
|
func extractSystemArchitectureFromKubeEnv(kubeEnv string) SystemArchitecture {
|
|
arch, found, err := extractAutoscalerVarFromKubeEnv(kubeEnv, "arch")
|
|
if err != nil {
|
|
klog.Errorf("error while obtaining arch from AUTOSCALER_ENV_VARS; using default %v", err)
|
|
return UnknownArch
|
|
}
|
|
if !found {
|
|
klog.V(4).Infof("no arch defined in AUTOSCALER_ENV_VARS; using default %v", err)
|
|
return DefaultArch
|
|
}
|
|
switch arch {
|
|
case string(Arm64):
|
|
return Arm64
|
|
case string(Amd64):
|
|
return Amd64
|
|
default:
|
|
return UnknownArch
|
|
}
|
|
}
|
|
|
|
func extractOperatingSystemDistributionFromKubeEnv(kubeEnv string) OperatingSystemDistribution {
|
|
osDistributionValue, found, err := extractAutoscalerVarFromKubeEnv(kubeEnv, "os_distribution")
|
|
if err != nil {
|
|
klog.Errorf("error while obtaining os from AUTOSCALER_ENV_VARS; %v", err)
|
|
return OperatingSystemDistributionUnknown
|
|
}
|
|
|
|
if !found {
|
|
klog.Warningf("no os-distribution defined in AUTOSCALER_ENV_VARS; using default %v", OperatingSystemDistributionDefault)
|
|
return OperatingSystemDistributionDefault
|
|
}
|
|
|
|
switch osDistributionValue {
|
|
|
|
case string(OperatingSystemDistributionUbuntu):
|
|
return OperatingSystemDistributionUbuntu
|
|
case string(OperatingSystemDistributionWindowsLTSC):
|
|
return OperatingSystemDistributionWindowsLTSC
|
|
case string(OperatingSystemDistributionWindowsSAC):
|
|
return OperatingSystemDistributionWindowsSAC
|
|
case string(OperatingSystemDistributionCOS):
|
|
return OperatingSystemDistributionCOS
|
|
// Deprecated
|
|
case "cos_containerd":
|
|
klog.Warning("cos_containerd os distribution is deprecated")
|
|
return OperatingSystemDistributionCOS
|
|
// Deprecated
|
|
case "ubuntu_containerd":
|
|
klog.Warning("ubuntu_containerd os distribution is deprecated")
|
|
return OperatingSystemDistributionUbuntu
|
|
default:
|
|
klog.Errorf("unexpected os-distribution=%v passed via AUTOSCALER_ENV_VARS", osDistributionValue)
|
|
return OperatingSystemDistributionUnknown
|
|
}
|
|
}
|
|
|
|
func getFloat64Option(options map[string]string, templateName, name string) (float64, bool) {
|
|
raw, ok := options[name]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
|
|
option, err := strconv.ParseFloat(raw, 64)
|
|
if err != nil {
|
|
klog.Warningf("failed to convert autoscaling_options option %q (value %q) for MIG %q to float: %v", name, raw, templateName, err)
|
|
return 0, false
|
|
}
|
|
|
|
return option, true
|
|
}
|
|
|
|
func getDurationOption(options map[string]string, templateName, name string) (time.Duration, bool) {
|
|
raw, ok := options[name]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
|
|
option, err := time.ParseDuration(raw)
|
|
if err != nil {
|
|
klog.Warningf("failed to convert autoscaling_options option %q (value %q) for MIG %q to duration: %v", name, raw, templateName, err)
|
|
return 0, false
|
|
}
|
|
|
|
return option, true
|
|
}
|
|
|
|
func extractAutoscalingOptionsFromKubeEnv(kubeEnvValue string) (map[string]string, error) {
|
|
optionsAsString, found, err := extractAutoscalerVarFromKubeEnv(kubeEnvValue, "autoscaling_options")
|
|
if err != nil {
|
|
klog.Warningf("error while obtaining autoscaling_options from AUTOSCALER_ENV_VARS: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
if !found {
|
|
klog.V(5).Info("no autoscaling_options defined in AUTOSCALER_ENV_VARS")
|
|
return make(map[string]string), nil
|
|
}
|
|
|
|
return parseKeyValueListToMap(optionsAsString)
|
|
}
|
|
|
|
func extractEvictionHardFromKubeEnv(kubeEnvValue string) (map[string]string, error) {
|
|
evictionHardAsString, found, err := extractAutoscalerVarFromKubeEnv(kubeEnvValue, "evictionHard")
|
|
if err != nil {
|
|
klog.Warning("error while obtaining eviction-hard from AUTOSCALER_ENV_VARS; %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
if !found {
|
|
klog.Warning("no evictionHard defined in AUTOSCALER_ENV_VARS;")
|
|
return make(map[string]string), nil
|
|
}
|
|
|
|
return parseKeyValueListToMap(evictionHardAsString)
|
|
}
|
|
|
|
func extractAutoscalerVarFromKubeEnv(kubeEnv, name string) (value string, found bool, err error) {
|
|
const autoscalerVars = "AUTOSCALER_ENV_VARS"
|
|
autoscalerVals, err := extractFromKubeEnv(kubeEnv, autoscalerVars)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
|
|
if strings.Trim(autoscalerVals, " ") == "" {
|
|
// empty or not present AUTOSCALER_ENV_VARS
|
|
return "", false, nil
|
|
}
|
|
|
|
for _, val := range strings.Split(autoscalerVals, ";") {
|
|
val = strings.Trim(val, " ")
|
|
items := strings.SplitN(val, "=", 2)
|
|
if len(items) != 2 {
|
|
return "", false, fmt.Errorf("malformed autoscaler var: %s", val)
|
|
}
|
|
if strings.Trim(items[0], " ") == name {
|
|
return strings.Trim(items[1], " \"'"), true, nil
|
|
}
|
|
}
|
|
klog.V(5).Infof("var %s not found in %s: %v", name, autoscalerVars, autoscalerVals)
|
|
return "", false, nil
|
|
}
|
|
|
|
func extractFromKubeEnv(kubeEnv, resource string) (string, error) {
|
|
kubeEnvMap := make(map[string]string)
|
|
err := yaml.Unmarshal([]byte(kubeEnv), &kubeEnvMap)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error unmarshalling kubeEnv: %v", err)
|
|
}
|
|
return kubeEnvMap[resource], nil
|
|
}
|
|
|
|
func parseKeyValueListToMap(kvList string) (map[string]string, error) {
|
|
result := make(map[string]string)
|
|
if len(kvList) == 0 {
|
|
return result, nil
|
|
}
|
|
for _, keyValue := range strings.Split(kvList, ",") {
|
|
kvItems := strings.SplitN(keyValue, "=", 2)
|
|
if len(kvItems) != 2 {
|
|
return nil, fmt.Errorf("error while parsing key-value list, val: %s", keyValue)
|
|
}
|
|
result[kvItems[0]] = kvItems[1]
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func buildTaints(kubeEnvTaints map[string]string) ([]apiv1.Taint, error) {
|
|
taints := make([]apiv1.Taint, 0)
|
|
for key, value := range kubeEnvTaints {
|
|
values := strings.SplitN(value, ":", 2)
|
|
if len(values) != 2 {
|
|
return nil, fmt.Errorf("error while parsing node taint value and effect: %s", value)
|
|
}
|
|
taints = append(taints, apiv1.Taint{
|
|
Key: key,
|
|
Value: values[0],
|
|
Effect: apiv1.TaintEffect(values[1]),
|
|
})
|
|
}
|
|
return taints, nil
|
|
}
|