12 KiB
Config Center
一个按 config-center-etcd-implementation.md 落地的自建配置中心。PostgreSQL 保存编辑态、版本和审计记录;发布事务写入 release 与 outbox;worker 再将完整 namespace 快照原子下发到 etcd。Web Console 已接入真实 API,不再使用浏览器内存数据。
已实现
- 应用、环境、命名空间、配置项 CRUD;
- 草稿/已发布值分离,新增、修改、待删除状态与发布 diff;
- PostgreSQL 事务化发布,基于 advisory lock 的并发版本分配;
- Outbox claim、租约恢复、指数退避、最大重试和 release 状态回写;
- etcd 完整快照 +
__meta同事务写入; - 运行时配置读取与 SSE/gRPC Watch,统一使用 etcd MVCC revision,支持断线续传与 compact 后全量恢复;
- 发布历史和“生成新版本”的可追溯回滚;
- 审计日志查询;
- 可选 JWT 登录、bcrypt 密码、全局管理员与应用级
viewer/app-ownerRBAC; - IP/CIDR、实例 ID、稳定百分比三类灰度规则,支持优先级覆盖;
- Go/Python SDK,支持 REST/SSE 与 gRPC transport、自动重连、全量替换和本地文件缓存兜底;
- React Web Console(含登录、灰度、用户授权页面)、Docker Compose 与本地内存开发模式;
- Prometheus 指标与告警规则、三副本服务/三节点 etcd Kubernetes 清单、SLO 压测工具。
- HTTP 延迟直方图/p95 告警、带校验和的数据库 migration 追踪、etcd mTLS 与定时备份维护任务。
快速启动
不安装 PostgreSQL/etcd 也可以启动。未设置连接信息时,服务会使用带演示数据的内存控制面和内存运行时:
make dev
另开终端启动 Web Console:
make web-install
npm --prefix web run dev
浏览器访问 http://localhost:5173。HTTP API 默认监听 http://localhost:8080,gRPC 默认监听 localhost:9091。
完整依赖环境:
docker compose up --build
Compose 会启动 PostgreSQL、etcd、Redis、Config Server 和 Web Console。打开 http://localhost:5173。PostgreSQL migration 会在服务启动时串行执行,并记录在 schema_migrations;已经应用的 migration 若内容被修改,服务会拒绝启动。
启用 Prometheus(http://localhost:9090):
docker compose --profile monitoring up --build
配置
| 环境变量 | 默认值 | 说明 |
|---|---|---|
HTTP_ADDR |
:8080 |
HTTP 监听地址 |
GRPC_ADDR |
:9091 |
gRPC 监听地址 |
GRPC_TLS_CERT_FILE |
空 | gRPC 服务端 TLS 证书;与私钥同时配置 |
GRPC_TLS_KEY_FILE |
空 | gRPC 服务端 TLS 私钥;与证书同时配置 |
DATABASE_URL |
空 | 为空时使用内存控制面 |
ETCD_ENDPOINTS |
空 | 逗号分隔;为空时使用内存运行时 |
ETCD_DIAL_TIMEOUT |
5s |
etcd 连接超时 |
ETCD_TLS_CA_FILE |
空 | etcd CA 证书路径;生产环境建议配置 |
ETCD_TLS_CERT_FILE |
空 | etcd mTLS 客户端证书路径,必须与私钥同时配置 |
ETCD_TLS_KEY_FILE |
空 | etcd mTLS 客户端私钥路径 |
ETCD_TLS_SERVER_NAME |
空 | 可选的 etcd 服务端证书名称覆盖 |
OUTBOX_INTERVAL |
500ms |
outbox 扫描周期 |
OUTBOX_BATCH_SIZE |
50 |
单次 claim 数量 |
OUTBOX_MAX_RETRY |
12 |
下发失败最大重试次数 |
CORS_ALLOWED_ORIGINS |
http://localhost:5173 |
允许的 Web Origin,逗号分隔 |
AUTH_ENABLED |
false |
是否启用 JWT 登录与 RBAC |
JWT_SECRET |
空 | HS256 密钥;启用认证时至少 32 个字符 |
JWT_TTL |
8h |
登录令牌有效期 |
BOOTSTRAP_ADMIN_USERNAME |
admin |
首次启动时幂等创建的管理员用户名 |
BOOTSTRAP_ADMIN_PASSWORD |
空 | 启用认证时必填,至少 12 个字符 |
BOOTSTRAP_ADMIN_DISPLAY_NAME |
Administrator |
初始管理员显示名称 |
可复制 .env.example 后按部署环境调整。认证启用后,除健康检查、登录和 /metrics 外的接口都要求 Authorization: Bearer <token>。初始管理员只会在不存在时创建,不会在重启时覆盖已有密码。生产环境应通过 Secret 管理数据库密码、JWT 密钥和初始密码,并启用 PostgreSQL/etcd TLS。
API
管理面响应统一为 { "data": ... },错误统一为 { "error": { "code", "message" } }。
| 方法 | 路径 | 说明 |
|---|---|---|
GET/POST |
/v1/applications |
应用列表/创建 |
PUT/DELETE |
/v1/applications/{id} |
应用更新/删除 |
GET/POST |
/v1/environments |
环境列表/创建 |
GET/POST |
/v1/namespaces |
命名空间列表/创建 |
GET/POST |
/v1/config-items |
配置项列表/创建 |
PUT/DELETE |
/v1/config-items/{id} |
修改/标记待删除 |
POST |
/v1/config-items/{id}/restore |
撤销待删除 |
POST |
/v1/publish |
创建发布版本,返回 202 |
GET |
/v1/releases?appId=&nsId=&envId= |
发布历史与下发状态 |
POST |
/v1/rollback |
回滚并生成新版本 |
GET |
/v1/audit-logs |
审计日志 |
POST |
/v1/auth/login |
用户登录并签发 JWT |
GET |
/v1/me |
当前用户与应用角色 |
GET/POST |
/v1/users |
用户列表/创建(管理员) |
GET/PUT/DELETE |
/v1/users/{id}/roles[/{appId}] |
应用角色管理(管理员) |
GET/POST |
/v1/gray-rules |
当前范围灰度规则列表/创建 |
PUT/DELETE |
/v1/gray-rules/{id} |
灰度规则更新/删除 |
GET |
/v1/config?env=&app=&namespace=&ip=&instance= |
读取运行时快照并匹配灰度规则 |
GET |
/v1/watch?env=&app=&namespace=&ip=&instance= |
SSE 全量同步与更新事件 |
GET |
/metrics |
Prometheus 指标 |
认证关闭时服务以开发管理员身份运行,并可用 X-User 记录操作者;认证开启时操作者来自 JWT,客户端不能通过请求头伪造。viewer 可以读取应用配置,app-owner 还可以维护命名空间、配置、发布、回滚和灰度规则,全局管理员可管理应用、环境、审计和用户授权。
正式 gRPC 契约位于 api/proto/configcenter/v1/config.proto,提供 ConfigService.GetConfig/WatchConfig 与 AdminService.PublishConfig/RollbackConfig。认证令牌通过 authorization: Bearer <token> metadata 传递。WatchConfig.start_revision 为包含式游标:首次订阅传 0,重连传 last_seen_revision + 1;若历史已被 compact,服务端发送最新 FULL_SYNC 后从快照 revision 的下一版本继续监听。详细一致性约束见 docs/adr/0001-runtime-revision-watch.md。
灰度匹配
ip 规则接受精确 IP 或 CIDR;instance 规则接受实例 ID 列表;percentage 使用 salt 与实例 ID(无实例时使用 IP)做稳定哈希,同一实例不会随机漂移。多条规则命中时按优先级从低到高覆盖,并在响应的 grayRuleIds 中返回命中规则。
发布示例
curl -X POST http://localhost:8080/v1/publish \
-H 'Content-Type: application/json' \
-H 'X-User: alice' \
-d '{"appId":1,"nsId":1,"envId":1,"comment":"enable feature"}'
数据库事务提交后返回 pending;outbox 写入 etcd 后,GET /v1/releases/{id} 会变为 applied 并带上 etcdRevision。
SDK
Go SDK:
client, err := configsdk.New(configsdk.Options{
BaseURL: "http://localhost:8080",
Env: "PROD",
App: "order-service",
Token: os.Getenv("CONFIGCENTER_TOKEN"),
Instance: "order-3",
CacheFile: "/var/lib/my-service/configcenter.json",
})
if err != nil { /* handle */ }
_ = client.Load(ctx, "application")
go client.WatchAndSync(ctx, "application")
port := client.GetInt("application", "server.port", 8080)
使用 gRPC transport:
client, err := configsdk.NewGRPC(configsdk.GRPCOptions{
Target: "configcenter.example.com:9091",
Env: "PROD",
App: "order-service",
Token: os.Getenv("CONFIGCENTER_TOKEN"),
Instance: "order-3",
CacheFile: "/var/lib/my-service/configcenter.json",
})
if err != nil { /* handle */ }
defer client.Close()
_ = client.Load(ctx, "application")
go client.WatchAndSync(ctx, "application")
Python SDK 位于 sdk/python:
from configcenter import ConfigClient
client = ConfigClient(
"http://localhost:8080", "PROD", "order-service", "/tmp/order-config.json",
token=os.environ.get("CONFIGCENTER_TOKEN"), instance="order-3",
)
client.load("application")
client.start_background_watch("application")
timeout = client.get("application", "order.timeout.minutes", "30")
Python gRPC transport 需要安装可选依赖 configcenter-client[grpc]:
client = ConfigClient(
"configcenter.example.com:9091",
"PROD",
"order-service",
"/tmp/order-config.json",
token=os.environ.get("CONFIGCENTER_TOKEN"),
instance="order-3",
transport="grpc",
)
client.load("application")
client.start_background_watch("application")
本地缓存采用完整快照和原子 rename;Config Server/etcd 暂时不可用时,进程可以读取上一次成功同步的缓存启动。
开发与验证
make test
make proto-check
go test -race ./...
make build
make web-build
docker compose config
make integration-smoke
make loadtest
核心端到端测试覆盖:CRUD → 发布事务 → outbox worker → 运行时读取 → 灰度覆盖与指标;单元/HTTP 集成测试还覆盖 JWT 过期与篡改、应用级 RBAC、多版本回滚和 SDK 鉴权/灰度参数。
压测工具默认验证运行时读取的错误率不超过 1%、p95 不超过 200ms,可覆盖目标和阈值:
go run ./cmd/loadtest \
-base-url=http://127.0.0.1:8080 -env=DEV -app=demo-service -namespace=application \
-token="$CONFIGCENTER_TOKEN" -concurrency=64 -duration=60s -max-p95=200ms
生产部署
deploy/kubernetes/configcenter.yaml 提供三副本无状态 Config Server、三节点 etcd、跨节点调度、PDB、HPA、探针、资源限制和 etcd mTLS。operations.yaml 额外提供 etcd 访问网络策略、每 6 小时快照、每周 defrag 和每日 PostgreSQL 备份。部署前替换镜像与 Origin,并创建密钥:
kubectl create namespace configcenter
kubectl -n configcenter create secret generic configcenter-secrets \
--from-literal=DATABASE_URL='postgres://...' \
--from-literal=JWT_SECRET='至少32位随机密钥' \
--from-literal=BOOTSTRAP_ADMIN_PASSWORD='至少12位初始密码'
kubectl -n configcenter create secret generic etcd-tls \
--from-file=ca.crt=/secure/path/ca.crt \
--from-file=server.crt=/secure/path/server.crt \
--from-file=server.key=/secure/path/server.key \
--from-file=peer.crt=/secure/path/peer.crt \
--from-file=peer.key=/secure/path/peer.key \
--from-file=client.crt=/secure/path/client.crt \
--from-file=client.key=/secure/path/client.key
kubectl apply -f deploy/kubernetes/
etcd 服务端证书需覆盖 etcd、etcd.configcenter.svc.cluster.local 和 *.etcd-peer.configcenter.svc.cluster.local,peer/client 证书应分别包含 Server Auth/Client Auth 所需用途。备份 PVC 只提供集群内保留,生产环境还应把快照异步复制到异地对象存储,并定期演练恢复。PostgreSQL 仍建议使用托管高可用或主从方案。
目录
cmd/server 服务入口
internal/api/httpapi REST + SSE
internal/api/grpcapi gRPC 服务、认证拦截器与错误映射
internal/store/postgres PostgreSQL repository 与 migration
internal/store/memory 本地开发/测试实现
internal/runtime/etcd etcd 快照与 Watch
internal/outbox 异步下发 worker
internal/watch 同 key Watch 扇出
internal/auth JWT、密码与应用级 RBAC
internal/gray 灰度规则校验与确定性匹配
internal/metrics Prometheus 指标
cmd/loadtest 运行时读取 SLO 压测
deploy Kubernetes 与监控告警配置
pkg/sdk/go Go SDK
pkg/proto 生成的 Go protobuf/gRPC 代码
sdk/python Python SDK
api/proto gRPC 契约
web React Web Console