chart: give the migrate hook its own ServiceAccount
ci / lint (push) Failing after 14m13s
ci / types (push) Has been skipped
ci / unit (push) Has been skipped
ci / integration (push) Has been skipped
ci / security (push) Has been skipped
ci / dockerfile (push) Has been skipped
ci / chart (push) Has been skipped
ci / image (api) (push) Has been skipped
ci / image (reconciler) (push) Has been skipped
ci / image (worker) (push) Has been skipped
ci / bump (push) Has been skipped

The Job ran as the api ServiceAccount, which is an ordinary chart resource.
Hooks are created before the release's ordinary manifests, so on a first
install that account does not exist and the Job never starts:

    Error creating: pods "svcforge-migrate-" is forbidden: error looking up
    service account svcforge/svcforge-api: serviceaccount "svcforge-api" not
    found

Not an ArgoCD quirk — helm orders hooks the same way, so both deploy paths
failed identically. It survived review because the chart was only ever checked
with `helm template` and `helm install --dry-run=server`, and neither creates a
Job. The pod is what fails, so only a real install can catch it.

The new account is a hook at weight -10, ahead of the Job at -5, and is bound
to no Role: the migration talks to Postgres and wants nothing from the
Kubernetes API. automountServiceAccountToken is off for the same reason.

Verified with a real `helm install` into a scratch namespace: STATUS deployed,
job Complete 1/1, four migrations applied, and the hook ServiceAccount was 27s
old against 10s for the ordinary ones — the ordering the bug turned on.
This commit is contained in:
Nguyen Minh Phuc
2026-07-20 06:33:31 +00:00
parent 89ad9625f3
commit 31fa9165ff
+32 -1
View File
@@ -13,6 +13,37 @@ after a failure — the one time you actually want it — and clears it on the n
Deliberately no terminationGracePeriodSeconds: 60 here. Three Deployments carry it; a
migration is not one of them.
*/}}
{{/*
The hook needs its own ServiceAccount, and it has to be a hook itself.
It used to run as the api ServiceAccount, which is an ordinary chart resource. Hooks are
created before the release's ordinary manifests, so on a first install that account does
not exist yet and the Job never starts:
Error creating: pods "svcforge-migrate-" is forbidden: error looking up service
account svcforge/svcforge-api: serviceaccount "svcforge-api" not found
This is not an ArgoCD quirk. `helm install` orders hooks the same way, so it failed
identically on both paths. It survived review because the chart was only ever checked with
`helm template` and `helm install --dry-run=server`, and neither creates a Job — the pod is
what fails, so nothing short of a real install can catch it.
Weight -10 so it is created before the Job at -5. Deliberately bound to no Role: the
migration talks to Postgres and needs nothing from the Kubernetes API.
*/}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "svcforge.fullname" . }}-migrate
labels:
{{- include "svcforge.labels" . | nindent 4 }}
app.kubernetes.io/component: migrate
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": before-hook-creation
automountServiceAccountToken: false
---
apiVersion: batch/v1
kind: Job
metadata:
@@ -36,7 +67,7 @@ spec:
app.kubernetes.io/component: migrate
spec:
restartPolicy: Never
serviceAccountName: {{ include "svcforge.serviceAccountName" (dict "ctx" $ "component" "api") }}
serviceAccountName: {{ include "svcforge.fullname" . }}-migrate
{{- with .Values.image.pullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}