Compare commits
6
Commits
7782b1e75d
...
80f6d5232b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80f6d5232b
|
||
|
|
a723372c6e
|
||
|
|
4823eeb767
|
||
|
|
0def13f0c7
|
||
|
|
227994bc10
|
||
|
|
0a14dded9d
|
@@ -29,6 +29,10 @@ case "${MACHINE}" in
|
||||
esac
|
||||
echo "Architecture: ${ARCH}"
|
||||
|
||||
KIND_VERSION="v0.33.0"
|
||||
KUBEBUILDER_VERSION="v4.15.0"
|
||||
KUBECTL_VERSION="v1.36.0"
|
||||
|
||||
echo ""
|
||||
echo "------------------------------------"
|
||||
echo "Setting up bash completion..."
|
||||
@@ -50,7 +54,7 @@ echo "------------------------------------"
|
||||
# Install kind
|
||||
if ! command -v kind &> /dev/null; then
|
||||
echo "Installing kind..."
|
||||
curl -Lo /usr/local/bin/kind "https://kind.sigs.k8s.io/dl/latest/kind-linux-${ARCH}"
|
||||
curl -Lo /usr/local/bin/kind "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-${ARCH}"
|
||||
chmod +x /usr/local/bin/kind
|
||||
echo "kind installed successfully"
|
||||
fi
|
||||
@@ -67,7 +71,7 @@ fi
|
||||
# Install kubebuilder
|
||||
if ! command -v kubebuilder &> /dev/null; then
|
||||
echo "Installing kubebuilder..."
|
||||
curl -Lo /usr/local/bin/kubebuilder "https://go.kubebuilder.io/dl/latest/linux/${ARCH}"
|
||||
curl -Lo /usr/local/bin/kubebuilder "https://github.com/kubernetes-sigs/kubebuilder/releases/download/${KUBEBUILDER_VERSION}/kubebuilder_linux_${ARCH}"
|
||||
chmod +x /usr/local/bin/kubebuilder
|
||||
echo "kubebuilder installed successfully"
|
||||
fi
|
||||
@@ -84,7 +88,6 @@ fi
|
||||
# Install kubectl
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
echo "Installing kubectl..."
|
||||
KUBECTL_VERSION=$(curl -Ls https://dl.k8s.io/release/stable.txt)
|
||||
curl -Lo /usr/local/bin/kubectl "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl"
|
||||
chmod +x /usr/local/bin/kubectl
|
||||
echo "kubectl installed successfully"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
name: E2E Tests
|
||||
|
||||
env:
|
||||
KIND_VERSION: v0.33.0
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -26,10 +29,10 @@ jobs:
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install the latest version of kind
|
||||
- name: Install kind
|
||||
run: |
|
||||
mkdir -p ./bin
|
||||
curl -Lo ./bin/kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)
|
||||
curl -Lo ./bin/kind https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-$(go env GOARCH)
|
||||
chmod +x ./bin/kind
|
||||
|
||||
- name: Verify Docker and kind
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
# postgresql-tenant-operator - AI Agent Guide
|
||||
|
||||
## Specification-Driven Development
|
||||
|
||||
- Use specification-driven development for every new feature and externally observable
|
||||
behavior change: decide and document the contract before writing implementation code.
|
||||
- Create or update the relevant specification first. It must define scope, non-goals,
|
||||
observable behavior, validation, failure semantics, security boundaries, and acceptance
|
||||
criteria at the level needed for a human to make the pending decisions.
|
||||
- Stop after the specification reaches a reviewable state and ask the user to approve it
|
||||
before implementing the behavior. Approval of a plan, issue, or earlier specification
|
||||
does not imply approval of materially new decisions.
|
||||
- Derive tests from the approved acceptance criteria, then implement the smallest vertical
|
||||
slice that makes those tests pass. Keep specification, tests, and implementation
|
||||
traceable to one another.
|
||||
- If implementation exposes an ambiguity or requires changing the approved behavior, stop,
|
||||
update the specification, and request review again before continuing.
|
||||
- Keep specifications focused on contracts and decisions. Do not prematurely freeze internal
|
||||
Go package structure, function names, or SQL details unless they are part of the contract.
|
||||
|
||||
## Human-Reviewable Changes
|
||||
|
||||
- Break every feature into small, coherent changes that a human can review independently.
|
||||
- Keep each change focused on one behavior or decision; do not mix unrelated refactors,
|
||||
formatting churn, dependency updates, or cleanup into a feature change.
|
||||
- Keep the project buildable and its relevant tests passing after each change whenever
|
||||
practical. Add or update focused tests alongside the behavior they verify.
|
||||
- Present generated files together with the source API or marker change that produced them,
|
||||
and call out generated diffs explicitly instead of treating them as separate design work.
|
||||
- Prefer a sequence of narrow vertical slices over one large implementation. Summarize the
|
||||
intent, observable behavior, and verification for each slice so a human can review it
|
||||
before the next slice grows on top of it.
|
||||
- A pull request may contain multiple small commits. Align each commit with one coherent,
|
||||
independently reviewable change.
|
||||
- When a change reaches a suitable commit boundary, ask the user whether to create the
|
||||
commit before committing it. Do not accumulate unrelated completed changes merely to
|
||||
reduce the number of commits.
|
||||
- Always stop and ask the user before opening or submitting a pull request. Approval to
|
||||
create commits or push a branch does not imply approval to create a pull request.
|
||||
|
||||
## Project Structure
|
||||
|
||||
**Single-group layout (default):**
|
||||
|
||||
+13
-8
@@ -30,14 +30,17 @@ kubeconfig 或本地生成的二进制。
|
||||
## 开发循环
|
||||
|
||||
1. 从最新 `main` 创建分支。
|
||||
2. 先用测试描述预期的 reconcile 行为,再实现最小改动。
|
||||
3. 修改 API type 或 Kubebuilder marker 后运行:
|
||||
2. 先新增或修改规格,覆盖范围、非目标、外部行为、校验、失败语义、安全边界和验收
|
||||
标准;获得人工批准前不得实现行为。
|
||||
3. 从已批准验收标准派生失败测试,再实现最小纵向切片。若实现暴露规格歧义,返回
|
||||
规格阶段重新审批。
|
||||
4. 修改 API type 或 Kubebuilder marker 后运行:
|
||||
|
||||
```sh
|
||||
make manifests generate
|
||||
```
|
||||
|
||||
4. 提交前运行:
|
||||
5. 提交前运行:
|
||||
|
||||
```sh
|
||||
make test
|
||||
@@ -45,9 +48,11 @@ kubeconfig 或本地生成的二进制。
|
||||
git diff --exit-code
|
||||
```
|
||||
|
||||
5. 推送分支并创建 PR。PR 说明应包含动机、行为变化、验证方式,以及对数据库或
|
||||
OpenBao 的风险。
|
||||
6. CI 通过后 squash merge,删除已合并分支。
|
||||
6. 达到一个小而完整、可独立 review 的边界时,先请求批准再创建 commit;一个 PR
|
||||
可以包含多个这样的 commit。
|
||||
7. 推送分支后,在创建 PR 前再次请求批准。PR 说明应包含规格链接、动机、行为变化、
|
||||
验证方式,以及对 PostgreSQL、OpenBao、ESO 和凭据的风险。
|
||||
8. CI 通过后按仓库策略合并,删除已合并分支。
|
||||
|
||||
`make test-e2e` 会创建并删除名为
|
||||
`postgresql-tenant-operator-test-e2e` 的 Kind 集群,只能在隔离环境运行,不能指向
|
||||
@@ -74,8 +79,8 @@ Docker daemon,供 Kind 创建临时节点容器。建议在 Gitea 中保护 `m
|
||||
- 外部调用必须有超时、可重试,并区分永久错误与暂时错误。
|
||||
- 日志、Event、Condition message 和测试输出不得包含密码或管理凭据。
|
||||
- 默认删除策略是 `Retain`;任何实际销毁路径都必须有 finalizer 和独立测试。
|
||||
- controller 测试优先使用 fake PostgreSQL/OpenBao client;API 交互使用 envtest;真实
|
||||
组件集成留给 Kind e2e。
|
||||
- controller 测试优先使用 fake PostgreSQL/OpenBao/ESO client;API 交互使用 envtest;
|
||||
PostgreSQL/OpenBao adapter 使用 Docker 集成测试,完整网络和 ESO 投射留给 Kind E2E。
|
||||
|
||||
## API 变更
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ endif
|
||||
# tools. (i.e. podman)
|
||||
CONTAINER_TOOL ?= docker
|
||||
|
||||
# DEV_COMPOSE manages disposable PostgreSQL and OpenBao dependencies for local work.
|
||||
DEV_COMPOSE ?= docker compose -f hack/dev/compose.yaml
|
||||
|
||||
# Setting SHELL to bash allows bash commands to be executed by recipes.
|
||||
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
|
||||
SHELL = /usr/bin/env bash -o pipefail
|
||||
@@ -43,6 +46,19 @@ help: ## Display this help.
|
||||
|
||||
##@ Development
|
||||
|
||||
.PHONY: dev-up
|
||||
dev-up: ## Start disposable PostgreSQL and OpenBao development dependencies.
|
||||
$(DEV_COMPOSE) up -d --wait
|
||||
|
||||
.PHONY: dev-smoke
|
||||
dev-smoke: dev-up ## Verify PostgreSQL and OpenBao development dependencies.
|
||||
$(DEV_COMPOSE) exec -T postgres psql -U postgres -d postgres -v ON_ERROR_STOP=1 -c 'SELECT 1'
|
||||
$(DEV_COMPOSE) exec -T openbao sh -ec 'bao kv put secret/postgresql-admin username=postgres password=postgres-dev-only >/dev/null; test "$$(bao kv get -field=username secret/postgresql-admin)" = postgres'
|
||||
|
||||
.PHONY: dev-down
|
||||
dev-down: ## Remove disposable development dependencies and their data.
|
||||
$(DEV_COMPOSE) down --volumes --remove-orphans
|
||||
|
||||
.PHONY: manifests
|
||||
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
|
||||
"$(CONTROLLER_GEN)" rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
|
||||
@@ -9,9 +9,11 @@ OpenBao 凭据生命周期暴露为 Kubernetes API。
|
||||
## 目标
|
||||
|
||||
- 通过 `PostgreSQLInstance` 注册可管理的外部 PostgreSQL 实例。
|
||||
- 通过 namespaced `PostgreSQLTenant` 声明 database、owner、login 和扩展。
|
||||
- 通过 namespaced `PostgreSQLTenant` 声明 database、作为 owner 的 login role 和扩展。
|
||||
- 生成的密码只写入 OpenBao,不写入 CR、Event 或日志。
|
||||
- 使用 `status.conditions` 暴露持续 reconcile 的结果。
|
||||
- 通过 ExternalSecret 将七个原子连接字段投射到 Kubernetes Secret。
|
||||
- 为非 Kubernetes 消费者输出不含认证信息的 OpenBao API URL。
|
||||
- 使用 `status.phase` 展示进度,以 `status.conditions` 暴露可依赖的 reconcile 结果。
|
||||
- 默认使用 `Retain` 删除策略,避免删除 CR 时意外删除数据。
|
||||
- 允许 GitOps、Terraform、`kubectl` 和未来的 Backstage 使用同一套 API。
|
||||
|
||||
@@ -26,18 +28,26 @@ metadata:
|
||||
spec:
|
||||
instanceRef: shared
|
||||
database: netbox
|
||||
ownerRole: netbox_owner
|
||||
loginRole: netbox
|
||||
extensions: [pg_trgm]
|
||||
credential:
|
||||
openBaoPath: kv/k8s/netbox/database
|
||||
secretName: shared-netbox-database-credentials
|
||||
deletionPolicy: Retain
|
||||
```
|
||||
|
||||
更完整的资源见 [`config/samples`](config/samples),初始架构和安全边界见
|
||||
[`docs/architecture.md`](docs/architecture.md)。
|
||||
上例描述获批后的目标 API;`config/samples` 当前仍随旧 API 骨架保留,将在实现 API
|
||||
合同的同一改动中重新生成。系统架构见
|
||||
[`docs/architecture.md`](docs/architecture.md)。API、部署、安全、迁移和运维合同见
|
||||
[`docs/api-reference.md`](docs/api-reference.md)、
|
||||
[`docs/deployment.md`](docs/deployment.md)、[`docs/security.md`](docs/security.md)、
|
||||
[`docs/migration.md`](docs/migration.md) 和 [`docs/operations.md`](docs/operations.md)。
|
||||
|
||||
系统已批准的规范性行为、验收标准和设计决策见
|
||||
[`docs/specification.md`](docs/specification.md)。
|
||||
|
||||
分支、提交、PR 和 CI 约定见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。
|
||||
Docker/devcontainer、PostgreSQL、OpenBao、envtest 与 Kind 的启动顺序见
|
||||
[`docs/development.md`](docs/development.md)。
|
||||
|
||||
## 本地开发
|
||||
|
||||
@@ -60,10 +70,10 @@ make run
|
||||
|
||||
## 路线
|
||||
|
||||
1. 完成 CRD 校验、默认值和状态约定。
|
||||
2. 抽象 PostgreSQL 与 OpenBao client,用 fake 实现测试 reconciliation。
|
||||
3. 实现 database、owner role、login role、grant 和 extension 的幂等收敛。
|
||||
4. 实现带 finalizer 的 `Retain` / `Delete` 删除流程。
|
||||
1. 按已批准规格完成 CRD 校验、默认值和状态约定。
|
||||
2. 实现 PostgreSQL registry、OpenBao 和 ESO adapter 的测试替身及集成测试。
|
||||
3. 实现凭据、login owner、database、grant、extension 和 Secret 投射的幂等收敛。
|
||||
4. 实现并演练带 finalizer 的 `Retain` / `Delete` 删除流程和迁移 runbook。
|
||||
|
||||
代码和 Gitea Actions 工作流预期托管在
|
||||
`git.ddupan.top/panxiao81/postgresql-tenant-operator`。
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
# v1alpha1 API 合同
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | Review |
|
||||
| API group | `database.ddupan.top` |
|
||||
| version | `v1alpha1` |
|
||||
| 最后更新 | 2026-09-10 |
|
||||
|
||||
本文把已批准的系统规格映射为 CRD 字段合同。批准后,API types、生成 CRD、sample 和
|
||||
测试必须与本文一致。当前代码仍是旧骨架,不能作为本页的事实来源。
|
||||
|
||||
## 通用约定
|
||||
|
||||
- PostgreSQL identifier 匹配 `^[a-z][a-z0-9_]{0,62}$`。
|
||||
- 所有引用名称使用 Kubernetes DNS label/name 的相应校验。
|
||||
- Tenant 的 `spec.instanceRef` 与 `metadata.name` 长度合计不超过 241 个字符,确保派生的
|
||||
`<instanceRef>-<metadata.name>-postgresql` 不超过 Kubernetes DNS subdomain 的
|
||||
253 字符限制。
|
||||
- 默认值由 CRD defaulting 提供;需要读取旧值的校验由 CEL 或 webhook 完成。
|
||||
- `status` 由 controller 独占写入,禁止出现密码、Token、管理用户名或完整连接串。
|
||||
- 两个 Kind 都只承诺一个 `Ready` Condition;调用方不得依赖内部协调阶段。
|
||||
|
||||
## PostgreSQLInstance
|
||||
|
||||
cluster-scoped,short name 为 `pginstance`。
|
||||
|
||||
### Spec
|
||||
|
||||
| JSON path | 类型 | 必填/默认 | 合同 |
|
||||
| --- | --- | --- | --- |
|
||||
| `spec.endpoint.host` | string | 必填 | PostgreSQL DNS 名;必须被服务端证书 DNS SAN 覆盖 |
|
||||
| `spec.endpoint.hostaddr` | string | 必填 | 单个 IPv4/IPv6;必须被服务端证书 IP SAN 覆盖 |
|
||||
| `spec.endpoint.port` | int32 | `5432` | 1–65535 |
|
||||
| `spec.endpoint.database` | string | `postgres` | 管理连接 database;合法 PostgreSQL identifier |
|
||||
| `spec.endpoint.sslMode` | enum | `verify-full` | `disable`、`require`、`verify-ca`、`verify-full` |
|
||||
| `spec.adminCredentialRef.path` | string | 必填 | 部署级 KV mount 内的 mount-relative path |
|
||||
| `spec.adminCredentialRef.usernameKey` | string | `username` | OpenBao record 中的键名 |
|
||||
| `spec.adminCredentialRef.passwordKey` | string | `password` | OpenBao record 中的键名 |
|
||||
| `spec.allowedExtensions` | set[string] | 空集合 | 合法 extension 名称的 allowlist |
|
||||
|
||||
`adminCredentialRef.path` 不以 `/` 开头,不含空段、`.`、`..`,也不包含 KV v2 API 的
|
||||
`data`/`metadata` 层。它只定位既有管理凭据;controller 不创建或修改该记录。
|
||||
|
||||
Instance endpoint、管理凭据引用和 allowlist 可以修改。修改后 controller 重新验证;
|
||||
删除 allowlist 项目不会自动从已有 Tenant database 删除 extension。
|
||||
|
||||
### Status
|
||||
|
||||
| JSON path | 类型 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `status.observedGeneration` | int64 | 最近完成有结论协调的 generation |
|
||||
| `status.phase` | enum | `Pending`、`Validating`、`InitializingRegistry`、`Ready`、`Deleting` |
|
||||
| `status.postgresqlVersion` | string | 从 server 回读的版本,不用于客户端解析 |
|
||||
| `status.conditions[]` | `metav1.Condition` | 至少包含唯一的 `Ready` |
|
||||
|
||||
print columns:`Endpoint=.spec.endpoint.host`、`Phase`、`Ready`、`Age`。
|
||||
|
||||
Instance `Ready=True` 要求管理凭据可读、TLS/认证成功、server metadata 可读、registry
|
||||
可访问且权限预检成功。它不代表数据库已经备份或高可用。
|
||||
|
||||
## PostgreSQLTenant
|
||||
|
||||
namespaced,short name 为 `pgtenant`。
|
||||
|
||||
### Spec
|
||||
|
||||
| JSON path | 类型 | 必填/默认 | 合同 |
|
||||
| --- | --- | --- | --- |
|
||||
| `spec.instanceRef` | string | 必填 | cluster-scoped Instance 名称 |
|
||||
| `spec.database` | string | `metadata.name` | 合法 PostgreSQL identifier |
|
||||
| `spec.loginRole` | string | `metadata.name` | database owner 兼应用 login |
|
||||
| `spec.extensions` | set[string] | 空集合 | 必须属于 Instance allowlist;成功创建后只允许追加 |
|
||||
| `spec.credential.secretName` | string | `<instance>-<name>-postgresql` | 合法的同 namespace ESO target Secret 名称 |
|
||||
| `spec.deletionPolicy` | enum | `Retain` | `Retain` 或 `Delete` |
|
||||
|
||||
Tenant 不声明 OpenBao mount 或 path。controller 使用部署级 mount/base path 和
|
||||
`namespace/name` 推导稳定路径,并用 UID metadata 验证所有权。ExternalSecret 固定为
|
||||
`<instanceRef>-<metadata.name>-postgresql`;目标 Secret 可以由用户指定,只需
|
||||
满足 Kubernetes Secret 名称校验,不限制命名内容;省略时使用相同默认名。
|
||||
|
||||
`instanceRef`、`database`、`loginRole` 和 `credential.secretName` 在首次成功创建外部
|
||||
状态后不可变。
|
||||
`extensions` 只允许集合不变或追加;移除返回 `ImmutableField`,不会执行
|
||||
`DROP EXTENSION`。`deletionPolicy` 在对象进入删除前可以修改;删除开始后以 finalizer
|
||||
首次观察到的值为准,避免清理过程中改变授权范围。
|
||||
|
||||
### Status
|
||||
|
||||
| JSON path | 类型 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `status.observedGeneration` | int64 | 最近完成有结论协调的 generation |
|
||||
| `status.phase` | enum | controller 状态机的权威 checkpoint |
|
||||
| `status.databaseOID` | uint32 | 回读的 database OID,仅供诊断 |
|
||||
| `status.credential.secretRef.name` | string | 同 namespace 目标 Secret 名称 |
|
||||
| `status.credential.openBaoURL` | string | 完整 KV v2 API URL,不含认证信息 |
|
||||
| `status.conditions[]` | `metav1.Condition` | 至少包含唯一的 `Ready` |
|
||||
|
||||
Secret reference 不重复 namespace,因为它必定与 Tenant 同 namespace。OpenBao URL 格式
|
||||
为 `<consumer-address>/v1/<mount>/data/<derived-path>`;不得包含 Token、用户名、密码或
|
||||
query credential。
|
||||
|
||||
Tenant phase 枚举为 `Pending`、`Planned`、`CredentialCreated`、`RoleCreated`、
|
||||
`DatabaseCreated`、`ExternalSecretCreated`、`CredentialProjected`、`Ready`、`Deleting`。
|
||||
它不包含 `Failed` 或 `Retained`;失败类型由 Condition Reason 表达。
|
||||
|
||||
print columns:`Instance`、`Database`、`Phase`、`Secret`、`Ready`、`Age`。完整 OpenBao URL 只在
|
||||
YAML/JSON status 中输出。
|
||||
|
||||
两个 Kind 的 `status.phase` 都是 controller 状态机的权威 checkpoint。controller 用它
|
||||
选择下一候选动作,但必须在动作前后核对外部事实,不能仅凭 phase 跳过幂等检查。status
|
||||
丢失或领先于实际状态时必须保守重建/纠正。自动化就绪判断仍应读取 `Ready` Condition;
|
||||
phase 用于进度展示、恢复和排障。
|
||||
|
||||
## Condition
|
||||
|
||||
每种类型最多一个 Condition;更新必须保留正确的 `lastTransitionTime` 语义。
|
||||
|
||||
| Reason | Kind | 可重试性 |
|
||||
| --- | --- | --- |
|
||||
| `Reconciling` | 两者 | 正常进行中 |
|
||||
| `Ready` | 两者 | 已收敛 |
|
||||
| `InvalidSpec` | 两者 | 修改 spec 前不会恢复 |
|
||||
| `ImmutableField` | Tenant | 恢复原值或重新迁移 |
|
||||
| `DependencyUnavailable` | 两者 | 自动重试 |
|
||||
| `AuthenticationFailed` | Instance | 修复凭据/TLS 后重试 |
|
||||
| `InsufficientPrivileges` | Instance | 修复管理 role 后重试 |
|
||||
| `InstanceNotReady` | Tenant | Instance 恢复后重试 |
|
||||
| `Conflict` | Tenant | 人工解除名称/所有权冲突 |
|
||||
| `ProvisioningFailed` | Tenant | 按错误类别退避重试 |
|
||||
| `CredentialProjectionFailed` | Tenant | ESO/Secret 恢复后重试 |
|
||||
|
||||
`Ready=True` 必须使用 Reason `Ready`。处理中为 `Unknown/Reconciling`;已知未满足合同为
|
||||
`False`。Condition message 可以包含资源名和错误类别,禁止包含凭据值或完整 Secret。
|
||||
|
||||
## 删除语义
|
||||
|
||||
- `Retain` 不需要等待外部依赖;删除 CR 后外部记录保留原 UID 并标记 unmanaged。
|
||||
- `Delete` 添加 finalizer,严格按规格的所有权验证和清理顺序执行;失败保持 finalizer。
|
||||
- controller 不为 `PostgreSQLInstance` 级联删除 Tenant 或外部资源;存在引用时 Instance
|
||||
删除应被 finalizer 阻止,直到 Tenant 被删除或管理员使用运维逃生流程。
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
apiVersion: database.ddupan.top/v1alpha1
|
||||
kind: PostgreSQLInstance
|
||||
metadata:
|
||||
name: shared
|
||||
spec:
|
||||
endpoint:
|
||||
host: postgresql.home.arpa
|
||||
hostaddr: 192.0.2.10
|
||||
port: 5432
|
||||
database: postgres
|
||||
sslMode: verify-full
|
||||
adminCredentialRef:
|
||||
path: infrastructure/postgresql/shared/admin
|
||||
allowedExtensions: [pg_trgm]
|
||||
---
|
||||
apiVersion: database.ddupan.top/v1alpha1
|
||||
kind: PostgreSQLTenant
|
||||
metadata:
|
||||
name: netbox
|
||||
namespace: netbox
|
||||
spec:
|
||||
instanceRef: shared
|
||||
database: netbox
|
||||
loginRole: netbox
|
||||
extensions: [pg_trgm]
|
||||
credential:
|
||||
secretName: shared-netbox-database-credentials
|
||||
deletionPolicy: Retain
|
||||
```
|
||||
+76
-41
@@ -1,59 +1,94 @@
|
||||
# 初始架构
|
||||
# 系统架构
|
||||
|
||||
## 职责边界
|
||||
本文是已批准 [`specification.md`](specification.md) 的架构视图。规范定义外部行为,
|
||||
本文解释组件边界;二者冲突时以规范为准。当前仓库仍处于 API 骨架阶段。
|
||||
|
||||
Kubernetes API 保存期望状态和最近一次观察结果;PostgreSQL catalog 是 database、
|
||||
role 和权限的事实来源;OpenBao 是凭据的事实来源。controller 不把明文密码写入
|
||||
Kubernetes API、Event 或日志。
|
||||
## 组件与数据流
|
||||
|
||||
```text
|
||||
Git / kubectl / Terraform / Backstage
|
||||
|
|
||||
v
|
||||
Kubernetes API (CRD)
|
||||
|
|
||||
v
|
||||
postgresql-tenant-operator
|
||||
| |
|
||||
v v
|
||||
external PostgreSQL OpenBao
|
||||
GitOps / kubectl / Terraform / Backstage
|
||||
|
|
||||
v
|
||||
Kubernetes API (CRD)
|
||||
|
|
||||
v
|
||||
postgresql-tenant-operator
|
||||
| | |
|
||||
v v v
|
||||
PostgreSQL DBMS OpenBao KV ExternalSecret
|
||||
catalog+registry |
|
||||
v
|
||||
Kubernetes Secret
|
||||
```
|
||||
|
||||
- Kubernetes `spec` 保存期望状态;`status.phase` 保存 controller 状态机 checkpoint,
|
||||
其他 status 字段保存可重建的观察结果。整个 status 都必须能由外部事实保守恢复。
|
||||
- PostgreSQL catalog 保存 database、role、grant 和 extension 的实际状态。
|
||||
- 两个 CR 的 `status.phase` 是 controller 状态机的权威 checkpoint。
|
||||
- PostgreSQL 管理 database 中的 controller registry 只负责所有权、安装身份和保留标记。
|
||||
- OpenBao KV v2 是应用凭据的事实来源。
|
||||
- External Secrets Operator(ESO)读取 OpenBao,并创建应用使用的 Kubernetes Secret。
|
||||
|
||||
controller 不运行 PostgreSQL/OpenBao,不管理 VM、存储、备份或 OpenBao PKI,也不直接
|
||||
把明文凭据写入 Kubernetes API。
|
||||
|
||||
## 资源模型
|
||||
|
||||
### PostgreSQLInstance
|
||||
`PostgreSQLInstance` 是 cluster-scoped,由平台管理员创建,描述外部 PostgreSQL 的
|
||||
DNS host、IP host address、端口、管理 database、TLS 模式、OpenBao 管理凭据引用和
|
||||
extension allowlist。
|
||||
|
||||
Cluster-scoped,由平台管理员创建。它描述服务器端点、管理凭据的 OpenBao 引用,
|
||||
以及租户可以申请的 extension 白名单。
|
||||
`PostgreSQLTenant` 是 namespaced。一个 Tenant 对应一个 database、一个同时作为 owner
|
||||
的 login role、一组只允许追加的 extension、一个由 controller 推导的 OpenBao KV
|
||||
记录,以及同 namespace 的 ExternalSecret 和目标 Secret。
|
||||
|
||||
### PostgreSQLTenant
|
||||
Tenant namespace 只提供 Kubernetes RBAC 和身份边界。database 与 role 名称在一个
|
||||
Instance 内仍然全局唯一。
|
||||
|
||||
Namespaced,描述一个应用租户,包括 database、无登录 owner role、应用 login
|
||||
role、extensions、凭据路径和删除策略。
|
||||
## Reconcile 与所有权
|
||||
|
||||
第一版规定一个 tenant 拥有一个 database 和一个 login role。跨租户 grant、多个
|
||||
login role 和定时轮换等需求出现后再扩展 API。
|
||||
系统采用最终一致性,不在 Kubernetes、PostgreSQL、OpenBao 和 ESO 之间假装存在分布式
|
||||
事务。每个外部写入前在 CR status 记录阶段,执行幂等操作,回读验证,再推进阶段:
|
||||
|
||||
## Reconcile 原则
|
||||
```text
|
||||
Planned -> CredentialCreated -> RoleCreated -> DatabaseCreated
|
||||
-> ExternalSecretCreated -> CredentialProjected -> Ready
|
||||
```
|
||||
|
||||
- 每轮从 PostgreSQL 和 OpenBao 读取实际状态,不把 `status` 当作事实来源。
|
||||
- 所有操作幂等;任意步骤失败后可以从下一轮继续。
|
||||
- 先验证 extension 白名单,再执行任何变更。
|
||||
- controller 生成密码,调用方只能得到 OpenBao 路径和状态。
|
||||
- `metadata.generation` 只表示 spec 变更,不承载凭据版本语义。
|
||||
controller 每轮同时读取 CR、registry、PostgreSQL catalog、OpenBao metadata 和 ESO
|
||||
投射状态。`status.phase` 是状态机 checkpoint,但不能替代外部回读;丢失或与事实冲突
|
||||
时必须保守重建/纠正。`metadata.generation` 只表示 spec 修改;Condition 的
|
||||
`observedGeneration` 表示该版本是否已经完成一次有结论的协调。
|
||||
|
||||
## 删除
|
||||
所有权使用 Instance UID、Tenant UID 与 namespace/name 验证。database/role COMMENT
|
||||
可以辅助排障,但不能代替 registry。未知资源只报告 `Conflict`,不得修改、接管或
|
||||
删除。Retain 后用相同名称重建 CR 会获得新 UID,因此仍然冲突。
|
||||
|
||||
默认 `deletionPolicy: Retain`。删除 CR 时保留 PostgreSQL database、roles 和
|
||||
OpenBao 数据。`Delete` 模式将在实现备份检查、活动连接处理和可测试的 finalizer
|
||||
状态机后加入实际销毁逻辑。
|
||||
## 创建与删除边界
|
||||
|
||||
## 暂不包含
|
||||
创建时先校验全部输入和冲突,再生成一次密码并写入 OpenBao,随后创建 role、database、
|
||||
extension 和 ExternalSecret。只有 ESO 已投射 Secret 且应用凭据实际登录成功,Tenant
|
||||
才可 Ready。
|
||||
|
||||
- PostgreSQL 实例、VM 或存储的创建。
|
||||
- Web UI 或独立 REST API;Kubernetes API 已提供 get、list、watch 和 RBAC。
|
||||
- PostgreSQL 高可用和备份编排。
|
||||
- 凭据轮换;controller 只负责首次生成长期凭据并写入 OpenBao。只有出现能够
|
||||
重新加载凭据并妥善处理现有连接的实际消费者后,才重新评估轮换协议。
|
||||
- 自动将 OpenBao 数据投射为 Kubernetes Secret;这由 External Secrets Operator
|
||||
负责。
|
||||
`Retain` 是默认删除策略,只移除 Kubernetes 管理关系并保留外部资源。显式 `Delete`
|
||||
使用 finalizer,在重新验证所有权后依次删除 ExternalSecret/Secret、连接、database、
|
||||
role、OpenBao KV 历史和 registry。详细恢复与逃生步骤见
|
||||
[`operations.md`](operations.md)。
|
||||
|
||||
## 网络与 TLS
|
||||
|
||||
Instance 同时公布 DNS `host` 和 IP `hostaddr`。PostgreSQL server 证书必须包含对应的
|
||||
DNS SAN 和 IP SAN,消费者自行选择可达目标,并可使用 `verify-full` 验证。OpenBao PKI
|
||||
持有 CA 私钥并签发服务端证书;controller 只挂载公开 CA bundle。
|
||||
|
||||
OpenBao 的 controller 内部地址和外部消费者地址可以不同。Tenant status 同时提供目标
|
||||
Kubernetes Secret reference 和不含认证信息的 OpenBao KV v2 API URL。
|
||||
|
||||
## 文档入口
|
||||
|
||||
- API 字段与 Condition:[`api-reference.md`](api-reference.md)
|
||||
- 安装、依赖和配置:[`deployment.md`](deployment.md)
|
||||
- 本地与 CI 测试:[`development.md`](development.md)
|
||||
- 安全模型与最小权限:[`security.md`](security.md)
|
||||
- 现有数据库迁移:[`migration.md`](migration.md)
|
||||
- 日常排障和删除逃生:[`operations.md`](operations.md)
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# 部署与配置
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | Review |
|
||||
| 环境 | homelab Kubernetes + 外部 PostgreSQL/OpenBao |
|
||||
| 最后更新 | 2026-09-10 |
|
||||
|
||||
本文定义 v1alpha1 的运行依赖、启动顺序和部署级配置。当前 manifests 尚未实现这些
|
||||
配置,示例是后续实现合同,不可直接用于现有脚手架。
|
||||
|
||||
## 依赖与顺序
|
||||
|
||||
1. 准备 PostgreSQL VM、持久盘、备份和网络入口。
|
||||
2. 用 OpenBao PKI 签发 PostgreSQL server 证书,包含 Instance `host` 的 DNS SAN 与
|
||||
`hostaddr` 的 IP SAN;配置 PostgreSQL 强制 TLS。
|
||||
3. 创建 PostgreSQL controller 管理 role 和管理 database 连接权限。
|
||||
4. 在 OpenBao KV v2 写入管理 role 凭据。
|
||||
5. 配置 OpenBao Kubernetes auth、controller policy 和面向 ESO 的读取 policy。
|
||||
6. 在 Kubernetes 安装 ESO,创建可读取租户路径的 `ClusterSecretStore`。
|
||||
7. 创建公开 CA bundle ConfigMap,并挂载到 controller 和需要直接验证数据库的应用。
|
||||
8. 部署 controller,再创建 Instance;等待 Ready 后才创建 Tenant。
|
||||
|
||||
任何一步都不得把真实密码、Token、kubeconfig 或 CA 私钥提交进 Git。
|
||||
|
||||
## Controller 配置合同
|
||||
|
||||
具体 CLI flag/env 名称将在实现时按下表确定;语义和作用域已经固定:
|
||||
|
||||
| 配置 | 必填/默认 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| OpenBao internal API address | 必填 | controller 可访问的 HTTPS 地址 |
|
||||
| OpenBao consumer API address | 默认同 internal | 写入 Tenant status,必须能被预期外部消费者解析 |
|
||||
| OpenBao auth mount | 默认 `kubernetes` | Kubernetes auth mount 名称 |
|
||||
| OpenBao auth role | 必填 | controller ServiceAccount 对应 role |
|
||||
| OpenBao KV mount | 默认 `kv` | KV v2 mount;开发可显式用 `secret` |
|
||||
| `--openbao-tenant-base-path` | 默认 `postgresql-tenants` | controller 专属 mount-relative 前缀 |
|
||||
| ESO ClusterSecretStore name | 必填 | controller 创建的 ExternalSecret 固定引用 |
|
||||
| CA bundle path | 必填(TLS) | 只读 PEM trust bundle,不含私钥 |
|
||||
| reconcile timeout | 有安全默认 | 单轮外部操作的总期限 |
|
||||
|
||||
Tenant 路径固定推导为 `<base-path>/<namespace>/<metadata.name>`。namespace/name 都已通过
|
||||
Kubernetes 名称校验,因此不再允许 CR 提供任意路径。KV v2 API URL 使用 consumer
|
||||
address 拼为 `<address>/v1/<mount>/data/<base-path>/<namespace>/<metadata.name>`。
|
||||
|
||||
base path 必须是合法 mount-relative path,不以 `/` 开头且不包含空段、`.`、`..`、
|
||||
`data`/`metadata` API 层。ExternalSecret 固定命名为
|
||||
`<instanceRef>-<metadata.name>-postgresql`;目标 Secret 可由 Tenant 指定,但名称必须
|
||||
满足 Kubernetes Secret 名称校验,不限制命名内容,默认与 ExternalSecret 同名。
|
||||
|
||||
配置变化不得隐式迁移既有凭据。修改 KV mount/base path 或 consumer address 前必须
|
||||
停止 controller、评估现有 Tenant,并走明确迁移;实现应把 mount/base path 视为安装
|
||||
身份的一部分并在 registry 留存,以便检测错误配置。
|
||||
|
||||
## PostgreSQL 管理 role
|
||||
|
||||
生产部署禁止使用 superuser。管理 role 至少需要:
|
||||
|
||||
- 连接管理 database、读取必要 catalog;
|
||||
- 创建/修改受管 login role;
|
||||
- 创建 database 并指定 owner;
|
||||
- 撤销 `PUBLIC` CONNECT、授予租户 role CONNECT;
|
||||
- 连接租户 database 并创建 allowlist extension;
|
||||
- 创建和维护 controller 专属 registry schema/table;
|
||||
- `Delete` 时禁止连接、终止目标 database session、删除已验证归属的 database/role。
|
||||
|
||||
部分 PostgreSQL 操作天然要求较高权限,尤其终止其他 session 和安装某些 extension。
|
||||
应优先使用 PostgreSQL 预定义角色、受控 SECURITY DEFINER 管理函数或限定数据库的
|
||||
授权;任何不得不使用 superuser 的 extension 都必须按实例单独记录,不得扩大默认
|
||||
controller 权限。最终可执行 SQL grant 将随 PostgreSQL adapter 集成测试固化。
|
||||
|
||||
## OpenBao 与 ESO
|
||||
|
||||
controller policy 分成两个范围:
|
||||
|
||||
- 只读 Instance 管理凭据路径;
|
||||
- 在固定 tenant base path 下 create/read/update/delete KV v2 data 和 metadata,Delete
|
||||
必须能永久删除全部版本及 metadata。
|
||||
|
||||
ESO 使用独立身份,只需读取 tenant base path;它不应读取 PostgreSQL 管理凭据。
|
||||
`ClusterSecretStore` 由平台管理员创建,controller 只引用,不创建或修改 Store。
|
||||
controller 创建的 ExternalSecret 与 Tenant 同 namespace,并设置 ownerReference;目标
|
||||
Secret 包含固定七键:`username`、`password`、`database`、`host`、`hostaddr`、`port`、
|
||||
`sslmode`。
|
||||
|
||||
## Kubernetes RBAC
|
||||
|
||||
- controller 可读/写 Instance、Tenant 的 status/finalizer 和 Event。
|
||||
- controller 可在 Tenant namespace 创建、读取、更新、删除 ExternalSecret,并只读检查
|
||||
对应 Secret 是否完成投射。
|
||||
- namespace 用户可以管理本 namespace Tenant,但不能管理 Instance、Store、controller
|
||||
配置或其他 namespace 的 ExternalSecret。
|
||||
- controller 无需读取目标 Secret 的 data;验证登录使用从 OpenBao 读取的应用凭据,
|
||||
对 Secret 只检查存在性和 ESO 状态。
|
||||
|
||||
## 升级与回滚
|
||||
|
||||
v1alpha1 尚不承诺跨版本转换。升级前备份 CR、PostgreSQL registry 和 OpenBao metadata,
|
||||
先在隔离 Kind 环境运行 E2E。禁止在同一组 CR 上同时运行两个 controller 版本。若新版本
|
||||
在执行任何破坏性迁移前失败,可回滚镜像;涉及 API/storage 或 registry schema 迁移时,
|
||||
必须先写独立升级规格和回滚步骤。
|
||||
|
||||
## 上线验证
|
||||
|
||||
```text
|
||||
PostgreSQL TLS 与备份验证
|
||||
-> OpenBao auth/policy 验证
|
||||
-> ClusterSecretStore Ready
|
||||
-> controller Ready/leader elected
|
||||
-> Instance Ready
|
||||
-> 测试 Tenant Ready
|
||||
-> DNS host 与 IP hostaddr 分别登录
|
||||
-> 删除测试 Tenant 并验证所选策略
|
||||
```
|
||||
|
||||
生产 homelab 上线前还必须完成 [`security.md`](security.md) 的权限检查和
|
||||
[`operations.md`](operations.md) 的备份/逃生检查。
|
||||
@@ -0,0 +1,187 @@
|
||||
# 开发与测试环境
|
||||
|
||||
本项目同时依赖 Kubernetes API、PostgreSQL、OpenBao 和 ESO。日常开发不连接 homelab
|
||||
中的真实服务:Kubernetes 使用 envtest 或一次性 Kind,另外两个依赖使用一次性
|
||||
容器。这样既避免污染真实数据,也能把启动顺序固化为命令。
|
||||
|
||||
## 是否需要开发 VM
|
||||
|
||||
默认不需要。仓库的 devcontainer 使用独立 Docker-in-Docker daemon,Go 工具链、
|
||||
Kind 节点和依赖容器都与宿主机环境隔离。宿主机只需要能够运行支持 privileged
|
||||
container 的 Docker/Dev Container 环境。
|
||||
|
||||
只有以下情况才建议增加一台可随时重建的开发 VM:
|
||||
|
||||
- 宿主机不允许 privileged devcontainer;
|
||||
- 无法安全使用 Docker socket 或 Docker-in-Docker;
|
||||
- 本机地址段与 Kind/Docker 网络持续冲突;
|
||||
- 需要长期运行、接近 homelab 网络和 TLS 配置的验收环境。
|
||||
|
||||
即使使用 VM,也应在 VM 内继续执行本文相同的容器化流程;不要把 VM 配置成第二套
|
||||
手工维护的开发环境。
|
||||
|
||||
## 环境分层
|
||||
|
||||
| 层次 | Kubernetes | PostgreSQL / OpenBao | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| 单元测试 | fake client | fake client | SQL 计划、状态转换和错误分类 |
|
||||
| controller 集成测试 | envtest | fake adapter | CRD、watch、status、finalizer、ExternalSecret 对象 |
|
||||
| adapter 集成测试 | 不需要 | Docker Compose | 真实协议、权限和幂等行为 |
|
||||
| E2E | 一次性 Kind + ESO | Kind 内测试实例 | 凭据投射、TLS、完整网络和删除路径 |
|
||||
|
||||
envtest 只启动 API server 和 etcd,没有 kubelet、scheduler、ESO 或 controller-manager,
|
||||
因此不能用它验证 Deployment、Pod 调度或 Service 网络。此类行为必须留给 Kind
|
||||
E2E。
|
||||
|
||||
## 首次准备
|
||||
|
||||
推荐用支持 Dev Containers 的编辑器打开仓库。devcontainer 会提供 Go、Docker、
|
||||
Kubebuilder、Kind 和 kubectl。脚本固定 Kubebuilder 4.15.0、Kind 0.33.0 和
|
||||
kubectl 1.36.0,与当前脚手架和 Kubernetes Go module 对齐。容器启动后先确认:
|
||||
|
||||
```sh
|
||||
go version
|
||||
docker info
|
||||
kubebuilder version
|
||||
kind version
|
||||
kubectl version --client
|
||||
```
|
||||
|
||||
不要在仓库中保存真实 OpenBao Token、数据库密码或 kubeconfig。Compose 中的
|
||||
`postgres-dev-only` 和 `dev-only-root-token` 是仅绑定回环地址、随容器销毁的公开
|
||||
测试值,不得复制到其他环境。
|
||||
|
||||
## 日常开发的正确顺序
|
||||
|
||||
### 1. 生成并验证纯 Go/Kubernetes 部分
|
||||
|
||||
```sh
|
||||
make manifests generate
|
||||
make test
|
||||
make lint
|
||||
```
|
||||
|
||||
`make test` 会下载与 `go.mod` 中 Kubernetes minor 版本匹配的 envtest 二进制,
|
||||
启动临时 API server/etcd,测试结束后自动关闭。
|
||||
|
||||
规格实现后,快速测试必须覆盖默认值/校验、Condition `observedGeneration`、两个 CR 的
|
||||
status 状态机、不可变字段、extension 只追加、registry 所有权和外部错误分类。envtest 只断言 controller 创建了正确
|
||||
的 ExternalSecret;它不能证明 ESO 已生成 Secret。
|
||||
|
||||
### 2. 启动 PostgreSQL/OpenBao adapter 依赖
|
||||
|
||||
只有开发 PostgreSQL/OpenBao adapter 或完整 reconcile 时才需要:
|
||||
|
||||
```sh
|
||||
make dev-up
|
||||
make dev-smoke
|
||||
```
|
||||
|
||||
启动顺序由 Compose healthcheck 保证:
|
||||
|
||||
1. 创建独立 Compose 网络;
|
||||
2. 启动 PostgreSQL 和 OpenBao;
|
||||
3. 等待 PostgreSQL `pg_isready` 成功;
|
||||
4. 等待 OpenBao `bao status` 成功;
|
||||
5. smoke test 执行 `SELECT 1`;
|
||||
6. smoke test 在 OpenBao dev server 默认的 `secret/` KV v2 mount 写入并读回测试管理
|
||||
凭据。
|
||||
|
||||
本机进程使用以下端点:
|
||||
|
||||
```text
|
||||
PostgreSQL: postgresql://postgres:[email protected]:15432/postgres
|
||||
OpenBao: http://127.0.0.1:18200
|
||||
Token: dev-only-root-token
|
||||
```
|
||||
|
||||
若端口冲突,可以只对当前命令覆盖:
|
||||
|
||||
```sh
|
||||
POSTGRES_DEV_PORT=25432 OPENBAO_DEV_PORT=28200 make dev-up
|
||||
```
|
||||
|
||||
后续执行 `dev-smoke` 和 controller 时必须使用相同端口变量。
|
||||
|
||||
Compose 使用明文 PostgreSQL/OpenBao dev 模式,不覆盖生产 TLS 合同。DNS SAN、IP SAN、
|
||||
Kubernetes auth、最小 policy 和 ESO 必须在 Kind E2E fixture 中验证。
|
||||
|
||||
### 3. 运行针对临时依赖的测试或 controller
|
||||
|
||||
adapter 集成测试加入后,统一通过独立 Make target 执行,不默认塞进快速单元测试。
|
||||
本机运行 controller 时,先确认当前 kubeconfig 指向专用 Kind,而不是真实 homelab:
|
||||
|
||||
```sh
|
||||
kubectl config current-context
|
||||
make setup-test-e2e
|
||||
kubectl config current-context
|
||||
make install
|
||||
make run
|
||||
```
|
||||
|
||||
此时 controller 运行在开发容器内,可以直接访问上面的回环端口。若要验证 Tenant
|
||||
Ready,专用 Kind 还必须安装 ESO、创建测试 ClusterSecretStore,并让 Kind workload
|
||||
能够访问测试 OpenBao。不要把包含
|
||||
`127.0.0.1` 端点的样例部署到 Kind 内;Pod 中的回环地址只指向 Pod 自身。
|
||||
|
||||
### 4. 清理
|
||||
|
||||
```sh
|
||||
make dev-down
|
||||
make cleanup-test-e2e
|
||||
```
|
||||
|
||||
`dev-down` 会删除 Compose volume;所有数据库和 OpenBao dev 数据都应视为一次性。
|
||||
|
||||
## E2E 顺序
|
||||
|
||||
CI 的 E2E 与本机 `make run` 不同:controller 会作为 Pod 运行在 Kind 中。因此完整
|
||||
E2E fixture 必须把测试 PostgreSQL、OpenBao 和 ESO 部署进 Kind,并等待依赖 Ready 后
|
||||
再创建 `PostgreSQLInstance` 和 `PostgreSQLTenant`:
|
||||
|
||||
```text
|
||||
创建 Kind
|
||||
-> 安装 CRD
|
||||
-> 部署 PostgreSQL/OpenBao fixture,签发含 DNS/IP SAN 的测试证书
|
||||
-> 安装 ESO,配置 OpenBao auth/policy 和 ClusterSecretStore
|
||||
-> 等待依赖 Ready 并写入测试管理凭据
|
||||
-> 构建并加载 controller image
|
||||
-> 部署 controller
|
||||
-> 创建 Instance
|
||||
-> 等待 Instance Ready
|
||||
-> 创建 Tenant
|
||||
-> 等待 Tenant Ready
|
||||
-> 验证 registry、PostgreSQL catalog、OpenBao KV、ExternalSecret 和 Secret
|
||||
-> 分别使用 DNS host 与 IP hostaddr 登录
|
||||
-> 删除 Tenant 并分别验证 Retain 与 Delete(含故障点重试)
|
||||
-> 删除 Kind
|
||||
```
|
||||
|
||||
当前 controller 尚未实现 PostgreSQL/OpenBao adapter,脚手架 E2E 只能验证 CRD、
|
||||
manager Deployment 和 metrics endpoint。上述真实依赖 fixture 应与第一个完整
|
||||
reconcile 纵向切片一起实现,不能在文档中声称已经通过。
|
||||
|
||||
## 测试数据与泄漏检查
|
||||
|
||||
- 只使用显眼的固定 canary 测试密码,测试后扫描日志、Event、Condition、metrics 和
|
||||
CR dump,出现 canary 即失败。
|
||||
- 每个最终一致性阶段都注入一次中断,重启后验证密码不变且阶段只向前推进。
|
||||
- 清空、落后或伪造超前的 `status.phase` 后验证它能从外部事实保守恢复/纠正,且不会
|
||||
跳过任何回读。
|
||||
- 为未知同名 database、role、Bao record 和伪造 COMMENT 分别构造 Conflict。
|
||||
- Delete 在每个外部删除步骤失败后重试,确认未误删非当前 UID 资源。
|
||||
- 迁移测试按 [`migration.md`](migration.md) 完整执行,不以单纯 `pg_restore` 成功代替
|
||||
应用读写和回滚验证。
|
||||
|
||||
## 故障排查
|
||||
|
||||
查看依赖状态与日志:
|
||||
|
||||
```sh
|
||||
docker compose -f hack/dev/compose.yaml ps
|
||||
docker compose -f hack/dev/compose.yaml logs postgres openbao
|
||||
```
|
||||
|
||||
如果 envtest 报端口监听失败,通常是当前执行环境禁止监听回环端口,而非 controller
|
||||
失败;在 devcontainer 或允许本机监听的 runner 中执行。若 Kind 无法创建,先运行
|
||||
`docker info`,确认当前用户可以访问 devcontainer 内的 Docker daemon。
|
||||
@@ -0,0 +1,125 @@
|
||||
# 现有数据库迁移 Runbook
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | Review;尚未在临时 PostgreSQL 演练 |
|
||||
| 适用范围 | 任意既有数据库迁移为新建 v1alpha1 Tenant |
|
||||
| 最后更新 | 2026-09-10 |
|
||||
|
||||
v1alpha1 不接管现有 database、role 或 OpenBao record。本流程通过逻辑 dump/restore 把
|
||||
数据迁移到 controller 创建的新资源,保留旧资源作为限时回滚点。
|
||||
|
||||
以下命令是顺序模板,不可原样复制到真实环境。先把尖括号变量解析成明确值,确认当前
|
||||
连接目标,再逐条执行。dump 可能包含敏感业务数据,必须放在加密临时存储且不得提交 Git。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 已验证 PostgreSQL/OpenBao 备份和恢复;记录恢复点。
|
||||
- Instance 已 Ready,目标 namespace 存在,ESO ClusterSecretStore Ready。
|
||||
- 最终 database/login role 当前由旧应用占用,但改名后的保留名称、新推导的 Bao path
|
||||
均不存在。
|
||||
- 已记录旧 database owner、grants、extensions、locale/encoding、连接配置和验证清单。
|
||||
- 已确认应用可停止写入,并确定回滚窗口和负责人。
|
||||
- 已确认旧 login role 不被其他 database/应用共享,且角色改名不会破坏未纳入本次维护
|
||||
的依赖。
|
||||
|
||||
## 迁移顺序
|
||||
|
||||
### 1. 盘点与预演
|
||||
|
||||
```sh
|
||||
pg_dump --schema-only --no-owner --no-privileges \
|
||||
--dbname='<old-admin-connection>' > schema-preview.sql
|
||||
```
|
||||
|
||||
检查不受 v1alpha1 管理的对象:额外 roles、跨库依赖、FDW、large objects、订阅、显式
|
||||
tablespace、owner/grant 和不在 allowlist 的 extension。无法映射为单 database + 单 login
|
||||
owner 的环境必须先人工简化,不能让 controller 猜测。
|
||||
|
||||
### 2. 创建一致性 dump
|
||||
|
||||
停止应用写入并确认活跃写事务结束,然后创建最终 custom-format dump:
|
||||
|
||||
```sh
|
||||
pg_dump --format=custom --no-owner --no-privileges \
|
||||
--file='<secure-temp>/tenant.dump' \
|
||||
--dbname='<old-admin-connection>'
|
||||
pg_restore --list '<secure-temp>/tenant.dump'
|
||||
```
|
||||
|
||||
不要删除旧 database/role。记录停写时间、dump checksum 和 PostgreSQL 版本。
|
||||
|
||||
### 3. 释放最终名称
|
||||
|
||||
保持应用停写,终止旧 database 的应用连接。连接其他管理 database,以管理员身份把旧
|
||||
database 和旧 login role 改为明确的保留名称:
|
||||
|
||||
```sql
|
||||
ALTER DATABASE <old_database> RENAME TO <old_database>_retained_<timestamp>;
|
||||
ALTER ROLE <old_login_role> RENAME TO <old_login_role>_retained_<timestamp>;
|
||||
```
|
||||
|
||||
identifier 必须由管理员工具安全引用,不能把未经校验的值直接拼入 SQL。PostgreSQL 在
|
||||
角色改名时会清除以旧角色名加盐的 MD5 密码;使用 MD5 的旧环境必须在维护前准备安全的
|
||||
密码重设/回滚方法。SCRAM verifier 不受角色名改动影响,但仍须实际验证回滚登录。
|
||||
|
||||
### 4. 创建受管空目标
|
||||
|
||||
应用 `PostgreSQLTenant`,使用未被占用的 database/loginRole,等待 Ready。确认:
|
||||
|
||||
- registry 记录 UID 正确;
|
||||
- OpenBao metadata 属于该 Tenant;
|
||||
- ExternalSecret Ready 且目标 Secret 已投射;
|
||||
- 新凭据可以通过 DNS host 和 IP hostaddr 分别登录空 database。
|
||||
|
||||
### 5. Restore
|
||||
|
||||
从 OpenBao 或目标 Secret 安全取得新应用凭据,不要把密码放进 shell history。以新 login
|
||||
owner 连接目标 database:
|
||||
|
||||
```sh
|
||||
pg_restore --exit-on-error --no-owner --no-privileges \
|
||||
--dbname='<new-application-connection>' \
|
||||
'<secure-temp>/tenant.dump'
|
||||
```
|
||||
|
||||
extension 应由 Tenant spec 创建。若 dump 仍包含 extension 定义,预演必须确认 restore
|
||||
行为幂等;不在 allowlist 的 extension 必须在迁移前解决。
|
||||
|
||||
### 6. 验证并切换
|
||||
|
||||
- 对比关键 schema、表数、行数/校验和、sequence、function 和 migration version。
|
||||
- 用新 login 验证读写、migration 和应用健康检查。
|
||||
- 将应用配置切换到新 Secret 或 OpenBao URL,保持旧数据库只读/停写。
|
||||
- 观察一个约定窗口,确认错误率、连接数和关键业务功能。
|
||||
|
||||
### 7. 收尾
|
||||
|
||||
回滚窗口结束后,按独立变更删除旧 database/role/旧凭据;它们不属于 controller,禁止
|
||||
通过 Tenant `Delete` 清理。安全删除 dump 和临时凭据材料,并记录验证结果。
|
||||
|
||||
## 回滚
|
||||
|
||||
在新目标出现问题且旧资源仍保留时:
|
||||
|
||||
1. 立即停止新目标写入。
|
||||
2. 评估切换后是否产生新数据;若有,先决定反向迁移或接受丢弃,不能盲目切回。
|
||||
3. 将应用连接切回 retained database/role;若必须恢复原名称,先确保新受管目标已用
|
||||
`Delete` 完整清理或改用不同名称,再安全地反向执行 rename。
|
||||
4. 恢复旧凭据(MD5 环境可能需要重设),验证旧服务。
|
||||
5. 保留失败 Tenant 供排障;选择 Retain 或 Delete 前明确其外部资源后果。
|
||||
|
||||
若已经删除旧资源,则只能使用已验证备份恢复,不再属于本 runbook 的快速回滚。
|
||||
|
||||
## 演练验收
|
||||
|
||||
发布首个可用版本前,必须在临时 PostgreSQL/OpenBao/Kind 环境执行本文并记录:
|
||||
|
||||
- 使用的 PostgreSQL major version 和命令版本;
|
||||
- dump/restore 返回码和对象差异;
|
||||
- DNS/IP TLS 登录结果;
|
||||
- ESO 投射与应用启动结果;
|
||||
- 回滚演练结果;
|
||||
- 哪些命令或前置检查需要修订。
|
||||
|
||||
完成演练前,本文不得标记为 `Verified`。
|
||||
@@ -0,0 +1,76 @@
|
||||
# 运维与故障处理
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | Review;命令待实现后演练 |
|
||||
| 最后更新 | 2026-09-10 |
|
||||
|
||||
## 日常检查
|
||||
|
||||
先看 API 合同,而不是从日志猜状态:
|
||||
|
||||
```sh
|
||||
kubectl get postgresqlinstances
|
||||
kubectl get postgresqltenants -A
|
||||
kubectl get postgresqltenant -n <namespace> <name> -o yaml
|
||||
kubectl describe postgresqltenant -n <namespace> <name>
|
||||
```
|
||||
|
||||
随后检查 controller 日志、ExternalSecret/Secret、OpenBao metadata、registry 和
|
||||
PostgreSQL catalog。排障时不得把 Secret data 或带 Token 的请求粘贴到 issue/日志。
|
||||
`status.phase` 是 controller 状态机 checkpoint,也用于定位当前步骤;`Ready`
|
||||
Condition/Reason 用于判断对外结果。phase 不能替代外部事实,清空或不一致时应由
|
||||
controller 自动重建/纠正。
|
||||
|
||||
## 常见 Reason
|
||||
|
||||
| Reason | 首要检查 |
|
||||
| --- | --- |
|
||||
| `InvalidSpec` / `ImmutableField` | API 字段、identifier、不可变/只追加约束 |
|
||||
| `DependencyUnavailable` | 网络、DNS、服务状态和超时 |
|
||||
| `AuthenticationFailed` | 管理凭据、CA、DNS/IP SAN、OpenBao auth |
|
||||
| `InsufficientPrivileges` | PostgreSQL grants、OpenBao policy、Kubernetes RBAC |
|
||||
| `InstanceNotReady` | 先恢复所引用 Instance |
|
||||
| `Conflict` | registry UID、同名 DB/role、OpenBao metadata;禁止直接覆盖 |
|
||||
| `CredentialProjectionFailed` | ClusterSecretStore、ExternalSecret Condition、目标 Secret |
|
||||
| `ProvisioningFailed` | `status.phase` 及对应外部资源的回读结果 |
|
||||
|
||||
修复依赖后让正常 reconcile 自动重试。不要通过删除/重建 CR 规避 Conflict;新 UID 只会
|
||||
使已有保留资源继续冲突。
|
||||
|
||||
## Retain 后的资源
|
||||
|
||||
Retain 删除完成后,database、role、OpenBao record 和 registry 所有权记录仍存在但标记
|
||||
unmanaged。v1alpha1 不支持重新关联。需要恢复管理时,使用 [`migration.md`](migration.md)
|
||||
把数据迁移到一个全新受管名称;不要手工把 registry UID 改成新 CR UID。
|
||||
|
||||
## Delete 卡住
|
||||
|
||||
1. 暂停应用写入并记录 Tenant UID、Instance UID、database、role 和 Bao path。
|
||||
2. 从 registry 和 OpenBao metadata 独立确认所有权。
|
||||
3. 检查删除阶段,修复 PostgreSQL/OpenBao/ESO 依赖,让 controller 继续。
|
||||
4. 若依赖永久丢失,列出每个可能残留的 database、role、KV metadata 和 Secret。
|
||||
5. 只有确认接受这些残留后,才人工移除 finalizer。
|
||||
|
||||
最终 finalizer 名称由 API 实现固定后补入命令。人工移除 finalizer不会执行剩余清理,
|
||||
也不会把外部资源变成可由新 CR 接管的资源。
|
||||
|
||||
## 备份与恢复
|
||||
|
||||
- PostgreSQL VM/磁盘备份必须与数据库一致性策略配套;仅复制在线磁盘不自动等于有效
|
||||
PostgreSQL 备份。
|
||||
- PostgreSQL 备份必须包含管理 database 中的 controller registry。
|
||||
- OpenBao 使用独立的受支持备份/快照流程,且恢复点应与 PostgreSQL 尽量接近。
|
||||
- Kubernetes 侧备份 CR、controller 配置、ClusterSecretStore 和公开 CA bundle,不备份
|
||||
明文 Secret 作为凭据事实来源。
|
||||
- 定期在隔离环境执行恢复演练,验证 registry、KV metadata、应用登录及 Retain/Delete。
|
||||
|
||||
恢复后先停止 controller,核对 PostgreSQL/OpenBao 时间点与 UID 映射,再启动单副本
|
||||
controller 观察;出现一侧存在、一侧缺失时不得手工生成新密码或改 registry,应先按
|
||||
Conflict 处理并决定恢复哪一侧。
|
||||
|
||||
## 升级与紧急停止
|
||||
|
||||
有疑似越权删除或凭据泄漏时,先把 controller Deployment scale 到 0,保留 CR、registry
|
||||
和日志证据,再撤销 OpenBao token/role 并限制 PostgreSQL 管理 role。恢复前在隔离环境
|
||||
复现并确认不会扩大破坏。一般依赖故障无需 scale down,最终一致性会自动重试。
|
||||
@@ -0,0 +1,68 @@
|
||||
# 安全模型
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | Review |
|
||||
| 最后更新 | 2026-09-10 |
|
||||
|
||||
## 保护目标
|
||||
|
||||
- 应用密码只存在于 OpenBao、ESO 投射的目标 Secret 和需要使用它的进程内存中。
|
||||
- controller 只能修改其 registry 能证明归属当前 Tenant UID 的资源。
|
||||
- namespace 租户不能越权管理 Instance、其他 namespace 或 controller 配置。
|
||||
- PostgreSQL 和 OpenBao 的网络身份使用受信 CA 验证,不因 DNS 不可用而降级 TLS。
|
||||
|
||||
## 信任边界
|
||||
|
||||
Kubernetes 管理员、OpenBao 管理员和 PostgreSQL 管理员是平台信任主体。能读取 Tenant
|
||||
目标 Secret 或对应 OpenBao path 的主体等同于持有数据库账号。database owner 可以
|
||||
改变自己 database 内的对象,因此 COMMENT 不能作为 controller 所有权依据。
|
||||
|
||||
VM/磁盘备份会包含 PostgreSQL registry 和租户数据,但不应包含 OpenBao 中的密码;完整
|
||||
灾难恢复必须同时保护 PostgreSQL 与 OpenBao,并控制两份备份的访问权限。
|
||||
|
||||
## 凭据处理
|
||||
|
||||
- controller 使用 Kubernetes auth 获取短期 OpenBao token,不配置长期静态 token。
|
||||
- 管理凭据只从 Instance 引用读取,不复制到 CR/status/Event/metric/trace。
|
||||
- 租户密码使用密码学安全随机源生成一次;中断恢复必须复用 OpenBao 现值。
|
||||
- controller 创建 ExternalSecret,不直接创建含 data/stringData 的 Secret。
|
||||
- 日志字段允许 namespace/name、UID、generation、阶段和错误类别;禁止记录请求/响应体、
|
||||
DSN、Authorization header、密码或完整 OpenBao URL path 作为 metric label。
|
||||
- panic、错误包装和测试失败输出必须经过凭据泄漏测试。
|
||||
|
||||
## TLS
|
||||
|
||||
- homelab 默认 `verify-full`,`disable` 只允许显式开发配置。
|
||||
- server 证书同时覆盖 DNS `host` 和 IP `hostaddr`;消费者自行选择连接目标。
|
||||
- OpenBao PKI 保管 CA 私钥并负责签发/续期。controller Deployment 只挂载公开 CA
|
||||
bundle,挂载只读且使用最小文件权限。
|
||||
- 证书轮换必须先发布同时信任新旧 CA 的 bundle,再轮换服务端证书,最后移除旧 CA。
|
||||
|
||||
## 最小权限
|
||||
|
||||
OpenBao controller identity 只能读取管理凭据范围并管理固定 tenant base path;ESO
|
||||
identity 只能读取 tenant base path。两者不得共用可访问管理凭据的 policy。
|
||||
|
||||
PostgreSQL 管理 role 不应是 superuser。若平台选择 SECURITY DEFINER 函数承载创建或
|
||||
删除操作,函数必须固定 `search_path`、严格校验 identifier、拒绝任意 SQL,并仅向
|
||||
controller role 授予 EXECUTE。controller 不调用 shell 或 `psql` 拼接用户输入。
|
||||
|
||||
Kubernetes RBAC 应把 cluster-scoped Instance 管理限制给平台管理员。Tenant editor
|
||||
不自动获得 Secret read;是否读取目标 Secret 由 namespace 内独立 RBAC 决定。
|
||||
|
||||
## 删除保护
|
||||
|
||||
Delete 是明确的数据销毁授权,但仍必须在每一步校验 Instance UID、Tenant UID、名称和
|
||||
OpenBao metadata。禁止对未知对象使用 `CASCADE`。删除 finalizer 卡住时只能按
|
||||
[`operations.md`](operations.md) 核实外部状态后人工移除;该操作可能遗留资源。
|
||||
|
||||
## 发布前安全验收
|
||||
|
||||
- 使用错误 CA、错误 DNS 名和错误 IP 时连接失败;正确 DNS/IP SAN 均成功。
|
||||
- namespace 用户不能修改 Instance 或跨 namespace Tenant/ExternalSecret。
|
||||
- controller/ESO 的 OpenBao policy 互相隔离,越权请求被拒绝。
|
||||
- 应用 login 不能创建 role/database,也不能连接其他租户 database。
|
||||
- 日志、Event、Condition、metrics、CR 导出和测试 artifact 不含 canary password/token。
|
||||
- 伪造 COMMENT、同名 database/role 或错误 UID metadata 均不能绕过 Conflict。
|
||||
- Delete 只销毁 registry 可证明归属当前 Tenant 的资源。
|
||||
@@ -0,0 +1,514 @@
|
||||
# PostgreSQL Tenant Operator 系统规格说明书
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | Approved |
|
||||
| 目标 API | `database.ddupan.top/v1alpha1` |
|
||||
| 最后更新 | 2026-09-10 |
|
||||
| 批准日期 | 2026-09-10 |
|
||||
| 规范范围 | 首次注册外部 PostgreSQL 实例并创建一个应用租户 |
|
||||
|
||||
本文档定义系统对用户和外部依赖呈现的行为,是 API、测试和实现共同遵守的合同。
|
||||
实现若需要改变本文合同,必须先修改规格并重新获得批准。
|
||||
|
||||
文中的“必须”“禁止”“应当”“可以”分别对应强制要求、强制限制、推荐行为和可选
|
||||
行为。
|
||||
|
||||
## 1. 背景
|
||||
|
||||
homelab 中的大部分应用共享一个运行在独立 VM 上的 PostgreSQL DBMS。应用需要各自
|
||||
独立的 database、作为 owner 的 login role 和密码,但不需要独立 PostgreSQL 实例。
|
||||
目前这些资源依靠人工 SQL 和人工 Secret 管理,难以重复、审计和检测漂移。
|
||||
|
||||
本系统使用 Kubernetes CRD 作为声明式 API,持续协调外部 PostgreSQL 与 OpenBao:
|
||||
|
||||
```text
|
||||
PostgreSQLInstance / PostgreSQLTenant
|
||||
|
|
||||
v
|
||||
postgresql-tenant-operator
|
||||
| |
|
||||
v v
|
||||
PostgreSQL catalog OpenBao KV v2
|
||||
```
|
||||
|
||||
## 2. 目标
|
||||
|
||||
v1alpha1 必须实现以下目标:
|
||||
|
||||
1. 注册一个已经存在的外部 PostgreSQL 实例并报告连接状态。
|
||||
2. 为一个应用租户创建独立 database 和一个同时作为 database owner 的 login role。
|
||||
3. 根据实例 allowlist 安装租户申请的 PostgreSQL extension。
|
||||
4. 首次生成高强度长期密码,并只把凭据明文写入 OpenBao KV v2。
|
||||
5. 为 Kubernetes 应用创建 ExternalSecret,由 ESO 将凭据投射到同 namespace Secret。
|
||||
6. 同时输出 OpenBao API URL,使 Kubernetes 外的应用可以直接读取凭据。
|
||||
7. 同时输出 PostgreSQL DNS hostname 和 IP address,不假定所有消费者都能使用集群内
|
||||
DNS。
|
||||
8. 持续检测并修正由本系统管理的非破坏性漂移。
|
||||
9. 通过 Kubernetes Condition 报告进度、成功和可操作的失败原因。
|
||||
10. 重复 reconcile、controller 重启及外部依赖暂时失败不得重复创建或破坏资源。
|
||||
11. 删除 Tenant CR 时默认保留外部资源;显式选择 `Delete` 时提供完整清理路径。
|
||||
|
||||
## 3. 非目标
|
||||
|
||||
v1alpha1 不负责:
|
||||
|
||||
- 创建、升级、备份或高可用运行 PostgreSQL DBMS/VM;
|
||||
- 创建或运维 OpenBao;
|
||||
- 直接写入包含凭据明文的 Kubernetes Secret;Secret 必须由 ESO 投射;
|
||||
- 动态凭据、定时或自动密码轮换;
|
||||
- Web UI、独立 REST API 或 Backstage 插件;
|
||||
- 跨实例迁移 database;
|
||||
- schema/table 级别租户、多 login role 或跨租户 grant;
|
||||
- 删除不属于本系统管理的 database、role、extension 或 OpenBao Secret;
|
||||
- 接管不是由本系统创建的外部资源;
|
||||
- 提供生产环境 SLA。
|
||||
|
||||
## 4. 参与者与事实来源
|
||||
|
||||
| 对象 | 事实来源 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 期望状态 | Kubernetes CR `spec` | 用户声明的合同 |
|
||||
| 最近观察结果与当前阶段 | Kubernetes CR `status` | 可以丢失并重建,不是外部事实来源 |
|
||||
| database/role/grant/extension | PostgreSQL catalog | 每轮 reconcile 必须重新读取 |
|
||||
| 受管资源所有权与保留标记 | PostgreSQL controller registry | 与受管 DBMS 一起备份和恢复 |
|
||||
| controller 工作流阶段 | Kubernetes CR `status.phase` | 状态机 checkpoint;可由外部事实保守重建 |
|
||||
| 应用凭据 | OpenBao KV v2 | Kubernetes API 中不得出现明文 |
|
||||
| Kubernetes 凭据投射 | External Secrets Operator | ExternalSecret 由本 controller 管理 |
|
||||
| PostgreSQL 管理凭据 | OpenBao KV v2 | 由 `PostgreSQLInstance` 引用 |
|
||||
|
||||
平台管理员管理 `PostgreSQLInstance`、controller 部署配置、OpenBao policy 和
|
||||
PostgreSQL 管理 role。应用或 GitOps 流程在获得 namespace RBAC 后管理
|
||||
`PostgreSQLTenant`。
|
||||
|
||||
## 5. 资源模型
|
||||
|
||||
### 5.1 PostgreSQLInstance
|
||||
|
||||
`PostgreSQLInstance` 是 cluster-scoped 资源,表示一个已经存在、可由 controller
|
||||
管理的 PostgreSQL server。
|
||||
|
||||
它必须声明:
|
||||
|
||||
- PostgreSQL host、port 和管理连接使用的 database;
|
||||
- PostgreSQL host address,供无法解析 DNS 的消费者使用;
|
||||
- TLS mode;
|
||||
- PostgreSQL 管理凭据在 OpenBao 中的位置和字段名;
|
||||
- 租户允许申请的 extension 集合。
|
||||
|
||||
实例 Ready 不代表 PostgreSQL 数据有备份或高可用,只表示 controller 当前可以安全
|
||||
建立管理连接、读取 server metadata、访问 controller registry 并使用所需管理能力。
|
||||
|
||||
### 5.2 PostgreSQLTenant
|
||||
|
||||
`PostgreSQLTenant` 是 namespaced 资源。v1alpha1 中,一个 Tenant 精确对应:
|
||||
|
||||
- 一个 `PostgreSQLInstance`;
|
||||
- 一个 database;
|
||||
- 一个同时作为 database owner、供应用使用的 `LOGIN` role;
|
||||
- 零个或多个 extension;
|
||||
- 一个 OpenBao KV v2 凭据位置;
|
||||
- 一个同 namespace ExternalSecret 及其目标 Kubernetes Secret。
|
||||
|
||||
Tenant 的 namespace 用于 Kubernetes RBAC 和身份识别,不代表 PostgreSQL schema。
|
||||
同一 Instance 中的 database 和 role 名称全局唯一。
|
||||
|
||||
## 6. 标识与默认值
|
||||
|
||||
以下是 v1alpha1 的标识合同:
|
||||
|
||||
| 字段 | 默认值 | 约束 |
|
||||
| --- | --- | --- |
|
||||
| Instance port | `5432` | 1–65535 |
|
||||
| Instance host address | 无 | 必须是合法 IPv4 或 IPv6 address |
|
||||
| 管理 database | `postgres` | 合法 PostgreSQL identifier |
|
||||
| TLS mode | `verify-full` | 禁止隐式降级 |
|
||||
| Tenant database | `metadata.name` | 同一 Instance 全局唯一 |
|
||||
| login role | `metadata.name` | 同一 Instance 全局唯一 |
|
||||
| deletion policy | `Retain` | `Retain` 或 `Delete` |
|
||||
|
||||
Tenant 的 `spec.instanceRef` 与 `metadata.name` 长度合计不得超过 241 个字符,确保
|
||||
派生的 ExternalSecret/Secret 默认名称
|
||||
`<instanceRef>-<metadata.name>-postgresql` 不超过 Kubernetes 253 字符限制。
|
||||
|
||||
database 和 role 名称必须作为 PostgreSQL identifier 参数安全引用,禁止通过字符串
|
||||
拼接执行。名称校验必须拒绝空字符串、NUL 和超过 PostgreSQL identifier 长度限制的
|
||||
值,并统一限制为小写字母、数字和下划线。
|
||||
|
||||
Tenant 首次成功后,`instanceRef`、database、login role 和凭据位置必须
|
||||
不可变。修改这些字段不是 rename 或 migration,API 必须拒绝或报告明确的
|
||||
`ImmutableField`。
|
||||
|
||||
## 7. PostgreSQL 权限合同
|
||||
|
||||
建议的 v1alpha1 权限模型如下:
|
||||
|
||||
1. database 必须由 login role 拥有。
|
||||
2. login role 必须是 `LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION`。
|
||||
3. 必须撤销 `PUBLIC` 对租户 database 的连接权限,再显式允许 login role 连接。
|
||||
4. controller 不得修改其他 database 或无关 role 的权限。
|
||||
5. controller 只保证请求的 extension 存在;移除 extension 不得自动执行
|
||||
`DROP EXTENSION`。
|
||||
|
||||
这意味着应用可以在自己的 database 内执行 schema migration,但不能创建其他
|
||||
database、role 或访问其他租户。v1alpha1 不创建只有形式意义、却未隔离运行时权限的
|
||||
额外 `NOLOGIN` owner。若以后应用能分别使用 migration 和 runtime 凭据,再通过新的
|
||||
权限 profile 引入 owner/migrator/runtime 角色模型。
|
||||
|
||||
## 8. OpenBao 凭据合同
|
||||
|
||||
### 8.1 Controller 自身认证
|
||||
|
||||
controller 必须使用 Kubernetes auth 登录 OpenBao。controller 使用的 OpenBao API
|
||||
address、提供给消费者的 OpenBao API address、auth mount、auth role 和 KV v2 mount
|
||||
属于部署配置,不属于任何 CR。两个 API address 可以相同;若 controller 使用集群内
|
||||
地址而外部消费者不能解析,则必须单独配置 consumer address。生产部署的 KV mount
|
||||
默认为 `kv`;开发环境可以配置为 OpenBao dev server 默认的 `secret`。长期 OpenBao
|
||||
Token 禁止写入 Deployment、CR 或镜像。
|
||||
|
||||
### 8.2 管理凭据
|
||||
|
||||
`PostgreSQLInstance` 只引用 PostgreSQL 管理用户名和密码所在的 OpenBao KV v2
|
||||
mount-relative path。controller 对该路径只需要读取权限。
|
||||
|
||||
### 8.3 租户凭据
|
||||
|
||||
Tenant 不声明凭据 path。controller 根据部署级 KV mount、base path 和 Tenant 的
|
||||
namespace/name 推导唯一的 mount-relative path。base path 来自 controller 启动参数
|
||||
`--openbao-tenant-base-path`,默认 `postgresql-tenants`。最终路径为
|
||||
`<base-path>/<namespace>/<metadata.name>`。推导结果禁止以 `/` 开头,禁止包含空路径段、
|
||||
`.`、`..`,也禁止把 KV v2 HTTP API 的 `data` 或 `metadata` 层编码进路径。
|
||||
|
||||
新 Tenant 的凭据建立顺序必须可从任意中断点恢复:
|
||||
|
||||
1. 验证 Instance、名称、extension 和目标 OpenBao 路径;
|
||||
2. 确认目标 database、role 和 OpenBao 记录不存在,或能够验证为同一 Tenant
|
||||
已创建的部分状态;
|
||||
3. 生成密码;
|
||||
4. 先创建带 controller 所有权 metadata 的 OpenBao KV v2 记录;
|
||||
5. 从 OpenBao 重新读取凭据;
|
||||
6. 使用该凭据创建作为 database owner 的 login role 和其他 PostgreSQL 资源;
|
||||
7. 用 login role 实际连接目标 database;
|
||||
8. 全部验证成功后将 Tenant 标记 Ready。
|
||||
|
||||
若第 4 步成功、后续 PostgreSQL 操作失败,下一轮必须读取同一份 OpenBao 凭据继续,
|
||||
不得生成第二个密码。若 PostgreSQL 先存在而 OpenBao 记录不存在,controller 必须报告
|
||||
Conflict,不得擅自重置已有 role 密码。
|
||||
|
||||
controller 必须在 PostgreSQL 管理 database 的专用 registry schema 中持久保存可验证的
|
||||
Instance UID、Tenant UID 与 namespace/name 关联,不能只依赖会丢失的 CR status 判断
|
||||
资源所有权。registry 必须可回读且不得改变数据库授权语义;database 或 role COMMENT
|
||||
不能作为权威所有权记录。
|
||||
|
||||
默认写入字段固定为:
|
||||
|
||||
```text
|
||||
username
|
||||
password
|
||||
database
|
||||
host
|
||||
hostaddr
|
||||
port
|
||||
sslmode
|
||||
```
|
||||
|
||||
这些字段是 controller 的规范化输出合同。controller 不生成包含密码的 URI、JDBC URL
|
||||
或应用专用键名。应用通过 ExternalSecret template、Helm values 或自身配置把原子字段
|
||||
映射为 `DATABASE_URL`、独立环境变量或配置文件;因此 URI escaping 和应用特有格式也
|
||||
由消费方负责。`host` 是 DNS 名称,`hostaddr` 是可直接连接的 IP;消费者自行选择其
|
||||
支持且可达的连接目标。PostgreSQL server 证书必须同时包含与 `host` 匹配的 DNS SAN
|
||||
和与 `hostaddr` 匹配的 IP SAN,使两种目标都能在 `verify-full` 下独立完成身份验证。
|
||||
|
||||
### 8.4 凭据输出与 ExternalSecret
|
||||
|
||||
controller 必须根据部署级 base path 推导 Tenant 的 KV path,Tenant 不能选择 mount 或
|
||||
任意远端路径。ExternalSecret 固定命名为
|
||||
`<instanceRef>-<metadata.name>-postgresql`。Tenant 可以通过
|
||||
`spec.credential.secretName` 指定目标 Kubernetes Secret 名称;省略时使用同一默认名。
|
||||
自定义名称只需是合法 Kubernetes Secret 名称,不限制命名内容;两者均与 Tenant 位于
|
||||
同一 namespace。
|
||||
|
||||
controller 必须创建同 namespace ExternalSecret,从固定的 ClusterSecretStore 读取七个
|
||||
原子字段。ExternalSecret 及目标 Secret 的名称通过 Tenant status 暴露。controller
|
||||
不得直接读取 OpenBao 密码后写入 Kubernetes Secret。
|
||||
|
||||
Tenant status 还必须提供完整、可由外部消费者使用的 OpenBao KV v2 API URL。URL 可以
|
||||
包含 consumer API address、mount 和 secret path,但不得包含 Token、密码或其他认证
|
||||
信息。默认 `kubectl get` 表格显示目标 Secret 名称;完整 OpenBao URL 通过
|
||||
`kubectl get postgresqltenant <name> -o yaml` 获取,避免表格列过长。
|
||||
|
||||
OpenBao metadata 必须能够标识 Tenant UID、namespace/name 和 Instance,使 controller
|
||||
区分自己的残留记录与外部记录。任何凭据值都不得进入日志、Event、Condition、metric
|
||||
label、trace、CR spec/status 或测试快照。
|
||||
|
||||
## 9. Reconcile 行为
|
||||
|
||||
系统采用最终一致性模型。Kubernetes、PostgreSQL、OpenBao 和 ESO 可以短暂处于不同
|
||||
阶段;controller 不尝试实现跨系统事务,而是以 Kubernetes CR `status.phase` 作为
|
||||
工作流 checkpoint,通过幂等外部操作和每轮回读验证最终收敛。
|
||||
|
||||
两个 CR 的状态机权威记录都在 `status.phase`。controller 根据 phase 选择下一项候选
|
||||
动作,但 phase 不能替代外部状态检查:执行前后仍须回读 PostgreSQL catalog、registry、
|
||||
OpenBao 和 Kubernetes/ESO。外部写入成功但 status 更新失败时,下一轮必须识别已完成
|
||||
事实并推进 phase,不得重复生成密码或报告虚假冲突。
|
||||
|
||||
status 丢失时,controller 必须从 registry 的所有权记录和各外部系统实际状态保守重建
|
||||
phase。若 status 被伪造或领先于实际状态,controller 必须纠正到安全阶段并补齐资源,
|
||||
不能跳过验证。registry 不保存或驱动协调 phase。
|
||||
|
||||
Instance phase 按当前 generation 表示连接与初始化进度:
|
||||
|
||||
```text
|
||||
Pending -> Validating -> InitializingRegistry -> Ready
|
||||
(any phase) --------------------------------> Deleting
|
||||
```
|
||||
|
||||
spec generation 改变后可以从 `Ready` 回到 `Validating`。Tenant phase 如下:
|
||||
|
||||
```text
|
||||
Pending -> Planned -> CredentialCreated -> RoleCreated -> DatabaseCreated
|
||||
-> ExternalSecretCreated -> CredentialProjected -> Ready -> Deleting
|
||||
```
|
||||
|
||||
失败不增加 `Failed` phase;phase 保留在无法推进的步骤,由 `Ready=False` 的 Reason 和
|
||||
message 表达 `Conflict`、认证失败或依赖不可用。Retain 删除完成后 CR 已不存在,因此
|
||||
没有持久的 `Retained` phase。
|
||||
|
||||
每轮 Tenant reconcile 必须按以下逻辑执行:
|
||||
|
||||
```text
|
||||
读取 Tenant
|
||||
-> 读取 Instance
|
||||
-> 校验不可变字段与输入
|
||||
-> 检查 Instance Ready
|
||||
-> 读取 OpenBao 与 PostgreSQL 实际状态
|
||||
-> 检测冲突或部分完成状态
|
||||
-> 执行非破坏性补齐
|
||||
-> 使用应用凭据验证登录
|
||||
-> 创建并验证 ExternalSecret/Secret 投射
|
||||
-> 回读实际状态
|
||||
-> 更新 status
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- 所有步骤必须幂等;
|
||||
- 每个外部写入前必须先在 CR status 持久化足够的操作意图,写入后必须回读并推进
|
||||
`status.phase`;
|
||||
- 暂时性网络、锁和依赖错误必须重试;
|
||||
- 输入错误、资源冲突和禁止操作不得忙循环重试,只在 generation 或依赖状态变化后
|
||||
重试;
|
||||
- 未知外部资源不得被修改、接管或删除;
|
||||
- 用户从 `spec.extensions` 移除 extension 时不得执行卸载,必须报告该字段在 v1alpha1
|
||||
中只允许追加;
|
||||
- controller 重启不得影响已经签发的应用密码;
|
||||
- `status` 丢失后必须可以从 registry、PostgreSQL、OpenBao 和 Kubernetes/ESO 重建。
|
||||
|
||||
## 10. Condition 合同
|
||||
|
||||
两个资源都必须提供唯一的 `Ready` Condition。可以增加辅助 Condition,但调用方只需
|
||||
依赖 `Ready`。
|
||||
|
||||
| 状态 | 含义 |
|
||||
| --- | --- |
|
||||
| `Ready=Unknown` | 正在首次观察或 reconcile,尚无结论 |
|
||||
| `Ready=False` | 当前 generation 未达到合同要求 |
|
||||
| `Ready=True` | 当前 generation 已回读验证成功 |
|
||||
|
||||
Condition 必须带正确的 `observedGeneration`。资源自身的
|
||||
`status.observedGeneration` 只在当前 generation 完成一次有结论的 reconcile 后更新。
|
||||
|
||||
最低 Reason 集合:
|
||||
|
||||
| Reason | 适用资源 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `Reconciling` | 两者 | 尚在处理 |
|
||||
| `Ready` | 两者 | 当前 generation 已验证 |
|
||||
| `InvalidSpec` | 两者 | 输入不满足规格 |
|
||||
| `DependencyUnavailable` | 两者 | PostgreSQL 或 OpenBao 暂时不可用 |
|
||||
| `AuthenticationFailed` | Instance | 管理凭据或 TLS 验证失败 |
|
||||
| `InsufficientPrivileges` | Instance | 管理 role 缺少必要权限 |
|
||||
| `InstanceNotReady` | Tenant | 引用的 Instance 未 Ready |
|
||||
| `Conflict` | Tenant | 目标名称或 OpenBao 路径已被其他主体占用 |
|
||||
| `ProvisioningFailed` | Tenant | 可重试的创建/验证失败 |
|
||||
| `CredentialProjectionFailed` | Tenant | ESO 或目标 Secret 未达到期望状态 |
|
||||
|
||||
Condition message 必须适合人类排障,但禁止包含连接串密码、Token 或完整 Secret 数据。
|
||||
|
||||
## 11. 删除与保留
|
||||
|
||||
### 11.1 Retain
|
||||
|
||||
`Retain` 是默认策略:
|
||||
|
||||
- 删除 Tenant CR 不得删除 database、role、extension 或 OpenBao 记录;
|
||||
- controller 不得因外部依赖不可用而永久阻止 Retain CR 删除;
|
||||
- 保留资源必须继续携带原 Tenant UID 和 namespace/name 的所有权记录,但在 CR 删除后
|
||||
明确处于 unmanaged 状态;
|
||||
- 重新创建同名 Tenant 会产生新的 UID,必须因已有资源不属于新 UID 而报告 Conflict;
|
||||
- v1alpha1 不提供重新关联、import 或 adoption;恢复管理必须使用第 12 节的迁移流程,
|
||||
或等待后续版本定义显式纳管协议。
|
||||
|
||||
### 11.2 Delete
|
||||
|
||||
用户在创建 Tenant 时显式设置 `deletionPolicy: Delete`,表示删除 CR 时授权永久清理
|
||||
该 Tenant 的外部资源。controller 必须使用 finalizer,并按以下顺序处理:
|
||||
|
||||
1. 再次验证 database、role 和 OpenBao 记录都属于当前 Tenant UID;
|
||||
2. 删除 ExternalSecret,并确认目标 Kubernetes Secret 已删除;
|
||||
3. 禁止该 login role 建立新连接;
|
||||
4. 终止该 database 的现有连接;
|
||||
5. 删除 database,database 内 extension 随之删除;
|
||||
6. 删除 login role;
|
||||
7. 删除 OpenBao KV 记录及其可恢复版本;
|
||||
8. 回读确认外部资源均不存在;
|
||||
9. 删除 controller registry 记录;
|
||||
10. 移除 finalizer,允许 Kubernetes 删除 CR。
|
||||
|
||||
任一步失败都必须保持 finalizer 并从安全检查开始重试。controller 禁止使用
|
||||
`CASCADE` 删除无法证明属于该 Tenant 的依赖对象。若 Instance 或 OpenBao 永久丢失,
|
||||
管理员可以在核实外部状态后手工移除 finalizer;该逃生操作必须在运维 runbook 中明确
|
||||
标记为可能遗留资源。
|
||||
|
||||
v1alpha1 不自动检查备份,也不承诺恢复被 `Delete` 删除的数据。显式选择 Delete 的
|
||||
用户承担数据销毁语义;默认 Retain 用于避免普通误删。
|
||||
|
||||
## 12. 现有环境迁移
|
||||
|
||||
v1alpha1 不接管现有 database 或 role,但必须提供可重复、可回滚的迁移 runbook。对每
|
||||
个现有应用租户,推荐的停机迁移顺序是:
|
||||
|
||||
1. 盘点 database、role、owner、grant 和 extension,并完成可恢复备份;
|
||||
2. 创建逻辑备份,必须使用可映射到新 owner 的格式,避免恢复旧 role ownership;
|
||||
3. 停止应用写入并确认没有活动写事务;
|
||||
4. 完成最终逻辑备份;
|
||||
5. 将旧 database 和 role 重命名为带迁移时间戳的保留名称,释放最终名称;
|
||||
6. 创建 `PostgreSQLTenant`,由 controller 创建最终 database、role 和 OpenBao 凭据;
|
||||
7. 等待 Tenant Ready;
|
||||
8. 以新 owner 恢复逻辑备份,并验证 row count、schema、extension 和应用权限;
|
||||
9. 让 ESO 投射新凭据,重启或重新部署应用;
|
||||
10. 验证应用读写后结束维护窗口;
|
||||
11. 保留旧 database、role 和备份直到回滚窗口结束,再由管理员手工清理。
|
||||
|
||||
回滚时停止新应用写入、恢复原名称或连接配置,并重新使用旧凭据。迁移工具不得把旧
|
||||
密码、管理凭据或 dump 文件提交到 Git。真实命令、锁定方式和各现有应用验证项见
|
||||
[`migration.md`](migration.md),并必须在实现首个可用版本前通过临时 PostgreSQL 实例
|
||||
演练。
|
||||
|
||||
## 13. 安全要求
|
||||
|
||||
1. 所有 PostgreSQL 与 OpenBao 网络访问必须支持超时和 context cancellation。
|
||||
2. homelab 部署必须通过 Deployment 挂载的共享 CA bundle 验证 TLS server identity;
|
||||
该 bundle 的信任根来自 OpenBao PKI,但不得包含 CA 私钥。Instance 默认使用
|
||||
`verify-full`,其 host 必须与服务器证书名称匹配。开发环境可以显式使用 `disable`
|
||||
明文连接。
|
||||
3. PostgreSQL 管理 role 应使用满足本规格的最小权限,不应使用 PostgreSQL
|
||||
superuser;若 extension 安装需要额外权限,必须单独记录例外。
|
||||
4. OpenBao policy 必须限制为:读取已登记的管理凭据范围,以及创建/读取本 controller
|
||||
管理的租户 KV 范围。
|
||||
5. namespace 用户不得修改 cluster-scoped Instance。
|
||||
6. 所有 identifier、extension name 和引用字段必须在发起外部调用前校验。
|
||||
7. controller 不得通过 shell 或 `psql` 子进程执行用户输入。
|
||||
8. 错误包装、结构化日志和 tracing 必须经过 Secret 泄露测试。
|
||||
|
||||
详细威胁模型和部署 policy 见 [`security.md`](security.md)。
|
||||
|
||||
## 14. 可观测性要求
|
||||
|
||||
v1alpha1 至少必须提供:
|
||||
|
||||
- Kubernetes Events:开始 provisioning、成功及需要人工处理的失败;
|
||||
- 结构化日志:resource namespace/name、Instance、generation、阶段和错误类别;
|
||||
- controller-runtime 默认 reconcile metrics;
|
||||
- 不包含 database、role、OpenBao path 等无界用户输入的低基数失败分类 metric。
|
||||
|
||||
日志和 metrics 的存在不能代替 Condition;Condition 是 API 使用者判断状态的主要方式。
|
||||
|
||||
## 15. 验收标准
|
||||
|
||||
实现 v1alpha1 第一条完整纵向切片前,测试必须覆盖:
|
||||
|
||||
1. 有效 Instance 可以建立 TLS 管理连接并变为 Ready。
|
||||
2. PostgreSQL 或 OpenBao 暂时不可用时 Ready=False,恢复后自动变为 Ready。
|
||||
3. 有效 Tenant 创建 database、作为 owner 的 login、grant、extension 和 OpenBao
|
||||
记录。
|
||||
4. 应用凭据可以实际连接且不能创建其他 database/role。
|
||||
5. 相同 generation 重复 reconcile 不改变密码、不重复创建资源。
|
||||
6. controller 在每个外部写入步骤后中断,重启后都能继续并得到相同最终状态。
|
||||
7. 预先存在且不属于当前 Tenant UID 的 database、role 或 OpenBao path 导致
|
||||
Conflict,且不修改已有资源。
|
||||
8. 未在 allowlist 的 extension 在任何外部写入前被拒绝。
|
||||
9. status 被清空后可以从两个外部事实来源重建。
|
||||
10. 删除 Retain Tenant 后外部资源仍存在且不再受管;重新创建同名 Tenant 报告
|
||||
Conflict。
|
||||
11. 日志、Event、Condition、metric 和 CR 中不存在生成的密码或管理凭据。
|
||||
12. 两个 namespace 对同一 Instance 申请相同名称时,只有第一个成功,第二个报告
|
||||
Conflict。
|
||||
13. 删除 Delete Tenant 时,任一步骤失败都可重试,且最终删除 database、login role、
|
||||
OpenBao KV 历史和 finalizer。
|
||||
14. 使用迁移 runbook 可以把一个现有 database 转移到新建的受管 database,并在回滚
|
||||
窗口内恢复旧服务。
|
||||
15. Tenant 只有在 ExternalSecret Ready、目标 Secret 存在且应用凭据实际可登录后才
|
||||
Ready。
|
||||
16. Tenant status 同时提供 Kubernetes Secret reference 和不含认证信息的 OpenBao API
|
||||
URL。
|
||||
17. DNS 不可用时,使用输出的 `hostaddr` 可以连接 PostgreSQL;server 证书同时覆盖
|
||||
`host` 的 DNS SAN 和 `hostaddr` 的 IP SAN,两种连接目标均可通过 `verify-full`。
|
||||
18. 两个 CR 的 `status.phase` 都能反映当前协调步骤;清空 status 后可以从外部事实重建,
|
||||
且伪造或过期 phase 不会使 controller 跳过验证或外部操作。
|
||||
|
||||
单元测试验证纯决策逻辑,adapter 集成测试使用 Docker PostgreSQL/OpenBao,controller
|
||||
集成测试使用 envtest,完整网络路径使用 Kind E2E。
|
||||
|
||||
## 16. 已确认决策
|
||||
|
||||
- v1alpha1 使用一个同时作为 database owner 的 login role,不创建额外 NOLOGIN owner。
|
||||
- v1alpha1 不接管任意现有资源,但必须提供并演练 dump/restore 迁移路径。
|
||||
- v1alpha1 同时实现默认 `Retain` 和显式 `Delete`;Delete 必须有 finalizer、所有权验证
|
||||
和完整清理路径。
|
||||
- OpenBao KV v2 mount 和 base path 是 controller 部署配置,mount 默认 `kv`,base path
|
||||
由 `--openbao-tenant-base-path` 配置并默认 `postgresql-tenants`;Tenant 不能选择 mount
|
||||
或任意远端 path,controller 根据 namespace/name 推导记录路径。
|
||||
- 租户 KV 记录固定写入 `username/password/database/host/hostaddr/port/sslmode` 七个
|
||||
原子字段;
|
||||
controller 不生成连接 URI,应用负责映射和拼装自身配置。
|
||||
- PostgreSQL TLS 使用 controller Deployment 挂载的共享 CA bundle。OpenBao PKI 是
|
||||
CA 权威并继续签发、续期 PostgreSQL server 证书;controller 只消费公开 trust
|
||||
bundle,不接触 CA 私钥。bundle 可以由 ConfigMap 或现有证书同步机制投射,不允许
|
||||
Tenant 或 Instance 选择其他 CA;开发环境可以显式使用 `sslMode: disable`。
|
||||
- 每个 PostgreSQLInstance 在其管理 database 中维护 controller 专用 registry schema。
|
||||
registry 是受管资源所有权、安装身份和 Retain 后 unmanaged 标记的权威记录;两个
|
||||
CR 的 `status.phase` 是 controller 状态机的权威 checkpoint,Instance status 不聚合
|
||||
Tenant 清单。
|
||||
- PostgreSQL database 和 role identifier 必须匹配 `^[a-z][a-z0-9_]{0,62}$`,不支持
|
||||
需要双引号的大小写或特殊字符名称。
|
||||
- External Secrets Operator 是 v1alpha1 的运行依赖。controller 管理同 namespace
|
||||
ExternalSecret,但不直接写明文 Secret;Tenant status 同时输出目标 Secret reference
|
||||
和供非 Kubernetes 消费者使用的 OpenBao API URL。
|
||||
- PostgreSQLInstance 同时声明 DNS `host` 和 IP `hostaddr`;PostgreSQL server 证书必须
|
||||
同时包含对应 DNS SAN 和 IP SAN,消费者自行选择连接目标。
|
||||
|
||||
## 17. 批准状态
|
||||
|
||||
具体设计决策和本文整体已于 2026-09-10 获得批准,可以进入 API reference、测试和
|
||||
实现阶段。同日确认状态机修订:两个 CR 的 `status.phase` 是 controller 工作流的权威
|
||||
checkpoint;PostgreSQL registry 只承担所有权、安装身份和保留状态。
|
||||
|
||||
## 18. 与当前脚手架的已知差异
|
||||
|
||||
当前 API skeleton 至少需要以下调整:
|
||||
|
||||
- 删除 Tenant 自选 OpenBao path 的能力,改由部署级 mount、base path 和 Tenant
|
||||
identity 推导,并修正当前包含 `kv/` 前缀的示例;
|
||||
- 增加 controller 部署级 OpenBao KV mount 和 TLS 配置;
|
||||
- 增加部署级 OpenBao consumer address、ClusterSecretStore 和 KV base path 配置;
|
||||
- 删除独立 `ownerRole` 字段,使 login role 成为 database owner;
|
||||
- 为 Instance 增加 `hostaddr`,为 Tenant 增加目标 Secret 配置及 Secret/Bao URL 输出
|
||||
status;
|
||||
- 按已确认的 identifier 合同收紧校验;
|
||||
- 增加 PostgreSQL controller registry,记录基于 UID 的所有权、安装身份和保留状态;
|
||||
- 修正凭据 type 中遗留的 rotation 注释;
|
||||
- 使 Condition、不可变字段和 extension 追加语义具备 API 校验或明确的 reconcile
|
||||
结果。
|
||||
|
||||
这些是规格批准后的实现工作,不属于本规格本身。
|
||||
@@ -0,0 +1,36 @@
|
||||
name: postgresql-tenant-operator-dev
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: ${POSTGRES_IMAGE:-postgres:17-alpine}
|
||||
environment:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres-dev-only
|
||||
ports:
|
||||
- "127.0.0.1:${POSTGRES_DEV_PORT:-15432}:5432"
|
||||
tmpfs:
|
||||
- /var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
retries: 30
|
||||
|
||||
openbao:
|
||||
image: ${OPENBAO_IMAGE:-openbao/openbao:2.6.1}
|
||||
command: server -dev
|
||||
environment:
|
||||
BAO_ADDR: http://127.0.0.1:8200
|
||||
BAO_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
|
||||
BAO_DEV_ROOT_TOKEN_ID: dev-only-root-token
|
||||
BAO_TOKEN: dev-only-root-token
|
||||
ports:
|
||||
- "127.0.0.1:${OPENBAO_DEV_PORT:-18200}:8200"
|
||||
cap_add:
|
||||
- IPC_LOCK
|
||||
healthcheck:
|
||||
test: ["CMD", "bao", "status"]
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
retries: 30
|
||||
Reference in New Issue
Block a user