From e3cfdae70c0fe4def93af72c501c8a72d2537ebd Mon Sep 17 00:00:00 2001 From: Jarvis Date: Tue, 29 Sep 2026 10:02:03 +0000 Subject: [PATCH 1/2] fix(aisix): stop rendering env names twice when extraEnvVars overrides them When extraEnvVars set a name the chart also rendered, the container carried the name twice. Kubernetes lets the later (user's) entry win, but a repeated name makes a later upgrade fail after a rollback and is refused outright by server-side apply, so a Helm 4 install with such an override fails. The chart now drops its own entry for a name extraEnvVars also sets, except for the names chart 1.5.0 rendered. A 1.5.0 release that overrides one of those already carries it twice, and a Helm 3 upgrade whose manifest carries it once deletes both entries, the override included. For those names the chart keeps rendering its entry (with 1.5.0's value where the chart no longer sets it) ahead of the user's, which also restores the overrides #397 dropped on upgrade for the names it moved into the ConfigMap. The effective value is unchanged: always the extraEnvVars one. --- .github/scripts/aisix-render-checks.sh | 21 +++++++ charts/aisix/README.md | 6 +- charts/aisix/README.md.gotmpl | 4 +- charts/aisix/templates/_helpers.tpl | 80 ++++++++++++++++++++++++++ charts/aisix/templates/deployment.yaml | 31 +--------- charts/aisix/values.yaml | 4 +- 6 files changed, 112 insertions(+), 34 deletions(-) diff --git a/.github/scripts/aisix-render-checks.sh b/.github/scripts/aisix-render-checks.sh index 539ce2d..d0c10f8 100644 --- a/.github/scripts/aisix-render-checks.sh +++ b/.github/scripts/aisix-render-checks.sh @@ -52,6 +52,27 @@ check "extraEnvVars PEMs keep the env wiring: no file keys, no mount, all three check "extraEnvVars PEMs render after the chart's own, exactly as 1.5.0 did" \ query '[e["name"] for e in pod["containers"][0]["env"] if e["name"] == "AISIX_MANAGED__CP_CERT_PEM"] == ["AISIX_MANAGED__CP_CERT_PEM"] * 2 and pod["containers"][0]["env"][[e["name"] for e in pod["containers"][0]["env"]].index("AISIX_MANAGED__CP_CERT_PEM")]["valueFrom"]["secretKeyRef"]["name"] == "aisix-gateway-certificate"' \ --set 'extraEnvVars[0].name=AISIX_MANAGED__CP_CERT_PEM' --set 'extraEnvVars[0].value=pem' +# extraEnvVars wins on a repeated name (Kubernetes takes the last entry), and +# the chart drops its own entry for a name new since 1.5.0: a repeated name +# breaks upgrades after a rollback and is refused by server-side apply. +new_names=(--set 'configSecrets.cache\.redis\.password.secretName=redis-auth' --set 'configSecrets.cache\.redis\.password.key=password' + --set 'extraEnvVars[0].name=AISIX_CACHE__REDIS__PASSWORD' --set 'extraEnvVars[0].value=user' --set 'extraEnvVars[1].name=TZ' --set 'extraEnvVars[1].value=UTC') +no_dups='all(len(names) == len(set(names)) for names in ([e["name"] for e in c.get("env", [])] for c in pod["containers"] + pod.get("initContainers", [])))' +check "extraEnvVars overriding a name new since 1.5.0 leaves no repeated name (control plane)" \ + query "$no_dups and env[\"AISIX_CACHE__REDIS__PASSWORD\"] == {\"name\": \"AISIX_CACHE__REDIS__PASSWORD\", \"value\": \"user\"}" "${new_names[@]}" +check "extraEnvVars overriding a name new since 1.5.0 leaves no repeated name (standalone)" \ + query "$no_dups and env[\"AISIX_CACHE__REDIS__PASSWORD\"][\"value\"] == \"user\"" -f "$chart/ci/standalone-values.yaml" "${new_names[@]}" +# A 1.5.0 release that overrides a name 1.5.0 rendered carries it twice; an +# upgrade whose manifest carries it once deletes both entries, the override +# included. So for those names the chart's own entry stays ahead of the user's. +old_names=(--set 'extraEnvVars[0].name=AISIX_MANAGED__CP_BASE_URL' --set 'extraEnvVars[0].value=https://cp.example.com' + --set 'extraEnvVars[1].name=AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS' --set-string 'extraEnvVars[1].value=42' + --set 'extraEnvVars[2].name=AISIX_PROXY__ADDR' --set 'extraEnvVars[2].value=0.0.0.0:8080') +check "extraEnvVars overriding a name 1.5.0 rendered keeps 1.5.0's pair, the user's entry last" \ + query '[(e["name"], e["value"]) for e in pod["containers"][0]["env"] if e["name"] in ("AISIX_MANAGED__CP_BASE_URL", "AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS", "AISIX_PROXY__ADDR")] == [("AISIX_PROXY__ADDR", "0.0.0.0:3000"), ("AISIX_MANAGED__CP_BASE_URL", "https://dp-manager.example.com:7944"), ("AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS", "15"), ("AISIX_MANAGED__CP_BASE_URL", "https://cp.example.com"), ("AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS", "42"), ("AISIX_PROXY__ADDR", "0.0.0.0:8080")]' \ + "${old_names[@]}" +check "1.5.0 never rendered control-plane names in standalone mode, so none repeats there" \ + query "$no_dups" -f "$chart/ci/standalone-values.yaml" "${old_names[@]:0:8}" check "configSecrets becomes a secretKeyRef env var" \ query 'env["AISIX_CACHE__REDIS__PASSWORD"]["valueFrom"]["secretKeyRef"] == {"name": "redis-auth", "key": "password"}' \ --set 'configSecrets.cache\.redis\.password.secretName=redis-auth' --set 'configSecrets.cache\.redis\.password.key=password' diff --git a/charts/aisix/README.md b/charts/aisix/README.md index 9bce3f9..8ba9ced 100644 --- a/charts/aisix/README.md +++ b/charts/aisix/README.md @@ -465,7 +465,9 @@ configSecrets: `extraEnvVars` is for system-level environment variables such as `TZ`, and for the variables a standalone resources file references. An `AISIX_*` variable set -there still overrides the file, as it always has. +there still overrides the file, as it always has, and still wins over a variable +the chart sets itself: that keeps working for compatibility, but the setting +belongs in its own value. ## Parameters @@ -545,7 +547,7 @@ there still overrides the file, as it always has. | controlPlane.enabled | bool | `true` | Read configuration from an AISIX control plane. Set to false to run standalone, from the `resources.yaml` file configured under `standalone` | | controlPlane.etcdEndpoint | string | `""` | Control-plane etcd endpoint as bare `host:port`. Leave empty unless the control plane publishes an etcd endpoint distinct from `baseURL` | | controlPlane.heartbeatIntervalSeconds | int | `15` | Heartbeat interval in seconds. The control plane marks a gateway connected on its first heartbeat. Clamped to [5, 300] by the gateway | -| extraEnvVars | list | `[]` | Extra environment variables for the gateway container: system-level environment variables (such as `TZ`) and variables referenced by the resources file. Gateway settings belong in `config` | +| extraEnvVars | list | `[]` | Extra environment variables for the gateway container: system-level environment variables (such as `TZ`) and variables referenced by the resources file. Gateway settings belong in `config`. A variable the chart sets itself can still be overridden here for compatibility, but that setting belongs in its own value | | extraVolumeMounts | list | `[]` | Extra volume mounts for the gateway container | | extraVolumes | list | `[]` | Extra volumes for the gateway pod | | fullnameOverride | string | `""` | Override the fully qualified resource name prefix | diff --git a/charts/aisix/README.md.gotmpl b/charts/aisix/README.md.gotmpl index e62dce7..c2d9e9d 100644 --- a/charts/aisix/README.md.gotmpl +++ b/charts/aisix/README.md.gotmpl @@ -459,7 +459,9 @@ configSecrets: `extraEnvVars` is for system-level environment variables such as `TZ`, and for the variables a standalone resources file references. An `AISIX_*` variable set -there still overrides the file, as it always has. +there still overrides the file, as it always has, and still wins over a variable +the chart sets itself: that keeps working for compatibility, but the setting +belongs in its own value. ## Parameters diff --git a/charts/aisix/templates/_helpers.tpl b/charts/aisix/templates/_helpers.tpl index fcf526c..876bc34 100644 --- a/charts/aisix/templates/_helpers.tpl +++ b/charts/aisix/templates/_helpers.tpl @@ -285,6 +285,86 @@ from the bundle Secret (which extraEnvVars then overrides), and no file keys. {{- end }} {{- end }} +{{/* +The gateway container's env: the chart's own variables, then `extraEnvVars`, +whose entries win on a repeated name. The chart leaves out its own entry for a +name `extraEnvVars` also sets — a repeated name breaks `helm upgrade` after a +rollback ("order in patch list") and is refused by server-side apply — except +for the names chart 1.5.0 rendered ("aisix.env150"). A 1.5.0 release that +overrides one of those carries the name twice, and an upgrade whose manifest +carries it once deletes both entries, the override included. For those names +the chart keeps rendering its entry ahead of the override, with the value 1.5.0 +gave it where the chart no longer sets it. +*/}} +{{- define "aisix.env" -}} +{{- $chart := list (dict "name" "AISIX_CONFIG_PATH" "value" (include "aisix.configPath" .)) }} +{{- if and .Values.controlPlane.enabled (include "aisix.cpPemFromEnv" .) }} +{{- range $slot, $key := dict "CERT" .Values.controlPlane.certificate.certKey "KEY" .Values.controlPlane.certificate.keyKey "CA" .Values.controlPlane.certificate.caKey }} +{{- $chart = append $chart (dict "name" (printf "AISIX_MANAGED__CP_%s_PEM" $slot) "valueFrom" (dict "secretKeyRef" (dict "name" (include "aisix.certSecretName" $) "key" $key))) }} +{{- end }} +{{- end }} +{{- if and (eq .Values.rateLimit.backend "redis") (or .Values.rateLimit.redis.url .Values.rateLimit.redis.existingSecret) }} +{{- $chart = append $chart (dict "name" "AISIX_RATELIMIT__REDIS__URL" "valueFrom" (dict "secretKeyRef" (dict "name" (include "aisix.redisSecretName" .) "key" (include "aisix.redisSecretKey" .)))) }} +{{- end }} +{{- range $path, $ref := (.Values.configSecrets | default dict) }} +{{- $chart = append $chart (dict "name" (include "aisix.configSecretEnvName" $path) "valueFrom" (dict "secretKeyRef" (dict "name" $ref.secretName "key" $ref.key))) }} +{{- end }} +{{- $extra := .Values.extraEnvVars | default list }} +{{- $overridden := list }} +{{- range $extra }} +{{- $overridden = append $overridden .name }} +{{- end }} +{{- $legacy := include "aisix.env150" . | fromYamlArray }} +{{- $legacyNames := list }} +{{- range $legacy }} +{{- $legacyNames = append $legacyNames .name }} +{{- end }} +{{- $env := list }} +{{- $chartNames := list }} +{{- range $chart }} +{{- $chartNames = append $chartNames .name }} +{{- if or (not (has .name $overridden)) (has .name $legacyNames) }} +{{- $env = append $env . }} +{{- end }} +{{- end }} +{{- range $legacy }} +{{- if and (has .name $overridden) (not (has .name $chartNames)) }} +{{- $env = append $env . }} +{{- end }} +{{- end }} +{{- toYaml (concat $env $extra) }} +{{- end }} + +{{/* +"aisix.env150" is the env list chart 1.5.0 rendered for these values — the +names "aisix.env" must not drop while an older release may still carry them +twice. Only the entries the chart no longer renders use these values. +*/}} +{{- define "aisix.env150" -}} +{{- $env := list (dict "name" "AISIX_CONFIG_PATH" "value" (ternary "/etc/aisix/config.managed.yaml" "/etc/aisix/standalone/config.yaml" .Values.controlPlane.enabled)) }} +{{- $env = append $env (dict "name" "AISIX_PROXY__ADDR" "value" (printf "0.0.0.0:%d" (int .Values.containerPorts.proxy))) }} +{{- if .Values.listeners }} +{{- $env = append $env (dict "name" "AISIX_PROXY__LISTENERS" "value" (include "aisix.proxyListenersJson" .)) }} +{{- end }} +{{- $env = append $env (dict "name" "AISIX_OBSERVABILITY__METRICS__PROMETHEUS__ADDR" "value" (printf "0.0.0.0:%d" (int .Values.containerPorts.metrics))) }} +{{- if .Values.controlPlane.enabled }} +{{- $env = append $env (dict "name" "AISIX_MANAGED__CP_BASE_URL" "value" .Values.controlPlane.baseURL) }} +{{- with .Values.controlPlane.etcdEndpoint }} +{{- $env = append $env (dict "name" "AISIX_MANAGED__CP_ETCD_ENDPOINT" "value" .) }} +{{- end }} +{{- $env = append $env (dict "name" "AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS" "value" (toString .Values.controlPlane.heartbeatIntervalSeconds)) }} +{{- range $slot, $key := dict "CERT" .Values.controlPlane.certificate.certKey "KEY" .Values.controlPlane.certificate.keyKey "CA" .Values.controlPlane.certificate.caKey }} +{{- $env = append $env (dict "name" (printf "AISIX_MANAGED__CP_%s_PEM" $slot) "valueFrom" (dict "secretKeyRef" (dict "name" (include "aisix.certSecretName" $) "key" $key))) }} +{{- end }} +{{- end }} +{{- /* 1.5.0 refused a Redis backend without one of these. */}} +{{- if and (eq .Values.rateLimit.backend "redis") (or .Values.rateLimit.redis.url .Values.rateLimit.redis.existingSecret) }} +{{- $env = append $env (dict "name" "AISIX_RATELIMIT__BACKEND" "value" "redis") }} +{{- $env = append $env (dict "name" "AISIX_RATELIMIT__REDIS__URL" "valueFrom" (dict "secretKeyRef" (dict "name" (include "aisix.redisSecretName" .) "key" (include "aisix.redisSecretKey" .)))) }} +{{- end }} +{{- toYaml $env }} +{{- end }} + {{/* The environment variable the gateway reads a `configSecrets` path from: AISIX_ plus the path upper-cased with `.` as `__`. diff --git a/charts/aisix/templates/deployment.yaml b/charts/aisix/templates/deployment.yaml index 30d407a..0e01bfa 100644 --- a/charts/aisix/templates/deployment.yaml +++ b/charts/aisix/templates/deployment.yaml @@ -78,36 +78,7 @@ spec: containerPort: {{ .Values.containerPorts.metrics }} protocol: TCP env: - - name: AISIX_CONFIG_PATH - value: {{ include "aisix.configPath" . }} - {{- if and .Values.controlPlane.enabled (include "aisix.cpPemFromEnv" .) }} - {{- range $slot, $key := dict "CERT" .Values.controlPlane.certificate.certKey "KEY" .Values.controlPlane.certificate.keyKey "CA" .Values.controlPlane.certificate.caKey }} - - name: AISIX_MANAGED__CP_{{ $slot }}_PEM - valueFrom: - secretKeyRef: - name: {{ include "aisix.certSecretName" $ }} - key: {{ $key }} - {{- end }} - {{- end }} - {{- if eq .Values.rateLimit.backend "redis" }} - {{- if or .Values.rateLimit.redis.url .Values.rateLimit.redis.existingSecret }} - - name: AISIX_RATELIMIT__REDIS__URL - valueFrom: - secretKeyRef: - name: {{ include "aisix.redisSecretName" . }} - key: {{ include "aisix.redisSecretKey" . }} - {{- end }} - {{- end }} - {{- range $path, $ref := (.Values.configSecrets | default dict) }} - - name: {{ include "aisix.configSecretEnvName" $path }} - valueFrom: - secretKeyRef: - name: {{ $ref.secretName }} - key: {{ $ref.key }} - {{- end }} - {{- with .Values.extraEnvVars }} - {{- toYaml . | nindent 12 }} - {{- end }} + {{- include "aisix.env" . | nindent 12 }} {{- if .Values.startupProbe.enabled }} # /livez answers as soon as the proxy listener is bound, which in # etcd mode is once the first configuration apply has succeeded. diff --git a/charts/aisix/values.yaml b/charts/aisix/values.yaml index 564c0db..6105e33 100644 --- a/charts/aisix/values.yaml +++ b/charts/aisix/values.yaml @@ -540,7 +540,9 @@ preStopSleepSeconds: 30 # -- Extra environment variables for the gateway container: system-level # environment variables (such as `TZ`) and variables referenced by the -# resources file. Gateway settings belong in `config` +# resources file. Gateway settings belong in `config`. A variable the chart +# sets itself can still be overridden here for compatibility, but that setting +# belongs in its own value extraEnvVars: [] # -- Extra volumes for the gateway pod From 37b11d08a2bf628dbb61c598710243cd6123d651 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Tue, 29 Sep 2026 10:23:03 +0000 Subject: [PATCH 2/2] docs(aisix): state that overriding a chart-managed name via extraEnvVars is a compatibility path only --- charts/aisix/README.md | 15 +++++++++++---- charts/aisix/README.md.gotmpl | 13 ++++++++++--- charts/aisix/values.yaml | 8 +++++--- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/charts/aisix/README.md b/charts/aisix/README.md index 8ba9ced..7e64851 100644 --- a/charts/aisix/README.md +++ b/charts/aisix/README.md @@ -465,9 +465,16 @@ configSecrets: `extraEnvVars` is for system-level environment variables such as `TZ`, and for the variables a standalone resources file references. An `AISIX_*` variable set -there still overrides the file, as it always has, and still wins over a variable -the chart sets itself: that keeps working for compatibility, but the setting -belongs in its own value. +there still overrides the file, as it always has. + +Overriding a variable the chart sets itself through `extraEnvVars` is only a +compatibility path for existing deployments, and the `extraEnvVars` value still +wins. For a name chart 1.5.0 set (such as `AISIX_MANAGED__CP_BASE_URL` or +`AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS`) the chart keeps rendering its own +entry ahead of yours, so a Helm 3 upgrade from 1.5.0 does not drop the override; +the container then lists the name twice, which Helm 4 server-side apply +rejects. New installs set the value in its own key in `values.yaml` or under +`config` instead. ## Parameters @@ -547,7 +554,7 @@ belongs in its own value. | controlPlane.enabled | bool | `true` | Read configuration from an AISIX control plane. Set to false to run standalone, from the `resources.yaml` file configured under `standalone` | | controlPlane.etcdEndpoint | string | `""` | Control-plane etcd endpoint as bare `host:port`. Leave empty unless the control plane publishes an etcd endpoint distinct from `baseURL` | | controlPlane.heartbeatIntervalSeconds | int | `15` | Heartbeat interval in seconds. The control plane marks a gateway connected on its first heartbeat. Clamped to [5, 300] by the gateway | -| extraEnvVars | list | `[]` | Extra environment variables for the gateway container: system-level environment variables (such as `TZ`) and variables referenced by the resources file. Gateway settings belong in `config`. A variable the chart sets itself can still be overridden here for compatibility, but that setting belongs in its own value | +| extraEnvVars | list | `[]` | Extra environment variables for the gateway container: system-level environment variables (such as `TZ`) and variables referenced by the resources file. Gateway settings belong in `config`. Overriding a variable the chart sets itself is only a compatibility path for existing deployments: for a name chart 1.5.0 set, the rendered container then lists it twice, which Helm 4 server-side apply rejects. New installs set the value in its own key or under `config` | | extraVolumeMounts | list | `[]` | Extra volume mounts for the gateway container | | extraVolumes | list | `[]` | Extra volumes for the gateway pod | | fullnameOverride | string | `""` | Override the fully qualified resource name prefix | diff --git a/charts/aisix/README.md.gotmpl b/charts/aisix/README.md.gotmpl index c2d9e9d..c984174 100644 --- a/charts/aisix/README.md.gotmpl +++ b/charts/aisix/README.md.gotmpl @@ -459,9 +459,16 @@ configSecrets: `extraEnvVars` is for system-level environment variables such as `TZ`, and for the variables a standalone resources file references. An `AISIX_*` variable set -there still overrides the file, as it always has, and still wins over a variable -the chart sets itself: that keeps working for compatibility, but the setting -belongs in its own value. +there still overrides the file, as it always has. + +Overriding a variable the chart sets itself through `extraEnvVars` is only a +compatibility path for existing deployments, and the `extraEnvVars` value still +wins. For a name chart 1.5.0 set (such as `AISIX_MANAGED__CP_BASE_URL` or +`AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS`) the chart keeps rendering its own +entry ahead of yours, so a Helm 3 upgrade from 1.5.0 does not drop the override; +the container then lists the name twice, which Helm 4 server-side apply +rejects. New installs set the value in its own key in `values.yaml` or under +`config` instead. ## Parameters diff --git a/charts/aisix/values.yaml b/charts/aisix/values.yaml index 6105e33..6b89e40 100644 --- a/charts/aisix/values.yaml +++ b/charts/aisix/values.yaml @@ -540,9 +540,11 @@ preStopSleepSeconds: 30 # -- Extra environment variables for the gateway container: system-level # environment variables (such as `TZ`) and variables referenced by the -# resources file. Gateway settings belong in `config`. A variable the chart -# sets itself can still be overridden here for compatibility, but that setting -# belongs in its own value +# resources file. Gateway settings belong in `config`. Overriding a variable +# the chart sets itself is only a compatibility path for existing deployments: +# for a name chart 1.5.0 set, the rendered container then lists it twice, which +# Helm 4 server-side apply rejects. New installs set the value in its own key +# or under `config` extraEnvVars: [] # -- Extra volumes for the gateway pod