Add the ability to specify dependencies

This commit is contained in:
Matt Pryor 2022-01-27 15:13:30 +00:00
parent bf453de098
commit b11f7b9a7d
20 changed files with 249 additions and 72 deletions

View File

@ -50,11 +50,6 @@ jobs:
# when determining the number of commits since the last tag
fetch-depth: 0
- name: Resolve chart dependencies in order
run: |
helm dependency update ./charts/cluster-addons
helm dependency update ./charts/openstack-cluster
- name: Get SemVer version for current commit
id: semver
uses: stackhpc/github-actions/semver@master

View File

@ -90,7 +90,8 @@ Lists are merged by concatenating them rather than overwriting.
{{- end }}
{{- end }}
{{- else }}
{{- default $left (index . 1) | toYaml }}
{{- $right := index . 1 }}
{{- kindIs "invalid" $right | ternary $left $right | toYaml }}
{{- end }}
{{- end }}
@ -147,7 +148,8 @@ by checking for the pending-[install,upgrade] status.
.release.name
}}
{{- range .crdManifests }}
kubectl create -f {{ . }} || kubectl replace -f {{ . }}
kubectl create -f {{ . }} || \
kubectl replace -f {{ . }}
{{- end }}
helm-upgrade {{ $releaseName }} {{ $chartName }} \
--atomic \
@ -212,7 +214,7 @@ Template for a script that installs or upgrades resources using Kustomize.
*/}}
{{- define "addon.kustomize.install" }}
kustomize build . | kubectl apply -f -
{{- range .resources }}
{{- range .watches }}
{{-
$namespace := required
"namespace is required for a resource to watch"
@ -237,7 +239,7 @@ Template for a script that deletes resources using Kustomize.
*/}}
{{- define "addon.kustomize.delete" }}
kustomize build . | kubectl delete -f -
{{- range .resources }}
{{- range .watches }}
{{-
$namespace := required
"namespace is required for a resource to watch"
@ -261,6 +263,13 @@ kubectl -n {{ $namespace }} wait --for=delete {{ $kind }}/{{ $name }}
Template that produces the default configuration.
*/}}
{{- define "addon.config.defaults" -}}
# Indicates whether the addon is enabled or not
enabled: true
# A list of other addons that this addon should wait for before installing
dependsOn: []
# The weight to use for the uninstall hook
# This can be used to influence the order in which addons are deleted
uninstallHookWeight: 0
image:
repository: ghcr.io/stackhpc/k8s-utils
tag: # Defaults to chart appVersion if not given
@ -336,20 +345,18 @@ pre-upgrade hook is produced to uninstall the addon.
{{- $ctx := index . 0 }}
{{- $name := index . 1 }}
{{- $overrides := index . 2 }}
{{- $enabled := index . 3 }}
{{- $weight := index . 4 }}
{{- $defaults := include "addon.config.defaults" $ctx | fromYaml }}
{{- $config := include "addon.mergeConcat" (list $defaults $overrides) | fromYaml }}
{{- if $enabled }}
{{- if $config.enabled }}
{{- include "addon.config.secret" (list $ctx $name $config) }}
---
{{- include "addon.job.install" (list $ctx $name $config) }}
---
{{- include "addon.job.uninstall" (list $ctx $name "pre-delete" $weight $config) }}
{{- include "addon.job.uninstall" (list $ctx $name "pre-delete" $config) }}
{{- else if $ctx.Release.IsUpgrade }}
{{- $secretName := include "addon.fullname" (list $ctx $name) | printf "%s-config" }}
{{- if lookup "v1" "Secret" $ctx.Release.Namespace $secretName }}
{{- include "addon.job.uninstall" (list $ctx $name "pre-upgrade" $weight $config) }}
{{- include "addon.job.uninstall" (list $ctx $name "pre-upgrade" $config) }}
{{- end }}
{{- end }}
{{- end }}
@ -362,8 +369,6 @@ for the configuration produced by the specified template.
{{- $ctx := index . 0 }}
{{- $name := index . 1 }}
{{- $configTemplate := index . 2 }}
{{- $enabled := index . 3 }}
{{- $weight := index . 4 }}
{{- $config := include $configTemplate $ctx | fromYaml }}
{{- include "addon.job.fromConfig" (list $ctx $name $config $enabled $weight) }}
{{- include "addon.job.fromConfig" (list $ctx $name $config) }}
{{- end }}

