24 KiB
Config Center SDK 实际应用接入与测试指南
本文用于把当前 Config Center SDK 接入真实 Go / Python 应用进行功能验证,覆盖首次接入、动态更新、本地缓存、灰度规则、认证和 gRPC 测试。
当前推荐的验证顺序:REST/SSE → 本地缓存/断线恢复 → 灰度规则 → 认证 → gRPC。不要一开始同时启用所有能力,否则出现问题时较难定位是配置数据、权限、网络还是传输层问题。
1. 当前 SDK 能力
当前仓库提供三种客户端形态:
- Go 轻量 REST/SSE:
pkg/sdk/gohttp。这是独立 Go module,要求 Go 1.24+,只依赖标准库,推荐普通业务应用首先使用。 - Go 完整 SDK:
pkg/sdk/go。支持 REST/SSE + gRPC,跟随 ConfigCenter 主 module 的 Go/toolchain 与 gRPC 安全版本。 - Python:
sdk/python/configcenter,支持 HTTP/SSE 与可选 gRPC transport。
轻量 Go SDK 和完整 SDK 的 HTTP/SSE transport 都提供:
- REST 获取当前完整配置快照;
- SSE 长连接监听配置变化;
- Watch 断线自动重连;
- 本地磁盘缓存;
- 进程重启后从本地缓存恢复;
- JWT Bearer Token;
IP/Instance灰度匹配;- 一个应用同时读取多个 Namespace。
完整 Go SDK / Python gRPC transport 另外支持 gRPC GetConfig / WatchConfig 和基于 revision 的断线恢复。Go 1.24 项目如果只需要 REST/SSE,不应为了 gRPC transport 被动引入更高版本 gRPC 工具链,直接使用 pkg/sdk/gohttp 即可。
SDK 接收的是完整 Namespace 快照。每次配置发生变化后,SDK 会用新快照替换该 Namespace 的本地内存数据,而不是逐字段 patch。
2. 测试前准备 Config Center
2.1 启动完整环境
在 ConfigCenter 项目根目录执行:
docker compose up -d --build --remove-orphans
当前 Compose 默认暴露:
| 服务 | 地址 |
|---|---|
| Web Console | http://127.0.0.1:5173 |
| HTTP / SSE API | http://127.0.0.1:18080 |
| gRPC | 容器内 :9091,当前默认未映射到宿主机 |
检查服务:
curl http://127.0.0.1:18080/health/ready
应返回健康状态。
2.2 创建测试配置
进入 Web Console:
http://127.0.0.1:5173
建议创建一套专门用于实际应用验证的数据,例如:
应用 ID / App Code: sdk-test-service
环境: DEV
Namespace: application
配置项示例:
feature.demo.enabled=true
request.timeout.ms=3000
log.level=INFO
welcome.message=hello-config-center
保存后必须点击发布。
配置项处于草稿状态时,SDK 不会读取到新值。SDK 读取的是已经发布到 etcd 的运行时配置。
2.3 在接 SDK 前直接验证运行时 API
curl 'http://127.0.0.1:18080/v1/config?env=DEV&app=sdk-test-service&namespace=application'
应能看到类似:
{
"data": {
"items": {
"feature.demo.enabled": "true",
"request.timeout.ms": "3000",
"log.level": "INFO",
"welcome.message": "hello-config-center"
},
"revision": 123,
"releaseVersion": 1
}
}
如果这一步不能成功,先不要接 SDK。应先检查应用代码、Namespace、环境、发布状态和权限。
3. 认证模式怎么选
3.1 第一轮实际应用验证:建议先关闭认证
如果当前目标是验证:
- 应用能否获取配置;
- 发布后能否自动更新;
- Config Center 重启后能否恢复;
- 本地缓存是否生效;
- 灰度配置是否生效;
建议第一轮先使用:
AUTH_ENABLED=false
此时 SDK 的 Token / token 留空即可。
这样可以先把配置链路和权限问题分离。
3.2 开启 JWT 认证
启用认证后:
AUTH_ENABLED=true
JWT_SECRET=<至少32字符的随机密钥>
BOOTSTRAP_ADMIN_USERNAME=admin
BOOTSTRAP_ADMIN_PASSWORD=<至少12字符>
SDK 当前不负责登录,需要应用在创建 SDK Client 前自行获得 JWT。
测试阶段可以调用:
curl -X POST http://127.0.0.1:18080/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{
"username":"sdk-reader",
"password":"your-password"
}'
响应中的:
{
"data": {
"token": "..."
}
}
传给 SDK。
该用户至少需要目标应用的:
viewer
角色。
3.3 当前 JWT SDK 限制
当前 Go/Python SDK 的 Token 在 Client 创建时写入,没有提供运行时 SetToken / refresh-token 机制。
因此如果 JWT 到期:
- 当前 Watch 会断开;
- SDK 会尝试重连;
- 但仍会继续使用旧 Token;
- 服务端持续返回认证失败;
- 需要应用重新获取 Token 并重建 SDK Client。
所以当前阶段:
- 功能验证可以先关闭认证;或
- 给测试账号配置足够覆盖测试窗口的
JWT_TTL; - 真正生产接入前,应补充 service identity / Token 刷新能力。
另外,当管理员禁用用户、重置密码或修改应用角色时,旧 JWT 会由于 token_version 变化立即失效,这是预期安全行为。
4. Go 应用接入
4.1 当前 SDK 的依赖方式
实际业务优先使用独立的轻量 Go module:
git.newai.day/longpeng/configcenter/pkg/sdk/gohttp
它只实现 REST/SSE transport,不依赖 grpc、protobuf、PostgreSQL 或 etcd 客户端,因此不会因为接入配置中心而抬升业务项目的这些依赖版本。当前 module 的最低 Go 版本是 1.24。
业务项目通过内网 Gitea 的正式 module 版本接入。当前目标版本:
require git.newai.day/longpeng/configcenter/pkg/sdk/gohttp v0.1.2
该 module 是 ConfigCenter 仓库中的嵌套 module,因此对应 Git tag 必须使用:
pkg/sdk/gohttp/v0.1.2
内网私有模块环境需要统一配置 GOPRIVATE=git.newai.day/*,使 Go 绕过公共 module proxy 和 checksum database。建议由开发机初始化脚本、DevContainer 或 CI Runner 统一下发,而不是使用 Git URL rewrite。
代码导入:
import configsdk "git.newai.day/longpeng/configcenter/pkg/sdk/gohttp"
然后执行:
go mod tidy
正式 CI/构建环境不依赖相邻仓库目录,也不需要 Git URL rewrite;只需要具备访问 git.newai.day 的 Git 凭据和私有 Go module 环境配置。
4.2 最小 Go REST/SSE 示例
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"os/signal"
"syscall"
configsdk "git.newai.day/longpeng/configcenter/pkg/sdk/gohttp"
)
func main() {
ctx, cancel := signal.NotifyContext(
context.Background(),
syscall.SIGINT,
syscall.SIGTERM,
)
defer cancel()
client, err := configsdk.New(configsdk.Options{
BaseURL: "http://127.0.0.1:18080",
Env: "DEV",
App: "sdk-test-service",
Token: os.Getenv("CONFIGCENTER_TOKEN"),
Instance: "sdk-test-instance-01",
CacheFile: "/tmp/sdk-test-service-config.json",
})
if err != nil {
log.Fatal(err)
}
// 启动时主动获取一次最新完整配置。
if err := client.Load(ctx, "application"); err != nil {
// 如果之前已经成功同步过,Client 构造时已读取 CacheFile,
// 因此这里可以根据业务策略决定是否允许使用本地缓存继续启动。
log.Printf("load config center failed, continue with local cache/defaults: %v", err)
}
fmt.Println("initial config:", client.Snapshot("application"))
// 后台持续同步。
go func() {
err := client.WatchAndSync(ctx, "application")
if err != nil && !errors.Is(err, context.Canceled) {
log.Printf("config watch stopped: %v", err)
}
}()
// 业务代码可以随时读取当前内存快照。
enabled := client.GetBool("application", "feature.demo.enabled", false)
timeout := client.GetInt("application", "request.timeout.ms", 3000)
message := client.GetString("application", "welcome.message", "default")
fmt.Printf("enabled=%v timeout=%d message=%s\n", enabled, timeout, message)
<-ctx.Done()
}
运行:
CONFIGCENTER_TOKEN='' go run .
如果认证已经开启:
CONFIGCENTER_TOKEN='<JWT>' go run .
4.3 Go SDK 读取 API
字符串
value := client.GetString(
"application",
"log.level",
"INFO",
)
整数
timeout := client.GetInt(
"application",
"request.timeout.ms",
3000,
)
如果配置不存在或不能转换为整数,则返回 fallback。
Bool
enabled := client.GetBool(
"application",
"feature.demo.enabled",
false,
)
支持 Go strconv.ParseBool 能识别的布尔值。
获取整个 Namespace
snapshot := client.Snapshot("application")
返回的是复制后的 map,可以安全修改,不会破坏 SDK 内部缓存。
4.4 多 Namespace
例如应用同时需要:
application
database
feature-flags
启动时:
namespaces := []string{"application", "database", "feature-flags"}
for _, namespace := range namespaces {
if err := client.Load(ctx, namespace); err != nil {
log.Printf("load %s failed: %v", namespace, err)
}
namespace := namespace
go func() {
if err := client.WatchAndSync(ctx, namespace); err != nil &&
!errors.Is(err, context.Canceled) {
log.Printf("watch %s failed: %v", namespace, err)
}
}()
}
每个 Namespace 使用独立的 Watch 流和 revision 游标。
4.5 Kubernetes 中 Instance 建议
灰度规则如果使用:
instance
percentage
建议 Instance 使用稳定的实例标识。
Kubernetes Deployment 可以注入 Pod Name:
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
Go:
Instance: os.Getenv("POD_NAME"),
不要优先使用动态 Pod IP 做 percentage 灰度,因为实例重建后 IP 可能变化,导致灰度桶变化。
5. Python 应用接入
5.1 本地安装 Python SDK
在业务项目虚拟环境中:
pip install -e /home/longpeng/workspace/ConfigCenter/sdk/python
如果需要 gRPC:
pip install -e '/home/longpeng/workspace/ConfigCenter/sdk/python[grpc]'
当前 Python SDK 要求:
Python >= 3.10
5.2 最小 Python REST/SSE 示例
import os
import time
from configcenter import ConfigClient
client = ConfigClient(
"http://127.0.0.1:18080",
"DEV",
"sdk-test-service",
"/tmp/sdk-test-service-python.json",
token=os.environ.get("CONFIGCENTER_TOKEN"),
instance="sdk-python-01",
)
try:
client.load("application")
except Exception as exc:
# 如果本机之前同步成功过,构造 ConfigClient 时已经加载磁盘缓存。
print("initial load failed; using cache/defaults:", exc)
print(client.snapshot("application"))
client.start_background_watch("application")
while True:
enabled = client.get("application", "feature.demo.enabled", "false")
timeout = int(client.get("application", "request.timeout.ms", "3000"))
print("enabled=", enabled, "timeout=", timeout)
time.sleep(5)
注意:Python SDK 当前 get() 不负责类型转换,返回的配置值本质上是字符串。业务代码需要自行转换。
5.3 Python 多 Namespace
for namespace in ["application", "database", "feature-flags"]:
try:
client.load(namespace)
except Exception as exc:
print(f"load {namespace} failed:", exc)
client.start_background_watch(namespace)
6. 动态配置应该怎么在业务代码中使用
SDK 当前更新的是本地内存快照,但不会自动重建你的业务对象。
例如配置:
db.max_connections=30
即使 SDK 已经收到新值:
db.max_connections=50
已有数据库连接池也不会自动变成 50。
因此配置可以分为两类。
6.1 可以实时读取的配置
适合直接使用 SDK Getter:
- Feature Flag;
- 限流阈值;
- 超时阈值;
- 开关;
- 动态路由策略;
- 日志级别判断;
- 业务规则参数。
例如:
if client.GetBool("application", "feature.demo.enabled", false) {
// new path
}
这种方式发布后下一次业务请求即可读取新值。
6.2 需要主动重建资源的配置
例如:
- 数据库 DSN;
- 数据库连接池大小;
- HTTP Listener 端口;
- Kafka / MQ Client;
- TLS Certificate;
- 大型线程池/Worker Pool 参数。
当前 SDK 没有 change callback,因此应用需要自己做 reconcile,例如周期性获取 Snapshot(),比较目标字段后安全重建对应资源。
生产使用时建议后续给 SDK 增加:
OnChange(namespace, callback)
或 subscription callback,避免每个应用重复实现变更检测。
7. 本地缓存怎么工作
Go:
CacheFile: "/var/lib/my-service/configcenter/cache.json"
Python:
ConfigClient(
...,
cache_file="/var/lib/my-service/configcenter/cache.json",
)
SDK 每次收到完整配置后:
- 更新内存快照;
- 保存 Namespace 当前 revision;
- 写入临时文件;
fsync;- 原子 rename 为正式缓存文件。
缓存文件权限按 SDK 实现设置为:
0600
目录会使用:
0700
进程启动时 Config Center 不可用
SDK Client 构造时会先尝试读取缓存文件。
因此:
上一次同步成功
↓
进程停止
↓
Config Center 临时不可用
↓
业务进程重新启动
↓
SDK 加载磁盘缓存
应用仍可以通过 Getter 得到上一次成功同步的值。
第一次启动没有缓存
如果第一次部署时 Config Center 就不可访问,则没有可恢复的远端配置。
因此所有关键 Getter 都应有合理 fallback,或者应用根据配置的重要级别选择 fail-fast。
例如数据库密码等关键配置不应该随意给一个错误默认值继续运行。
8. 灰度规则测试
SDK 的:
IP
Instance
会随 GetConfig 和 WatchConfig 一起传给服务端。
例如 Go:
client, _ := configsdk.New(configsdk.Options{
BaseURL: "http://127.0.0.1:18080",
Env: "DEV",
App: "sdk-test-service",
Instance: "instance-a",
})
在 Web Console 中创建:
类型: instance
匹配: instance-a
覆盖:
feature.demo.enabled=false
保存灰度规则后,对 instance-a 的客户端应该收到:
feature.demo.enabled=false
其他 Instance 仍保持基础配置。
建议测试:
instance-a → 命中灰度
instance-b → 不命中
然后禁用/删除灰度规则,验证 instance-a 是否实时恢复基础配置。
灰度规则变更可能发生在同一个 etcd revision 上;服务端会通过强制 refresh 推送新的完整快照,SDK 不需要业务方处理这个差异。
9. gRPC 接入
9.1 当前 Compose 的注意事项
Config Server 默认监听:
:9091
但当前 compose.yaml 只映射:
18080:8080
因此宿主机业务应用默认无法访问容器里的 9091。
本地测试可以临时增加:
services:
server:
ports:
- "18080:8080"
- "19091:9091"
然后:
docker compose up -d --build
测试地址变成:
127.0.0.1:19091
如果业务应用本身也运行在同一个 Compose 网络,则可直接访问:
server:9091
9.2 Go gRPC:本地明文测试
Go SDK 的 NewGRPC() 默认要求 TLS。
当前本地 Config Server 默认没有配置 gRPC TLS,所以本地测试必须明确使用 insecure transport:
package main
import (
"context"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
configsdk "github.com/longpeng/configcenter/pkg/sdk/go"
)
func main() {
client, err := configsdk.NewGRPC(configsdk.GRPCOptions{
Target: "127.0.0.1:19091",
Env: "DEV",
App: "sdk-test-service",
Instance: "grpc-test-01",
CacheFile: "/tmp/configcenter-grpc.json",
DialOptions: []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
},
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
if err := client.Load(context.Background(), "application"); err != nil {
log.Fatal(err)
}
log.Println(client.Snapshot("application"))
}
9.3 Go gRPC:TLS 环境
如果 Config Server 已配置:
GRPC_TLS_CERT_FILE=/certs/server.crt
GRPC_TLS_KEY_FILE=/certs/server.key
且证书链能够被客户端系统信任,则可以直接:
client, err := configsdk.NewGRPC(configsdk.GRPCOptions{
Target: "configcenter.example.com:9091",
Env: "PROD",
App: "order-service",
})
NewGRPC() 默认使用 TLS 1.2+。
如果是企业自签 CA,则应通过自定义 grpc.DialOption 提供企业 CA,不要在生产环境使用 insecure credentials。
9.4 Python gRPC
本地明文:
from configcenter import ConfigClient
client = ConfigClient(
"127.0.0.1:19091",
"DEV",
"sdk-test-service",
"/tmp/python-grpc-cache.json",
transport="grpc",
grpc_secure=False,
instance="python-grpc-01",
)
client.load("application")
client.start_background_watch("application")
生产 TLS:
client = ConfigClient(
"configcenter.example.com:9091",
"PROD",
"order-service",
"/var/lib/order-service/configcenter.json",
transport="grpc",
grpc_secure=True,
)
Python SDK 当前 grpc_secure=True 使用系统默认可信 CA;企业自签 CA 的自定义 credential 能力还没有暴露到 SDK 构造参数。
10. 实际应用推荐启动顺序
推荐业务应用按以下顺序启动:
1. 创建 SDK Client
↓
2. 自动读取本地 CacheFile
↓
3. Load(namespace) 获取最新配置
↓
4. 校验关键配置
↓
5. 初始化数据库/MQ/业务组件
↓
6. 启动 WatchAndSync
↓
7. 对允许动态变化的配置持续读取 SDK 内存快照
关键点:
- SDK 缓存不能替代关键配置校验;
- 不要因为存在 fallback 就让错误数据库地址之类的配置静默启动;
Load()成功后再启动依赖配置的业务组件最稳妥;- Watch 负责后续更新,不应该代替首次
Load()。
11. 推荐的第一轮实际应用测试用例
建议选择一个非核心应用或测试服务,依次执行以下测试。
Case 1:首次读取
发布:
welcome.message=v1
启动业务应用。
预期:
SDK Load 成功
welcome.message = v1
生成本地 CacheFile
Case 2:动态更新
配置中心修改:
welcome.message=v2
点击发布。
预期:
无需重启业务应用
SDK 内存 Snapshot 变为 v2
Case 3:连续快速发布
连续发布:
v3
v4
v5
预期:
最终读取 v5
revision 单调递增
不能长期停留在中间版本
Case 4:Config Server 重启
执行:
docker compose restart server
预期:
业务应用不中断
内存配置继续可读
Watch 自动重连
Server 恢复后继续收到新配置
Case 5:业务应用重启 + Config Center 暂时不可用
先保证应用已经同步成功并生成 CacheFile。
然后停止 Config Server:
docker compose stop server
重启业务应用。
预期:
SDK 从本地 CacheFile 恢复上一次配置
Load() 返回网络错误
业务根据自身策略决定是否使用缓存继续启动
再启动服务:
docker compose start server
预期 Watch 恢复。
Case 6:实例灰度
设置:
instance-a → feature.demo.enabled=true
instance-b → 基础值 false
分别启动两个 Client。
预期:
instance-a = true
instance-b = false
Case 7:灰度规则实时撤销
删除或禁用 instance-a 的规则。
预期:
instance-a 不重启
自动恢复基础配置
Case 8:错误 Namespace
客户端读取:
application-error
预期:
Load 返回 not found
应用不会把错误 Namespace 当空配置静默覆盖关键配置
Case 9:认证权限
开启认证后给用户:
viewer: sdk-test-service
预期可以读取。
删除其应用角色后:
旧 JWT 立即失效
新请求 / Watch 重连失败
这验证 token_version 即时失效链路。
12. 应用侧建议增加的日志和指标
实际业务测试时至少记录:
configcenter_initial_load_success
configcenter_initial_load_failed
configcenter_cache_loaded
configcenter_watch_started
configcenter_watch_stopped
configcenter_current_revision
configcenter_namespace
不要把完整配置内容全部写入日志,因为配置中心可能包含:
- 数据库地址;
- Token;
- 密钥;
- 内部服务地址;
- 其他敏感参数。
建议日志只记录:
namespace
revision
key count
同步成功/失败
13. 当前 SDK 已知边界
在真正大规模生产接入前,应明确当前版本仍有以下边界。
13.1 Token 不支持热刷新
当前 Token 在 Client 构造时固定。JWT 到期后需要重建 Client。
13.2 没有配置变更 Callback
SDK 会更新内存,但没有:
OnChange
Subscribe
Callback
业务对象的重建需要应用自己实现 reconcile。
13.3 Python Watch 缺少错误回调
Python watch_and_sync() 会捕获异常并指数退避,但当前没有公开的日志/指标 hook。
13.4 Python gRPC 自签 CA 注入能力不足
当前只支持:
grpc_secure=True / False
尚未暴露自定义 root CA / client certificate。
13.5 Go SDK 目前不是独立 module
业务依赖当前会指向整个 ConfigCenter Go module。后续建议拆为独立 module,例如:
github.com/longpeng/configcenter-go-sdk
或:
github.com/longpeng/configcenter/sdk/go/v1
以便独立版本管理和发布。
14. 当前阶段推荐使用方式
对于第一批真实应用测试,建议使用下面的组合:
Transport: REST + SSE
Environment: DEV / TEST
Authentication: 第一轮关闭,第二轮开启
Instance: Pod Name / 稳定实例 ID
CacheFile: 开启
Namespace: application 起步
关键配置: 启动时 Load + 校验
动态配置: WatchAndSync + Getter
验证通过后,再测试:
gRPC
JWT/RBAC
instance 灰度
percentage 灰度
Config Server 故障
etcd 故障
网络断连
长时间运行
不要第一批就把数据库密码、证书等最关键启动配置完全迁移到配置中心。建议先从 Feature Flag、超时、限流、日志参数等低风险动态配置开始。
15. 一套最小验收标准
一个真实应用接入测试至少应满足:
- 应用启动时能够读取已发布配置;
- 未发布草稿不会被读取;
- 发布后应用无需重启即可读取新值;
- 连续发布后最终配置正确;
- Config Server 重启后 Watch 能恢复;
- 网络短暂断开后 Watch 能恢复;
- 本地缓存文件能够正常生成;
- Config Center 不可用时重启应用能够读取上一次缓存;
- Instance 灰度命中正确;
- 灰度规则撤销后能恢复基础配置;
- 开启认证时 viewer 可以读取;
- 无权限用户不能读取;
- 用户角色撤销后旧 JWT 立即失效;
- 应用日志中不输出敏感配置明文;
- 应用侧对关键配置存在校验和合理降级策略。
完成这些测试后,再将该应用作为模板推广到更多业务系统。