Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions charts/aisix-cp/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ apiVersion: v2
name: aisix-cp
description: Helm chart for AISIX control plane (cp-api, dp-manager, dashboard)
type: application
version: 1.5.0
appVersion: "1.5.0"
version: 1.6.0
appVersion: "1.6.0"

maintainers:
- name: API7
Expand Down
225 changes: 117 additions & 108 deletions charts/aisix-cp/README.md

Large diffs are not rendered by default.

147 changes: 75 additions & 72 deletions charts/aisix-cp/README.md.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ 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:
It installs two 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.
* `cp-api` — the Admin API and the console, and the externally reachable entry
point. It serves the dashboard itself, 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).

Expand All @@ -36,13 +36,12 @@ to gateway hosts.
* 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
that supplies none of them stops before anything reaches the cluster. One is
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,
Expand All @@ -68,8 +67,8 @@ to gateway hosts.
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
unreadable. Store it and its `secrets.masterKeyID` the way you store a database
backup — see
[Backup and Recovery](https://docs.api7.ai/ai-gateway/on-premises/backup-and-recovery).

## Install
Expand All @@ -81,7 +80,6 @@ upgrade with, and it is the only copy of the master key.
cat > cp-values.yaml <<EOF
secrets:
masterKey: "$(openssl rand -base64 32)"
betterAuthSecret: "$(openssl rand -base64 48)"
postgresql:
auth:
postgresPassword: "$(openssl rand -hex 24)"
Expand All @@ -100,14 +98,13 @@ helm install aisix-cp api7/aisix-cp --namespace aisix --create-namespace \
-f cp-values.yaml
```

Or pass the four values on the command line instead of keeping a file — the
Or pass the three values on the command line instead of keeping a file — the
same install, as long as you record what you generated:

```sh
helm install aisix-cp api7/aisix-cp --namespace aisix --create-namespace \
--version {{ template "chart.version" . }} \
--set secrets.masterKey="$(openssl rand -base64 32)" \
--set secrets.betterAuthSecret="$(openssl rand -base64 48)" \
--set postgresql.auth.postgresPassword="$(openssl rand -hex 24)" \
--set postgresql.auth.password="$(openssl rand -hex 24)"
```
Expand All @@ -121,7 +118,7 @@ kubectl -n aisix rollout status deploy/aisix-cp-api

### Reach the console

All three services are `ClusterIP` by default. `cp-api` serves both the Admin
Both services are `ClusterIP` by default. `cp-api` serves both the Admin
API and the dashboard, so one port-forward reaches everything:

```sh
Expand All @@ -134,12 +131,37 @@ matches this port-forward (`127.0.0.1` works too). Reaching the console at any
other address — an Ingress, a LoadBalancer, a different port — is refused at
sign-in with an "address not allowed" error until you either point
`api.publicBaseURL` at that address and upgrade, or add it to
`AISIX_TRUSTED_ORIGINS` through `ui.extraEnvVars`, which is additive and so
covers a console reached through more than one hostname.
`api.trustedOrigins`, which is additive and so covers a console reached through
more than one hostname.

On first visit, select **Create an account**, register the first user, accept
the agreement, and create the first organization. The account you create this
way is the deployment's first administrator; there is no seeded password.
### Sign in as the first administrator

Nobody signs up on their own in a self-hosted install: accounts come from
invitations. Instead, when `cp-api` starts on a database with no users yet, it
creates the organization `Default` with `api.bootstrapAdmin.email`
(`admin@example.com` unless you set it) as its owner. The console asks that
account for a new password at the first sign-in. It happens once — changing
these values afterwards changes nothing — and setting the email to `""`
creates no administrator.

With neither `api.bootstrapAdmin.password` nor
`api.bootstrapAdmin.existingSecret` set, the chart generates the initial
password into its own Secret and keeps it across `helm upgrade`; the install
notes print the command that reads it:

```sh
kubectl -n aisix get secret aisix-cp-secrets \
-o jsonpath='{.data.bootstrap-admin-password}' | base64 -d; echo
```

The chart keeps that password by reading its Secret back with `lookup`, which
`helm template` and GitOps renderers such as Argo CD cannot do — they generate a
new password on every render. Deploy that way only with
`api.bootstrapAdmin.password` or `api.bootstrapAdmin.existingSecret` set.

`api.deploymentMode` stays `on-premises`, the default, for every self-hosted
install. The other mode, `saas`, is for the multi-tenant hosted service and is
not meant for self-hosted installs.

### Attach a gateway

Expand All @@ -150,8 +172,8 @@ presents — left empty it does neither, and a gateway dialing an IP literal
fails the handshake with nothing logged to say why.

The console's generated commands carry a gateway image, taken from
`api.dpImage`. Left empty — the default — it follows the chart's `appVersion`,
so this release hands out `docker.io/api7/aisix:{{ template "chart.appVersion" . }}`.
`api.dpImage`. Left empty — the default — `cp-api` hands out the gateway of its
own release, so this release hands out `docker.io/api7/aisix:{{ template "chart.appVersion" . }}`.
Set it only to pin a different image, and remember that a value set explicitly
carries forward across upgrades, so gateways added later come up on the old
image.
Expand Down Expand Up @@ -179,11 +201,24 @@ 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.

Upgrading from 1.5.0 changes the following. The dashboard no longer runs as
its own Deployment: `cp-api` serves it, and the `aisix-cp-ui` Service is kept,
now selecting the `cp-api` pods, so a proxy still pointed at it keeps working.
`secrets.betterAuthSecret` and the dashboard's image, resource and volume values
are no longer read; a private database CA that was mounted into the dashboard
goes in `externalDatabase.sslRootCert` instead. Settings 1.5.0 took as
environment variables through `extraEnvVars` now have values of their own — `api.trustedOrigins`,
`api.allowUnsupportedUpgrade` and the rest in the [Parameters](#parameters)
table. An existing override keeps working across the upgrade, but move it into
the value; `extraEnvVars` is for system-level environment such as `TZ`.
Self sign-up is off in a self-hosted install, so accounts added after the
upgrade come from invitations.

`cp-api` refuses to start when the database was last run by a release older
than this one supports upgrading from, naming the version it found and the
oldest it accepts. Upgrade to a supported version first; to proceed anyway,
after taking a backup, pass `AISIX_ALLOW_UNSUPPORTED_UPGRADE=1` through
`api.extraEnvVars`. See
after taking a backup, set `api.allowUnsupportedUpgrade: true` for that one
upgrade and set it back afterwards. See
[Upgrade](https://docs.api7.ai/ai-gateway/on-premises/upgrade).

## Uninstall
Expand Down Expand Up @@ -240,55 +275,28 @@ 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.
rejection is skipped with them, but `secrets.masterKey` is 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
with `SELF_SIGNED_CERT_IN_CHAIN` even while the dashboard page is reachable.

Provision a ConfigMap named `aisix-postgres-ca` in the **dashboard namespace**,
containing the public PEM CA certificate under the key `ca.crt`. Then add these
values to your existing external-database configuration:
When the database's certificate is issued by a private CA, as with most managed
databases, put the PEM CA bundle in a Secret and point
`externalDatabase.sslRootCert` at it. `cp-api` and `dp-manager` mount it
read-only and use it as libpq `sslrootcert`, so `verify-ca` and `verify-full`
check the server against that CA instead of the image's system trust store:

```yaml
ui:
extraEnvVars:
- name: NODE_EXTRA_CA_CERTS
value: /etc/aisix/postgres-ca/ca.crt
extraVolumes:
- name: postgres-ca
configMap:
name: aisix-postgres-ca
items:
- key: ca.crt
path: ca.crt
extraVolumeMounts:
- name: postgres-ca
mountPath: /etc/aisix/postgres-ca
readOnly: true
externalDatabase:
sslmode: verify-full
sslRootCert:
existingSecret: aisix-cp-db-ca # key: ca.crt
```

For a Secret-backed volume, replace `configMap.name` with `secret.secretName`
and keep the same `items` mapping. Only the public CA certificate is needed;
do not distribute the CA private key. Kubernetes cannot mount a ConfigMap or
Secret from another namespace, so CA distribution is the operator's responsibility.

These lists append to the built-in Next.js cache volume and mount. Choose unique
volume names and mount paths; do not reuse `next-cache` or `/app/.next/cache`.
The defaults are empty and preserve the existing deployment behavior.

Keep PostgreSQL TLS certificate verification enabled. After updating or rotating
the CA, roll out the dashboard again: Node.js loads `NODE_EXTRA_CA_CERTS` when
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.
```sh
kubectl -n aisix create secret generic aisix-cp-db-ca --from-file=ca.crt=./ca.pem
```

### A console served from another origin

`cp-api` proxies the dashboard, so a normal install has one origin and nothing
`cp-api` serves 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
Expand Down Expand Up @@ -316,8 +324,8 @@ 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
constraint. Since 1.3.0 it pins no UID: the pod security contexts for `api`
and `dpm` 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.

Expand Down Expand Up @@ -354,11 +362,6 @@ dpm:
runAsUser: 10001
runAsGroup: 101
fsGroup: 101
ui:
podSecurityContext:
runAsUser: 1001
runAsGroup: 65533
fsGroup: 65533
```

### Prometheus metrics
Expand All @@ -378,9 +381,9 @@ api:

## Parameters

An empty `api.image.tag`, `dpm.image.tag` or `ui.image.tag` resolves to the
chart's `appVersion`, and an empty `api.dpImage` to
`docker.io/api7/aisix:<appVersion>`, so a default install runs the control
An empty `api.image.tag` or `dpm.image.tag` resolves to the chart's
`appVersion`, and an empty `api.dpImage` makes `cp-api` hand out
`docker.io/api7/aisix:<its version>`, so a default install runs the control
plane and hands out the gateway image from the same release as the chart.

{{ template "chart.valuesSection" . }}
39 changes: 29 additions & 10 deletions charts/aisix-cp/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
AISIX control plane has been deployed.
{{ if eq (.Values.api.deploymentMode | default "on-premises") "saas" }}AISIX Cloud{{ else }}AISIX Enterprise{{ end }} control plane has been deployed.

Components:
- cp-api: {{ include "aisix-cp.fullname" . }}-api:{{ .Values.api.service.port }}
- cp-api: {{ include "aisix-cp.fullname" . }}-api:{{ .Values.api.service.port }} (API and dashboard)
- dp-manager: {{ include "aisix-cp.fullname" . }}-dpm:{{ .Values.dpm.service.port }}
- dashboard: {{ include "aisix-cp.fullname" . }}-ui:{{ .Values.ui.service.port }}
- PostgreSQL: {{ include "aisix-cp.pgHost" . }}:{{ include "aisix-cp.pgPort" . }}

Access the dashboard via cp-api (reverse proxy):
cp-api serves the dashboard. Access it through the api Service:
kubectl port-forward svc/{{ include "aisix-cp.fullname" . }}-api {{ .Values.api.service.port }}:{{ .Values.api.service.port }} -n {{ .Release.Namespace }}
{{- $base := trimSuffix "/" (lower .Values.api.publicBaseURL) }}
{{- if or (eq $base (printf "http://localhost:%v" .Values.api.service.port)) (eq $base (printf "http://127.0.0.1:%v" .Values.api.service.port)) }}
Then open {{ .Values.api.publicBaseURL }} (localhost and 127.0.0.1 both work)

Sign-in is checked against the trusted origins: the api.publicBaseURL origin,
its loopback twin — localhost and 127.0.0.1 are interchangeable — and anything
in AISIX_TRUSTED_ORIGINS. Reaching the dashboard at some OTHER address (an
in api.trustedOrigins. Reaching the dashboard at some OTHER address (an
Ingress, a LoadBalancer, a different port) is refused at sign-in with an
"address not allowed" error. To allow one, either point api.publicBaseURL at it and
upgrade the release, or add it to AISIX_TRUSTED_ORIGINS via ui.extraEnvVars —
upgrade the release, or add it to api.trustedOrigins —
that one is additive, so use it when the console is reached through more than
one hostname.
{{- else }}
Then open {{ .Values.api.publicBaseURL }}

Sign-in is checked against the trusted origins: the api.publicBaseURL origin,
its loopback twin, and anything in AISIX_TRUSTED_ORIGINS. The port-forward
its loopback twin, and anything in api.trustedOrigins. The port-forward
above publishes the dashboard on http://localhost:{{ .Values.api.service.port }},
which is not that origin — so unless you have already listed it in
AISIX_TRUSTED_ORIGINS, the page loads and sign-in is refused. To allow it,
add http://localhost:{{ .Values.api.service.port }} to AISIX_TRUSTED_ORIGINS
via ui.extraEnvVars.
api.trustedOrigins, the page loads and sign-in is refused. To allow it,
add http://localhost:{{ .Values.api.service.port }} to api.trustedOrigins.
{{- if or (hasPrefix "http://localhost:" $base) (hasPrefix "http://127.0.0.1:" $base) }}
Or forward the port api.publicBaseURL names instead — local port first,
Service port second:
Expand All @@ -38,6 +36,27 @@ Access the dashboard via cp-api (reverse proxy):
{{- end }}
{{- end }}

{{- $bootstrap := .Values.api.bootstrapAdmin | default dict }}
{{- if and (ne (.Values.api.deploymentMode | default "on-premises") "saas") (or $bootstrap.email .Release.IsInstall) }}

First administrator:
{{- if $bootstrap.email }}
On a database with no users yet, cp-api creates the organization Default
with {{ $bootstrap.email }} as its owner. The console asks for a new
password at the first sign-in, before anything else.
{{- if $bootstrap.existingSecret }}
The initial password is in the Secret {{ $bootstrap.existingSecret }}, key {{ $bootstrap.existingSecretKey | default "bootstrap-admin-password" }}.
{{- else }}
Read the initial password with:
kubectl get secret {{ include "aisix-cp.secretName" . }} -n {{ .Release.Namespace }} -o jsonpath='{.data.bootstrap-admin-password}' | base64 -d; echo
{{- end }}
{{- else }}
Nobody can sign in yet: accounts are created by invitation, and
api.bootstrapAdmin.email is empty. Set it and upgrade the release to
create the first administrator.
{{- end }}
{{- end }}

Connect a data-plane (managed mode):
{{- if eq .Values.dpm.service.type "NodePort" }}
DPM is exposed via NodePort.
Expand Down
Loading
Loading