View File

@ -25,9 +25,51 @@ template:
{{- end }}
securityContext: {{ toYaml $config.podSecurityContext | nindent 6 }}
restartPolicy: OnFailure
{{- if not $config.kubeconfigSecret.name }}
serviceAccountName: {{ tpl $config.serviceAccountName $ctx }}
{{- end }}
initContainers:
- name: wait-for-api
image: {{ printf "%s:%s" $config.image.repository (default $ctx.Chart.AppVersion $config.image.tag) }}
imagePullPolicy: {{ $config.image.pullPolicy }}
securityContext: {{ toYaml $config.securityContext | nindent 10 }}
args:
- /bin/bash
- -c
- |
set -x
until kubectl api-resources >/dev/null 2>&1; do
sleep 5
done
{{- if $config.kubeconfigSecret.name }}
env:
- name: KUBECONFIG
value: /config/kubeconfig
{{- end }}
resources: {{ toYaml $config.resources | nindent 10 }}
volumeMounts:
- name: config
mountPath: /config
readOnly: true
{{- range $dep := $config.dependsOn }}
- name: wait-for-{{ $dep }}
image: {{ printf "%s:%s" $config.image.repository (default $ctx.Chart.AppVersion $config.image.tag) }}
imagePullPolicy: {{ $config.image.pullPolicy }}
securityContext: {{ toYaml $config.securityContext | nindent 10 }}
args:
- /bin/bash
- -c
- |
set -ex
{{- $labels := include "addon.job.selectorLabels" (list $ctx $dep "install") | fromYaml }}
{{- range $i, $label := keys $labels -}}
{{- if $i }}
LABELS="$LABELS,{{ $label }}={{ index $labels $label }}"
{{- else }}
LABELS="{{ $label }}={{ index $labels $label }}"
{{- end }}
{{- end }}
kubectl wait --for=condition=Complete job -n {{ $ctx.Release.Namespace }} -l "$LABELS" --all --timeout=-1s
resources: {{ toYaml $config.resources | nindent 10 }}
{{- end }}
containers:
- name: install
image: {{ printf "%s:%s" $config.image.repository (default $ctx.Chart.AppVersion $config.image.tag) }}
@ -92,7 +134,7 @@ apiVersion: batch/v1
kind: Job
metadata:
{{- $checksum := include "addon.job.install.spec" . | sha256sum }}
name: {{ include "addon.job.name" (list $ctx $name "install") }}-{{ trunc 8 $checksum }}
name: {{ include "addon.job.name" (list $ctx $name "install") }}-{{ trunc 5 $checksum }}
labels: {{ include "addon.job.labels" (list $ctx $name "install") | nindent 4 }}
spec: {{ include "addon.job.install.spec" . | nindent 2 }}
{{- end }}

View File

@ -2,8 +2,7 @@
{{- $ctx := index . 0 }}
{{- $name := index . 1 }}
{{- $hook := index . 2 }}
{{- $hookWeight := index . 3 }}
{{- $config := index . 4 }}
{{- $config := index . 3 }}
apiVersion: batch/v1
kind: Job
metadata:
@ -11,14 +10,14 @@ metadata:
labels: {{ include "addon.job.labels" (list $ctx $name "uninstall") | nindent 4 }}
annotations:
helm.sh/hook: {{ $hook }}
helm.sh/hook-weight: {{ $hookWeight | quote }}
helm.sh/hook-weight: {{ $config.uninstallHookWeight | quote }}
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
spec:
backoffLimit: {{ $config.backoffLimit }}
activeDeadlineSeconds: {{ $config.activeDeadlineSeconds }}
template:
metadata:
labels: {{ include "addon.job.selectorLabels" (list $ctx $name "install") | nindent 8 }}
labels: {{ include "addon.job.selectorLabels" (list $ctx $name "uninstall") | nindent 8 }}
spec:
{{- with $config.imagePullSecrets }}
imagePullSecrets: {{ toYaml . | nindent 8 }}

