Dapr 0.11.1 补丁解析:HA 模式下 Sentry 根证书竞态引发 Sidecar 认证失败的根因与修复
2026/9/12 11:48:21 网站建设 项目流程

Dapr 0.11.1 补丁解析:HA 模式下 Sentry 根证书竞态引发 Sidecar 认证失败的根因与修复

【免费下载链接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.项目地址: https://gitcode.com/GitHub_Trending/da/dapr

Dapr 0.11.1 是紧随 0.11.0 特性版发布的一个补丁版本,其唯一使命是修复一个在高可用(HA)模式下导致 Dapr 控制平面与 Sidecar 全线不可用的认证回归问题(issue #2187)。本文以该补丁为核心,结合当前仓库中 pkg/sentry 的源码实现与 Helm 图表 配置,完整还原问题现象、根因推导、修复思路,并给出 HA 模式下信任捆绑(Trust Bundle)的正确运维与升级姿势。

版本定位:为什么 0.11.0 之后紧接着发布 0.11.1

0.11.0 发布说明 引入了一批重量级能力:声明式 pub/sub 订阅、基于 SPIFFE 的服务调用访问控制、密钥作用域(secrets scoping)、跨命名空间服务调用、全新的 Helm chart 仓库(迁移至 dapr.github.io/helm-charts)等。同时,0.11.0 也为 Sentry 工作负载证书加入了强 SPIFFE ID(见 v0.11.0.md 中 Dapr Runtime 部分的 "Added strong SPIFFE based IDs to sentry workload certificates")。

正是在这样一次涉及 mTLS 证书体系的大版本迭代中,一个回归悄悄溜了进来:当集群以 HA 模式部署时,Dapr 控制平面服务无法正常工作。0.11.1 作为补丁版本专门回滚了这条回归,因此它没有新特性,只有一项 Fixes。

问题现象:HA 模式下一启动就"全线崩溃"

根据 v0.11.1.md 的 Overview 描述,该问题表现为:在启用 HA(高可用)模式时,Dapr 控制平面服务整体无法工作

需要说明的是,HA 模式在当时的 Helm 图表中意味着控制平面各组件(operator、placement、sentry、sidecar-injector 等)以多副本(典型为 3 副本)方式运行。当前仓库 charts/dapr/values.yaml 中仍保留了这套语义:

ha: enabled: false topologyKey: "topology.kubernetes.io/zone" podAntiAffinityPolicy: preferredDuringSchedulingIgnoredDuringExecution replicaCount: 3 disruption: minimumAvailable: "" maximumUnavailable: "25%"

Sentry 的 Deployment 模板 也据此在单副本与 HA 多副本之间切换:

spec: {{- if eq .Values.global.ha.enabled true }} replicas: {{ .Values.global.ha.replicaCount }} {{- else }} replicas: {{ .Values.replicaCount }} {{- end }}

而 charts/dapr/README.md 对 HA 相关参数的说明如下:

参数说明默认值
global.ha.enabled控制平面启用高可用模式false
global.ha.replicaCountHA 模式下控制平面服务副本数(Placement 固定 3 副本,不可配置)3
global.ha.podAntiAffinityPolicyPod 反亲和调度策略:preferredDuringSchedulingIgnoredDuringExecution(软)或requiredDuringSchedulingIgnoredDuringExecution(硬)preferredDuringSchedulingIgnoredDuringExecution
global.ha.disruption.minimumAvailable控制平面允许的最小可用实例数(数字或百分比)
global.ha.disruption.maximumUnavailable控制平面允许的最大不可用实例数(数字或百分比)25%

根因分析:多个 Sentry 实例竞态创建根证书

这是 0.11.1 文档给出的核心结论,也是全文最值得深挖的部分。逐条拆解:

  1. Sentry 是 Dapr 的证书颁发机构(CA)。Dapr 的 mTLS 体系里,Sentry 负责签发根证书、中间证书,以及为控制平面组件和 Sidecar 签发工作负载证书(基于 SPIFFE 身份)。

  2. HA 模式下 Sentry 有 3 个副本同时运行。在 0.11.0 的实现中,每个 Sentry 实例在启动时如果没有发现已存在的根证书,就会各自独立生成一套全新的根证书

  3. 竞态窗口:3 个实例几乎同时启动、同时判断"没有根证书"、同时各自生成了一套互不相同的根 CA。直到 Pod 重启并从 Secret 存储中重新加载证书为止,每个实例都持有不同的根 CA,于是:

    • 不同 Sentry 实例给控制平面 Pod 和 Sidecar 签发了来自不同信任根的证书
    • 对端在校验证书链时无法把对方证书回溯到自己的信任锚点,连接被拒绝
  4. 结果就是文档所述:HA 模式下控制平面服务不可用,且表现为"启动即失败、重启也难以自愈"——因为只要没有正确从共享存储加载,每个副本依然持有各自的根。

源码印证:当前仓库中 Sentry 的"加载优先、缺失才生成"模型

虽然 0.11.1 是 2020 年的补丁,但它的修复方向——所有 Sentry 副本共享同一个信任捆绑,只有在捆绑缺失时才生成并持久化——在后继版本中被保留并持续强化。当前仓库的 ca.go 可以清楚地看到这一模型的最终形态:

func New(ctx context.Context, conf config.Config) (Signer, error) { var castore store if conf.Mode == modes.KubernetesMode { // 使用 Kubernetes Secret 存储信任捆绑 castore = &kube{...} } else { // 自托管模式使用本地文件系统 castore = &selfhosted{config: conf} } bndle, err := castore.get(ctx) if err != nil { return nil, fmt.Errorf("failed to get CA bundle: %w", err) } var needsWrite bool if bndle.X509 == nil { needsWrite = true log.Info("Root and issuer certs not found: generating self signed CA") // 生成 Ed25519 根密钥 → bundle.GenerateX509(...) → 自签名 CA } else { log.Info("Root and issuer certs found: using credentials from store") } ... if needsWrite { if err := castore.store(ctx, bndle); err != nil { ... } } }

关键点:

  • 读取优先:启动后第一件事是castore.get()从存储加载信任捆绑(kube.go);
  • 缺失才生成:只有bndle.X509 == nil时才走bundle.GenerateX509生成自签名 CA(bundle.go);
  • 生成即持久化:生成后立即castore.store()写回存储,并触发monitoring.IssuerCertChanged()指标。

信任捆绑存放在哪里

在 Kubernetes 模式下,信任捆绑存放在名为dapr-trust-bundle的 Secret 与 ConfigMap 中(kube.go):

const ( TrustBundleK8sName = "dapr-trust-bundle" )
  • Secret:保存私密材料——ca.crt(信任锚点)、issuer.crt(签发证书链)、issuer.key(签发私钥);
  • ConfigMap:保存公开的根证书ca.crt,供集群内其他组件(如 sidecar、operator)获取信任锚点。

kube.get()会同时读取 Secret 和 ConfigMap,并校验两者中的ca.crt是否一致,不一致时判定需要重新生成(kube.go):

configMap, err := k.client.CoreV1().ConfigMaps(k.namespace).Get(ctx, TrustBundleK8sName, ...) if configMapRootCert, ok := configMap.Data[filepath.Base(k.config.RootCertPath)]; !ok || (hasRootCert && configMapRootCert != string(trustAnchors)) { generateX509 = true }

kube.store()则把证书与密钥原子地写回 Secret,并把公开根证书同步进 ConfigMap(kube.go)。对应地,dapr_sentry_deployment.yaml 中 Sentry Pod 以只读方式挂载该 Secret 作为凭证卷:

serviceAccountName: dapr-sentry volumes: - name: credentials secret: secretName: dapr-trust-bundle

证书如何生成

bundle.go 的GenerateX509展示了证书体系的三层结构:

  1. 根证书(Root Cert)generateRootCert生成,IsCA=trueKeyUsageCertSign,TTL 默认 365 天(cert.go);
  2. 签发证书(Issuer Cert)generateIssuerCert生成,Subject 使用 SPIFFE IDspiffe://<trust-domain>/ns/<namespace>/dapr-sentry(cert.go);
  3. 工作负载证书(Workload Cert)GenerateWorkloadCert按请求者身份生成,携带 SPIFFE URI 与ServerAuth/ClientAuth扩展用途(cert.go)。

签发入口SignIdentity位于 ca.go:它为每个请求构造 SPIFFE ID(spiffe.FromStrings(td, namespace, appID)),然后以 issuer 证书为模板、以根私钥签发。

这正是 0.11.1 问题的放大镜:这套"生成 → 持久化 → 复用"的流程,只要多个副本在没有共享存储兜底时各自走一遍"生成"分支,就会出现多套根证书并存;而一旦共享存储成为常态路径(读取优先),所有副本必然收敛到同一套证书,竞态即被消除。

修复方案:PR #2185 干了什么

v0.11.1.md 明确记录:该问题由 PR #2185 修复。结合文档描述与后续源码形态,可以还原修复的核心思路:

  • 消除多实例各自的"根证书生成竞态":Sentry 启动时优先从共享的 Secret 存储加载已有证书,而不是无脑各自生成;
  • 统一信任锚点:让 HA 模式下所有 Sentry 副本、控制平面 Pod 与 Sidecar 都收敛到同一套根 CA 与签发链,保证互相校验通过;
  • 生成结果必须落盘共享:即便需要生成(如首次部署或证书轮换),也必须在 Pod 重启前把证书持久化到共享存储,避免"重启前各持一套"的窗口。

从当前仓库的 kube_test.go 测试可以看出,"Secret 不存在则报错、ConfigMap 不存在则报错、两者内容不一致则触发重新生成"这些行为都有对应的单元测试覆盖(如 "if secret doesn't exist, expect error"、"if configmap doesn't exist, expect error" 等用例),说明信任捆绑的一致性校验已成为受测试保障的既定行为。

运维视角:HA 模式下信任捆绑的正确管理

理解了根因之后,HA 集群的信任捆绑运维就有章可循了:

1. 启用 HA 模式

当前仓库 charts/dapr/README.md 给出的安装命令:

helm install dapr dapr/dapr --namespace dapr-system --create-namespace --set global.ha.enabled=true --wait

HA 模式下 Sentry 会以global.ha.replicaCount(默认 3)个副本运行,并通过 PodDisruptionBudget(默认maxUnavailable: 25%)保护滚动升级期间的可用性。dapr-trust-bundleSecret/ConfigMap 在安装时即由 Helm 模板创建(见 dapr_sentry_deployment.yaml 中的 Secret/ConfigMap 定义),成为所有副本共享的唯一信任源。

2. 备份与轮换信任捆绑

信任捆绑属于一旦丢失/不一致就必须整体重建的关键资产。0.11.0 升级指南(v0.11.0.md)中给出的导出命令至今仍是备份的实用手段:

dapr mtls export -o ./certs

该命令会导出ca.crtissuer.crtissuer.key三个文件,可用于灾难恢复或跨集群复制。

3. 升级时显式指定证书,避免重新生成

在升级控制平面时,0.11.0 指南(v0.11.0.md)建议把导出的证书通过--set-file显式注入 Helm chart,确保新旧控制平面持有同一套信任锚点,杜绝升级过程中"部分副本持有新根、部分持有旧根"的混用:

helm upgrade dapr dapr/dapr --version 0.11.0 --namespace dapr-system --reset-values \ --set-file dapr_sentry.tls.root.certPEM=./certs/ca.crt \ --set-file dapr_sentry.tls.issuer.certPEM=./certs/issuer.crt \ --set-file dapr_sentry.tls.issuer.keyPEM=./certs/issuer.key

Sentry 子图表 values 中tls.issuer.certPEM / keyPEMtls.root.certPEM正是承接这些注入值的入口,模板会将其写入dapr-trust-bundleSecret/ConfigMap。

4. 升级后验证

升级完成后,用以下命令确认控制平面各组件均为 Running 且 HEALTHY:

kubectl get pods -w -n dapr-system dapr status -k

随后对 Dapr 应用执行滚动重启以拉取新版本 Sidecar:

kubectl rollout restart deploy/<deployment-name>

质量保障补强:e2e 测试纳入 HA 模式

0.11.1 文档还记录了一项配套改进:issue #2188 提出除了单 Pod 部署,e2e 测试还必须在 HA 模式下运行,该 issue 随后由 PR #2189 关闭。

这背后是测试基建的务实补课:此前 e2e 只覆盖单副本部署,天然无法暴露"多实例竞态"这类只有并发才会触发的问题。把 HA 模式纳入回归矩阵后,未来任何涉及 Sentry 证书生成/加载的改动都会被 HA 场景兜住,避免同类回归再次漏网。

总结

Dapr 0.11.1 是一个"小补丁、大教训"的版本:

  • 回归源点:0.11.0 大版本迭代中,HA 模式下多 Sentry 副本各自生成根证书的竞态被引入;
  • 失效链路:多套根 CA → 各副本签发不同证书 → 控制平面与 Sidecar 无法互信 → 连接被拒绝;
  • 修复本质:信任捆绑"读取优先、缺失才生成、生成即共享持久化",让所有组件收敛到唯一信任锚点(PR #2185);
  • 防复发机制:e2e 测试增加 HA 模式覆盖(issue #2188 / PR #2189)。

从当前仓库 pkg/sentry/server/ca 的源码看,这一修复方向已被完整继承:Kubernetes 模式下 Sentry 通过dapr-trust-bundleSecret/ConfigMap 共享信任捆绑,ca.go 以"加载 → 校验 → 缺失才生成 → 生成即持久化"的流程从机制上杜绝了多副本证书分叉。对于 HA 集群的运维者,牢记三条即可:启用 HA 时确认共享信任捆绑就位、升级时显式注入证书、升级后滚动重启应用

【免费下载链接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.项目地址: https://gitcode.com/GitHub_Trending/da/dapr

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询