diff --git a/hack/stack.sh b/hack/stack.sh index 9063748a..b4b405b9 100755 --- a/hack/stack.sh +++ b/hack/stack.sh @@ -74,25 +74,26 @@ newgrp docker < dict: } +class ClusterAutoscalerHelmRepository(Base): + def get_object(self) -> objects.HelmRepository: + return objects.HelmRepository( + self.api, + { + "apiVersion": objects.HelmRepository.version, + "kind": objects.HelmRepository.kind, + "metadata": { + "name": "autoscaler", + "namespace": "magnum-system", + }, + "spec": { + "interval": "1m", + "url": "https://kubernetes.github.io/autoscaler", + }, + }, + ) + + +class ClusterAutoscalerHelmRelease(ClusterBase): + def get_object(self) -> objects.HelmRelease: + cluster_name = utils.get_or_generate_cluster_api_name(self.api, self.cluster) + return objects.HelmRelease( + self.api, + { + "apiVersion": objects.HelmRelease.version, + "kind": objects.HelmRelease.kind, + "metadata": { + "name": cluster_name, + "namespace": "magnum-system", + }, + "spec": { + "interval": "60s", + "chart": { + "spec": { + "chart": "cluster-autoscaler", + "version": AUTOSCALER_HELM_CHART_VERSION, + "sourceRef": { + "kind": objects.HelmRepository.kind, + "name": "autoscaler", + }, + }, + }, + "values": { + "fullnameOverride": f"{cluster_name}-autoscaler", + "cloudProvider": "clusterapi", + "clusterAPIMode": "kubeconfig-incluster", + "clusterAPIKubeconfigSecret": f"{cluster_name}-kubeconfig", + "autoDiscovery": { + "clusterName": cluster_name, + }, + "nodeSelector": { + "openstack-control-plane": "enabled", + }, + }, + }, + }, + ) + + class ClusterResourcesConfigMap(ClusterBase): def __init__( self, @@ -103,7 +164,6 @@ def get_object(self) -> pykube.ConfigMap: manifests_path = pkg_resources.resource_filename( "magnum_cluster_api", "manifests" ) - calico_version = utils.get_cluster_label(self.cluster, "calico_tag", CALICO_TAG) ccm_version = utils.get_cluster_label( self.cluster, "cloud_provider_tag", CLOUD_PROVIDER_TAG @@ -201,6 +261,16 @@ def get_object(self) -> pykube.ConfigMap: }, ) + def get_or_none(self) -> objects.Cluster: + return pykube.ConfigMap.objects( + self.api, namespace="magnum-system" + ).get_or_none(name=self.cluster.uuid) + + def delete(self): + cr_cm = self.get_or_none() + if cr_cm: + cr_cm.delete() + class ClusterResourceSet(ClusterBase): def get_object(self) -> objects.ClusterResourceSet: @@ -1073,6 +1143,9 @@ def get_or_none(self) -> objects.Cluster: ) def get_object(self) -> objects.Cluster: + auto_scaling_enabled = utils.get_cluster_label_as_bool( + self.cluster, "auto_scaling_enabled", False + ) return objects.Cluster( self.api, { @@ -1120,7 +1193,17 @@ def get_object(self) -> objects.Cluster: { "class": "default-worker", "name": ng.name, - "replicas": ng.node_count, + "replicas": None + if auto_scaling_enabled + else ng.node_count, + "metadata": { + "annotations": { + "cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size": f"{utils.get_node_group_min_node_count(ng)}", # noqa: E501 + "cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size": f"{utils.get_node_group_max_node_count(ng)}", # noqa: E501 + } + } + if auto_scaling_enabled + else {}, "failureDomain": utils.get_cluster_label( self.cluster, "availability_zone", "" ), @@ -1289,6 +1372,35 @@ def delete(self): capi_cluster.delete() +def set_autoscaler_metadata_in_machinedeployment( + api: pykube.HTTPClient, + cluster: magnum_objects.Cluster, + nodegroup: magnum_objects.NodeGroup, +): + # Set autoscaler annotations to MachineDeployment(MD)s because annotations in Cluster topology + # are not propogated to MDs. Upstream issue: https://github.com/kubernetes-sigs/cluster-api/pull/7088 + + if not utils.get_cluster_label_as_bool(cluster, "auto_scaling_enabled", False): + return + mds = objects.MachineDeployment.objects(api).filter( + namespace="magnum-system", + selector={ + "cluster.x-k8s.io/cluster-name": utils.get_or_generate_cluster_api_name( + api, cluster + ), + "topology.cluster.x-k8s.io/deployment-name": nodegroup.name, + }, + ) + for md in mds: + md.obj["metadata"]["annotations"][ + "cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size" + ] = f"{utils.get_node_group_max_node_count(nodegroup)}" + md.obj["metadata"]["annotations"][ + "cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size" + ] = f"{utils.get_node_group_min_node_count(nodegroup)}" + md.update() + + def apply_cluster_from_magnum_cluster( context: context.RequestContext, api: pykube.HTTPClient, @@ -1320,6 +1432,9 @@ def apply_cluster_from_magnum_cluster( ClusterResourcesConfigMap(context, api, cluster).apply() ClusterResourceSet(api, cluster).apply() Cluster(context, api, cluster).apply() + ClusterAutoscalerHelmRepository(api).apply() + if utils.get_cluster_label_as_bool(cluster, "auto_scaling_enabled", False): + ClusterAutoscalerHelmRelease(api, cluster).apply() def get_kubeadm_control_plane( diff --git a/magnum_cluster_api/utils.py b/magnum_cluster_api/utils.py index c4368bdd..79e0b0a1 100644 --- a/magnum_cluster_api/utils.py +++ b/magnum_cluster_api/utils.py @@ -33,7 +33,7 @@ def generate_cluster_api_name( api: pykube.HTTPClient, cluster: magnum_objects.Cluster, ) -> str: - name = f"{cluster.name}-{shortuuid.uuid()[:10].lower()}" + name = f"{cluster.name}-{shortuuid.uuid()[:10].lower()}".replace(".", "-") if ( objects.Cluster.objects(api) .filter(namespace="magnum-system") @@ -81,6 +81,25 @@ def get_node_group_label( return node_group.labels.get(key, get_cluster_label(cluster, key, default)) +def get_node_group_min_node_count( + node_group: magnum_objects.NodeGroup, + default=1, +) -> int: + if node_group.min_node_count == 0: + return default + else: + return node_group.min_node_count + + +def get_node_group_max_node_count( + node_group: magnum_objects.NodeGroup, +) -> int: + if node_group.max_node_count is None: + return get_node_group_min_node_count(node_group) + 1 + else: + return node_group.max_node_count + + def get_cluster_label(cluster: magnum_objects.Cluster, key: str, default: str) -> str: return cluster.labels.get( key, get_cluster_template_label(cluster.cluster_template, key, default)