From 056077d75efbf5578d32d766ffe1dae78fe79169 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Thu, 18 Jun 2026 19:09:31 +0800 Subject: [PATCH 1/3] feat(aisix-cloud): add AISIX private-deployment control-plane chart Publishes the AISIX control plane (cp-api, dp-manager, dashboard) as a public Helm chart so users can `helm install api7/aisix-cloud` from https://charts.api7.ai (#789). Ported from the AISIX-Cloud repo's internal chart, with the image tags defaulting to the chart appVersion (and the DP image to ghcr.io/api7/aisix:) so a versioned install pulls matching release images. Uses the aisix-cp-* image names. Added to ct lint and helm-docs; install coverage lives in the AISIX-Cloud ci-helm pipeline. --- .github/workflows/ci.yaml | 3 +- charts/aisix-cloud/.helmignore | 1 + charts/aisix-cloud/Chart.lock | 6 + charts/aisix-cloud/Chart.yaml | 17 ++ charts/aisix-cloud/README.md | 123 +++++++++++ .../charts/postgresql-12.12.10.tgz | Bin 0 -> 62562 bytes charts/aisix-cloud/templates/NOTES.txt | 37 ++++ charts/aisix-cloud/templates/_helpers.tpl | 199 ++++++++++++++++++ .../aisix-cloud/templates/api-deployment.yaml | 98 +++++++++ charts/aisix-cloud/templates/api-service.yaml | 16 ++ .../aisix-cloud/templates/dpm-deployment.yaml | 84 ++++++++ charts/aisix-cloud/templates/dpm-service.yaml | 19 ++ .../templates/external-db-secret.yaml | 11 + charts/aisix-cloud/templates/secret.yaml | 37 ++++ .../aisix-cloud/templates/serviceaccount.yaml | 13 ++ .../aisix-cloud/templates/ui-deployment.yaml | 84 ++++++++ charts/aisix-cloud/templates/ui-service.yaml | 16 ++ charts/aisix-cloud/values.yaml | 186 ++++++++++++++++ 18 files changed, 949 insertions(+), 1 deletion(-) create mode 100644 charts/aisix-cloud/.helmignore create mode 100644 charts/aisix-cloud/Chart.lock create mode 100644 charts/aisix-cloud/Chart.yaml create mode 100644 charts/aisix-cloud/README.md create mode 100644 charts/aisix-cloud/charts/postgresql-12.12.10.tgz create mode 100644 charts/aisix-cloud/templates/NOTES.txt create mode 100644 charts/aisix-cloud/templates/_helpers.tpl create mode 100644 charts/aisix-cloud/templates/api-deployment.yaml create mode 100644 charts/aisix-cloud/templates/api-service.yaml create mode 100644 charts/aisix-cloud/templates/dpm-deployment.yaml create mode 100644 charts/aisix-cloud/templates/dpm-service.yaml create mode 100644 charts/aisix-cloud/templates/external-db-secret.yaml create mode 100644 charts/aisix-cloud/templates/secret.yaml create mode 100644 charts/aisix-cloud/templates/serviceaccount.yaml create mode 100644 charts/aisix-cloud/templates/ui-deployment.yaml create mode 100644 charts/aisix-cloud/templates/ui-service.yaml create mode 100644 charts/aisix-cloud/values.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3578aed..db7191e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,7 +36,8 @@ jobs: && ct lint \ --charts charts/api7 \ --charts charts/gateway \ - --charts charts/ingress-controller' + --charts charts/ingress-controller \ + --charts charts/aisix-cloud' - name: Verify Chart.lock files run: | diff --git a/charts/aisix-cloud/.helmignore b/charts/aisix-cloud/.helmignore new file mode 100644 index 0000000..6b8710a --- /dev/null +++ b/charts/aisix-cloud/.helmignore @@ -0,0 +1 @@ +.git diff --git a/charts/aisix-cloud/Chart.lock b/charts/aisix-cloud/Chart.lock new file mode 100644 index 0000000..cd7018e --- /dev/null +++ b/charts/aisix-cloud/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 12.12.10 +digest: sha256:3b8c03cf5b8742b8110494d29a4793f20920294a504bd85940d02bb00d0bc0ea +generated: "2026-05-13T18:33:38.3659398+08:00" diff --git a/charts/aisix-cloud/Chart.yaml b/charts/aisix-cloud/Chart.yaml new file mode 100644 index 0000000..7462896 --- /dev/null +++ b/charts/aisix-cloud/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: aisix-cloud +description: Helm chart for AISIX-Cloud control plane (cp-api, dp-manager, dashboard) +type: application +version: 0.1.0 +appVersion: "0.1.0" + +maintainers: + - name: API7 + email: support@api7.ai + url: https://api7.ai + +dependencies: + - name: postgresql + condition: postgresql.builtin + version: "12.12.10" + repository: "https://charts.bitnami.com/bitnami" diff --git a/charts/aisix-cloud/README.md b/charts/aisix-cloud/README.md new file mode 100644 index 0000000..d2d8564 --- /dev/null +++ b/charts/aisix-cloud/README.md @@ -0,0 +1,123 @@ +# aisix-cloud + +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square) + +Helm chart for AISIX-Cloud control plane (cp-api, dp-manager, dashboard) + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| API7 | | | + +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| https://charts.bitnami.com/bitnami | postgresql | 12.12.10 | + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| api.affinity | object | `{}` | | +| api.dpImage | string | `""` | | +| api.dpmgrBaseURL | string | `""` | | +| api.extraEnvVars | list | `[]` | | +| api.image.pullPolicy | string | `"IfNotPresent"` | | +| api.image.repository | string | `"ghcr.io/api7/aisix-cp-api"` | | +| api.image.tag | string | `""` | | +| api.nodeSelector | object | `{}` | | +| api.oauthEnabled | bool | `false` | | +| api.podSecurityContext.fsGroup | int | `101` | | +| api.podSecurityContext.runAsGroup | int | `101` | | +| api.podSecurityContext.runAsNonRoot | bool | `true` | | +| api.podSecurityContext.runAsUser | int | `10001` | | +| api.podSecurityContext.seccompProfile.type | string | `"RuntimeDefault"` | | +| api.publicBaseURL | string | `"http://localhost:8080"` | | +| api.replicaCount | int | `1` | | +| api.resources.limits.cpu | string | `"1"` | | +| api.resources.limits.memory | string | `"512Mi"` | | +| api.resources.requests.cpu | string | `"100m"` | | +| api.resources.requests.memory | string | `"128Mi"` | | +| api.securityContext.allowPrivilegeEscalation | bool | `false` | | +| api.securityContext.capabilities.drop[0] | string | `"ALL"` | | +| api.securityContext.readOnlyRootFilesystem | bool | `true` | | +| api.service.port | int | `8080` | | +| api.service.type | string | `"ClusterIP"` | | +| api.tolerations | list | `[]` | | +| dpm.affinity | object | `{}` | | +| dpm.extraEnvVars | list | `[]` | | +| dpm.image.pullPolicy | string | `"IfNotPresent"` | | +| dpm.image.repository | string | `"ghcr.io/api7/aisix-cp-dpm"` | | +| dpm.image.tag | string | `""` | | +| dpm.nodeSelector | object | `{}` | | +| dpm.podSecurityContext.fsGroup | int | `101` | | +| dpm.podSecurityContext.runAsGroup | int | `101` | | +| dpm.podSecurityContext.runAsNonRoot | bool | `true` | | +| dpm.podSecurityContext.runAsUser | int | `10001` | | +| dpm.podSecurityContext.seccompProfile.type | string | `"RuntimeDefault"` | | +| dpm.replicaCount | int | `1` | | +| dpm.resources.limits.cpu | string | `"1"` | | +| dpm.resources.limits.memory | string | `"512Mi"` | | +| dpm.resources.requests.cpu | string | `"100m"` | | +| dpm.resources.requests.memory | string | `"128Mi"` | | +| dpm.securityContext.allowPrivilegeEscalation | bool | `false` | | +| dpm.securityContext.capabilities.drop[0] | string | `"ALL"` | | +| dpm.securityContext.readOnlyRootFilesystem | bool | `true` | | +| dpm.service.nodePort | string | `""` | | +| dpm.service.port | int | `7944` | | +| dpm.service.type | string | `"ClusterIP"` | | +| dpm.tolerations | list | `[]` | | +| externalDatabase.database | string | `"aisix_cloud"` | | +| externalDatabase.existingSecret | string | `""` | | +| externalDatabase.host | string | `""` | | +| externalDatabase.password | string | `""` | | +| externalDatabase.port | int | `5432` | | +| externalDatabase.sslmode | string | `"disable"` | | +| externalDatabase.username | string | `"aisix"` | | +| global.imagePullSecrets | list | `[]` | | +| global.storageClass | string | `""` | | +| postgresql.auth.database | string | `"aisix_cloud"` | | +| postgresql.auth.existingSecret | string | `""` | | +| postgresql.auth.password | string | `"changeme"` | | +| postgresql.auth.postgresPassword | string | `"changeme"` | | +| postgresql.auth.usePostgresUserForAppConnections | bool | `true` | | +| postgresql.auth.username | string | `"aisix"` | | +| postgresql.builtin | bool | `true` | | +| postgresql.fullnameOverride | string | `""` | | +| postgresql.image.registry | string | `"docker.io"` | | +| postgresql.image.repository | string | `"api7/postgresql"` | | +| postgresql.image.tag | string | `"15.4.0-debian-11-r45"` | | +| postgresql.primary.persistence.size | string | `"8Gi"` | | +| postgresql.primary.service.ports.postgresql | int | `5432` | | +| secrets.betterAuthSecret | string | `"CHANGE_ME_GENERATE_WITH_openssl_rand_-base64_48"` | | +| secrets.masterKey | string | `"CHANGE_ME_GENERATE_WITH_openssl_rand_-base64_32"` | | +| secrets.masterKeyID | string | `"env:default"` | | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.name | string | `""` | | +| ui.affinity | object | `{}` | | +| ui.defaultLocale | string | `"en"` | | +| ui.extraEnvVars | list | `[]` | | +| ui.image.pullPolicy | string | `"IfNotPresent"` | | +| ui.image.repository | string | `"ghcr.io/api7/aisix-cp-ui"` | | +| ui.image.tag | string | `""` | | +| ui.nodeSelector | object | `{}` | | +| ui.podSecurityContext.fsGroup | int | `65533` | | +| ui.podSecurityContext.runAsGroup | int | `65533` | | +| ui.podSecurityContext.runAsNonRoot | bool | `true` | | +| ui.podSecurityContext.runAsUser | int | `1001` | | +| ui.podSecurityContext.seccompProfile.type | string | `"RuntimeDefault"` | | +| ui.replicaCount | int | `1` | | +| ui.resources.limits.cpu | string | `"500m"` | | +| ui.resources.limits.memory | string | `"256Mi"` | | +| ui.resources.requests.cpu | string | `"50m"` | | +| ui.resources.requests.memory | string | `"64Mi"` | | +| ui.securityContext.allowPrivilegeEscalation | bool | `false` | | +| ui.securityContext.capabilities.drop[0] | string | `"ALL"` | | +| ui.securityContext.readOnlyRootFilesystem | bool | `true` | | +| ui.service.port | int | `3000` | | +| ui.service.type | string | `"ClusterIP"` | | +| ui.tolerations | list | `[]` | | + diff --git a/charts/aisix-cloud/charts/postgresql-12.12.10.tgz b/charts/aisix-cloud/charts/postgresql-12.12.10.tgz new file mode 100644 index 0000000000000000000000000000000000000000..89bcc970ab5e6df5463d6a1d3c8af850698efe6b GIT binary patch literal 62562 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

