175 lines
8.0 KiB
Markdown
175 lines
8.0 KiB
Markdown
# 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
|
||
```
|