prole/supabase/helm/knoe-supabase/templates/kong/config.yaml
chrisfu 0f2fe93ebf fix(net): GCE BackendConfig rejects type: TCP; switch to HTTP /healthz
Follow-up to 391c4f5. Live deploy showed GCE's L7 BackendConfig CRD
hard-rejects type: TCP with:

  Error syncing to GCP: error running backend syncing routine:
  error ensuring health check:
  Protocol "TCP" is not valid, must be one of [HTTP,HTTPS,HTTP2]

The sync never completes, so the LB has no healthy backend and TCP
connections to the public endpoint just close (ERR_CONNECTION_CLOSED).

Fix: switch all three BackendConfigs to type: HTTP with request paths
that return 200:

- supabase-kong & knoe-svc-kong: add a dedicated /healthz route to the
  Kong declarative config via the request-termination plugin, which
  returns 200 synchronously with no upstream call. Equivalent liveness
  semantics to the TCP check we wanted (backend is alive as long as Kong
  accepts connections) but over HTTP, which GCE actually accepts.
  - supabase/helm/knoe-supabase/templates/kong/config.yaml
  - etc/init_kong.sh (inline kong.yml heredoc)

- supabase-studio: Studio returns 301 on / (Next.js default) so we
  point the probe at /favicon.ico -- Next.js serves it as a static asset
  with 200 unconditionally. Not as clean as a real readiness endpoint
  but Studio does not expose one that returns 200 without auth.
  - supabase/helm/knoe-supabase/templates/studio/backendconfig.yaml

Verified locally via helm template -f values.generated.json: the
rendered BackendConfigs come out with the HTTP protocol + correct paths,
and the Kong ConfigMap has the healthz service block before the
auth-v1-open service.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 12:05:40 -07:00

283 lines
9.4 KiB
YAML