VQk(m 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 x-zBAytSHwSn|L8%-%e!`$$Y!;ho1F` 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 and with actual values): + + docker run --rm \ + -e AISIX_CONFIG_PATH=/etc/aisix/config.managed.yaml \ + -e AISIX_MANAGED__CP_BASE_URL=https://: \ + -e AISIX_MANAGED__CP_ETCD_ENDPOINT=: \ + -e AISIX_MANAGED__CP_CERT_PEM='' \ + -e AISIX_MANAGED__CP_KEY_PEM='' \ + -e AISIX_MANAGED__CP_CA_PEM='' \ + -v aisix-mtls:/var/lib/aisix \ + ghcr.io/api7/aisix:dev +{{- else }} + DPM is a ClusterIP service. Expose it via Ingress, LoadBalancer, or port-forward: + kubectl port-forward svc/{{ include "aisix-cloud.fullname" . }}-dpm {{ .Values.dpm.service.port }}:{{ .Values.dpm.service.port }} -n {{ .Release.Namespace }} +{{- end }} diff --git a/charts/aisix-cloud/templates/_helpers.tpl b/charts/aisix-cloud/templates/_helpers.tpl new file mode 100644 index 0000000..e37af9c --- /dev/null +++ b/charts/aisix-cloud/templates/_helpers.tpl @@ -0,0 +1,199 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "aisix-cloud.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "aisix-cloud.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "aisix-cloud.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "aisix-cloud.labels" -}} +helm.sh/chart: {{ include "aisix-cloud.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: aisix-cloud +{{- end }} + +{{/* +Selector labels for a component. +Usage: {{ include "aisix-cloud.selectorLabels" (dict "root" . "component" "api") }} +*/}} +{{- define "aisix-cloud.selectorLabels" -}} +app.kubernetes.io/name: {{ include "aisix-cloud.name" .root }} +app.kubernetes.io/instance: {{ .root.Release.Name }} +app.kubernetes.io/component: {{ .component }} +{{- end }} + +{{/* +PostgreSQL host — uses builtin subchart service name or external host. +*/}} +{{- define "aisix-cloud.pgHost" -}} +{{- if .Values.postgresql.builtin }} +{{- if .Values.postgresql.fullnameOverride }} +{{- .Values.postgresql.fullnameOverride }} +{{- else }} +{{- printf "%s-postgresql" .Release.Name }} +{{- end }} +{{- else }} +{{- required "externalDatabase.host is required when postgresql.builtin is false" .Values.externalDatabase.host }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL port. +*/}} +{{- define "aisix-cloud.pgPort" -}} +{{- if .Values.postgresql.builtin }} +{{- .Values.postgresql.primary.service.ports.postgresql }} +{{- else }} +{{- .Values.externalDatabase.port }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL username. +*/}} +{{- define "aisix-cloud.pgUser" -}} +{{- if .Values.postgresql.builtin }} +{{- if .Values.postgresql.auth.usePostgresUserForAppConnections }} +{{- "postgres" }} +{{- else }} +{{- .Values.postgresql.auth.username }} +{{- end }} +{{- else }} +{{- .Values.externalDatabase.username }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL database name. +*/}} +{{- define "aisix-cloud.pgDatabase" -}} +{{- if .Values.postgresql.builtin }} +{{- .Values.postgresql.auth.database }} +{{- else }} +{{- .Values.externalDatabase.database }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL SSL mode. +*/}} +{{- define "aisix-cloud.pgSSLMode" -}} +{{- if .Values.postgresql.builtin }} +{{- "disable" }} +{{- else }} +{{- .Values.externalDatabase.sslmode | default "disable" }} +{{- end }} +{{- end }} + +{{/* +Database URL constructed from postgresql config. +Uses $(PGPASSWORD) env var substitution for runtime secret injection. +*/}} +{{- define "aisix-cloud.databaseURL" -}} +postgres://{{ include "aisix-cloud.pgUser" . }}:$(PGPASSWORD)@{{ include "aisix-cloud.pgHost" . }}:{{ include "aisix-cloud.pgPort" . }}/{{ include "aisix-cloud.pgDatabase" . }}?sslmode={{ include "aisix-cloud.pgSSLMode" . }} +{{- end }} + +{{/* +Secret name for aisix-cloud secrets. +*/}} +{{- define "aisix-cloud.secretName" -}} +{{ include "aisix-cloud.fullname" . }}-secrets +{{- end }} + +{{/* +Name of the Secret containing the PostgreSQL password. +*/}} +{{- define "aisix-cloud.pgSecretName" -}} +{{- if .Values.postgresql.builtin }} +{{- if .Values.postgresql.fullnameOverride }} +{{- .Values.postgresql.fullnameOverride }} +{{- else }} +{{- printf "%s-postgresql" .Release.Name }} +{{- end }} +{{- else if .Values.externalDatabase.existingSecret }} +{{- .Values.externalDatabase.existingSecret }} +{{- else }} +{{- printf "%s-external-db" (include "aisix-cloud.fullname" .) }} +{{- end }} +{{- end }} + +{{/* +Secret key containing the PostgreSQL password for application connections. +*/}} +{{- define "aisix-cloud.pgPasswordSecretKey" -}} +{{- if and .Values.postgresql.builtin .Values.postgresql.auth.usePostgresUserForAppConnections }} +{{- "postgres-password" }} +{{- else }} +{{- "password" }} +{{- end }} +{{- end }} + +{{/* +ServiceAccount name. +*/}} +{{- define "aisix-cloud.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "aisix-cloud.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Image pull secrets from global config. +*/}} +{{- define "aisix-cloud.imagePullSecrets" -}} +{{- with .Values.global.imagePullSecrets }} +imagePullSecrets: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} + +{{/* +Init container that blocks until PostgreSQL accepts connections. +Reuses the same PG image shipped by the chart so no extra pull is needed. +*/}} +{{- define "aisix-cloud.pgWaitInitContainer" -}} +- name: wait-for-pg + image: "{{ .Values.postgresql.image.registry }}/{{ .Values.postgresql.image.repository }}:{{ .Values.postgresql.image.tag }}" + imagePullPolicy: IfNotPresent + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ include "aisix-cloud.pgSecretName" . }} + key: {{ include "aisix-cloud.pgPasswordSecretKey" . }} + command: + - sh + - -c + - | + until pg_isready -h {{ include "aisix-cloud.pgHost" . }} -p {{ include "aisix-cloud.pgPort" . }} -U {{ include "aisix-cloud.pgUser" . }}; do + echo "waiting for postgresql..." + sleep 2 + done +{{- end }} diff --git a/charts/aisix-cloud/templates/api-deployment.yaml b/charts/aisix-cloud/templates/api-deployment.yaml new file mode 100644 index 0000000..788f34f --- /dev/null +++ b/charts/aisix-cloud/templates/api-deployment.yaml @@ -0,0 +1,98 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "aisix-cloud.fullname" . }}-api + labels: + {{- include "aisix-cloud.labels" . | nindent 4 }} + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "api") | nindent 4 }} +spec: + replicas: {{ .Values.api.replicaCount }} + selector: + matchLabels: + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "api") | nindent 6 }} + template: + metadata: + labels: + {{- include "aisix-cloud.labels" . | nindent 8 }} + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "api") | nindent 8 }} + spec: + {{- include "aisix-cloud.imagePullSecrets" . | nindent 6 }} + serviceAccountName: {{ include "aisix-cloud.serviceAccountName" . }} + {{- with .Values.api.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + {{- include "aisix-cloud.pgWaitInitContainer" . | nindent 8 }} + containers: + - name: api + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.api.image.pullPolicy }} + {{- with .Values.api.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ include "aisix-cloud.pgSecretName" . }} + key: {{ include "aisix-cloud.pgPasswordSecretKey" . }} + - name: AISIX_CLOUD_LISTEN + value: ":8080" + - name: AISIX_CLOUD_DATABASE_URL + value: {{ include "aisix-cloud.databaseURL" . }} + - name: AISIX_CLOUD_PUBLIC_BASE_URL + value: {{ .Values.api.publicBaseURL | quote }} + - name: AISIX_CLOUD_MASTER_KEY + valueFrom: + secretKeyRef: + name: {{ include "aisix-cloud.secretName" . }} + key: master-key + - name: AISIX_CLOUD_MASTER_KEY_ID + value: {{ .Values.secrets.masterKeyID | quote }} + - name: AISIX_CLOUD_DASHBOARD_URL + value: "http://{{ include "aisix-cloud.fullname" . }}-ui:{{ .Values.ui.service.port }}" + {{- if .Values.api.dpmgrBaseURL }} + - name: AISIX_CLOUD_DPMGR_BASE_URL + value: {{ .Values.api.dpmgrBaseURL | quote }} + {{- end }} + - name: AISIX_CLOUD_OAUTH_ENABLED + value: {{ .Values.api.oauthEnabled | quote }} + - name: AISIX_CLOUD_DP_IMAGE + value: {{ .Values.api.dpImage | default (printf "ghcr.io/api7/aisix:%s" .Chart.AppVersion) | quote }} + {{- with .Values.api.extraEnvVars }} + {{- toYaml . | nindent 12 }} + {{- end }} + livenessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + {{- with .Values.api.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.api.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.api.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.api.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/aisix-cloud/templates/api-service.yaml b/charts/aisix-cloud/templates/api-service.yaml new file mode 100644 index 0000000..58a9c1c --- /dev/null +++ b/charts/aisix-cloud/templates/api-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "aisix-cloud.fullname" . }}-api + labels: + {{- include "aisix-cloud.labels" . | nindent 4 }} + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "api") | nindent 4 }} +spec: + type: {{ .Values.api.service.type }} + ports: + - port: {{ .Values.api.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "api") | nindent 4 }} diff --git a/charts/aisix-cloud/templates/dpm-deployment.yaml b/charts/aisix-cloud/templates/dpm-deployment.yaml new file mode 100644 index 0000000..75fbdc8 --- /dev/null +++ b/charts/aisix-cloud/templates/dpm-deployment.yaml @@ -0,0 +1,84 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "aisix-cloud.fullname" . }}-dpm + labels: + {{- include "aisix-cloud.labels" . | nindent 4 }} + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "dpm") | nindent 4 }} +spec: + replicas: {{ .Values.dpm.replicaCount }} + selector: + matchLabels: + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "dpm") | nindent 6 }} + template: + metadata: + labels: + {{- include "aisix-cloud.labels" . | nindent 8 }} + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "dpm") | nindent 8 }} + spec: + {{- include "aisix-cloud.imagePullSecrets" . | nindent 6 }} + serviceAccountName: {{ include "aisix-cloud.serviceAccountName" . }} + {{- with .Values.dpm.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + {{- include "aisix-cloud.pgWaitInitContainer" . | nindent 8 }} + containers: + - name: dpm + image: "{{ .Values.dpm.image.repository }}:{{ .Values.dpm.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.dpm.image.pullPolicy }} + {{- with .Values.dpm.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: tls + containerPort: 7944 + protocol: TCP + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ include "aisix-cloud.pgSecretName" . }} + key: {{ include "aisix-cloud.pgPasswordSecretKey" . }} + - name: AISIX_DPMGR_LISTEN + value: ":7944" + - name: AISIX_DPMGR_DATABASE_URL + value: {{ include "aisix-cloud.databaseURL" . }} + - name: AISIX_DPMGR_MASTER_KEY + valueFrom: + secretKeyRef: + name: {{ include "aisix-cloud.secretName" . }} + key: master-key + - name: AISIX_DPMGR_MASTER_KEY_ID + value: {{ .Values.secrets.masterKeyID | quote }} + {{- with .Values.dpm.extraEnvVars }} + {{- toYaml . | nindent 12 }} + {{- end }} + readinessProbe: + tcpSocket: + port: tls + initialDelaySeconds: 5 + periodSeconds: 5 + livenessProbe: + tcpSocket: + port: tls + initialDelaySeconds: 10 + periodSeconds: 10 + {{- with .Values.dpm.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.dpm.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.dpm.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.dpm.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/aisix-cloud/templates/dpm-service.yaml b/charts/aisix-cloud/templates/dpm-service.yaml new file mode 100644 index 0000000..b3221ec --- /dev/null +++ b/charts/aisix-cloud/templates/dpm-service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "aisix-cloud.fullname" . }}-dpm + labels: + {{- include "aisix-cloud.labels" . | nindent 4 }} + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "dpm") | nindent 4 }} +spec: + type: {{ .Values.dpm.service.type }} + ports: + - port: {{ .Values.dpm.service.port }} + targetPort: tls + protocol: TCP + name: tls + {{- if and (eq .Values.dpm.service.type "NodePort") .Values.dpm.service.nodePort }} + nodePort: {{ .Values.dpm.service.nodePort }} + {{- end }} + selector: + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "dpm") | nindent 4 }} diff --git a/charts/aisix-cloud/templates/external-db-secret.yaml b/charts/aisix-cloud/templates/external-db-secret.yaml new file mode 100644 index 0000000..fb9088b --- /dev/null +++ b/charts/aisix-cloud/templates/external-db-secret.yaml @@ -0,0 +1,11 @@ +{{- if and (not .Values.postgresql.builtin) (not .Values.externalDatabase.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "aisix-cloud.fullname" . }}-external-db + labels: + {{- include "aisix-cloud.labels" . | nindent 4 }} +type: Opaque +data: + password: {{ required "externalDatabase.password is required when existingSecret is not set" .Values.externalDatabase.password | b64enc }} +{{- end }} diff --git a/charts/aisix-cloud/templates/secret.yaml b/charts/aisix-cloud/templates/secret.yaml new file mode 100644 index 0000000..00f5035 --- /dev/null +++ b/charts/aisix-cloud/templates/secret.yaml @@ -0,0 +1,37 @@ +{{/* +Secrets rendered from explicit values. No auto-generation — values must +be set explicitly to guarantee stability across re-deploys where helm +lookup is unavailable during server-side rendering (ArgoCD, Flux, etc.). +*/}} +{{- if or (eq .Values.secrets.masterKey "") (hasPrefix "CHANGE_ME" .Values.secrets.masterKey) }} + {{- fail "secrets.masterKey must be set to a real base64-encoded 32-byte key (generate with: openssl rand -base64 32)" }} +{{- end }} +{{- if or (eq .Values.secrets.betterAuthSecret "") (hasPrefix "CHANGE_ME" .Values.secrets.betterAuthSecret) }} + {{- fail "secrets.betterAuthSecret must be set to a real secret (generate with: openssl rand -base64 48)" }} +{{- end }} +{{/* +Hold builtin PostgreSQL credentials to the same standard as the +application secrets above: reject the documented placeholder so an +operator can't deploy a cluster-reachable database with a known +password. Skipped when an existingSecret injects the credentials. +*/}} +{{- if and .Values.postgresql.builtin (not .Values.postgresql.auth.existingSecret) }} + {{- range $field := list "password" "postgresPassword" }} + {{- $val := get $.Values.postgresql.auth $field | toString }} + {{- if or (eq $val "") (eq (lower $val) "changeme") (hasPrefix "CHANGE_ME" $val) }} + {{- fail (printf "postgresql.auth.%s must be set to a real password when postgresql.builtin=true (the default 'changeme' is rejected). Generate one with: openssl rand -base64 24 — or inject credentials via postgresql.auth.existingSecret." $field) }} + {{- end }} + {{- end }} +{{- end }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "aisix-cloud.secretName" . }} + labels: + {{- include "aisix-cloud.labels" . | nindent 4 }} + annotations: + helm.sh/resource-policy: keep +type: Opaque +data: + master-key: {{ .Values.secrets.masterKey | b64enc }} + better-auth-secret: {{ .Values.secrets.betterAuthSecret | b64enc }} diff --git a/charts/aisix-cloud/templates/serviceaccount.yaml b/charts/aisix-cloud/templates/serviceaccount.yaml new file mode 100644 index 0000000..dda2447 --- /dev/null +++ b/charts/aisix-cloud/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "aisix-cloud.serviceAccountName" . }} + labels: + {{- include "aisix-cloud.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: false +{{- end }} diff --git a/charts/aisix-cloud/templates/ui-deployment.yaml b/charts/aisix-cloud/templates/ui-deployment.yaml new file mode 100644 index 0000000..a90c421 --- /dev/null +++ b/charts/aisix-cloud/templates/ui-deployment.yaml @@ -0,0 +1,84 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "aisix-cloud.fullname" . }}-ui + labels: + {{- include "aisix-cloud.labels" . | nindent 4 }} + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "ui") | nindent 4 }} +spec: + replicas: {{ .Values.ui.replicaCount }} + selector: + matchLabels: + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "ui") | nindent 6 }} + template: + metadata: + labels: + {{- include "aisix-cloud.labels" . | nindent 8 }} + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "ui") | nindent 8 }} + spec: + {{- include "aisix-cloud.imagePullSecrets" . | nindent 6 }} + serviceAccountName: {{ include "aisix-cloud.serviceAccountName" . }} + {{- with .Values.ui.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: ui + image: "{{ .Values.ui.image.repository }}:{{ .Values.ui.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.ui.image.pullPolicy }} + {{- with .Values.ui.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: 3000 + protocol: TCP + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ include "aisix-cloud.pgSecretName" . }} + key: {{ include "aisix-cloud.pgPasswordSecretKey" . }} + - name: BETTER_AUTH_URL + value: {{ .Values.api.publicBaseURL | quote }} + - name: BETTER_AUTH_SECRET + valueFrom: + secretKeyRef: + name: {{ include "aisix-cloud.secretName" . }} + key: better-auth-secret + - name: AISIX_CLOUD_DATABASE_URL + value: {{ include "aisix-cloud.databaseURL" . }} + - name: AISIX_DASHBOARD_LOCALE + value: {{ .Values.ui.defaultLocale | quote }} + {{- with .Values.ui.extraEnvVars }} + {{- toYaml . | nindent 12 }} + {{- end }} + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + {{- with .Values.ui.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ui.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ui.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ui.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/aisix-cloud/templates/ui-service.yaml b/charts/aisix-cloud/templates/ui-service.yaml new file mode 100644 index 0000000..968ebfd --- /dev/null +++ b/charts/aisix-cloud/templates/ui-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "aisix-cloud.fullname" . }}-ui + labels: + {{- include "aisix-cloud.labels" . | nindent 4 }} + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "ui") | nindent 4 }} +spec: + type: {{ .Values.ui.service.type }} + ports: + - port: {{ .Values.ui.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "aisix-cloud.selectorLabels" (dict "root" . "component" "ui") | nindent 4 }} diff --git a/charts/aisix-cloud/values.yaml b/charts/aisix-cloud/values.yaml new file mode 100644 index 0000000..82d6567 --- /dev/null +++ b/charts/aisix-cloud/values.yaml @@ -0,0 +1,186 @@ +## @section Global parameters +global: + storageClass: "" + imagePullSecrets: [] + +## @section cp-api +api: + replicaCount: 1 + image: + repository: ghcr.io/api7/aisix-cp-api + pullPolicy: IfNotPresent + tag: "" # empty -> .Chart.AppVersion + service: + type: ClusterIP + port: 8080 + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: "1" + memory: 512Mi + podSecurityContext: + runAsNonRoot: true + runAsUser: 10001 + runAsGroup: 101 + fsGroup: 101 + seccompProfile: + type: RuntimeDefault + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + nodeSelector: {} + tolerations: [] + affinity: {} + extraEnvVars: [] + publicBaseURL: "http://localhost:8080" + dpmgrBaseURL: "" + oauthEnabled: false + dpImage: "" # empty -> ghcr.io/api7/aisix: + +## @section dp-manager +dpm: + replicaCount: 1 + image: + repository: ghcr.io/api7/aisix-cp-dpm + pullPolicy: IfNotPresent + tag: "" # empty -> .Chart.AppVersion + service: + type: ClusterIP + port: 7944 + nodePort: "" + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: "1" + memory: 512Mi + podSecurityContext: + runAsNonRoot: true + runAsUser: 10001 + runAsGroup: 101 + fsGroup: 101 + seccompProfile: + type: RuntimeDefault + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + nodeSelector: {} + tolerations: [] + affinity: {} + extraEnvVars: [] + +## @section dashboard (Next.js) +ui: + replicaCount: 1 + # Fixed dashboard UI language for this deployment. Supported: "en", "zh". + # There is no in-UI language switcher; the whole console renders in this + # locale (default English). Read server-side at request time. + defaultLocale: "en" + image: + repository: ghcr.io/api7/aisix-cp-ui + pullPolicy: IfNotPresent + tag: "" # empty -> .Chart.AppVersion + service: + type: ClusterIP + port: 3000 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 500m + memory: 256Mi + podSecurityContext: + runAsNonRoot: true + runAsUser: 1001 + runAsGroup: 65533 + fsGroup: 65533 + seccompProfile: + type: RuntimeDefault + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + nodeSelector: {} + tolerations: [] + affinity: {} + extraEnvVars: [] + +## @section ServiceAccount +serviceAccount: + create: true + name: "" + annotations: {} + +## @section Secrets +## IMPORTANT: You MUST replace the placeholder values below before deploying. +## Deploying with CHANGE_ME placeholders will fail at helm render/install time. +## +## masterKey: base64-encoded 32-byte AES-256 key for envelope encryption. +## Generate with: openssl rand -base64 32 +## betterAuthSecret: random string used as HMAC signing secret for Better Auth sessions. +## Generate with: openssl rand -base64 48 +secrets: + masterKey: "CHANGE_ME_GENERATE_WITH_openssl_rand_-base64_32" + masterKeyID: "env:default" + betterAuthSecret: "CHANGE_ME_GENERATE_WITH_openssl_rand_-base64_48" + +## @section PostgreSQL +## Set builtin=true to deploy Bitnami PostgreSQL subchart. +## Set builtin=false and configure externalDatabase to use an existing PostgreSQL instance. +postgresql: + builtin: true + fullnameOverride: "" + image: + registry: docker.io + repository: api7/postgresql + tag: 15.4.0-debian-11-r45 + primary: + persistence: + size: 8Gi + service: + ports: + postgresql: 5432 + auth: + ## Use the built-in PostgreSQL superuser for application connections. + ## cp-api currently runs schema and role migrations on startup, including + ## ALTER ROLE statements that require superuser privileges. + usePostgresUserForAppConnections: true + username: aisix + ## Inject DB credentials from a pre-created Secret instead of the values + ## below (the secure path). When set, the password fields are ignored and + ## the placeholder rejection is skipped. + existingSecret: "" + ## IMPORTANT: Set a stable password before deploying. + ## If left as default, the password is baked into the PG data volume on first init + ## and must remain unchanged across re-deploys. + ## The chart REJECTS the default 'changeme' at render/install time when + ## postgresql.builtin=true — set a real password (openssl rand -base64 24) + ## or use existingSecret above. + password: changeme + postgresPassword: changeme + database: aisix_cloud + +## @section External Database +## Only used when postgresql.builtin is false. +externalDatabase: + host: "" + port: 5432 + username: aisix + database: aisix_cloud + ## Name of an existing Secret containing the database password (key: "password"). + existingSecret: "" + ## If existingSecret is empty, this password is used directly. + password: "" + sslmode: disable From ae5f0b505fbd7facae097d2ff70243e52f360198 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Thu, 18 Jun 2026 19:10:09 +0800 Subject: [PATCH 2/3] docs(agents): list the aisix-cloud chart --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index 5763ca6..2ba2c18 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,7 @@ This repo (`api7/api7-helm-chart`) holds the Helm charts for API7 EE components | `gateway` (Data Plane) | `charts/gateway` | yes — appVersion is the EE version | | `api7-ingress-controller` | `charts/ingress-controller` | no — independent product version | | `developer-portal-fe` | `charts/developer-portal-fe` | no — independent product version | +| `aisix-cloud` (AISIX private-deployment control plane) | `charts/aisix-cloud` | no — independent product version (source of truth: `api7/AISIX-Cloud` `helm/aisix-cloud`) | ## Multi-line maintenance model From 9186a6354d165f176c2971f7ec28ced72b8127f7 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 22 Jun 2026 14:35:18 +0800 Subject: [PATCH 3/3] fix(aisix-cloud): honor postgresql.auth.existingSecret; version DP image in NOTES - pgSecretName: in builtin mode, resolve to postgresql.auth.existingSecret when set so app pods reference the BYO secret instead of the (uncreated) Bitnami-generated name. - NOTES.txt: drop hardcoded :dev DP image, default to appVersion. --- charts/aisix-cloud/templates/NOTES.txt | 2 +- charts/aisix-cloud/templates/_helpers.tpl | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/charts/aisix-cloud/templates/NOTES.txt b/charts/aisix-cloud/templates/NOTES.txt index cde809f..167e878 100644 --- a/charts/aisix-cloud/templates/NOTES.txt +++ b/charts/aisix-cloud/templates/NOTES.txt @@ -30,7 +30,7 @@ Connect a data-plane (managed mode): -e AISIX_MANAGED__CP_KEY_PEM='' \ -e AISIX_MANAGED__CP_CA_PEM='' \ -v aisix-mtls:/var/lib/aisix \ - ghcr.io/api7/aisix:dev + {{ .Values.api.dpImage | default (printf "ghcr.io/api7/aisix:%s" .Chart.AppVersion) }} {{- else }} DPM is a ClusterIP service. Expose it via Ingress, LoadBalancer, or port-forward: kubectl port-forward svc/{{ include "aisix-cloud.fullname" . }}-dpm {{ .Values.dpm.service.port }}:{{ .Values.dpm.service.port }} -n {{ .Release.Namespace }} diff --git a/charts/aisix-cloud/templates/_helpers.tpl b/charts/aisix-cloud/templates/_helpers.tpl index e37af9c..ce723c7 100644 --- a/charts/aisix-cloud/templates/_helpers.tpl +++ b/charts/aisix-cloud/templates/_helpers.tpl @@ -130,7 +130,9 @@ Name of the Secret containing the PostgreSQL password. */}} {{- define "aisix-cloud.pgSecretName" -}} {{- if .Values.postgresql.builtin }} -{{- if .Values.postgresql.fullnameOverride }} +{{- if .Values.postgresql.auth.existingSecret }} +{{- .Values.postgresql.auth.existingSecret }} +{{- else if .Values.postgresql.fullnameOverride }} {{- .Values.postgresql.fullnameOverride }} {{- else }} {{- printf "%s-postgresql" .Release.Name }}