feat(k8s): Helm chart for the whole stack on a single-node cluster (refs #25)
One chart whose values.yaml is a near-literal transcription of infra/docker-compose.yml, rendered by three generic templates (Deployment, Job, Service) over a `workloads` map — so the two stacks can be diffed by eye instead of by archaeology, and adding a service is a values edit. Platform-forced deviations, each commented where it appears: - `args`, never `command`: compose replaces the image CMD, Kubernetes replaces the ENTRYPOINT. The chart fails to render on `command`, because the symptom (postgres refusing to run as root, Keycloak exec-ing `start-dev`) is nothing like the cause. - The four Django services apply their own setup_configuration in the web pod rather than in a separate init Job: both scripts migrate, and without compose's depends_on they race the same database. - OpenZaak and Objecten are addressed by service FQDN, because Django rejects a single-label host in a URL — the reason compose passes container IPs around. - NodePorts, no ingress; databases are emptyDir until persistence.storageClass is set, so the stack comes up on a cluster with no CSI driver. The upstream config inputs stay in the repo and become ConfigMaps via infra/helm/seed-configmaps.sh — the Kubernetes sibling of infra/seed-config.sh — so the compose stack and the chart cannot fork. infra/helm/registry.yaml runs an in-cluster registry because Talos cannot side-load an image and a laptop-side one needs a root-level firewall change.
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
{{/*
|
||||
One pod spec for every workload, Deployment and Job alike. The chart is
|
||||
values-driven on purpose: `.Values.workloads` is a near-literal transcription of
|
||||
infra/docker-compose.yml, so the two stacks can be diffed by eye instead of by
|
||||
archaeology. Adding a service is a values edit, not a template edit.
|
||||
|
||||
Called as: include "big.podspec" (dict "root" $ "name" $name "w" $w)
|
||||
*/}}
|
||||
{{- define "big.podspec" -}}
|
||||
{{- $root := .root -}}
|
||||
{{- $name := .name -}}
|
||||
{{- $w := .w -}}
|
||||
{{- with $root.Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- with $w.waitFor }}
|
||||
initContainers:
|
||||
- name: wait-for-deps
|
||||
image: {{ $root.Values.images.busybox }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
for t in {{ join " " . }}; do
|
||||
echo "waiting for $t"
|
||||
until nc -z "${t%:*}" "${t#*:}"; do sleep 2; done
|
||||
done
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ $name }}
|
||||
image: {{ include "big.image" (dict "root" $root "name" $name "w" $w) }}
|
||||
# Only this repo's images get the configured policy: their `dev` tag is mutable.
|
||||
# Upstream tags are pinned, so IfNotPresent keeps them out of pod-template diffs —
|
||||
# which matters because a changed template makes a Job unpatchable (immutable).
|
||||
imagePullPolicy: {{ if $w.own }}{{ $root.Values.images.pullPolicy }}{{ else }}IfNotPresent{{ end }}
|
||||
{{- if $w.command }}
|
||||
{{- fail (printf "workload %s: use `args`, not `command` — compose's `command:` replaces CMD, but Kubernetes' `command:` replaces the image ENTRYPOINT (postgres would run as root, keycloak would exec `start-dev`)" $name) }}
|
||||
{{- end }}
|
||||
{{- with $w.args }}
|
||||
args:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with $w.envFrom }}
|
||||
envFrom:
|
||||
{{- range . }}
|
||||
- configMapRef:
|
||||
# optional: an env group whose feature is disabled (e.g. otel) simply
|
||||
# isn't rendered, and the pod must still start.
|
||||
name: {{ printf "%s-env" . }}
|
||||
optional: true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with $w.env }}
|
||||
env:
|
||||
{{- include "big.env" (list $root .) | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with $w.ports }}
|
||||
ports:
|
||||
{{- range . }}
|
||||
- name: {{ .name }}
|
||||
containerPort: {{ .targetPort | default .port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with $w.probe }}
|
||||
readinessProbe:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with $w.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- if or $w.files $w.data }}
|
||||
volumeMounts:
|
||||
{{- range $w.files }}
|
||||
- name: {{ .configMap }}
|
||||
mountPath: {{ .mountPath }}
|
||||
{{- with .subPath }}
|
||||
subPath: {{ . }}
|
||||
{{- end }}
|
||||
readOnly: true
|
||||
{{- end }}
|
||||
{{- with $w.data }}
|
||||
- name: data
|
||||
mountPath: {{ .mountPath }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if or $w.files $w.data }}
|
||||
volumes:
|
||||
{{- range $w.files }}
|
||||
- name: {{ .configMap }}
|
||||
configMap:
|
||||
name: {{ .configMap }}
|
||||
{{- with .defaultMode }}
|
||||
defaultMode: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with $w.data }}
|
||||
- name: data
|
||||
{{- if $root.Values.persistence.storageClass }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ $name }}-data
|
||||
{{- else }}
|
||||
# No StorageClass configured: the databases are emptyDir, so the stack needs
|
||||
# no CSI driver to come up. Data then lives as long as the pod does — see
|
||||
# docs/runbooks/kubernetes-talos.md for switching on local-path.
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Image ref: `own: true` workloads are built from this repo, everything else is upstream. */}}
|
||||
{{- define "big.image" -}}
|
||||
{{- $root := .root -}}
|
||||
{{- $w := .w -}}
|
||||
{{- if $w.own -}}
|
||||
{{- $ref := printf "%s/%s:%s" $root.Values.images.repositoryPrefix .name $root.Values.images.tag -}}
|
||||
{{- with $root.Values.images.registry }}{{ printf "%s/%s" . $ref }}{{ else }}{{ $ref }}{{ end }}
|
||||
{{- else -}}
|
||||
{{- $w.image -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Env list from a map. Every value is run through `tpl`, so values.yaml can name
|
||||
cluster-internal hosts ({{ .Release.Namespace }}) and the node address
|
||||
({{ .Values.host }}) without the chart hard-coding either.
|
||||
*/}}
|
||||
{{- define "big.env" -}}
|
||||
{{- $root := index . 0 -}}
|
||||
{{- range $k, $v := index . 1 }}
|
||||
- name: {{ $k }}
|
||||
value: {{ tpl (toString $v) $root | quote }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "big.labels" -}}
|
||||
app.kubernetes.io/name: {{ .name }}
|
||||
app.kubernetes.io/instance: {{ .root.Release.Name }}
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
{{- end -}}
|
||||
Reference in New Issue
Block a user