View File

@ -1,10 +1,23 @@
# cluster-addons chart
# cluster-addons chart <!-- omit in toc -->
This [Helm chart](https://helm.sh/) manages the deployment of addons for a
[Kubernetes](https://kubernetes.io) cluster. It is primarily intended to be used with
the cluster management charts from this repository, e.g.
[openstack-cluster](../openstack-cluster), but should work for any Kubernetes cluster.
## Contents <!-- omit in toc -->
- [How does it work?](#how-does-it-work)
- [Targetting a remote cluster](#targetting-a-remote-cluster)
- [Container Network Interface (CNI) plugins](#container-network-interface-cni-plugins)
- [OpenStack integrations](#openstack-integrations)
- [OpenStack credentials](#openstack-credentials)
- [cert-manager](#cert-manager)
- [Ingress controllers](#ingress-controllers)
- [Extra addons](#extra-addons)
## How does it work?
The addons are deployed by launching
[Kubernetes jobs](https://kubernetes.io/docs/concepts/workloads/controllers/job/), each of
which is responsible for installing or updating a single addon. These jobs can
@ -267,6 +280,11 @@ and can even use a custom image containing specialist tools if required.
Each addon should have the form (not all options are required at all times):
```yaml
# The name of any components that this addon depends on being installed before it can be installed
# This can be the name of an addon, including the name of other extra addons
# It can also be either "ingress" or "storage" to wait for storage providers and ingress controllers
# respectively, regardless of the explicit implementation
dependsOn: []
# One of helm, kustomize or custom
installType: custom
# Options for a Helm addon

View File

@ -47,15 +47,143 @@ app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
Renders the default job configuration.
*/}}
{{- define "cluster-addons.job.defaults" -}}
{{- with .Values.jobDefaults }}
{{- $ctx := index . 0 }}
{{- $name := index . 1 }}
{{- with $ctx.Values.jobDefaults }}
{{- toYaml . }}
{{- end }}
{{- if .Values.kubeconfigSecret.name }}
{{- if $ctx.Values.kubeconfigSecret.name }}
kubeconfigSecret:
name: {{ tpl .Values.kubeconfigSecret.name . }}
{{- with .Values.kubeconfigSecret.key }}
name: {{ tpl $ctx.Values.kubeconfigSecret.name $ctx }}
{{- with $ctx.Values.kubeconfigSecret.key }}
key: {{ . }}
{{- end }}
{{- end }}
serviceAccountName: {{ tpl .Values.serviceAccount.name . }}
serviceAccountName: {{ tpl $ctx.Values.serviceAccount.name $ctx }}
enabled: {{ include "cluster-addons.enabled" . }}
dependsOn: {{
(include "cluster-addons.dependsOn.enabled" . | fromYaml).value |
default list |
toYaml |
nindent 2
}}
uninstallHookWeight: {{ include "cluster-addons.uninstallHookWeight" . }}
{{- end }}
{{/*
Determines if an addon is enabled given the name.
*/}}
{{- define "cluster-addons.enabled" -}}
{{- $ctx := index . 0 }}
{{- $name := index . 1 }}
{{- if eq $name "ccm-openstack" }}
{{- and $ctx.Values.openstack.enabled $ctx.Values.openstack.ccm.enabled | toYaml }}
{{- else if eq $name "cert-manager" }}
{{- $ctx.Values.certManager.enabled | toYaml }}
{{- else if eq $name "cloud-config" }}
{{- $ctx.Values.openstack.enabled | toYaml }}
{{- else if eq $name "cni-calico" }}
{{- and $ctx.Values.cni.enabled (eq $ctx.Values.cni.type "calico") | toYaml }}
{{- else if eq $name "cni-cilium" }}
{{- and $ctx.Values.cni.enabled (eq $ctx.Values.cni.type "cilium") | toYaml }}
{{- else if eq $name "csi-cinder" }}
{{- and $ctx.Values.openstack.enabled $ctx.Values.openstack.csiCinder.enabled | toYaml }}
{{- else if eq $name "ingress-nginx" }}
{{- and $ctx.Values.ingress.enabled $ctx.Values.ingress.nginx.enabled | toYaml }}
{{- else if eq $name "metrics-server" }}
{{- $ctx.Values.metricsServer.enabled | toYaml }}
{{- else if eq $name "monitoring" }}
{{- $ctx.Values.monitoring.enabled | toYaml }}
{{- else if hasKey $ctx.Values.extraAddons $name }}
{{- dig $name "enabled" true $ctx.Values.extraAddons | toYaml }}
{{- else }}
{{- printf "Unrecognised addon '%s'" $name | fail }}
{{- end }}
{{- end }}
{{/*
Produces the explicit dependencies for an addon.
The result is returned as an object so it can be used with fromYaml.
*/}}
{{- define "cluster-addons.dependsOn.explicit" -}}
{{- $ctx := index . 0 }}
{{- $name := index . 1 }}
value:
{{- if (list "ccm-openstack" "csi-cinder" | has $name) }}
- cloud-config
{{- else if eq $name "monitoring" }}
- storage
- ingress
{{- else if hasKey $ctx.Values.extraAddons $name }}
{{- dig $name "dependsOn" list $ctx.Values.extraAddons | toYaml | nindent 2 }}
{{- else }}
[]
{{- end }}
{{- end }}
{{/*
Produces the dependencies for an addon, resolving any categories and including the
bootstrap addons as an implicit dependency (unless the addon itself is a bootstrap
addon).
The result is returned as an object so it can be used with fromYaml.
*/}}
{{- define "cluster-addons.dependsOn.all" -}}
{{- $ctx := index . 0 }}
{{- $name := index . 1 }}
{{- $categories := $ctx.Values.categories }}
{{- $explicit := (include "cluster-addons.dependsOn.explicit" . | fromYaml).value | default list }}
value:
{{- if not (has $name $categories.bootstrap) }}
{{- range $categories.bootstrap }}
- {{ . }}
{{- end }}
{{- end }}
{{- range $explicit }}
{{- if hasKey $categories . }}
{{- range (dig . list $categories) }}
- {{ . }}
{{- end }}
{{- else }}
- {{ . }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Produces the dependencies for an addon, ensuring uniqueness and only including
those that are enabled.
The result is returned as an object so it can be used with fromYaml.
*/}}
{{- define "cluster-addons.dependsOn.enabled" -}}
{{- $ctx := index . 0 }}
{{- $unique := (include "cluster-addons.dependsOn.all" . | fromYaml).value | default list | uniq }}
value:
{{- range $unique }}
{{- if eq (include "cluster-addons.enabled" (list $ctx .)) "true" }}
- {{ . }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Produces the uninstall hook weight for the specified addon, ensuring that it is
removed before any of its dependencies. Only addons that are enabled are considered.
Addons with no enabled dependencies have a weight of zero. Addons with at least one
enabled dependency have a weight that is one less than the minimum of the weights
of the dependencies.
*/}}
{{- define "cluster-addons.uninstallHookWeight" -}}
{{- $ctx := index . 0 -}}
{{- $name := index . 1 -}}
{{- $weight := 1 }}
{{- $enabled := (include "cluster-addons.dependsOn.enabled" . | fromYaml).value | default list -}}
{{- range $enabled -}}
{{- $dependencyWeight := include "cluster-addons.uninstallHookWeight" (list $ctx .) | atoi -}}
{{- $weight = min $weight $dependencyWeight -}}
{{- end -}}
{{- sub $weight 1 -}}
{{- end }}

View File

@ -1,5 +1,5 @@
{{- define "cluster-addons.ccm-openstack.config" -}}
{{- include "cluster-addons.job.defaults" . }}
{{- include "cluster-addons.job.defaults" (list . "ccm-openstack") }}
installType: kustomize
kustomize:
kustomizationTemplate: |
@ -43,7 +43,5 @@ kustomize:
.
"ccm-openstack"
"cluster-addons.ccm-openstack.config"
(and .Values.openstack.enabled .Values.openstack.ccm.enabled)
1
)
}}

View File

@ -1,5 +1,5 @@
{{- define "cluster-addons.cert-manager.config" -}}
{{- include "cluster-addons.job.defaults" . }}
{{- include "cluster-addons.job.defaults" (list . "cert-manager") }}
installType: helm
helm: {{ omit .Values.certManager "enabled" "acmeHttp01Issuer" | toYaml | nindent 2 }}
{{- if and .Values.ingress.enabled .Values.certManager.acmeHttp01Issuer.enabled }}
@ -33,7 +33,5 @@ hooks:
.
"cert-manager"
"cluster-addons.cert-manager.config"
.Values.certManager.enabled
0
)
}}

View File

@ -5,7 +5,7 @@
.Values.openstack.cloudCredentialsSecretName
}}
{{- $secretName := tpl $secretNameTemplate . }}
{{- include "cluster-addons.job.defaults" . }}
{{- include "cluster-addons.job.defaults" (list . "cloud-config") }}
installType: custom
custom:
install: |
@ -48,7 +48,5 @@ extraFiles:
.
"cloud-config"
"cluster-addons.cloud-config.config"
.Values.openstack.enabled
3
)
}}

View File

@ -1,5 +1,5 @@
{{- define "cluster-addons.cni-calico.config" -}}
{{- include "cluster-addons.job.defaults" . }}
{{- include "cluster-addons.job.defaults" (list . "cni-calico") }}
installType: kustomize
kustomize:
kustomizationTemplate: |
@ -29,7 +29,5 @@ kustomize:
.
"cni-calico"
"cluster-addons.cni-calico.config"
(and .Values.cni.enabled (eq .Values.cni.type "calico"))
2
)
}}

View File

@ -1,5 +1,5 @@
{{- define "cluster-addons.cni-cilium.config" -}}
{{- include "cluster-addons.job.defaults" . }}
{{- include "cluster-addons.job.defaults" (list . "cni-cilium") }}
installType: helm
helm: {{ toYaml .Values.cni.cilium | nindent 2 }}
{{- end }}
@ -9,7 +9,5 @@ helm: {{ toYaml .Values.cni.cilium | nindent 2 }}
.
"cni-cilium"
"cluster-addons.cni-cilium.config"
(and .Values.cni.enabled (eq .Values.cni.type "cilium"))
2
)
}}

View File

@ -1,5 +1,5 @@
{{- define "cluster-addons.csi-cinder.config" -}}
{{- include "cluster-addons.job.defaults" . }}
{{- include "cluster-addons.job.defaults" (list . "csi-cinder") }}
installType: kustomize
kustomize:
kustomizationTemplate: |
@ -44,7 +44,5 @@ extraFiles:
.
"csi-cinder"
"cluster-addons.csi-cinder.config"
(and .Values.openstack.enabled .Values.openstack.csiCinder.enabled)
1
)
}}

View File

@ -1,8 +1,17 @@
{{- define "cluster-addons.extra-addons.config" -}}
{{- $ctx := index . 0 }}
{{- $config := index . 1 }}
{{- $name := index . 1 }}
{{-
include "cluster-addons.job.defaults" $ctx |
$config := omit
(index . 2)
"kubeconfigSecret"
"serviceAccountName"
"enabled"
"dependsOn"
"uninstallHookWeight"
}}
{{-
include "cluster-addons.job.defaults" (list $ctx $name) |
fromYaml |
merge $config |
toYaml
@ -11,14 +20,6 @@
{{- range $name, $config := .Values.extraAddons }}
---
{{- $merged := include "cluster-addons.extra-addons.config" (list $ $config) | fromYaml }}
{{-
include "addon.job.fromConfig" (list
$
$name
(omit $merged "enabled")
(default true $merged.enabled)
0
)
}}
{{- $merged := include "cluster-addons.extra-addons.config" (list $ $name $config) | fromYaml }}
{{- include "addon.job.fromConfig" (list $ $name $merged) }}
{{- end }}

View File

@ -1,5 +1,5 @@
{{- define "cluster-addons.ingress-nginx.config" -}}
{{- include "cluster-addons.job.defaults" . }}
{{- include "cluster-addons.job.defaults" (list . "ingress-nginx") }}
installType: helm
helm: {{ omit .Values.ingress.nginx "enabled" | toYaml | nindent 2 }}
{{- end }}
@ -9,7 +9,5 @@ helm: {{ omit .Values.ingress.nginx "enabled" | toYaml | nindent 2 }}
.
"ingress-nginx"
"cluster-addons.ingress-nginx.config"
(and .Values.ingress.enabled .Values.ingress.nginx.enabled)
0
)
}}

View File

@ -1,5 +1,5 @@
{{- define "cluster-addons.metrics-server.config" -}}
{{- include "cluster-addons.job.defaults" . }}
{{- include "cluster-addons.job.defaults" (list . "metrics-server") }}
installType: kustomize
kustomize:
kustomizationTemplate: |
@ -21,7 +21,5 @@ kustomize:
.
"metrics-server"
"cluster-addons.metrics-server.config"
.Values.metricsServer.enabled
0
)
}}

View File

@ -1,5 +1,5 @@
{{- define "cluster-addons.monitoring.config" -}}
{{- include "cluster-addons.job.defaults" . }}
{{- include "cluster-addons.job.defaults" (list . "monitoring") }}
installType: helm
helm: {{ omit .Values.monitoring "enabled" | toYaml | nindent 2 }}
{{- end }}
@ -9,7 +9,5 @@ helm: {{ omit .Values.monitoring "enabled" | toYaml | nindent 2 }}
.
"monitoring"
"cluster-addons.monitoring.config"
.Values.monitoring.enabled
0
)
}}

View File

@ -19,5 +19,6 @@ rules:
verbs:
- list
- get
- watch
- patch
{{- end }}

View File

@ -61,11 +61,17 @@ jobDefaults: {}
# nodeSelector:
# affinity:
# The available categories for dependencies and the addons that belong to them
categories:
bootstrap: [cloud-config, ccm-openstack, cni-calico, cni-cilium]
storage: [csi-cinder]
ingress: [ingress-nginx]
# Settings for the CNI addon
cni:
# Indicates if a CNI should be deployed
enabled: true
# The type of CNI to deploy - supported values are calico, cilium, weave
# The type of CNI to deploy - supported values are calico or cilium
type: cilium
# Settings for the calico CNI
calico:

View File

@ -10,4 +10,4 @@ dependencies:
version: ">=0-0"
repository: file://../cluster-addons
alias: addons
conditions: addons.enabled
condition: addons.enabled

View File

@ -46,7 +46,7 @@ if helm-exists $RELEASE $NAMESPACE_ARG; then
echo "Rolling back failed upgrade..."
helm rollback $RELEASE $NAMESPACE_ARG --cleanup-on-fail --wait --wait-for-jobs $TIMEOUT_ARG
elif [ "$status" = "pending-rollback" ]; then
echo "Competing pending rollback..."
echo "Completing pending rollback..."
helm rollback $RELEASE $NAMESPACE_ARG --cleanup-on-fail --wait --wait-for-jobs $TIMEOUT_ARG
fi
fi