mirror of
https://github.com/iflytek/skillhub.git
synced 2026-11-14 09:01:16 +00:00
fix(chart): isolate analytics config so only Web rolls
Move analytics values into a Web-only ConfigMap. The server checksum hashes the shared ConfigMap, so analytics-only changes used to replace server pods. Contract tests render before/after values and assert that analytics-only changes roll only Web while backend changes still roll the server. Docs: Compose restart does not re-read .env.release, old tabs keep the old config, a valid but unknown Umami website returns 400 and stores nothing, and new adapters also need deploy-side validation, proxy endpoints and tests. OpenSpec now separates executed Helm template checks from the real-cluster verification that has not been run. Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
This commit is contained in:
parent
6cee2fedd5
commit
f8ab71e28e
10 changed files with 79 additions and 20 deletions
|
|
@ -33,6 +33,7 @@ SKILLHUB_WEB_BASE_PATH=
|
|||
|
||||
# 可选前端动作统计,默认关闭。配置方法见 docs/frontend-analytics-integration.md。
|
||||
# 只放公开参数,不放平台密钥。JSON 保持一行;Compose .env 中可用单引号包裹。
|
||||
# 修改后需重新创建 Web(up -d --force-recreate web),restart 不会重新读取本文件。
|
||||
SKILLHUB_WEB_ANALYTICS_CONFIG=
|
||||
# 仅 Web 代理使用的 HTTP(S) origin,无路径、凭证、查询或末尾斜杠。
|
||||
SKILLHUB_WEB_ANALYTICS_UPSTREAM=
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ Sentinel、实际 MinIO S3 连接,以及由 Kubernetes API 接受的 TLS Ingre
|
|||
|
||||
默认关闭。`web.analytics.config` 接受 JSON 字符串,`web.analytics.upstream` 接受仅部署可见的
|
||||
HTTP(S) origin。支持 Umami,不需要编辑 JS 或 Nginx;先在平台创建网站并取得 UUID。
|
||||
配置变更触发 Web 滚动更新,旧标签页不会自动更新。字段、授权接口、初始化、隐私及验收见
|
||||
值放在 Web 专用 ConfigMap,只改统计配置时仅 Web 滚动更新、后端 Pod 不替换;旧标签页不会自动更新。字段、授权接口、初始化、隐私及验收见
|
||||
[前端动作统计接入指南](../../docs/frontend-analytics-integration.md)。
|
||||
|
||||
### 高可用模式
|
||||
|
|
|
|||
|
|
@ -60,6 +60,4 @@ data:
|
|||
# Sub-path deployment (empty keeps a fixed-base image's baked base; set e.g. /portal/)
|
||||
web-base-path: {{ .Values.web.basePath | default "" | quote }}
|
||||
web-api-base-url: {{ .Values.web.apiBaseUrl | default "" | quote }}
|
||||
web-analytics-config: {{ dig "analytics" "config" "" .Values.web | quote }}
|
||||
web-analytics-upstream: {{ dig "analytics" "upstream" "" .Values.web | quote }}
|
||||
builtin-skills-enabled: {{ .Values.builtinSkills.enabled | quote }}
|
||||
|
|
|
|||
13
charts/skillhub/templates/web-analytics-configmap.yaml
Normal file
13
charts/skillhub/templates/web-analytics-configmap.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{{- /*
|
||||
Web-only analytics settings. Kept out of the shared ConfigMap so that changing
|
||||
analytics only rolls Web; the server checksum hashes the shared ConfigMap.
|
||||
*/}}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "skillhub.fullname" . }}-web-analytics
|
||||
labels:
|
||||
{{- include "skillhub.web.labels" . | nindent 4 }}
|
||||
data:
|
||||
config: {{ dig "analytics" "config" "" .Values.web | quote }}
|
||||
upstream: {{ dig "analytics" "upstream" "" .Values.web | quote }}
|
||||
|
|
@ -61,13 +61,13 @@ spec:
|
|||
- name: SKILLHUB_WEB_ANALYTICS_CONFIG
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "skillhub.fullname" . }}-config
|
||||
key: web-analytics-config
|
||||
name: {{ include "skillhub.fullname" . }}-web-analytics
|
||||
key: config
|
||||
- name: SKILLHUB_WEB_ANALYTICS_UPSTREAM
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "skillhub.fullname" . }}-config
|
||||
key: web-analytics-upstream
|
||||
name: {{ include "skillhub.fullname" . }}-web-analytics
|
||||
key: upstream
|
||||
{{- with .Values.web.extraEnv }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
|
|
|
|||
|
|
@ -475,7 +475,12 @@ assert_rejected subpath-reserved-analytics --set-string web.basePath=/_skillhub-
|
|||
assert_rejected subpath-reserved-analytics-nested --set-string web.basePath=/_skillhub-analytics/nested/
|
||||
|
||||
render analytics-default "$CHART_DIR" >"$TMP_DIR/analytics-default.yaml"
|
||||
grep -Fq 'web-analytics-config: ""' "$TMP_DIR/analytics-default.yaml" || fail "analytics must default off"
|
||||
grep -Fq 'name: analytics-default-skillhub-web-analytics' "$TMP_DIR/analytics-default.yaml" \
|
||||
|| fail "analytics must render its Web-only ConfigMap"
|
||||
grep -Fq ' config: ""' "$TMP_DIR/analytics-default.yaml" || fail "analytics must default off"
|
||||
if grep -Eq '^ web-analytics-(config|upstream):' "$TMP_DIR/analytics-default.yaml"; then
|
||||
fail "analytics keys must stay out of the shared ConfigMap hashed by the server"
|
||||
fi
|
||||
# --set treats {...} as a list, so pass the JSON string through a values file,
|
||||
# the same way the operator guide documents it.
|
||||
cat >"$TMP_DIR/analytics-values.yaml" <<'VALUES'
|
||||
|
|
@ -484,11 +489,48 @@ web:
|
|||
config: '{"enabled":true,"adapter":"umami","consent":"granted","adapterConfig":{"websiteId":"4398de8e-40df-4483-9eea-ed213643525f"}}'
|
||||
upstream: https://stats.example.com
|
||||
VALUES
|
||||
render analytics-enabled "$CHART_DIR" -f "$TMP_DIR/analytics-values.yaml" >"$TMP_DIR/analytics-enabled.yaml"
|
||||
render analytics-default "$CHART_DIR" -f "$TMP_DIR/analytics-values.yaml" >"$TMP_DIR/analytics-enabled.yaml"
|
||||
for key in SKILLHUB_WEB_ANALYTICS_CONFIG SKILLHUB_WEB_ANALYTICS_UPSTREAM; do
|
||||
grep -Fq "name: $key" "$TMP_DIR/analytics-enabled.yaml" || fail "missing analytics environment"
|
||||
grep -A4 -F "name: $key" "$TMP_DIR/analytics-enabled.yaml" \
|
||||
| grep -Fq 'name: analytics-default-skillhub-web-analytics' \
|
||||
|| fail "$key must come from the Web-only analytics ConfigMap"
|
||||
done
|
||||
grep -Fq 'web-analytics-upstream: "https://stats.example.com"' "$TMP_DIR/analytics-enabled.yaml" || fail "missing analytics origin"
|
||||
grep -Fq ' upstream: "https://stats.example.com"' "$TMP_DIR/analytics-enabled.yaml" || fail "missing analytics origin"
|
||||
|
||||
# Pod template annotations: "<deployment-name> <annotation> <value>" per line.
|
||||
pod_checksums() {
|
||||
awk '
|
||||
$1 == "kind:" { kind=$2; name="" }
|
||||
kind == "Deployment" && name == "" && $1 == "name:" { name=$2 }
|
||||
kind == "Deployment" && $1 ~ /^checksum\// { print name, $1, $2 }
|
||||
' "$1" | sort
|
||||
}
|
||||
checksum_of() {
|
||||
pod_checksums "$1" | awk -v d="$2" -v k="$3" '$1 == d && $2 == k { print $3 }'
|
||||
}
|
||||
assert_checksum() {
|
||||
local before=$1 after=$2 deployment=$3 annotation=$4 expect=$5
|
||||
local a b
|
||||
a=$(checksum_of "$before" "$deployment" "$annotation")
|
||||
b=$(checksum_of "$after" "$deployment" "$annotation")
|
||||
[[ -n $a && -n $b ]] || fail "$deployment must render $annotation"
|
||||
if [[ $expect == same && $a != "$b" ]]; then
|
||||
fail "$deployment $annotation must not change"
|
||||
elif [[ $expect == changed && $a == "$b" ]]; then
|
||||
fail "$deployment $annotation must change"
|
||||
fi
|
||||
}
|
||||
web=analytics-default-skillhub-web
|
||||
server=analytics-default-skillhub-server
|
||||
# Changing only analytics must roll Web and leave every other workload alone.
|
||||
assert_checksum "$TMP_DIR/analytics-default.yaml" "$TMP_DIR/analytics-enabled.yaml" "$web" checksum/config: changed
|
||||
diff <(pod_checksums "$TMP_DIR/analytics-default.yaml" | grep -v "^$web ") \
|
||||
<(pod_checksums "$TMP_DIR/analytics-enabled.yaml" | grep -v "^$web ") >/dev/null \
|
||||
|| fail "analytics-only changes must not replace server or other pods"
|
||||
# Backend configuration still rolls the server.
|
||||
render analytics-default "$CHART_DIR" -f "$TMP_DIR/analytics-values.yaml" \
|
||||
--set-string bootstrapAdmin.displayName=Changed >"$TMP_DIR/analytics-server-change.yaml"
|
||||
assert_checksum "$TMP_DIR/analytics-enabled.yaml" "$TMP_DIR/analytics-server-change.yaml" "$server" checksum/config: changed
|
||||
|
||||
# publicBaseUrl is concatenated with paths (/cli/auth, /.well-known/clawhub.json),
|
||||
# so a query or fragment corrupts the generated URLs. Reject it independently of
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@ kubectl -n skillhub logs deployment/skillhub-web --tail=100
|
|||
```
|
||||
|
||||
deployment 名称使用 Chart 默认 release 名 `skillhub`;自定义名称时替换命令中的名称。
|
||||
值进入 ConfigMap 和 Web 环境变量,checksum 改变会触发 Web 滚动更新。
|
||||
值写入 Web 专用的 `<release>-web-analytics` ConfigMap,只改统计配置时仅 Web 滚动更新,
|
||||
后端 Pod 不会被替换。
|
||||
Chart 不自动部署 Umami。先在已有平台创建网站,并确认 Pod 出站网络、DNS 和证书可用。
|
||||
集群内 origin 使用完整服务名,例如 `http://umami.stats.svc.cluster.local:3000`,
|
||||
Nginx 的运行时 DNS 不自动补 Pod 的搜索后缀。
|
||||
|
|
@ -177,8 +178,9 @@ await window.skillhubAnalytics.start()
|
|||
|
||||
start() 是当前页的显式授权,支持初始 consent=denied。重复 start 不重复安装。
|
||||
stop() 幂等;等待中的模块/adapter 不能迟到后重启采集。stop 返回后不新派发请求,
|
||||
但已经开始的 fetch 无法撤回。规则变更使用 stop 再初始化;环境变量变化需重建或重启 Web。
|
||||
旧标签页不会自动收到新环境参数。修改 env 文件本身不会改变正在运行的容器。
|
||||
但已经开始的 fetch 无法撤回。规则变更使用 stop 再初始化。
|
||||
修改 `.env.release` 后必须重新创建 Web(`up -d --force-recreate web`);`docker compose restart`
|
||||
只重启原容器,不会重新注入环境变量。旧标签页不会自动收到新配置,需要用户刷新或关闭。
|
||||
|
||||
## 6. 根路径、子路径与验收
|
||||
|
||||
|
|
@ -228,6 +230,7 @@ curl -i http://localhost/_skillhub-analytics/api/websites
|
|||
| SDK 失败后无事件 | 加载失败/超时后不重试;恢复平台后新开页面,或 stop 再 start。 |
|
||||
| 模块/资源返回 HTML | 检查部署版本、base path 和入口路由,不能改成任意远程脚本。 |
|
||||
| CSP 拦截 | 按原策略授权,不删除 CSP 或放宽通配符。 |
|
||||
| send 返回 400 Website not found | websiteId 是合法 UUID,但 Umami 里没有这个网站。事件不会入库;配置校验只检查格式,通过不代表采集成功,需按第 6 节到平台核对入库。 |
|
||||
| 有点击但没有统计 | 规则未配置、宿主不支持、超过 Umami 名称 50 字符,或 best-effort 丢失。 |
|
||||
| 上传 413 | 检查整条外层代理链,正常 Web 仍是 100M,统计未降低它。 |
|
||||
|
||||
|
|
@ -262,8 +265,9 @@ Umami 仍可能由传输元数据推导浏览器、系统、设备、地域和
|
|||
超时或 stop 会丢弃。就绪后的请求彼此独立。不要同时加载第二个 Umami tracker。
|
||||
stop 的派发保护依赖固定版本在 fetch 前同步序列化的顺序,升级必须重新验证。
|
||||
|
||||
自定义 adapter 属于开发流程,不是部署流程:在 `web/src/features/analytics/adapters/`
|
||||
增加参数校验与 send/stop factory,注册到静态 registry,再增加部署端校验和精确代理端点。
|
||||
自定义 adapter 属于开发流程,不是部署流程。除前端 adapter 外,还要改部署侧:
|
||||
在 `web/src/features/analytics/adapters/` 增加参数校验与 send/stop factory 并注册到静态 registry;
|
||||
在 Web 启动脚本增加该平台的配置校验和精确代理端点;补充前端与 `web-analytics-test.py` 测试。
|
||||
factory 检查取消信号后才创建 SDK。通用 controller 不认识厂商字段,业务组件只提供被动标签。
|
||||
复用唯一的 `skillhub-intents.mjs`,不复制 helper,也不开放远程 JS URL。
|
||||
新增标签时运行 `node web/scripts/analytics-manifest.mjs --write` 并审核语义和原生交互。
|
||||
|
|
|
|||
|
|
@ -113,7 +113,8 @@ The loader runs separately after the first React DOM commit, never blocking moun
|
|||
Disabled, absent, invalid or denied configuration does not import the controller.
|
||||
Public `window.skillhubAnalytics.start()` explicitly grants current-page consent;
|
||||
stop() immediately disables observation. Enabled=false cannot be overridden by
|
||||
start. Deployment changes require recreating/restarting Web; old tabs do not update.
|
||||
start. Deployment changes require recreating Web (`docker compose restart` does not
|
||||
re-read the env file); old tabs do not update.
|
||||
|
||||
## Configuration and proxy
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ Nginx 通过独立 include 片段选择已注册适配器的精确端点:Umami
|
|||
|
||||
现有平台接入:填写 JSON 和内部 upstream,更新 Web。可选自托管组合:在 release Compose 上叠加统计服务与其持久化数据库,使用显式凭证,不新增 Nginx;创建网站/取得 UUID 等一次性平台步骤必须诚实说明。入口和 Helm 均默认关闭。
|
||||
|
||||
部署配置变更经重建/重启 Web 使新页面生效;旧标签页不自动接收新环境参数。动态停止通过当前页面接口生效;紧急全局停止可关闭独立采集路由。文档不得宣称仅修改 env 即刻撤销已在发送的请求。模块使用正常发布构建产物与缓存策略,不把可变配置放入 immutable JS。
|
||||
部署配置变更需重新创建 Web(Compose restart 不重新注入环境变量;Helm 统计值放在 Web 专用 ConfigMap,只滚动 Web)使新页面生效;旧标签页不自动接收新环境参数。动态停止通过当前页面接口生效;紧急全局停止可关闭独立采集路由。文档不得宣称仅修改 env 即刻撤销已在发送的请求。模块使用正常发布构建产物与缓存策略,不把可变配置放入 immutable JS。
|
||||
|
||||
### 5. Serialization and loading implementation
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
## 4. PR-2 (follow-up): deployment
|
||||
- [x] 4.1 Safely serialize and validate configuration without breaking existing runtime config.
|
||||
- [x] 4.2 Add isolated proxy snippets with request-time DNS, certificate validation and exact endpoints.
|
||||
- [ ] 4.3 Wire Docker/Compose/Helm defaults and reserved sub-path validation. (Implemented; Compose/runtime checks passed. Helm CLI rendering remains unverified.)
|
||||
- [x] 4.3 Wire Docker/Compose/Helm defaults and reserved sub-path validation. Helm keeps analytics in a Web-only ConfigMap. Template checks executed with helm 3.19.0: `helm lint --strict` and `configuration-contracts.sh`, which renders before/after values and asserts that analytics-only changes roll only Web while backend changes still roll the server. A real-cluster Helm install/upgrade with analytics enabled has not been run.
|
||||
- [x] 4.4 Provide optional persistent self-hosted Compose combination without another Nginx.
|
||||
|
||||
## 5. PR-2 (follow-up): documentation
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
## 6. PR-2 (follow-up): quality gates
|
||||
- [ ] 6.1 Consolidated independent implementation review, fixes and final base checkpoint.
|
||||
- [ ] 6.2 Reviewed requirement/scenario/test matrix and targeted source/deployment checks. (Worker matrix and local checks complete; independent review and Helm execution pending.)
|
||||
- [ ] 6.2 Reviewed requirement/scenario/test matrix and targeted source/deployment checks. (Worker matrix, local checks and Helm template checks complete; maintainer verified the real image, backend, Umami, Postgres and browser path. Real-cluster Helm verification and independent review pending.)
|
||||
- [ ] 6.3 Exact-SHA release Compose S3/auth smoke and Chromium normal/changed/failure scenarios, including default zero module/SDK/request and root/sub-path upload/login/download/CSP/cache compatibility. (Local browser/real-collector and HTTP proxy checks passed; full backend S3/auth smoke remains pending.)
|
||||
- [ ] 6.4 CI/privacy/readiness, one validation report and retained preview for human acceptance.
|
||||
- [ ] 6.5 Close Issue #989 only after configuration-only deployment is complete and verified, not when PR-1 merges.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue