diff --git a/charts/aisix-cp/README.md b/charts/aisix-cp/README.md index 1dadf24..d1091da 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,221 @@ 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 +* 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` | + + 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 +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. +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 +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 +294,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..2428898 100644 --- a/charts/aisix-cp/README.md.gotmpl +++ b/charts/aisix-cp/README.md.gotmpl @@ -4,11 +4,246 @@ {{ 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 +* 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` | + + 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 +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. +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 +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 +286,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" . }}