diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2c688d0..5f3e2cb 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 ddf72eb..de79241 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 fbace59..89c4ef9 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 0000000..46d4325 --- /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 8148bff..9b76d95 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 a2ec893..cfd1bce 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 37fcb82..02201f9 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 947a3b0..14ba951 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 8f690d9..3a061cb 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: {}