# Kong Gateway — cluster-internal API gateway (namespace `api`). # # Chart: kong/kong 3.4.1 (appVersion 3.9). Only overrides are listed; every key # here was checked against `helm show values kong/kong --version 3.4.1`, because # Helm silently ignores unknown keys — a typo is a no-op, not an error. # # ── Topology ──────────────────────────────────────────────────────────────── # external: client -> nginx (TLS, wildcard *.riotpiao.com) -> kong-proxy:80 # internal: pod -> kong-proxy.api.svc.cluster.local:80 # # nginx stays the single edge and the only LoadBalancer (192.168.1.160). Kong is # the policy/routing layer behind it, so it needs no LB IP and no TLS of its own # — hence ClusterIP and proxy.tls disabled. Giving Kong its own IP from # homelab-pool would mean duplicating cert-manager wiring and diverging from the # CoreDNS convention that sends every *.riotpiao.com host to nginx. # # ── Routing model ─────────────────────────────────────────────────────────── # Consumers publish an Ingress with `ingressClassName: kong`; the controller # turns it into a Kong route. `nginx` remains the default IngressClass, so this # is strictly opt-in and no existing Ingress changes behaviour. # Without this the release name is prefixed onto everything (`kong-kong-proxy`). # Pinning it keeps the Service name stable and independent of the release name, # which matters because the nginx Ingress in k8s/bootstrap/ingress/ingress.yaml # references it by name. fullnameOverride: kong # Two replicas so a node drain or rollout doesn't take the gateway down. Kong is # stateless in DB-less mode, so replicas are pure redundancy. replicaCount: 2 # Opt in to the `llm-serving-default-deny` NetworkPolicy, which admits port 8080 # only from pods carrying this label. That policy is a compensating control, not # hygiene: vLLM v0.11.0 is frozen on Volta and will never receive patches for # several remote/unauthenticated advisories, so it must not be broadly reachable. # # Without this label Cilium DROPS the packets rather than refusing them, so the # symptom is a request that hangs until the client's timeout — not a connection # error. /v1/models still worked while this was missing, because # request-termination answers inside Kong and never touches an upstream. podLabels: llm-client: "true" env: # DB-less. Config comes from Kubernetes objects via the ingress controller, so # git stays the source of truth. A Postgres-backed Kong would put live routing # config in a database mutated through the Admin API — state outside git, plus # migration Jobs on every upgrade. database: "off" # `nginx_proxy_` injects a directive into the proxy location block; # this renders `proxy_buffering off;`. # # Required for LLM streaming. With buffering on (the default) nginx accumulates # the upstream response before forwarding, so an SSE stream from # `"stream": true` arrives in lumps or stalls until the generation finishes — # which defeats the point of streaming. The matching setting is already on the # nginx Ingress in ingress.yaml; both hops have to be unbuffered or the # buffered one dominates. nginx_proxy_proxy_buffering: "off" # Any plugin that rewrites the request body — request-transformer on the # llm-chat-* routes — reads it through `kong.request.get_body()`, and that # returns nothing once nginx has spilled the body past # client_body_buffer_size into a temp file. The plugin then re-serializes a # body with no `messages`, and the upstream answers # HTTP 400 {"error":{"message":"[] is too short - 'messages'"}} # Measured on /v1/ornith/chat/completions: 10588 B -> 200, 11088 B -> 400. # An agent request carrying tool schemas clears that in one turn, so the # buffer has to hold a whole conversation, not a chat message. nginx_http_client_body_buffer_size: "16m" nginx_http_client_max_body_size: "16m" ingressController: enabled: true ingressClass: kong # The chart's ingress-class template is gated on # `.Capabilities.APIVersions.Has "networking.k8s.io/v1/IngressClass"`, so a # bare `helm template` renders nothing. ArgoCD passes --api-versions from the # live cluster, so it does render there — verify `kubectl get ingressclass # kong` after the first sync rather than assuming it. createIngressClass: true # Deliberately empty: setting is-default-class here would hijack every Ingress # in the cluster that omits ingressClassName. nginx keeps that role. ingressClassAnnotations: {} proxy: enabled: true # Chart default is LoadBalancer, which would claim an IP from homelab-pool. type: ClusterIP http: enabled: true servicePort: 80 containerPort: 8000 # nginx already terminated TLS; a second handshake to the same cluster buys # nothing and would need Kong to hold its own certificate. tls: enabled: false # No Service for the Admin API. The controller reaches it over localhost inside # the pod, so exposing it would only create an unauthenticated write path to the # gateway's entire configuration. admin: enabled: false # Kong Manager UI — chart default is `enabled: true` with type NodePort, which # would open a port on every node. Not wanted. manager: enabled: false resources: requests: cpu: 200m memory: 256Mi limits: cpu: "2" memory: 1Gi podDisruptionBudget: enabled: true minAvailable: 1 # Status listener (metrics/health) is on by default at :8100 (chart default, # verified via `helm show values`). This just wires the ServiceMonitor the # chart already knows how to generate for it, so kong_http_requests_total / # kong_latency_* / kong_bandwidth_bytes land in Prometheus. Paired with the # cluster-wide `prometheus` KongClusterPlugin in kong-metrics.yaml. serviceMonitor: enabled: true labels: release: kube-prometheus-stack # Spread the two replicas across nodes; `ScheduleAnyway` so a single-node # situation degrades to co-location instead of leaving a pod Pending. topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: app.kubernetes.io/name: kong app.kubernetes.io/instance: kong