urlaubsverwaltung: correct the env-collision claim, answer the recovery question

The chart told operators that adding a duplicate env name to an installed
release makes the next upgrade fail in the API server, quoting "The order in
patch list ... doesn't match $setElementOrder list". Measured against helm
3.19.0 and Kubernetes 1.35, it does not fail: the upgrade succeeds, helm prints
"Release has been upgraded", and Kubernetes merges the env list by name, so the
duplicate pair collapses to the chart's own entry and the operator's override is
discarded without a word. At install time the same values behave differently -
both entries reach the pod, the later one wins, and the API server says so with
"hides previous definition of ..., which may be dropped when using apply".

The advice that followed from the old claim survives - such a switch only takes
at install time - but the stated reason was wrong in the dangerous direction. A
reader who expects a loud failure treats a green upgrade as proof the override
took effect, and green is exactly what a silently dropped override looks like.
The text now names the real failure mode and tells the reader to read the value
back off the Deployment. README carries the same correction, because every chart
in this repo offers the same additive env block.

Also records what the earlier measurement left open. With the database scaled
back up the pod returns to Ready on its own after about five seconds, restart
count still 0, so leaving the liveness group untouched costs no manual recovery.
And the limit of that signal belongs next to it: the db indicator validates a
connection, not the schema, so a database that came back empty still put the pod
back to Ready with /actuator/health at 200.
This commit is contained in:
jklein committed 2026-09-02 13:22:58 +02:00
1 parent f8acdfdfe1
commit f761767630
3 files changed
+48 -18

No files matched your search

+39 -13
View File
@@ -226,7 +226,14 @@ THE THREE HEALTH PATHS AND WHAT EACH ONE ANSWERS FOR
restarting the pod does not bring the database back. Adding db to the
liveness group would turn every outage into a crash loop on top of the
outage. Measured on this chart, with the database scaled to zero underneath a
running pod: readiness 503, liveness 200, pod 0/1 Ready, restart count 0.
running pod: readiness 503, liveness 200, pod 0/1 Ready, restart count 0 —
the pod left Ready about 20 seconds after the database went away.
The other half of that measurement matters just as much: when the database
came back, the pod returned to Ready by itself within about five seconds,
still with restart count 0. Nothing had to be rolled, deleted or scaled by
hand. That is what leaving the liveness group alone buys — the outage is
ridden out and then simply over.
How readiness actually fails is worth knowing before you tune it. The db
indicator does not answer 503 quickly — it waits out the JDBC connection
@@ -248,6 +255,13 @@ WHAT A READY POD DOES AND DOES NOT PROVE
health indicator watches it afterwards, so a provider that goes down after
the rollout leaves a Ready pod on which nobody can log in.
Ready also proves reachability, not correctness: the db indicator validates a
connection, it never looks at the schema. Measured here — a database that came
back EMPTY put the pod straight back to Ready with /actuator/health at 200,
while not one table existed in it. A restored-but-wrong database reads as
healthy, so a probe going green after a database incident is not on its own
evidence that the data came back with it.
A database outage now shows up in the pod's status: READY drops to 0/1 while
STATUS stays Running and RESTARTS stays put. That combination — running, not
restarting, not Ready — is the signature of a dependency outage rather than
@@ -308,24 +322,36 @@ MAIL AND THE APPLICATION URL ARE PLACEHOLDERS UNTIL YOU SET THEM
are silently not delivered. The setting is only honest while there is no mail
server to be honest about.
Note that this variable repeats a name the chart already sets, so per the
rule below it can only be added at INSTALL time — putting it into an already
installed release makes the next upgrade fail in the API server.
Note that this variable repeats a name the chart already sets, so per the rule
below it only takes effect when it goes in at INSTALL time. Put into an
already installed release it does nothing at all, and says so nowhere: helm
reports a successful upgrade while the container keeps "false".
urlaubsverwaltung.env IS ADDITIVE ONLY, AND A COLLISION BREAKS THE UPGRADE
urlaubsverwaltung.env IS ADDITIVE ONLY, AND A COLLISION IS LOST ON UPGRADE
The entries are appended AFTER the chart's own list{{ if $envNames }} (this release appends:
{{ join ", " $envNames }}){{ end }}. An entry that repeats a name the
chart already sets does not replace it — it renders the variable twice, and
that has a sharp edge: adding such a name to an ALREADY INSTALLED release
makes the next upgrade fail in the API server, before anything changes, with
what happens to that pair depends on WHEN it first appears:
UPGRADE FAILED: cannot patch "{{ .Release.Name }}-{{ .Chart.Name }}-deployment" ...
The order in patch list ... doesn't match $setElementOrder list
AT INSTALL both entries reach the pod and the later one — yours — wins in the
container. The API server accepts it with a warning:
because env entries are merged by name and a duplicate name breaks that
ordering. A fresh install with the same values goes through, so the failure
looks arbitrary until you know it. Treat the variables above as fixed for the
life of a release: to really change one, reinstall.
Warning: spec.template.spec.containers[0].env[N]: hides previous definition
of "NAME", which may be dropped when using apply
AT UPGRADE that warning comes true. Kubernetes merges env by name, so the pair
collapses back to ONE entry and the surviving value is the CHART's, not yours.
Nothing fails: helm prints "Release has been upgraded", the revision counts up,
and the override is simply gone. Measured on this chart (helm 3.19.0,
Kubernetes 1.35): "helm get manifest" shows the variable twice while the live
Deployment shows it once, with the chart's value.
So the trap is silence, not an error. Treat the variables above as fixed for
the life of a release: to really change one, reinstall. And never read a green
upgrade as proof that an override took effect — read it off the object:
kubectl -n {{ .Release.Namespace }} get deployment {{ .Release.Name }}-{{ .Chart.Name }}-deployment \
-o jsonpath='{.spec.template.spec.containers[0].env}'
Anything sensitive belongs in valueFrom.secretKeyRef rather than a plain
value: a value is stored in the pod spec and readable by anyone who can read