From 3ad7df0686bf08589c29aaba551b0016e1caccf9 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Thu, 17 Sep 2026 12:54:23 +0000 Subject: [PATCH] feat(aisix): serve several proxy listeners, each with its own TLS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gateway can now bind more than one proxy listener (`proxy.listeners`), so one deployment can serve HTTPS and plain HTTP side by side. Expose that through the chart as `listeners`, a list of `{name, containerPort, servicePort, nodePort?, tls.secretName?}`. Empty — the default — changes nothing: the single listener from `containerPorts.proxy` is published as `service.port`, and every template renders exactly as before. A non-empty list is the complete set of proxy listeners. The container gets one port per entry, the one proxy Service publishes one port per entry targeting it by name, each TLS entry mounts its `kubernetes.io/tls` Secret read-only at `/etc/aisix/tls/`, and the listener set reaches the gateway as `AISIX_PROXY__LISTENERS` — one JSON document, since the gateway reads no indexed environment variables and takes certificates as file paths. `AISIX_PROXY__ADDR` keeps being injected because the gateway still requires `proxy.addr` to be present and valid; with `proxy.listeners` set it logs that it ignores it and binds nothing on it. Every listener serves the same routes, `/livez` and `/readyz` included, so the probes target the first entry, over HTTPS when that entry terminates TLS (the kubelet does not verify the certificate). Rendering fails on an entry missing `name`, `containerPort` or `servicePort`, and on a duplicate name or container port — the gateway rejects two listeners on one address at startup, which is worth catching at template time. CI installs the chart with an `https` + `http` pair against a self-signed Secret and checks that both Service ports answer `/livez`, alongside the existing single-listener install. Ref api7/AISIX-Cloud#1662 --- .github/workflows/ci.yaml | 41 ++++++++++++++++ charts/aisix/README.md | 65 ++++++++++++++++++++++++-- charts/aisix/README.md.gotmpl | 58 +++++++++++++++++++++++ charts/aisix/ci/listeners-values.yaml | 25 ++++++++++ charts/aisix/templates/NOTES.txt | 28 ++++++++--- charts/aisix/templates/_helpers.tpl | 65 ++++++++++++++++++++++++++ charts/aisix/templates/deployment.yaml | 49 +++++++++++++++++-- charts/aisix/templates/service.yaml | 12 +++++ charts/aisix/values.yaml | 39 ++++++++++++++-- 9 files changed, 367 insertions(+), 15 deletions(-) create mode 100644 charts/aisix/ci/listeners-values.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2c688d0b..5f3e2cbe 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -117,6 +117,47 @@ jobs: kubectl delete namespace "$ns" ' + - name: Test aisix chart with several proxy listeners + run: | + kubectl cluster-info + ns=aisix-listeners + kubectl create namespace "$ns" + # Self-signed is enough: the only TLS clients here are the kubelet's + # probes and curl -k, and neither verifies the certificate. + openssl req -x509 -newkey rsa:2048 -nodes -days 1 \ + -subj "/CN=aisix.$ns.svc" \ + -keyout proxy-tls.key -out proxy-tls.crt + kubectl -n "$ns" create secret tls aisix-ci-proxy-tls \ + --cert=proxy-tls.crt --key=proxy-tls.key + rm -f proxy-tls.key proxy-tls.crt + docker run --rm --interactive --network host \ + --name ct-aisix-listeners \ + --volume $HOME/.kube/config:/root/.kube/config \ + --volume $PWD:/workdir \ + --workdir /workdir \ + quay.io/helmpack/chart-testing:v3.10.1 sh -c ' + set -e + ns=aisix-listeners + if ! helm install aisix charts/aisix --namespace "$ns" \ + --values charts/aisix/ci/standalone-values.yaml \ + --values charts/aisix/ci/listeners-values.yaml \ + --wait --timeout 5m; then + kubectl -n "$ns" get pods -o wide || true + kubectl -n "$ns" describe pods || true + kubectl -n "$ns" logs -l app.kubernetes.io/name=aisix --tail=200 || true + exit 1 + fi + # Both listeners answer /livez through the one proxy Service. + kubectl -n "$ns" run curl-https --rm --attach --restart=Never \ + --image=curlimages/curl:8.11.1 --command -- \ + curl -fsS -k https://aisix:443/livez + kubectl -n "$ns" run curl-http --rm --attach --restart=Never \ + --image=curlimages/curl:8.11.1 --command -- \ + curl -fsS http://aisix:80/livez + helm uninstall aisix --namespace "$ns" + kubectl delete namespace "$ns" + ' + - name: Setup Go uses: actions/setup-go@v5 with: diff --git a/charts/aisix/README.md b/charts/aisix/README.md index ddf72ebb..de792414 100644 --- a/charts/aisix/README.md +++ b/charts/aisix/README.md @@ -327,6 +327,64 @@ service: externalTrafficPolicy: Local ``` +### Serve HTTPS and plain HTTP together + +By default the gateway serves one plain-HTTP proxy listener, on +`containerPorts.proxy`, published as `service.port`. Set `listeners` to serve +several at once — each on its own port, with its own TLS: + +```yaml +listeners: + - name: https # port name, shared by the container port and the Service port + containerPort: 3443 + servicePort: 443 + nodePort: 0 # optional; only for non-ClusterIP Service types + tls: + secretName: aisix-proxy-tls # kubernetes.io/tls Secret (keys tls.crt / tls.key) + - name: http + containerPort: 3000 + servicePort: 80 +``` + +A non-empty `listeners` is the complete set of proxy listeners and replaces the +single default one: nothing binds `containerPorts.proxy`, and `service.port` / +`service.nodePort` are not read — each entry carries its own. There is still one +proxy Service; it publishes a port per entry. Every listener serves the same +routes, `/livez` and `/readyz` included, so the probes target the first entry +(over HTTPS when that entry terminates TLS; the kubelet does not verify the +certificate). This needs a gateway image that supports `proxy.listeners`. + +TLS material is read from files, so each TLS listener needs a +`kubernetes.io/tls` Secret; the chart mounts it read-only at +`/etc/aisix/tls/`. Create it from a certificate and key you already have: + +```sh +kubectl -n aisix create secret tls aisix-proxy-tls \ + --cert=./tls.crt --key=./tls.key +``` + +Or have [cert-manager](https://cert-manager.io) issue and renew it into the same +Secret: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: aisix-proxy-tls + namespace: aisix +spec: + secretName: aisix-proxy-tls + dnsNames: + - gateway.example.com + issuerRef: + name: letsencrypt + kind: ClusterIssuer +``` + +A rotated certificate reaches the gateway as a changed file in that mount; roll +the pods to pick it up with +`kubectl rollout restart deploy/-aisix -n `. + ### Bind a privileged port The image carries the `CAP_NET_BIND_SERVICE` file capability, so the gateway binds @@ -381,7 +439,7 @@ extraEnvVars: | autoscaling.targetCPUUtilizationPercentage | int | `70` | Target average CPU utilization, in percent of the CPU request. Set to null to drop the CPU metric | | autoscaling.targetMemoryUtilizationPercentage | string | `nil` | Target average memory utilization, in percent of the memory request. Null by default: gateway memory tracks in-flight streams more than load | | containerPorts.metrics | int | `9090` | Port the Prometheus metrics listener binds inside the container | -| containerPorts.proxy | int | `3000` | Port the proxy listener binds inside the container. The image carries the `CAP_NET_BIND_SERVICE` file capability, so a privileged port works without running as root — see `securityContext` below | +| containerPorts.proxy | int | `3000` | Port the proxy listener binds inside the container. Nothing binds it when `listeners` is set — that list then carries every proxy port, and the gateway keeps requiring this address only to ignore it. The image carries the `CAP_NET_BIND_SERVICE` file capability, so a privileged port works without running as root — see `securityContext` below | | controlPlane.baseURL | string | `""` | Data-plane manager mTLS endpoint the gateway connects out to, e.g. `https://dpm.example.com:7944`. Required. | | controlPlane.certificate.ca | string | `""` | CA bundle PEM. Used only when `existingSecret` is empty | | controlPlane.certificate.caKey | string | `"ca.pem"` | Secret key holding the CA bundle PEM | @@ -411,6 +469,7 @@ extraEnvVars: | keda.pollingInterval | int | `15` | How often KEDA evaluates the triggers, in seconds | | keda.restoreToOriginalReplicaCount | bool | `false` | Restore the original replica count when the ScaledObject is deleted | | keda.triggers | list | `[]` | KEDA triggers. Required when `keda.enabled` is true. For example: `[{type: prometheus, metadata: {serverAddress: "http://prometheus:9090", query: "sum(rate(aisix_llm_requests_total[2m]))", threshold: "100"}}]` | +| listeners | list | `[]` | Proxy listeners, one entry per port. Empty keeps the single plain-HTTP listener described by `containerPorts.proxy` and `service.port` — see "Serve HTTPS and plain HTTP together" above | | livenessProbe.enabled | bool | `true` | | | livenessProbe.failureThreshold | int | `3` | | | livenessProbe.initialDelaySeconds | int | `10` | | @@ -453,8 +512,8 @@ extraEnvVars: | securityContext.readOnlyRootFilesystem | bool | `true` | | | service.annotations | object | `{}` | Extra annotations for the proxy Service, e.g. cloud load-balancer settings | | service.externalTrafficPolicy | string | `""` | `externalTrafficPolicy` for the proxy Service. `Local` preserves the client source IP on NodePort / LoadBalancer types | -| service.nodePort | string | `""` | Proxy Service nodePort, when `service.type` is NodePort or LoadBalancer | -| service.port | int | `80` | Proxy Service port | +| service.nodePort | string | `""` | Proxy Service nodePort, when `service.type` is NodePort or LoadBalancer. Unused when `listeners` is set — each entry there carries its own `nodePort` | +| service.port | int | `80` | Proxy Service port. Unused when `listeners` is set — each entry there carries its own `servicePort` | | service.type | string | `"ClusterIP"` | Proxy Service type | | serviceAccount.annotations | object | `{}` | ServiceAccount annotations | | serviceAccount.create | bool | `true` | Create a ServiceAccount for the gateway | diff --git a/charts/aisix/README.md.gotmpl b/charts/aisix/README.md.gotmpl index fbace591..89c4ef95 100644 --- a/charts/aisix/README.md.gotmpl +++ b/charts/aisix/README.md.gotmpl @@ -321,6 +321,64 @@ service: externalTrafficPolicy: Local ``` +### Serve HTTPS and plain HTTP together + +By default the gateway serves one plain-HTTP proxy listener, on +`containerPorts.proxy`, published as `service.port`. Set `listeners` to serve +several at once — each on its own port, with its own TLS: + +```yaml +listeners: + - name: https # port name, shared by the container port and the Service port + containerPort: 3443 + servicePort: 443 + nodePort: 0 # optional; only for non-ClusterIP Service types + tls: + secretName: aisix-proxy-tls # kubernetes.io/tls Secret (keys tls.crt / tls.key) + - name: http + containerPort: 3000 + servicePort: 80 +``` + +A non-empty `listeners` is the complete set of proxy listeners and replaces the +single default one: nothing binds `containerPorts.proxy`, and `service.port` / +`service.nodePort` are not read — each entry carries its own. There is still one +proxy Service; it publishes a port per entry. Every listener serves the same +routes, `/livez` and `/readyz` included, so the probes target the first entry +(over HTTPS when that entry terminates TLS; the kubelet does not verify the +certificate). This needs a gateway image that supports `proxy.listeners`. + +TLS material is read from files, so each TLS listener needs a +`kubernetes.io/tls` Secret; the chart mounts it read-only at +`/etc/aisix/tls/`. Create it from a certificate and key you already have: + +```sh +kubectl -n aisix create secret tls aisix-proxy-tls \ + --cert=./tls.crt --key=./tls.key +``` + +Or have [cert-manager](https://cert-manager.io) issue and renew it into the same +Secret: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: aisix-proxy-tls + namespace: aisix +spec: + secretName: aisix-proxy-tls + dnsNames: + - gateway.example.com + issuerRef: + name: letsencrypt + kind: ClusterIssuer +``` + +A rotated certificate reaches the gateway as a changed file in that mount; roll +the pods to pick it up with +`kubectl rollout restart deploy/-aisix -n `. + ### Bind a privileged port The image carries the `CAP_NET_BIND_SERVICE` file capability, so the gateway binds diff --git a/charts/aisix/ci/listeners-values.yaml b/charts/aisix/ci/listeners-values.yaml new file mode 100644 index 00000000..46d43257 --- /dev/null +++ b/charts/aisix/ci/listeners-values.yaml @@ -0,0 +1,25 @@ +# Values used by chart-testing for the multiple-proxy-listeners mode: HTTPS on +# 3443 and plain HTTP on 3000, published on Service ports 443 and 80. +# +# `ct lint` renders every file in this directory on its own, so this one also +# carries control-plane placeholders to render — the same ones as +# ci/default-values.yaml, and equally never created in a cluster. The install +# test layers this file over ci/standalone-values.yaml, which turns the control +# plane off, so only the listeners below are read there. +controlPlane: + baseURL: "https://dp-manager.example.com:7944" + certificate: + existingSecret: "aisix-gateway-certificate" + +# The referenced Secret is created by the CI step before it installs the chart: +# a self-signed certificate is enough, since the probes run against the +# listener from the kubelet, which does not verify it. +listeners: + - name: https + containerPort: 3443 + servicePort: 443 + tls: + secretName: aisix-ci-proxy-tls + - name: http + containerPort: 3000 + servicePort: 80 diff --git a/charts/aisix/templates/NOTES.txt b/charts/aisix/templates/NOTES.txt index 8148bffd..9b76d95f 100644 --- a/charts/aisix/templates/NOTES.txt +++ b/charts/aisix/templates/NOTES.txt @@ -1,3 +1,14 @@ +{{- $proxyPort := .Values.service.port -}} +{{- $scheme := "http" -}} +{{- $curl := "curl" -}} +{{- if .Values.listeners -}} +{{- $first := first .Values.listeners -}} +{{- $proxyPort = $first.servicePort -}} +{{- if include "aisix.proxyListenerTLS" . -}} +{{- $scheme = "https" -}} +{{- $curl = "curl -k" -}} +{{- end -}} +{{- end -}} AISIX gateway {{ .Chart.AppVersion }} has been deployed as {{ include "aisix.fullname" . }}. {{ if .Values.controlPlane.enabled -}} @@ -24,21 +35,26 @@ After editing the {{ if .Values.standalone.existingSecret }}Secret{{ else }}Conf Watch it come up: kubectl rollout status deploy/{{ include "aisix.fullname" . }} -n {{ .Release.Namespace }} - -Send a request through it: +{{ if .Values.listeners }} +It serves these proxy listeners, all carrying the same routes: +{{ range .Values.listeners }} + {{ .name }}: Service port {{ .servicePort }} -> container port {{ .containerPort }}{{ if .tls }}{{ if .tls.secretName }}, TLS from Secret {{ .tls.secretName }}{{ end }}{{ end }} +{{- end }} +{{ end }} +Send a request through it{{ if .Values.listeners }} on the {{ (first .Values.listeners).name }} listener{{ end }}: {{- if eq .Values.service.type "ClusterIP" }} - kubectl port-forward svc/{{ include "aisix.fullname" . }} 8080:{{ .Values.service.port }} -n {{ .Release.Namespace }} - curl http://localhost:8080/v1/models -H "Authorization: Bearer " + kubectl port-forward svc/{{ include "aisix.fullname" . }} 8080:{{ $proxyPort }} -n {{ .Release.Namespace }} + {{ $curl }} {{ $scheme }}://localhost:8080/v1/models -H "Authorization: Bearer " {{- else if eq .Values.service.type "NodePort" }} export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}') export NODE_PORT=$(kubectl get svc {{ include "aisix.fullname" . }} -n {{ .Release.Namespace }} -o jsonpath='{.spec.ports[0].nodePort}') - curl http://$NODE_IP:$NODE_PORT/v1/models -H "Authorization: Bearer " + {{ $curl }} {{ $scheme }}://$NODE_IP:$NODE_PORT/v1/models -H "Authorization: Bearer " {{- else }} export LB_IP=$(kubectl get svc {{ include "aisix.fullname" . }} -n {{ .Release.Namespace }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - curl http://$LB_IP:{{ .Values.service.port }}/v1/models -H "Authorization: Bearer " + {{ $curl }} {{ $scheme }}://$LB_IP:{{ $proxyPort }}/v1/models -H "Authorization: Bearer " {{- end }} {{ if .Values.autoscaling.enabled -}} diff --git a/charts/aisix/templates/_helpers.tpl b/charts/aisix/templates/_helpers.tpl index a2ec893f..cfd1bce1 100644 --- a/charts/aisix/templates/_helpers.tpl +++ b/charts/aisix/templates/_helpers.tpl @@ -125,6 +125,50 @@ Secret key holding the rate-limit Redis URL. {{- end }} {{- end }} +{{/* +Multiple proxy listeners. + +`listeners` empty is the single-listener default and every one of these is +inert, so a default render is unchanged. + +"aisix.proxyPortName" is the port the probes target: the first listener, or +the built-in "proxy" port. + +"aisix.proxyListenerTLS" is non-empty when that first listener terminates TLS, +so the probes know to speak HTTPS to it. + +"aisix.proxyListenersJson" builds the AISIX_PROXY__LISTENERS value. The gateway +takes the whole list as one JSON document — indexed environment variables are +not a form it accepts — and reads TLS material from files, so each TLS listener +points at the directory its Secret is mounted in. +*/}} +{{- define "aisix.proxyPortName" -}} +{{- if .Values.listeners }}{{ (first .Values.listeners).name }}{{ else }}proxy{{ end }} +{{- end }} + +{{- define "aisix.proxyListenerTLSDir" -}}/etc/aisix/tls/{{ .name }}{{- end }} + +{{- define "aisix.proxyListenerTLS" -}} +{{- if .Values.listeners }} +{{- with (first .Values.listeners).tls }}{{ if .secretName }}true{{ end }}{{ end }} +{{- end }} +{{- end }} + +{{- define "aisix.proxyListenersJson" -}} +{{- $listeners := list }} +{{- range $listener := .Values.listeners }} +{{- $entry := dict "addr" (printf "0.0.0.0:%d" (int $listener.containerPort)) }} +{{- if $listener.tls }} +{{- if $listener.tls.secretName }} +{{- $dir := include "aisix.proxyListenerTLSDir" $listener }} +{{- $_ := set $entry "tls" (dict "cert_file" (printf "%s/tls.crt" $dir) "key_file" (printf "%s/tls.key" $dir)) }} +{{- end }} +{{- end }} +{{- $listeners = append $listeners $entry }} +{{- end }} +{{- toJson $listeners }} +{{- end }} + {{/* Reject value combinations that render successfully but cannot run. */}} @@ -158,4 +202,25 @@ Reject value combinations that render successfully but cannot run. {{- fail "rateLimit.backend=redis requires rateLimit.redis.url or rateLimit.redis.existingSecret" }} {{- end }} {{- end }} +{{- $names := list }} +{{- $ports := list }} +{{- range $i, $listener := .Values.listeners }} +{{- if not $listener.name }} +{{- fail (printf "listeners[%d].name is required: it names both the container port and the Service port" $i) }} +{{- end }} +{{- if not $listener.containerPort }} +{{- fail (printf "listeners[%d] (%s) requires containerPort" $i $listener.name) }} +{{- end }} +{{- if not $listener.servicePort }} +{{- fail (printf "listeners[%d] (%s) requires servicePort" $i $listener.name) }} +{{- end }} +{{- if has $listener.name $names }} +{{- fail (printf "listeners[%d]: duplicate name %s — listener names must be unique" $i $listener.name) }} +{{- end }} +{{- if has (int $listener.containerPort) $ports }} +{{- fail (printf "listeners[%d] (%s): duplicate containerPort %d — the gateway rejects two listeners on one address" $i $listener.name (int $listener.containerPort)) }} +{{- end }} +{{- $names = append $names $listener.name }} +{{- $ports = append $ports (int $listener.containerPort) }} +{{- end }} {{- end }} diff --git a/charts/aisix/templates/deployment.yaml b/charts/aisix/templates/deployment.yaml index 37fcb82c..02201f9c 100644 --- a/charts/aisix/templates/deployment.yaml +++ b/charts/aisix/templates/deployment.yaml @@ -76,9 +76,17 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} ports: + {{- if .Values.listeners }} + {{- range .Values.listeners }} + - name: {{ .name }} + containerPort: {{ .containerPort }} + protocol: TCP + {{- end }} + {{- else }} - name: proxy containerPort: {{ .Values.containerPorts.proxy }} protocol: TCP + {{- end }} - name: metrics containerPort: {{ .Values.containerPorts.metrics }} protocol: TCP @@ -91,6 +99,14 @@ spec: {{- end }} - name: AISIX_PROXY__ADDR value: "0.0.0.0:{{ .Values.containerPorts.proxy }}" + {{- if .Values.listeners }} + # The complete set of proxy listeners, as one JSON document: the + # gateway takes no indexed environment variables. It replaces the + # single listener AISIX_PROXY__ADDR describes — that address is + # then required but ignored, and nothing binds it. + - name: AISIX_PROXY__LISTENERS + value: {{ include "aisix.proxyListenersJson" . | quote }} + {{- end }} - name: AISIX_OBSERVABILITY__METRICS__PROMETHEUS__ADDR value: "0.0.0.0:{{ .Values.containerPorts.metrics }}" {{- if .Values.controlPlane.enabled }} @@ -139,7 +155,10 @@ spec: startupProbe: httpGet: path: /livez - port: proxy + port: {{ include "aisix.proxyPortName" . }} + {{- if include "aisix.proxyListenerTLS" . }} + scheme: HTTPS + {{- end }} periodSeconds: {{ .Values.startupProbe.periodSeconds }} failureThreshold: {{ .Values.startupProbe.failureThreshold }} {{- end }} @@ -161,7 +180,10 @@ spec: readinessProbe: httpGet: path: /readyz - port: proxy + port: {{ include "aisix.proxyPortName" . }} + {{- if include "aisix.proxyListenerTLS" . }} + scheme: HTTPS + {{- end }} periodSeconds: {{ .Values.readinessProbe.periodSeconds }} failureThreshold: {{ .Values.readinessProbe.failureThreshold }} {{- end }} @@ -169,7 +191,10 @@ spec: livenessProbe: httpGet: path: /livez - port: proxy + port: {{ include "aisix.proxyPortName" . }} + {{- if include "aisix.proxyListenerTLS" . }} + scheme: HTTPS + {{- end }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.livenessProbe.periodSeconds }} failureThreshold: {{ .Values.livenessProbe.failureThreshold }} @@ -201,6 +226,15 @@ spec: mountPath: {{ include "aisix.standaloneResourcesDir" . }} readOnly: true {{- end }} + {{- range .Values.listeners }} + {{- if .tls }} + {{- if .tls.secretName }} + - name: tls-{{ .name }} + mountPath: {{ include "aisix.proxyListenerTLSDir" . }} + readOnly: true + {{- end }} + {{- end }} + {{- end }} {{- with .Values.extraVolumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} @@ -220,6 +254,15 @@ spec: secretName: {{ include "aisix.resourcesObjectName" . }} {{- end }} {{- end }} + {{- range .Values.listeners }} + {{- if .tls }} + {{- if .tls.secretName }} + - name: tls-{{ .name }} + secret: + secretName: {{ .tls.secretName }} + {{- end }} + {{- end }} + {{- end }} {{- with .Values.extraVolumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/charts/aisix/templates/service.yaml b/charts/aisix/templates/service.yaml index 947a3b01..14ba951d 100644 --- a/charts/aisix/templates/service.yaml +++ b/charts/aisix/templates/service.yaml @@ -15,6 +15,17 @@ spec: externalTrafficPolicy: {{ . }} {{- end }} ports: + {{- if .Values.listeners }} + {{- range .Values.listeners }} + - name: {{ .name }} + port: {{ .servicePort }} + targetPort: {{ .name }} + protocol: TCP + {{- if and .nodePort (ne (toString $.Values.service.type) "ClusterIP") }} + nodePort: {{ .nodePort }} + {{- end }} + {{- end }} + {{- else }} - name: proxy port: {{ .Values.service.port }} targetPort: proxy @@ -22,5 +33,6 @@ spec: {{- if and .Values.service.nodePort (ne (toString .Values.service.type) "ClusterIP") }} nodePort: {{ .Values.service.nodePort }} {{- end }} + {{- end }} selector: {{- include "aisix.selectorLabels" . | nindent 4 }} diff --git a/charts/aisix/values.yaml b/charts/aisix/values.yaml index 8f690d9f..3a061cb8 100644 --- a/charts/aisix/values.yaml +++ b/charts/aisix/values.yaml @@ -85,19 +85,52 @@ standalone: existingConfigMap: "" containerPorts: - # -- Port the proxy listener binds inside the container. The image carries + # -- Port the proxy listener binds inside the container. Nothing binds it + # when `listeners` is set — that list then carries every proxy port, and the + # gateway keeps requiring this address only to ignore it. The image carries # the `CAP_NET_BIND_SERVICE` file capability, so a privileged port works # without running as root — see `securityContext` below proxy: 3000 # -- Port the Prometheus metrics listener binds inside the container metrics: 9090 +## Several proxy listeners at once — for example HTTPS and plain HTTP side by +## side, each on its own port. Empty (the default) keeps the single plain-HTTP +## listener described by `containerPorts.proxy` and `service.port`. +## +## A non-empty list is the complete set of proxy listeners and replaces that +## single one: nothing binds `containerPorts.proxy` any more, `service.port` +## and `service.nodePort` are not read, the one proxy Service publishes a port +## per entry, and the probes target the first entry. Every listener serves the +## same routes, including `/livez` and `/readyz`; TLS is per listener. +## +## Needs a gateway image that supports `proxy.listeners`. TLS material is read +## from a `kubernetes.io/tls` Secret you supply (keys `tls.crt` / `tls.key`), +## mounted read-only at `/etc/aisix/tls/`. +# -- Proxy listeners, one entry per port. Empty keeps the single plain-HTTP +# listener described by `containerPorts.proxy` and `service.port` — see +# "Serve HTTPS and plain HTTP together" above +listeners: [] +# listeners: +# - name: https # port name, shared by the container port and the Service port +# containerPort: 3443 +# servicePort: 443 +# nodePort: 0 # optional; only for non-ClusterIP Service types +# tls: +# secretName: aisix-proxy-tls # kubernetes.io/tls Secret (keys tls.crt / tls.key), e.g. issued by cert-manager +# - name: http +# containerPort: 3000 +# servicePort: 80 + service: # -- Proxy Service type type: ClusterIP - # -- Proxy Service port + # -- Proxy Service port. Unused when `listeners` is set — each entry there + # carries its own `servicePort` port: 80 - # -- Proxy Service nodePort, when `service.type` is NodePort or LoadBalancer + # -- Proxy Service nodePort, when `service.type` is NodePort or + # LoadBalancer. Unused when `listeners` is set — each entry there carries its + # own `nodePort` nodePort: "" # -- Extra annotations for the proxy Service, e.g. cloud load-balancer settings annotations: {}