Files
configcenter/docs/sdk-integration-guide.md
longpeng 1369e01ba0
Some checks are pending
CI / verify (push) Waiting to run
chore: use internal module path for http sdk
2026-08-30 12:55:33 +08:00

24 KiB
Raw Permalink Blame History

Config Center SDK 实际应用接入与测试指南

本文用于把当前 Config Center SDK 接入真实 Go / Python 应用进行功能验证,覆盖首次接入、动态更新、本地缓存、灰度规则、认证和 gRPC 测试。

当前推荐的验证顺序:REST/SSE → 本地缓存/断线恢复 → 灰度规则 → 认证 → gRPC。不要一开始同时启用所有能力,否则出现问题时较难定位是配置数据、权限、网络还是传输层问题。


1. 当前 SDK 能力

当前仓库提供三种客户端形态:

  • Go 轻量 REST/SSEpkg/sdk/gohttp。这是独立 Go module要求 Go 1.24+,只依赖标准库,推荐普通业务应用首先使用。
  • Go 完整 SDKpkg/sdk/go。支持 REST/SSE + gRPC跟随 ConfigCenter 主 module 的 Go/toolchain 与 gRPC 安全版本。
  • Pythonsdk/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 到期:

  1. 当前 Watch 会断开;
  2. SDK 会尝试重连;
  3. 但仍会继续使用旧 Token
  4. 服务端持续返回认证失败;
  5. 需要应用重新获取 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不依赖 grpcprotobuf、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 每次收到完整配置后:

  1. 更新内存快照;
  2. 保存 Namespace 当前 revision
  3. 写入临时文件;
  4. fsync
  5. 原子 rename 为正式缓存文件。

缓存文件权限按 SDK 实现设置为:

0600

目录会使用:

0700

进程启动时 Config Center 不可用

SDK Client 构造时会先尝试读取缓存文件。

因此:

上一次同步成功
        ↓
进程停止
        ↓
Config Center 临时不可用
        ↓
业务进程重新启动
        ↓
SDK 加载磁盘缓存

应用仍可以通过 Getter 得到上一次成功同步的值。

第一次启动没有缓存

如果第一次部署时 Config Center 就不可访问,则没有可恢复的远端配置。

因此所有关键 Getter 都应有合理 fallback或者应用根据配置的重要级别选择 fail-fast。

例如数据库密码等关键配置不应该随意给一个错误默认值继续运行。


8. 灰度规则测试

SDK 的:

IP
Instance

会随 GetConfigWatchConfig 一起传给服务端。

例如 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 gRPCTLS 环境

如果 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 4Config 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 立即失效;
  • 应用日志中不输出敏感配置明文;
  • 应用侧对关键配置存在校验和合理降级策略。

完成这些测试后,再将该应用作为模板推广到更多业务系统。