From fc4bd34db89d1d9cb5223fe0d9389ae8963267ad Mon Sep 17 00:00:00 2001 From: Jarvis Date: Fri, 18 Sep 2026 06:14:17 +0000 Subject: [PATCH 1/2] docs(aisix-cp): give the README prerequisites, install and upgrade The chart's README rendered only the header, maintainers, requirements, the private-CA section and the values table. A user reading it met a render-time rejection with no documented way forward: the chart refuses to render while secrets.masterKey, secrets.betterAuthSecret, postgresql.auth.password or postgresql.auth.postgresPassword still hold their placeholders, and none of that was written down anywhere the chart ships. Give it the structure charts/aisix already has, adapted to the control plane: Prerequisites (the four required secrets, why the database passwords must be URL-safe, why the master key has to be carried forward), Install (repo add, pinned --version, reaching the console through cp-api, first-run signup, the gateway image default), Upgrade (own values file rather than --reuse-values, control plane before gateways), Uninstall (what survives it), and configuration examples for an external database, the existing private CA section, a console on another origin, OpenShift, and metrics. Every snippet renders against this chart; the values table is unchanged. --- charts/aisix-cp/README.md | 318 ++++++++++++++++++++++++++++++- charts/aisix-cp/README.md.gotmpl | 318 ++++++++++++++++++++++++++++++- 2 files changed, 634 insertions(+), 2 deletions(-) diff --git a/charts/aisix-cp/README.md b/charts/aisix-cp/README.md index 1dadf24..3d7b562 100644 --- a/charts/aisix-cp/README.md +++ b/charts/aisix-cp/README.md @@ -4,6 +4,27 @@ Helm chart for AISIX control plane (cp-api, dp-manager, dashboard) +AISIX is an AI gateway: it fronts LLM providers with routing, rate limiting, +budgets, caching, guardrails, and observability behind an OpenAI-compatible API. +This chart installs the **control plane** — the console and management services +that configure the gateways and collect what they report. + +It installs three services and, by default, a PostgreSQL database: + +* `cp-api` — the Admin API, and the externally reachable entry point. It also + reverse-proxies the dashboard, so the console and the API share one origin. +* `dp-manager` — issues gateway certificates over mutual TLS and delivers + configuration to the gateways. +* `dashboard` — the Next.js console, reached through `cp-api`. +* PostgreSQL — the shared datastore. The chart deploys a bundled single + instance by default, or connects to an [external database](#external-postgresql). + +The gateways are **not** installed by this chart. Install them separately with +the [`aisix`](../aisix/README.md) chart, or any of the other options in the +[on-premises installation guide](https://docs.api7.ai/ai-gateway/on-premises/deployment); +they connect out to `dp-manager`, so the control plane needs no inbound access +to gateway hosts. + ## Maintainers | Name | Email | Url | @@ -16,7 +37,210 @@ Helm chart for AISIX control plane (cp-api, dp-manager, dashboard) |------------|------|---------| | https://charts.bitnami.com/bitnami | postgresql | 12.12.10 | -## Private PostgreSQL CA trust for the dashboard +## Prerequisites + +* Kubernetes v1.23+ +* Helm v3+ +* OpenSSL, to generate the secrets below +* Four secrets, which have no defaults you can deploy with. The chart **fails + the render** while any of them still holds its placeholder, so an install with + none of them set stops before anything reaches the cluster: + + | Value | Generate with | + | --- | --- | + | `secrets.masterKey` | `openssl rand -base64 32` | + | `secrets.betterAuthSecret` | `openssl rand -base64 48` | + | `postgresql.auth.postgresPassword` | `openssl rand -hex 24` | + | `postgresql.auth.password` | `openssl rand -hex 24` | + + Use **URL-safe** database passwords — `openssl rand -hex 24`, not + `-base64`. The password of whichever role serves application connections is + embedded in a `postgres://` DSN, and `+`, `/` and `=` corrupt it. That role is + `postgres` by default, because `postgresql.auth.usePostgresUserForAppConnections` + defaults to `true`; set both passwords URL-safe and the question does not + arise. The two `postgresql.auth.*` values are read only when + `postgresql.builtin` is `true` (the default) and + `postgresql.auth.existingSecret` is empty. + +**Keep `secrets.masterKey`.** It encrypts stored provider credentials and the +private key of the certificate authority that issued your gateway +certificates. Neither is recoverable from a database backup without it, so an +upgrade or a reinstall that supplies a *new* key leaves the existing rows +unreadable. Store it, its `secrets.masterKeyID`, and `secrets.betterAuthSecret` +the way you store a database backup — see +[Backup and Recovery](https://docs.api7.ai/ai-gateway/on-premises/backup-and-recovery). + +## Install + +Generate the secrets into a values file and keep that file: it is what you +upgrade with, and it is the only copy of the master key. + +```sh +cat > cp-values.yaml <' +``` + +`externalDatabase.existingSecret` reads the password from a Secret you manage, +under the key `password`; `externalDatabase.password` takes it inline instead. +The external database must be reachable before the control plane starts — only +the bundled mode gets a wait-for-database init container. The two +`postgresql.auth.*` passwords are not read in this mode, and the placeholder +rejection is skipped with them, but `secrets.masterKey` and +`secrets.betterAuthSecret` are still required. + +### Private PostgreSQL CA trust for the dashboard When external PostgreSQL uses a private CA, the dashboard's Node.js PostgreSQL client needs to trust that CA. Otherwise database-backed authentication can fail @@ -59,6 +283,98 @@ the process starts, and this chart does not automatically restart Pods when an externally managed certificate changes. Manage the CA resource and these values in your deployment source so subsequent GitOps syncs preserve the configuration. +### A console served from another origin + +`cp-api` proxies the dashboard, so a normal install has one origin and nothing +is ever cross-origin — which is why `api.corsAllowedOrigins` is empty by +default and writes no CORS headers at all. Populate it only to let a dashboard +served from somewhere else call this API directly, such as a frontend-only +preview deployment: + +```yaml +api: + publicBaseURL: https://console.example.com + corsAllowedOrigins: + - https://console.example.com + - https://*-api7ai.vercel.app +``` + +An entry is a bare origin (`https://host[:port]`), or `https://*` plus a host +suffix starting with `-` or `.` and naming at least three labels. It must be +spelled the way a **browser** serializes an origin: no path, query, fragment, +userinfo or trailing dot, no bare `*`, https only (loopback may use http), a +punycode host, no default port written out. The chart applies cp-api's own +rules and fails the render on anything else, so a value that installs is one +cp-api will start with. Read a wildcard suffix as collision-resistance rather +than a boundary — it is a byte suffix, and a shared preview host allocates +names first-come-first-served — so a grant that needs a real boundary belongs +on a domain you control. + +### OpenShift + +The chart installs under the default `restricted-v2` security context +constraint. Since 1.3.0 it pins no UID: the pod security contexts for `api`, +`dpm` and `ui` carry only `runAsNonRoot` and `seccompProfile`, so the platform +assigns the UID and GID, and the only paths written at runtime are `emptyDir` +volumes. No custom SCC, `anyuid`, or service-account change is needed. + +The bundled PostgreSQL is a dependency chart and still pins its own UID, which +a parent chart cannot make conditional. Turn its two security contexts off at +install time: + +```sh +helm install aisix-cp api7/aisix-cp --namespace aisix --create-namespace \ + --version 1.3.0 \ + -f cp-values.yaml \ + --set postgresql.primary.podSecurityContext.enabled=false \ + --set postgresql.primary.containerSecurityContext.enabled=false +``` + +Nothing else needs disabling — in particular do not turn off +`postgresql.shmVolume`, or PostgreSQL falls back to the runtime's 64Mi +`/dev/shm` and parallel queries fail with `could not resize shared memory +segment`. Using an [external database](#external-postgresql) avoids the +question entirely, and is the better production choice on any platform. + +To pin a fixed UID back — on a cluster that does not assign one, or to match +existing volume ownership — set the three keys the defaults leave out. These +are the values releases before 1.3.0 pinned: + +```yaml +api: + podSecurityContext: + runAsUser: 10001 + runAsGroup: 101 + fsGroup: 101 +dpm: + podSecurityContext: + runAsUser: 10001 + runAsGroup: 101 + fsGroup: 101 +ui: + podSecurityContext: + runAsUser: 1001 + runAsGroup: 65533 + fsGroup: 65533 +``` + +### Prometheus metrics + +`cp-api` serves its own operational metrics — the `aisix_cp_` family — on a +separate listener and a separate `ClusterIP` Service, never on the API port. +It is on by default; create a ServiceMonitor for it with: + +```yaml +api: + metrics: + serviceMonitor: + enabled: true + labels: + release: prometheus +``` + +## Parameters + ## Values | Key | Type | Default | Description | diff --git a/charts/aisix-cp/README.md.gotmpl b/charts/aisix-cp/README.md.gotmpl index aa0a908..3dc8656 100644 --- a/charts/aisix-cp/README.md.gotmpl +++ b/charts/aisix-cp/README.md.gotmpl @@ -4,11 +4,235 @@ {{ template "chart.description" . }} +AISIX is an AI gateway: it fronts LLM providers with routing, rate limiting, +budgets, caching, guardrails, and observability behind an OpenAI-compatible API. +This chart installs the **control plane** — the console and management services +that configure the gateways and collect what they report. + +It installs three services and, by default, a PostgreSQL database: + +* `cp-api` — the Admin API, and the externally reachable entry point. It also + reverse-proxies the dashboard, so the console and the API share one origin. +* `dp-manager` — issues gateway certificates over mutual TLS and delivers + configuration to the gateways. +* `dashboard` — the Next.js console, reached through `cp-api`. +* PostgreSQL — the shared datastore. The chart deploys a bundled single + instance by default, or connects to an [external database](#external-postgresql). + +The gateways are **not** installed by this chart. Install them separately with +the [`aisix`](../aisix/README.md) chart, or any of the other options in the +[on-premises installation guide](https://docs.api7.ai/ai-gateway/on-premises/deployment); +they connect out to `dp-manager`, so the control plane needs no inbound access +to gateway hosts. + {{ template "chart.maintainersSection" . }} {{ template "chart.requirementsSection" . }} -## Private PostgreSQL CA trust for the dashboard +## Prerequisites + +* Kubernetes v1.23+ +* Helm v3+ +* OpenSSL, to generate the secrets below +* Four secrets, which have no defaults you can deploy with. The chart **fails + the render** while any of them still holds its placeholder, so an install with + none of them set stops before anything reaches the cluster: + + | Value | Generate with | + | --- | --- | + | `secrets.masterKey` | `openssl rand -base64 32` | + | `secrets.betterAuthSecret` | `openssl rand -base64 48` | + | `postgresql.auth.postgresPassword` | `openssl rand -hex 24` | + | `postgresql.auth.password` | `openssl rand -hex 24` | + + Use **URL-safe** database passwords — `openssl rand -hex 24`, not + `-base64`. The password of whichever role serves application connections is + embedded in a `postgres://` DSN, and `+`, `/` and `=` corrupt it. That role is + `postgres` by default, because `postgresql.auth.usePostgresUserForAppConnections` + defaults to `true`; set both passwords URL-safe and the question does not + arise. The two `postgresql.auth.*` values are read only when + `postgresql.builtin` is `true` (the default) and + `postgresql.auth.existingSecret` is empty. + +**Keep `secrets.masterKey`.** It encrypts stored provider credentials and the +private key of the certificate authority that issued your gateway +certificates. Neither is recoverable from a database backup without it, so an +upgrade or a reinstall that supplies a *new* key leaves the existing rows +unreadable. Store it, its `secrets.masterKeyID`, and `secrets.betterAuthSecret` +the way you store a database backup — see +[Backup and Recovery](https://docs.api7.ai/ai-gateway/on-premises/backup-and-recovery). + +## Install + +Generate the secrets into a values file and keep that file: it is what you +upgrade with, and it is the only copy of the master key. + +```sh +cat > cp-values.yaml <' +``` + +`externalDatabase.existingSecret` reads the password from a Secret you manage, +under the key `password`; `externalDatabase.password` takes it inline instead. +The external database must be reachable before the control plane starts — only +the bundled mode gets a wait-for-database init container. The two +`postgresql.auth.*` passwords are not read in this mode, and the placeholder +rejection is skipped with them, but `secrets.masterKey` and +`secrets.betterAuthSecret` are still required. + +### Private PostgreSQL CA trust for the dashboard When external PostgreSQL uses a private CA, the dashboard's Node.js PostgreSQL client needs to trust that CA. Otherwise database-backed authentication can fail @@ -51,4 +275,96 @@ the process starts, and this chart does not automatically restart Pods when an externally managed certificate changes. Manage the CA resource and these values in your deployment source so subsequent GitOps syncs preserve the configuration. +### A console served from another origin + +`cp-api` proxies the dashboard, so a normal install has one origin and nothing +is ever cross-origin — which is why `api.corsAllowedOrigins` is empty by +default and writes no CORS headers at all. Populate it only to let a dashboard +served from somewhere else call this API directly, such as a frontend-only +preview deployment: + +```yaml +api: + publicBaseURL: https://console.example.com + corsAllowedOrigins: + - https://console.example.com + - https://*-api7ai.vercel.app +``` + +An entry is a bare origin (`https://host[:port]`), or `https://*` plus a host +suffix starting with `-` or `.` and naming at least three labels. It must be +spelled the way a **browser** serializes an origin: no path, query, fragment, +userinfo or trailing dot, no bare `*`, https only (loopback may use http), a +punycode host, no default port written out. The chart applies cp-api's own +rules and fails the render on anything else, so a value that installs is one +cp-api will start with. Read a wildcard suffix as collision-resistance rather +than a boundary — it is a byte suffix, and a shared preview host allocates +names first-come-first-served — so a grant that needs a real boundary belongs +on a domain you control. + +### OpenShift + +The chart installs under the default `restricted-v2` security context +constraint. Since 1.3.0 it pins no UID: the pod security contexts for `api`, +`dpm` and `ui` carry only `runAsNonRoot` and `seccompProfile`, so the platform +assigns the UID and GID, and the only paths written at runtime are `emptyDir` +volumes. No custom SCC, `anyuid`, or service-account change is needed. + +The bundled PostgreSQL is a dependency chart and still pins its own UID, which +a parent chart cannot make conditional. Turn its two security contexts off at +install time: + +```sh +helm install aisix-cp api7/aisix-cp --namespace aisix --create-namespace \ + --version {{ template "chart.version" . }} \ + -f cp-values.yaml \ + --set postgresql.primary.podSecurityContext.enabled=false \ + --set postgresql.primary.containerSecurityContext.enabled=false +``` + +Nothing else needs disabling — in particular do not turn off +`postgresql.shmVolume`, or PostgreSQL falls back to the runtime's 64Mi +`/dev/shm` and parallel queries fail with `could not resize shared memory +segment`. Using an [external database](#external-postgresql) avoids the +question entirely, and is the better production choice on any platform. + +To pin a fixed UID back — on a cluster that does not assign one, or to match +existing volume ownership — set the three keys the defaults leave out. These +are the values releases before 1.3.0 pinned: + +```yaml +api: + podSecurityContext: + runAsUser: 10001 + runAsGroup: 101 + fsGroup: 101 +dpm: + podSecurityContext: + runAsUser: 10001 + runAsGroup: 101 + fsGroup: 101 +ui: + podSecurityContext: + runAsUser: 1001 + runAsGroup: 65533 + fsGroup: 65533 +``` + +### Prometheus metrics + +`cp-api` serves its own operational metrics — the `aisix_cp_` family — on a +separate listener and a separate `ClusterIP` Service, never on the API port. +It is on by default; create a ServiceMonitor for it with: + +```yaml +api: + metrics: + serviceMonitor: + enabled: true + labels: + release: prometheus +``` + +## Parameters + {{ template "chart.valuesSection" . }} From a649a5eb453ed0c0ee4dafe5826356811809b9c1 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Fri, 18 Sep 2026 06:23:31 +0000 Subject: [PATCH 2/2] docs(aisix-cp): state the required secrets per database mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prerequisites listed four required secrets, which is the bundled-database case rather than the rule: templates/secret.yaml validates the two application secrets in every mode but the postgresql.auth.* pair only when postgresql.builtin is true and postgresql.auth.existingSecret is empty, and external mode instead needs externalDatabase.existingSecret or externalDatabase.password — with neither, external-db-secret.yaml fails the render on its own `required`. Split the list accordingly and say what external mode requires. Also pass the release namespace to `helm get values`: the documented release lives in the aisix namespace, and the command otherwise searches whichever namespace the operator's context points at. --- charts/aisix-cp/README.md | 39 ++++++++++++++++++++------------ charts/aisix-cp/README.md.gotmpl | 39 ++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/charts/aisix-cp/README.md b/charts/aisix-cp/README.md index 3d7b562..d1091da 100644 --- a/charts/aisix-cp/README.md +++ b/charts/aisix-cp/README.md @@ -42,25 +42,35 @@ to gateway hosts. * Kubernetes v1.23+ * Helm v3+ * OpenSSL, to generate the secrets below -* Four secrets, which have no defaults you can deploy with. The chart **fails - the render** while any of them still holds its placeholder, so an install with - none of them set stops before anything reaches the cluster: +* Secrets, which have no defaults you can deploy with. **The chart fails the + render** while one is missing or still holds its placeholder, so an install + that supplies none of them stops before anything reaches the cluster. Two are + needed in every mode: | Value | Generate with | | --- | --- | | `secrets.masterKey` | `openssl rand -base64 32` | | `secrets.betterAuthSecret` | `openssl rand -base64 48` | + + The database credentials depend on which database you use. With the bundled + PostgreSQL — `postgresql.builtin: true`, the default — set both of these, + unless `postgresql.auth.existingSecret` supplies them instead: + + | Value | Generate with | + | --- | --- | | `postgresql.auth.postgresPassword` | `openssl rand -hex 24` | | `postgresql.auth.password` | `openssl rand -hex 24` | - Use **URL-safe** database passwords — `openssl rand -hex 24`, not - `-base64`. The password of whichever role serves application connections is - embedded in a `postgres://` DSN, and `+`, `/` and `=` corrupt it. That role is - `postgres` by default, because `postgresql.auth.usePostgresUserForAppConnections` - defaults to `true`; set both passwords URL-safe and the question does not - arise. The two `postgresql.auth.*` values are read only when - `postgresql.builtin` is `true` (the default) and - `postgresql.auth.existingSecret` is empty. + With an [external database](#external-postgresql) neither is read; that mode + needs `externalDatabase.existingSecret` or `externalDatabase.password` + instead, and fails the render with neither. + + Whichever mode, use a **URL-safe** database password — `openssl rand -hex 24`, + not `-base64`. The password of the role that serves application connections is + embedded in a `postgres://` DSN, and `+`, `/` and `=` corrupt it. On the + bundled database that role is `postgres` by default, because + `postgresql.auth.usePostgresUserForAppConnections` defaults to `true`; set + both passwords URL-safe and the question does not arise. **Keep `secrets.masterKey`.** It encrypts stored provider credentials and the private key of the certificate authority that issued your gateway @@ -167,12 +177,12 @@ helm repo update helm upgrade aisix-cp api7/aisix-cp --namespace aisix -f cp-values.yaml ``` -Upgrade with your own values file rather than `--reuse-values`: the same four +Upgrade with your own values file rather than `--reuse-values`: the same secrets must be supplied again, and `--reuse-values` replays the previous release's fully resolved values, chart defaults included, so a default this chart changed — the probe budgets among them — is not adopted. If you never -kept a file, `helm get values aisix-cp` prints the overrides the release was -installed with. +kept a file, `helm get values aisix-cp --namespace aisix` prints the overrides +the release was installed with. Wait for the `cp-api` pods to become ready before upgrading the gateways: the schema migration runs on first start under the new version. @@ -234,6 +244,7 @@ kubectl -n aisix create secret generic aisix-cp-db \ `externalDatabase.existingSecret` reads the password from a Secret you manage, under the key `password`; `externalDatabase.password` takes it inline instead. +One of the two is required — with neither, the render fails. The external database must be reachable before the control plane starts — only the bundled mode gets a wait-for-database init container. The two `postgresql.auth.*` passwords are not read in this mode, and the placeholder diff --git a/charts/aisix-cp/README.md.gotmpl b/charts/aisix-cp/README.md.gotmpl index 3dc8656..2428898 100644 --- a/charts/aisix-cp/README.md.gotmpl +++ b/charts/aisix-cp/README.md.gotmpl @@ -34,25 +34,35 @@ to gateway hosts. * Kubernetes v1.23+ * Helm v3+ * OpenSSL, to generate the secrets below -* Four secrets, which have no defaults you can deploy with. The chart **fails - the render** while any of them still holds its placeholder, so an install with - none of them set stops before anything reaches the cluster: +* Secrets, which have no defaults you can deploy with. **The chart fails the + render** while one is missing or still holds its placeholder, so an install + that supplies none of them stops before anything reaches the cluster. Two are + needed in every mode: | Value | Generate with | | --- | --- | | `secrets.masterKey` | `openssl rand -base64 32` | | `secrets.betterAuthSecret` | `openssl rand -base64 48` | + + The database credentials depend on which database you use. With the bundled + PostgreSQL — `postgresql.builtin: true`, the default — set both of these, + unless `postgresql.auth.existingSecret` supplies them instead: + + | Value | Generate with | + | --- | --- | | `postgresql.auth.postgresPassword` | `openssl rand -hex 24` | | `postgresql.auth.password` | `openssl rand -hex 24` | - Use **URL-safe** database passwords — `openssl rand -hex 24`, not - `-base64`. The password of whichever role serves application connections is - embedded in a `postgres://` DSN, and `+`, `/` and `=` corrupt it. That role is - `postgres` by default, because `postgresql.auth.usePostgresUserForAppConnections` - defaults to `true`; set both passwords URL-safe and the question does not - arise. The two `postgresql.auth.*` values are read only when - `postgresql.builtin` is `true` (the default) and - `postgresql.auth.existingSecret` is empty. + With an [external database](#external-postgresql) neither is read; that mode + needs `externalDatabase.existingSecret` or `externalDatabase.password` + instead, and fails the render with neither. + + Whichever mode, use a **URL-safe** database password — `openssl rand -hex 24`, + not `-base64`. The password of the role that serves application connections is + embedded in a `postgres://` DSN, and `+`, `/` and `=` corrupt it. On the + bundled database that role is `postgres` by default, because + `postgresql.auth.usePostgresUserForAppConnections` defaults to `true`; set + both passwords URL-safe and the question does not arise. **Keep `secrets.masterKey`.** It encrypts stored provider credentials and the private key of the certificate authority that issued your gateway @@ -159,12 +169,12 @@ helm repo update helm upgrade aisix-cp api7/aisix-cp --namespace aisix -f cp-values.yaml ``` -Upgrade with your own values file rather than `--reuse-values`: the same four +Upgrade with your own values file rather than `--reuse-values`: the same secrets must be supplied again, and `--reuse-values` replays the previous release's fully resolved values, chart defaults included, so a default this chart changed — the probe budgets among them — is not adopted. If you never -kept a file, `helm get values aisix-cp` prints the overrides the release was -installed with. +kept a file, `helm get values aisix-cp --namespace aisix` prints the overrides +the release was installed with. Wait for the `cp-api` pods to become ready before upgrading the gateways: the schema migration runs on first start under the new version. @@ -226,6 +236,7 @@ kubectl -n aisix create secret generic aisix-cp-db \ `externalDatabase.existingSecret` reads the password from a Secret you manage, under the key `password`; `externalDatabase.password` takes it inline instead. +One of the two is required — with neither, the render fails. The external database must be reachable before the control plane starts — only the bundled mode gets a wait-for-database init container. The two `postgresql.auth.*` passwords are not read in this mode, and the placeholder