docs: 完善 v1alpha1 设计与运维合同
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
# 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 的相应校验。
|
||||
- 默认值由 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 | `<name>-postgresql` | 同 namespace ESO target Secret 名称 |
|
||||
| `spec.deletionPolicy` | enum | `Retain` | `Retain` 或 `Delete` |
|
||||
|
||||
Tenant 不声明 OpenBao mount 或 path。controller 使用部署级 mount/base path 和
|
||||
`namespace/name` 推导稳定路径,并用 UID metadata 验证所有权。
|
||||
|
||||
`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: netbox-postgresql
|
||||
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,112 @@
|
||||
# 部署与配置
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 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 | 必填 | controller 专属 mount-relative 前缀 |
|
||||
| ESO ClusterSecretStore name | 必填 | controller 创建的 ExternalSecret 固定引用 |
|
||||
| CA bundle path | 必填(TLS) | 只读 PEM trust bundle,不含私钥 |
|
||||
| reconcile timeout | 有安全默认 | 单轮外部操作的总期限 |
|
||||
|
||||
Tenant 路径固定推导为 `<base-path>/<namespace>/<name>`。namespace/name 都已通过
|
||||
Kubernetes 名称校验,因此不再允许 CR 提供任意路径。KV v2 API URL 使用 consumer
|
||||
address 拼为 `<address>/v1/<mount>/data/<base-path>/<namespace>/<name>`。
|
||||
|
||||
配置变化不得隐式迁移既有凭据。修改 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) 的备份/逃生检查。
|
||||
+34
-11
@@ -1,6 +1,6 @@
|
||||
# 开发与测试环境
|
||||
|
||||
本项目同时依赖 Kubernetes API、PostgreSQL 和 OpenBao。日常开发不连接 homelab
|
||||
本项目同时依赖 Kubernetes API、PostgreSQL、OpenBao 和 ESO。日常开发不连接 homelab
|
||||
中的真实服务:Kubernetes 使用 envtest 或一次性 Kind,另外两个依赖使用一次性
|
||||
容器。这样既避免污染真实数据,也能把启动顺序固化为命令。
|
||||
|
||||
@@ -25,11 +25,11 @@ container 的 Docker/Dev Container 环境。
|
||||
| 层次 | Kubernetes | PostgreSQL / OpenBao | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| 单元测试 | fake client | fake client | SQL 计划、状态转换和错误分类 |
|
||||
| controller 集成测试 | envtest | fake 或 Docker | CRD、watch、status、finalizer |
|
||||
| controller 集成测试 | envtest | fake adapter | CRD、watch、status、finalizer、ExternalSecret 对象 |
|
||||
| adapter 集成测试 | 不需要 | Docker Compose | 真实协议、权限和幂等行为 |
|
||||
| E2E | 一次性 Kind | Kind 内测试实例 | 验证容器化 controller 与完整网络路径 |
|
||||
| E2E | 一次性 Kind + ESO | Kind 内测试实例 | 凭据投射、TLS、完整网络和删除路径 |
|
||||
|
||||
envtest 只启动 API server 和 etcd,没有 kubelet、scheduler 或 controller-manager,
|
||||
envtest 只启动 API server 和 etcd,没有 kubelet、scheduler、ESO 或 controller-manager,
|
||||
因此不能用它验证 Deployment、Pod 调度或 Service 网络。此类行为必须留给 Kind
|
||||
E2E。
|
||||
|
||||
@@ -64,7 +64,11 @@ make lint
|
||||
`make test` 会下载与 `go.mod` 中 Kubernetes minor 版本匹配的 envtest 二进制,
|
||||
启动临时 API server/etcd,测试结束后自动关闭。
|
||||
|
||||
### 2. 启动外部依赖
|
||||
规格实现后,快速测试必须覆盖默认值/校验、Condition `observedGeneration`、两个 CR 的
|
||||
status 状态机、不可变字段、extension 只追加、registry 所有权和外部错误分类。envtest 只断言 controller 创建了正确
|
||||
的 ExternalSecret;它不能证明 ESO 已生成 Secret。
|
||||
|
||||
### 2. 启动 PostgreSQL/OpenBao adapter 依赖
|
||||
|
||||
只有开发 PostgreSQL/OpenBao adapter 或完整 reconcile 时才需要:
|
||||
|
||||
@@ -99,6 +103,9 @@ 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 执行,不默认塞进快速单元测试。
|
||||
@@ -112,7 +119,9 @@ make install
|
||||
make run
|
||||
```
|
||||
|
||||
此时 controller 运行在开发容器内,可以直接访问上面的回环端口。不要把包含
|
||||
此时 controller 运行在开发容器内,可以直接访问上面的回环端口。若要验证 Tenant
|
||||
Ready,专用 Kind 还必须安装 ESO、创建测试 ClusterSecretStore,并让 Kind workload
|
||||
能够访问测试 OpenBao。不要把包含
|
||||
`127.0.0.1` 端点的样例部署到 Kind 内;Pod 中的回环地址只指向 Pod 自身。
|
||||
|
||||
### 4. 清理
|
||||
@@ -127,22 +136,24 @@ make cleanup-test-e2e
|
||||
## E2E 顺序
|
||||
|
||||
CI 的 E2E 与本机 `make run` 不同:controller 会作为 Pod 运行在 Kind 中。因此完整
|
||||
E2E fixture 必须把测试 PostgreSQL 和 OpenBao也部署进 Kind,并等待两者 Ready 后
|
||||
E2E fixture 必须把测试 PostgreSQL、OpenBao 和 ESO 部署进 Kind,并等待依赖 Ready 后
|
||||
再创建 `PostgreSQLInstance` 和 `PostgreSQLTenant`:
|
||||
|
||||
```text
|
||||
创建 Kind
|
||||
-> 安装 CRD
|
||||
-> 部署 PostgreSQL/OpenBao fixture
|
||||
-> 等待依赖 Ready并写入测试管理凭据
|
||||
-> 部署 PostgreSQL/OpenBao fixture,签发含 DNS/IP SAN 的测试证书
|
||||
-> 安装 ESO,配置 OpenBao auth/policy 和 ClusterSecretStore
|
||||
-> 等待依赖 Ready 并写入测试管理凭据
|
||||
-> 构建并加载 controller image
|
||||
-> 部署 controller
|
||||
-> 创建 Instance
|
||||
-> 等待 Instance Ready
|
||||
-> 创建 Tenant
|
||||
-> 等待 Tenant Ready
|
||||
-> 验证 PostgreSQL catalog 与 OpenBao KV
|
||||
-> 删除 Tenant并验证 Retain
|
||||
-> 验证 registry、PostgreSQL catalog、OpenBao KV、ExternalSecret 和 Secret
|
||||
-> 分别使用 DNS host 与 IP hostaddr 登录
|
||||
-> 删除 Tenant 并分别验证 Retain 与 Delete(含故障点重试)
|
||||
-> 删除 Kind
|
||||
```
|
||||
|
||||
@@ -150,6 +161,18 @@ E2E fixture 必须把测试 PostgreSQL 和 OpenBao也部署进 Kind,并等待
|
||||
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` 成功代替
|
||||
应用读写和回滚验证。
|
||||
|
||||
## 故障排查
|
||||
|
||||
查看依赖状态与日志:
|
||||
|
||||
@@ -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 的资源。
|
||||
+45
-21
@@ -17,7 +17,7 @@
|
||||
## 1. 背景
|
||||
|
||||
homelab 中的大部分应用共享一个运行在独立 VM 上的 PostgreSQL DBMS。应用需要各自
|
||||
独立的 database、owner role、login role 和密码,但不需要独立 PostgreSQL 实例。
|
||||
独立的 database、作为 owner 的 login role 和密码,但不需要独立 PostgreSQL 实例。
|
||||
目前这些资源依靠人工 SQL 和人工 Secret 管理,难以重复、审计和检测漂移。
|
||||
|
||||
本系统使用 Kubernetes CRD 作为声明式 API,持续协调外部 PostgreSQL 与 OpenBao:
|
||||
@@ -69,9 +69,10 @@ v1alpha1 不负责:
|
||||
| 对象 | 事实来源 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 期望状态 | Kubernetes CR `spec` | 用户声明的合同 |
|
||||
| 最近观察结果 | Kubernetes CR `status` | 可以丢失并重建,不是外部事实来源 |
|
||||
| 最近观察结果与当前阶段 | Kubernetes CR `status` | 可以丢失并重建,不是外部事实来源 |
|
||||
| database/role/grant/extension | PostgreSQL catalog | 每轮 reconcile 必须重新读取 |
|
||||
| 受管资源所有权与协调阶段 | PostgreSQL controller registry | 与受管 DBMS 一起备份和恢复 |
|
||||
| 受管资源所有权与保留标记 | 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` 引用 |
|
||||
@@ -106,7 +107,7 @@ PostgreSQL 管理 role。应用或 GitOps 流程在获得 namespace RBAC 后管
|
||||
- 一个 database;
|
||||
- 一个同时作为 database owner、供应用使用的 `LOGIN` role;
|
||||
- 零个或多个 extension;
|
||||
- 一个 OpenBao KV v2 凭据位置。
|
||||
- 一个 OpenBao KV v2 凭据位置;
|
||||
- 一个同 namespace ExternalSecret 及其目标 Kubernetes Secret。
|
||||
|
||||
Tenant 的 namespace 用于 Kubernetes RBAC 和身份识别,不代表 PostgreSQL schema。
|
||||
@@ -233,19 +234,36 @@ label、trace、CR spec/status 或测试快照。
|
||||
|
||||
## 9. Reconcile 行为
|
||||
|
||||
系统采用最终一致性模型。Kubernetes、PostgreSQL 和 OpenBao 可以短暂处于不同阶段;
|
||||
controller 不尝试实现跨系统事务,而是通过 PostgreSQL 中持久化的协调阶段、幂等外部
|
||||
操作和每轮回读验证最终收敛。
|
||||
系统采用最终一致性模型。Kubernetes、PostgreSQL、OpenBao 和 ESO 可以短暂处于不同
|
||||
阶段;controller 不尝试实现跨系统事务,而是以 Kubernetes CR `status.phase` 作为
|
||||
工作流 checkpoint,通过幂等外部操作和每轮回读验证最终收敛。
|
||||
|
||||
每个 Tenant 的 registry 记录至少经历以下单向阶段:
|
||||
两个 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
|
||||
Planned -> CredentialCreated -> RoleCreated -> DatabaseCreated
|
||||
-> ExternalSecretCreated -> CredentialProjected -> Ready
|
||||
Pending -> Validating -> InitializingRegistry -> Ready
|
||||
(any phase) --------------------------------> Deleting
|
||||
```
|
||||
|
||||
阶段用于恢复进度,但不能替代实际状态检查。controller 重启后必须同时检查 registry、
|
||||
PostgreSQL catalog 和 OpenBao,再决定继续、保持 Ready 或报告 Conflict。
|
||||
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 必须按以下逻辑执行:
|
||||
|
||||
@@ -266,7 +284,8 @@ PostgreSQL catalog 和 OpenBao,再决定继续、保持 Ready 或报告 Confli
|
||||
要求:
|
||||
|
||||
- 所有步骤必须幂等;
|
||||
- 每个外部写入前必须先持久化足够的操作意图,写入后必须回读并推进 registry 阶段;
|
||||
- 每个外部写入前必须先在 CR status 持久化足够的操作意图,写入后必须回读并推进
|
||||
`status.phase`;
|
||||
- 暂时性网络、锁和依赖错误必须重试;
|
||||
- 输入错误、资源冲突和禁止操作不得忙循环重试,只在 generation 或依赖状态变化后
|
||||
重试;
|
||||
@@ -274,7 +293,7 @@ PostgreSQL catalog 和 OpenBao,再决定继续、保持 Ready 或报告 Confli
|
||||
- 用户从 `spec.extensions` 移除 extension 时不得执行卸载,必须报告该字段在 v1alpha1
|
||||
中只允许追加;
|
||||
- controller 重启不得影响已经签发的应用密码;
|
||||
- `status` 丢失后必须可以从 PostgreSQL 和 OpenBao 重建。
|
||||
- `status` 丢失后必须可以从 registry、PostgreSQL、OpenBao 和 Kubernetes/ESO 重建。
|
||||
|
||||
## 10. Condition 合同
|
||||
|
||||
@@ -363,8 +382,9 @@ v1alpha1 不接管现有 database 或 role,但必须提供可重复、可回
|
||||
11. 保留旧 database、role 和备份直到回滚窗口结束,再由管理员手工清理。
|
||||
|
||||
回滚时停止新应用写入、恢复原名称或连接配置,并重新使用旧凭据。迁移工具不得把旧
|
||||
密码、管理凭据或 dump 文件提交到 Git。真实命令、锁定方式和各现有应用验证项在实现
|
||||
首个可用版本前写入独立 `docs/migration.md` 并通过临时 PostgreSQL 实例演练。
|
||||
密码、管理凭据或 dump 文件提交到 Git。真实命令、锁定方式和各现有应用验证项见
|
||||
[`migration.md`](migration.md),并必须在实现首个可用版本前通过临时 PostgreSQL 实例
|
||||
演练。
|
||||
|
||||
## 13. 安全要求
|
||||
|
||||
@@ -382,7 +402,7 @@ v1alpha1 不接管现有 database 或 role,但必须提供可重复、可回
|
||||
7. controller 不得通过 shell 或 `psql` 子进程执行用户输入。
|
||||
8. 错误包装、结构化日志和 tracing 必须经过 Secret 泄露测试。
|
||||
|
||||
详细威胁模型和部署 policy 将在 `docs/security.md` 中定义。
|
||||
详细威胁模型和部署 policy 见 [`security.md`](security.md)。
|
||||
|
||||
## 14. 可观测性要求
|
||||
|
||||
@@ -425,6 +445,8 @@ v1alpha1 至少必须提供:
|
||||
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。
|
||||
@@ -445,8 +467,9 @@ v1alpha1 至少必须提供:
|
||||
bundle,不接触 CA 私钥。bundle 可以由 ConfigMap 或现有证书同步机制投射,不允许
|
||||
Tenant 或 Instance 选择其他 CA;开发环境可以显式使用 `sslMode: disable`。
|
||||
- 每个 PostgreSQLInstance 在其管理 database 中维护 controller 专用 registry schema。
|
||||
registry 是受管资源所有权和协调阶段的权威记录;Tenant status 只是观察缓存,
|
||||
Instance status 不聚合 Tenant 清单。
|
||||
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
|
||||
@@ -458,7 +481,8 @@ v1alpha1 至少必须提供:
|
||||
## 17. 批准状态
|
||||
|
||||
具体设计决策和本文整体已于 2026-09-10 获得批准,可以进入 API reference、测试和
|
||||
实现阶段。
|
||||
实现阶段。同日确认状态机修订:两个 CR 的 `status.phase` 是 controller 工作流的权威
|
||||
checkpoint;PostgreSQL registry 只承担所有权、安装身份和保留状态。
|
||||
|
||||
## 18. 与当前脚手架的已知差异
|
||||
|
||||
@@ -471,7 +495,7 @@ v1alpha1 至少必须提供:
|
||||
- 删除独立 `ownerRole` 字段,使 login role 成为 database owner;
|
||||
- 为 Instance 增加 `hostaddr`,为 Tenant 增加目标 Secret 配置及凭据输出 status;
|
||||
- 按已确认的 identifier 合同收紧校验;
|
||||
- 增加 PostgreSQL controller registry,记录基于 UID 的所有权和最终一致性协调阶段;
|
||||
- 增加 PostgreSQL controller registry,记录基于 UID 的所有权、安装身份和保留状态;
|
||||
- 修正凭据 type 中遗留的 rotation 注释;
|
||||
- 使 Condition、不可变字段和 extension 追加语义具备 API 校验或明确的 reconcile
|
||||
结果。
|
||||
|
||||
Reference in New Issue
Block a user