Skip to content

Commit

Permalink
Support Cluster API - Cluster AutoScaler integration
Browse files Browse the repository at this point in the history
Signed-off-by: Archisman <archisman@obmondo.com>
  • Loading branch information
Archisman committed Nov 11, 2024
1 parent b50e11a commit fe80b49
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 24 deletions.
3 changes: 3 additions & 0 deletions cmd/bootstrap_cluster/bootstrap_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ func BootstrapCluster(ctx *cli.Context) error {
"clusterctl move --kubeconfig %s --namespace %s --to-kubeconfig %s",
constants.OutputPathManagementClusterKubeconfig, capiClusterNamespace, constants.OutputPathProvisionedClusterKubeconfig,
))

// Sync cluster-autoscaler ArgoCD App.
utils.ExecuteCommandOrDie("argocd app sync argo-cd/cluster-autoscaler")
}

slog.Info("Cluster provisioned successfully 🎉🎉 !", slog.String("kubeconfig", constants.OutputPathProvisionedClusterKubeconfig))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ global:
{{- end }}
kubernetes:
version: {{ .K8sVersion }}
capa:
autoScaler: true
kubeaid:
repo: {{ .KubeaidForkURL }}
kubeaidConfig:
Expand All @@ -33,5 +31,5 @@ aws:
instanceType: {{ .ControlPlaneInstanceType }}
ami:
id: {{ .ControlPlaneAMI }}
machinePools:
{{ .MachinePools | toYaml | indent 2 }}
nodeGroups:
{{ .NodeGroups | toYaml | indent 2 }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
cluster-autoscaler:
cloudProvider: clusterapi

# Cluster API mode : incluster-incluster / incluster-kubeconfig / kubeconfig-incluster / kubeconfig-kubeconfig / single-kubeconfig.
# Syntax: workloadClusterMode-ManagementClusterMode
#
# For 'kubeconfig-kubeconfig', 'incluster-kubeconfig' and 'single-kubeconfig' you always must
# mount the external kubeconfig using either 'extraVolumeSecrets' or 'extraMounts' and
# 'extraVolumes'.
#
# If you dont set 'clusterAPIKubeconfigSecret'and thus use an in-cluster config or want to use a
# non CAPI generated kubeconfig you must do so for the workload kubeconfig as well
#
# REFER : https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/clusterapi/README.md#connecting-cluster-autoscaler-to-cluster-api-management-and-workload-clusters
clusterAPIMode: incluster-incluster

autoDiscovery:
clusterName: {{ .ClusterName }}
namespace: {{ .CAPIClusterNamespace }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cluster-autoscaler
namespace: argo-cd

spec:
destination:
namespace: cluster-autoscaler
server: https://kubernetes.default.svc
sources:
- repoURL: {{ .KubeaidForkURL }}
path: argocd-helm-charts/cluster-autoscaler
targetRevision: HEAD
helm:
valueFiles:
- $values/k8s/{{ .ClusterName }}/argocd-apps/cluster-autoscaler.values.yaml
- repoURL: {{ .KubeaidConfigForkURL }}
targetRevision: HEAD
ref: values
project: default
syncPolicy:
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
4 changes: 2 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ type (
ControlPlaneAMI string `yaml:"controlPlaneAMI" validate:"required"`
ControlPlaneReplicas int `yaml:"controlPlaneReplicas" validate:"required"`

MachinePools []AWSMachinePool `yaml:"machinePools"`
NodeGroups []NodeGroups `yaml:"nodeGroups"`
}

AWSMachinePool struct {
NodeGroups struct {
Name string `yaml:"name" validate:"required"`
Replicas int `yaml:"replicas" validate:"required"`
InstanceType string `yaml:"instanceType" validate:"required"`
Expand Down
4 changes: 3 additions & 1 deletion config/templates/aws.sample.config.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ cloud:
controlPlaneAMI: {{ .AMI }}
controlPlaneReplicas: 1

machinePools:
nodeGroups:
- name: primary
ami:
id: {{ .AMI }}
Expand All @@ -42,6 +42,8 @@ cloud:
# REFER : https://cluster-api.sigs.k8s.io/developer/architecture/controllers/metadata-propagation#machine
# labels: []

# taints []

monitoring:
kubePrometheusVersion: v0.14.0
grafanaURL: ""
Expand Down
34 changes: 17 additions & 17 deletions config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
)

var (
// A user defined MachinePool label key should belong to one of these domains.
// A user defined NodeGroup label key should belong to one of these domains.
// REFER : https://cluster-api.sigs.k8s.io/developer/architecture/controllers/metadata-propagation#machine.
ValidMachinePoolLabelDomains = []string{
ValidNodeGroupLabelDomains = []string{
"node.cluster.x-k8s.io/",
"node-role.kubernetes.io/",
"node-restriction.kubernetes.io/",
Expand All @@ -23,7 +23,7 @@ var (

// Validates the parsed config.
// Panics on failure.
// TODO : Extract the MachinePool labels and taints validation task from 'cloud specifics' section.
// TODO : Extract the NodeGroup labels and taints validation task from 'cloud specifics' section.
func validateConfig(config *Config) {
// Validate based on struct tags.
validate := validator.New(validator.WithRequiredStructEnabled())
Expand All @@ -35,38 +35,38 @@ func validateConfig(config *Config) {
switch {
case config.Cloud.AWS != nil:

for _, machinePool := range config.Cloud.AWS.MachinePools {
// Validate MachinePools labels.
for _, nodeGroup := range config.Cloud.AWS.NodeGroups {
// Validate NodeGroups labels.
//
// (1) according to Kubernetes specifications.
if err := labels.Validate(machinePool.Labels); err != nil {
log.Fatalf("MachinePool labels validation failed : %v", err)
if err := labels.Validate(nodeGroup.Labels); err != nil {
log.Fatalf("NodeGroup labels validation failed : %v", err)
}
//
// (2) according to ClusterAPI specifications.
for key := range machinePool.Labels {
for key := range nodeGroup.Labels {
// Check if the label belongs to a domain considered valid by ClusterAPI.
isValidMachinePoolLabelDomain := false
for _, machinePoolLabelDomains := range ValidMachinePoolLabelDomains {
if strings.HasPrefix(key, machinePoolLabelDomains) {
isValidMachinePoolLabelDomain = true
isValidNodeGroupLabelDomain := false
for _, nodeGroupLabelDomains := range ValidNodeGroupLabelDomains {
if strings.HasPrefix(key, nodeGroupLabelDomains) {
isValidNodeGroupLabelDomain = true
break
}
}
if !isValidMachinePoolLabelDomain {
slog.Error("MachinePool label key should belong to one of these domains", slog.Any("domains", ValidMachinePoolLabelDomains))
if !isValidNodeGroupLabelDomain {
slog.Error("NodeGroup label key should belong to one of these domains", slog.Any("domains", ValidNodeGroupLabelDomains))
os.Exit(1)
}
}

taintsAsKVPairs := map[string]string{}
for _, taint := range machinePool.Taints {
for _, taint := range nodeGroup.Taints {
taintsAsKVPairs[taint.Key] = fmt.Sprintf("%s:%s", taint.Value, taint.Effect)
}
//
// Validate MachinePool taints.
// Validate NodeGroup taints.
if err := labels.ValidateTaints(taintsAsKVPairs); err != nil {
log.Fatalf("MachinePool taint validation failed : %v", err)
log.Fatalf("NodeGroup taint validation failed : %v", err)
}
}

Expand Down
4 changes: 4 additions & 0 deletions constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ var (
"argocd-apps/templates/cluster-api.app.yaml.tmpl",
"argocd-apps/cluster-api.values.yaml.tmpl",

// Cluster Autoscaler.
"argocd-apps/templates/cluster-autoscaler.app.yaml.tmpl",
"argocd-apps/cluster-autoscaler.values.yaml.tmpl",

// Traefik.
"argocd-apps/templates/traefik.app.yaml.tmpl",
"argocd-apps/traefik.values.yaml.tmpl",
Expand Down

0 comments on commit fe80b49

Please sign in to comment.