{{- if .Values.deployment.kong.enabled -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "supabase.kong.fullname" . }}
labels:
{{- include "supabase.labels" . | nindent 4 }}
data:
wrapper.sh: |
#!/bin/bash
set -euo pipefail
echo "Replacing env placeholders of /usr/local/kong/kong.yml"
sed \
-e "s|\${SUPABASE_ANON_KEY}|${SUPABASE_ANON_KEY}|" \
-e "s|\${SUPABASE_SERVICE_KEY}|${SUPABASE_SERVICE_KEY}|" \
-e "s|\${DASHBOARD_USERNAME}|${DASHBOARD_USERNAME}|" \
-e "s|\${DASHBOARD_PASSWORD}|${DASHBOARD_PASSWORD}|" \
/usr/local/kong/template.yml \
> /usr/local/kong/kong.yml
exec /docker-entrypoint.sh kong docker-start
template.yml: |
_format_version: '2.1'
_transform: true
consumers:
{{- if .Values.secret.dashboard }}
- username: DASHBOARD
{{- end }}
- username: anon
keyauth_credentials:
- key: ${SUPABASE_ANON_KEY}
- username: service_role
keyauth_credentials:
- key: ${SUPABASE_SERVICE_KEY}
acls:
- consumer: anon
group: anon
- consumer: service_role
group: admin
{{- if .Values.secret.dashboard }}
basicauth_credentials:
- consumer: DASHBOARD
username: ${DASHBOARD_USERNAME}
password: ${DASHBOARD_PASSWORD}
{{- end }}
services:
# Dedicated health endpoint used by the GCE LB BackendConfig.
# request-termination returns 200 synchronously without hitting any
# upstream, so the probe passes as long as the Kong proxy itself is
# accepting requests -- same liveness semantics as the TCP check we
# originally wanted, but using the HTTP protocol that GCE's L7
# BackendConfig CRD actually accepts (TCP is rejected with
# `Protocol "TCP" is not valid, must be one of [HTTP,HTTPS,HTTP2]`).
- name: healthz
url: http://127.0.0.1:8000/
routes:
- name: healthz
strip_path: true
paths:
- /healthz
plugins:
- name: request-termination
config:
status_code: 200
message: ok
{{- if .Values.deployment.auth.enabled }}
- name: auth-v1-open
url: http://{{ include "supabase.auth.fullname" . }}:{{ .Values.service.auth.port }}/verify
routes:
- name: auth-v1-open
strip_path: true
paths:
- /auth/v1/verify
plugins:
- name: cors
- name: auth-v1-open-callback
url: http://{{ include "supabase.auth.fullname" . }}:{{ .Values.service.auth.port }}/callback
routes:
- name: auth-v1-open-callback
strip_path: true
paths:
- /auth/v1/callback
plugins:
- name: cors
- name: auth-v1-open-authorize
url: http://{{ include "supabase.auth.fullname" . }}:{{ .Values.service.auth.port }}/authorize
routes:
- name: auth-v1-open-authorize
strip_path: true
paths:
- /auth/v1/authorize
plugins:
- name: cors
- name: auth-v1
_comment: "GoTrue: /auth/v1/* -> http://{{ include "supabase.auth.fullname" . }}:{{ .Values.service.auth.port }}/*"
url: http://{{ include "supabase.auth.fullname" . }}:{{ .Values.service.auth.port }}
routes:
- name: auth-v1-all
strip_path: true
paths:
- /auth/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
{{- end }}
{{- if .Values.deployment.rest.enabled }}
- name: rest-v1
_comment: "PostgREST: /rest/v1/* -> http://{{ include "supabase.rest.fullname" . }}:{{ .Values.service.rest.port }}/*"
url: http://{{ include "supabase.rest.fullname" . }}:{{ .Values.service.rest.port }}/
routes:
- name: rest-v1-all
strip_path: true
paths:
- /rest/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: true
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: graphql-v1
_comment: 'PostgREST: /graphql/v1/* -> http://{{ include "supabase.rest.fullname" . }}:{{ .Values.service.rest.port }}/rpc/graphql'
url: http://{{ include "supabase.rest.fullname" . }}:{{ .Values.service.rest.port }}/rpc/graphql
routes:
- name: graphql-v1-all
strip_path: true
paths:
- /graphql/v1
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: true
- name: request-transformer
config:
add:
headers:
- Content-Profile:graphql_public
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
{{- end }}
{{- if .Values.deployment.realtime.enabled }}
- name: realtime-v1-ws
_comment: "Realtime: /realtime/v1/* -> ws://{{ include "supabase.realtime.fullname" . }}:{{ .Values.service.realtime.port }}/socket/*"
url: http://{{ include "supabase.realtime.fullname" . }}:{{ .Values.service.realtime.port }}/socket
protocol: ws
routes:
- name: realtime-v1-ws
strip_path: true
paths:
- /realtime/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: realtime-v1-rest
_comment: 'Realtime: /realtime/v1/* -> http://{{ include "supabase.realtime.fullname" . }}:{{ .Values.service.realtime.port }}/api/*'
url: http://{{ include "supabase.realtime.fullname" . }}:{{ .Values.service.realtime.port }}/api
protocol: http
routes:
- name: realtime-v1-rest
strip_path: true
paths:
- /realtime/v1/api
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
{{- end }}
{{- if .Values.deployment.storage.enabled }}
- name: storage-v1
_comment: "Storage: /storage/v1/* -> http://{{ include "supabase.storage.fullname" . }}:{{ .Values.service.storage.port }}/*"
url: http://{{ include "supabase.storage.fullname" . }}:{{ .Values.service.storage.port }}/
routes:
- name: storage-v1-all
strip_path: true
paths:
- /storage/v1/
plugins:
- name: cors
{{- end }}
{{- if .Values.deployment.functions.enabled }}
- name: functions-v1
_comment: 'Edge Functions: /functions/v1/* -> http://{{ include "supabase.functions.fullname" . }}:{{ .Values.service.functions.port }}/*'
url: http://{{ include "supabase.functions.fullname" . }}:{{ .Values.service.functions.port }}/
routes:
- name: functions-v1-all
strip_path: true
paths:
- /functions/v1/
plugins:
- name: cors
{{- end }}
{{/*
## Not used - Studio and Vector talk directly to analytics via Docker networking.
## If external access is needed, add routes with key-auth matching Logflare's x-api-key auth.
*/}}
{{/*
{{- if .Values.deployment.analytics.enabled }}
- name: analytics-v1
_comment: 'Analytics: /analytics/v1/* -> http://{{ include "supabase.analytics.fullname" . }}:{{ .Values.service.analytics.port }}/*'
url: http://{{ include "supabase.analytics.fullname" . }}:{{ .Values.service.analytics.port }}/
routes:
- name: analytics-v1-all
strip_path: true
paths:
- /analytics/v1/
{{- end }}
*/}}
{{- if .Values.deployment.meta.enabled }}
- name: meta
_comment: "pg-meta: /pg/* -> http://{{ include "supabase.meta.fullname" . }}:{{ .Values.service.meta.port }}/*"
url: http://{{ include "supabase.meta.fullname" . }}:{{ .Values.service.meta.port }}/
routes:
- name: meta-all
strip_path: true
paths:
- /pg/
plugins:
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
{{- end }}
- name: dashboard
_comment: 'Studio: /* -> http://{{ include "supabase.studio.fullname" . }}:{{ .Values.service.studio.port }}/*'
url: http://{{ include "supabase.studio.fullname" . }}:{{ .Values.service.studio.port }}/
routes:
- name: dashboard-all
strip_path: true
paths:
- /
{{- if .Values.secret.dashboard }}
plugins:
- name: cors
- name: basic-auth
config:
hide_credentials: true
{{- end }}
{{- end }}