fix: use workbench service name for manager config

This commit is contained in:
richjohnrambo
2026-09-17 22:55:28 +08:00
parent fe7d4ccb3e
commit 7099a33cde
8 changed files with 450 additions and 8 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ ENV IM_WORKBENCH_CONFIG=/app/configs/workbench.test.json
ENV IM_WORKBENCH_INTERNAL_SERVICE_NAME=hotgo-admin
ENV IM_WORKBENCH_INTERNAL_SECRET=hotgo-workbench-internal
ENV IM_WORKBENCH_MANAGER_BASE_URL=https://admin.118im.com
ENV IM_WORKBENCH_MANAGER_SERVICE_NAME=im-server
ENV IM_WORKBENCH_MANAGER_SERVICE_NAME=im-workbench
ENV IM_CONFIG_API_SECRET=
ENV IM_INTERNAL_API_SECRET=
COPY --from=builder /out/im_workbench /app/im_workbench
+1 -1
View File
@@ -51,7 +51,7 @@ X-Request-ID: {uuid}
```text
IM_WORKBENCH_MANAGER_BASE_URL=https://admin.118im.com
IM_WORKBENCH_MANAGER_SERVICE_NAME=im-server
IM_WORKBENCH_MANAGER_SERVICE_NAME=im-workbench
IM_WORKBENCH_MANAGER_SECRET={IM_CONFIG_API_SECRET}
```
+1 -1
View File
@@ -29,7 +29,7 @@
},
"manager": {
"base_url": "",
"service_name": "im-server",
"service_name": "im-workbench",
"secret": "",
"uid_key_group": "im",
"uid_key_keys": [
+1 -1
View File
@@ -33,7 +33,7 @@
},
"manager": {
"base_url": "https://admin.118im.com",
"service_name": "im-server",
"service_name": "im-workbench",
"secret": "",
"uid_key_group": "im",
"uid_key_keys": [
@@ -0,0 +1,442 @@
# 工作台与 Manager 内部配置运维说明
更新时间:2026-09-17
## 1. 背景
`im_workbench` 需要从 `im_manager` 读取系统配置,例如:
- `im.uid_encryption_key`
读取链路如下:
```text
im_workbench
-> GET https://admin.118im.com/internal/im-config/third-party/im?keys=uid_encryption_key,UID_ENCRYPTION_KEY,uid_key
-> im_manager 校验 HMAC 签名
-> 返回系统配置
```
这个接口是服务间内部接口,不是普通前端接口。调用方必须带 HMAC 请求头:
```http
X-Service-Name: im-workbench
X-Timestamp: <unix seconds>
X-Nonce: <random nonce>
X-Signature: <hmac-sha256 hex>
```
签名使用的密钥来自 `im_manager` 测试环境 Secret 中的:
```text
manager-system / im-manager-secrets / IM_CONFIG_API_SECRET
```
注意:不要把真实密钥写进 Git 文档或代码。
## 2. 为什么 .env 不生效
测试环境运行在 Kubernetes Pod 中,服务启动时不会自动读取仓库里的 `.env` 文件。
当前代码和启动方式是:
- `im_workbench` 只读取进程环境变量和 `IM_WORKBENCH_CONFIG` 指向的 JSON 配置文件。
- `im_manager` 的 entrypoint 使用 Pod 环境变量做 `envsubst` 生成 `config.yaml`。
- 两个服务都没有 `godotenv.Load()`、`source .env` 或 docker compose `env_file` 逻辑。
所以测试环境真正生效的是:
```text
Kubernetes Secret / Deployment 环境变量
```
不是仓库中的 `.env`。
## 3. 当前 Kubernetes 配置关系
### im_workbench
命名空间:
```text
workbench-system
```
Deployment:
```text
im-workbench
```
当前 Pod 里只显式注入:
```text
IM_WORKBENCH_CONFIG=/etc/im-workbench/workbench.json
```
`/etc/im-workbench/workbench.json` 来自:
```text
workbench-system / im-workbench-secrets / workbench.json
```
因此 workbench 的运行时配置要改这个 Secret 里的 `workbench.json`,或在 Deployment 里显式加环境变量。
### im_manager
命名空间:
```text
manager-system
```
Deployment:
```text
im-manager
```
Secret:
```text
manager-system / im-manager-secrets
```
其中和内部配置接口相关的 key:
```text
IM_CONFIG_API_SERVICE_NAME
IM_CONFIG_API_SECRET
```
## 4. 必须配置什么
### 4.1 im_manager 需要允许 im_workbench 调配置接口
`im_manager` 的 `IM_CONFIG_API_SERVICE_NAME` 需要支持两个调用方:
```text
im-server,im-workbench
```
原因:
- `im_server` 也需要读取 manager 配置。
- `im_workbench` 也需要读取 manager 配置。
- 如果只配置 `im-workbench`,会影响 `im_server`。
- 如果只配置 `im-server`,`im_workbench` 用正确 service name 调用会 401。
目标配置:
```text
IM_CONFIG_API_SERVICE_NAME=im-server,im-workbench
```
`IM_CONFIG_API_SECRET` 保持现有值,不要改成空,也不要泄露到日志或文档。
### 4.2 im_workbench 需要配置 manager 连接信息
`workbench.json` 中需要有:
```json
{
"manager": {
"base_url": "https://admin.118im.com",
"service_name": "im-workbench",
"secret": "<填 manager-system/im-manager-secrets 中的 IM_CONFIG_API_SECRET>",
"uid_key_group": "im",
"uid_key_keys": [
"uid_encryption_key",
"UID_ENCRYPTION_KEY",
"uid_key"
],
"timeout_ms": 3000
}
}
```
也可以不用改 JSON,改成 Deployment 环境变量:
```text
IM_WORKBENCH_MANAGER_BASE_URL=https://admin.118im.com
IM_WORKBENCH_MANAGER_SERVICE_NAME=im-workbench
IM_WORKBENCH_MANAGER_SECRET=<填 manager-system/im-manager-secrets 中的 IM_CONFIG_API_SECRET>
```
建议优先统一放到 `workbench.json`,因为当前 workbench 已经通过 Secret 挂载 JSON 配置。
## 5. 推荐操作流程
### 步骤 1:更新 manager Secret
将:
```text
manager-system / im-manager-secrets / IM_CONFIG_API_SERVICE_NAME
```
改为:
```text
im-server,im-workbench
```
`IM_CONFIG_API_SECRET` 保持现有值。
### 步骤 2:更新 workbench Secret
从:
```text
manager-system / im-manager-secrets / IM_CONFIG_API_SECRET
```
取值,写入:
```text
workbench-system / im-workbench-secrets / workbench.json
```
目标字段:
```json
{
"manager": {
"base_url": "https://admin.118im.com",
"service_name": "im-workbench",
"secret": "<IM_CONFIG_API_SECRET>"
}
}
```
### 步骤 3:重启服务
更新 Secret 后需要重启 Pod,确保进程重新读取配置。
建议顺序:
```text
1. 重启 im-manager
2. 重启 im-workbench
```
原因:
- 先让 manager 支持 `im-workbench` service name。
- 再让 workbench 使用 `im-workbench` service name 去调用 manager。
## 6. 验证方法
### 6.1 验证 manager 是否接受 im-workbench
用 `IM_CONFIG_API_SECRET` 对请求签名,请求:
```text
GET https://admin.118im.com/internal/im-config/third-party/im?keys=uid_encryption_key,UID_ENCRYPTION_KEY,uid_key
X-Service-Name: im-workbench
```
期望结果:
```json
{
"code": 0,
"data": {
"values": {
"uid_encryption_key": "..."
}
}
}
```
如果返回 401,重点检查:
- `IM_CONFIG_API_SERVICE_NAME` 是否包含 `im-workbench`
- 请求里的 `X-Service-Name` 是否是 `im-workbench`
- `IM_CONFIG_API_SECRET` 是否和 manager 当前 Secret 一致
- 签名文本是否为:
```text
METHOD
RequestURI
BodySHA256
Timestamp
Nonce
```
### 6.2 验证 workbench 是否加载到 manager 配置
进入 workbench Pod 看环境和配置摘要,注意不要打印真实 secret:
```bash
kubectl -n workbench-system exec <im-workbench-pod> -- sh -c 'env | grep IM_WORKBENCH_MANAGER'
```
或查看挂载配置:
```bash
kubectl -n workbench-system exec <im-workbench-pod> -- sh -c 'cat /etc/im-workbench/workbench.json'
```
检查:
```text
manager.base_url = https://admin.118im.com
manager.service_name = im-workbench
manager.secret 非空
```
### 6.3 验证业务接口
从 IM 登录获取 token 后,请求 workbench 接口:
```http
GET https://api.118im.com/v1/workbench
Authorization: Bearer <IM token>
X-MeetingIM-APIKey: <api key>
X-IM-Schema: 1
X-Tenant-Code: <tenant code>
```
期望:
```json
{
"code": 0
}
```
再创建会议,确认 user id 编解码和展示名正常:
```http
POST https://api.118im.com/v1/workbench/meetings
Idempotency-Key: <unique id>
Authorization: Bearer <IM token>
X-MeetingIM-APIKey: <api key>
X-IM-Schema: 1
X-Tenant-Code: <tenant code>
Content-Type: application/json
```
请求体示例:
```json
{
"title": "运维验证会议",
"start_at": "2026-09-18T08:00:00Z",
"end_at": "2026-09-18T09:00:00Z",
"location": "",
"recorder_user_id": "usr...",
"attendee_user_ids": ["usr..."],
"reminder_minutes": []
}
```
期望:
```json
{
"code": 0,
"data": {
"state": "scheduled",
"organizer": {
"user_id": "usr...",
"display_name": "..."
},
"participant_names": ["..."]
}
}
```
## 7. 常见问题
### 7.1 为什么不能从 manager 读取 manager secret
因为 `IM_CONFIG_API_SECRET` 是调用 manager 配置接口的 HMAC 引导凭证。
如果要读取 manager 配置接口,必须先有这个 secret 才能签名。
所以它不能从同一个 manager 配置接口中获取,只能来自部署环境,例如 Kubernetes Secret。
### 7.2 为什么 service name 不是 im-server
`X-Service-Name` 表示调用方服务名。
- `im_server` 调 manager 时,应使用 `im-server`
- `im_workbench` 调 manager 时,应使用 `im-workbench`
manager 侧应该允许多个合法调用方:
```text
im-server,im-workbench
```
### 7.3 为什么配置了 .env 还是没用
因为 Kubernetes 不会自动读取仓库 `.env`。
`.env` 只有在以下情况才会生效:
- 程序代码主动 `godotenv.Load()`
- 启动脚本 `source .env`
- docker compose 配了 `env_file`
- CI/CD 将 `.env` 内容写进 Deployment 或 Secret
当前测试环境都不是这些方式。
### 7.4 只改 workbench 会怎样
如果只把 workbench 改成:
```text
X-Service-Name: im-workbench
```
但 manager 仍然只允许:
```text
IM_CONFIG_API_SERVICE_NAME=im-server
```
那么 workbench 调 manager 会返回 401。
所以需要 manager 和 workbench 一起配置。
## 8. 最终配置清单
manager:
```text
Namespace: manager-system
Secret: im-manager-secrets
IM_CONFIG_API_SERVICE_NAME=im-server,im-workbench
IM_CONFIG_API_SECRET=<保持现有值>
```
workbench:
```text
Namespace: workbench-system
Secret: im-workbench-secrets
Key: workbench.json
```
`workbench.json`:
```json
{
"manager": {
"base_url": "https://admin.118im.com",
"service_name": "im-workbench",
"secret": "<manager 的 IM_CONFIG_API_SECRET>",
"uid_key_group": "im",
"uid_key_keys": [
"uid_encryption_key",
"UID_ENCRYPTION_KEY",
"uid_key"
],
"timeout_ms": 3000
}
}
```
+1 -1
View File
@@ -105,7 +105,7 @@ func defaultConfig() *Config {
RequiredAPIKey: true,
},
Manager: ManagerConfig{
ServiceName: "im-server",
ServiceName: "im-workbench",
UIDKeyGroup: "im",
UIDKeyKeys: []string{"uid_encryption_key", "UID_ENCRYPTION_KEY", "uid_key"},
TimeoutMS: 3000,
+1 -1
View File
@@ -114,7 +114,7 @@ func (c *Client) newRequest(ctx context.Context, method string, rawURL string, b
_, _ = mac.Write([]byte(signingText))
serviceName := strings.TrimSpace(c.cfg.ServiceName)
if serviceName == "" {
serviceName = "im-server"
serviceName = "im-workbench"
}
req.Header.Set("Accept", "application/json")
req.Header.Set("X-Service-Name", serviceName)
+2 -2
View File
@@ -24,7 +24,7 @@ func TestResolveUIDKeyPrefersManager(t *testing.T) {
if r.URL.Query().Get("keys") != "uid_encryption_key,UID_ENCRYPTION_KEY,uid_key" {
t.Fatalf("unexpected keys %s", r.URL.Query().Get("keys"))
}
if r.Header.Get("X-Service-Name") != "im-server" {
if r.Header.Get("X-Service-Name") != "im-workbench" {
t.Fatalf("unexpected service %s", r.Header.Get("X-Service-Name"))
}
if !validSignature(t, r, secret) {
@@ -36,7 +36,7 @@ func TestResolveUIDKeyPrefersManager(t *testing.T) {
got := ResolveUIDKey(context.Background(), config.ManagerConfig{
BaseURL: server.URL,
ServiceName: "im-server",
ServiceName: "im-workbench",
Secret: secret,
UIDKeyGroup: "im",
UIDKeyKeys: []string{"uid_encryption_key", "UID_ENCRYPTION_KEY", "uid_key"},