SPIRE 多活 / 双机 / 高可用(HA)/ 容灾 分析
分析对象:SPIRE v1.15.2 所有
路径:行号,已用一手代码/文档核对。 配套文档:整体架构 · SVID 颁发流程
0. 核心结论(TL;DR)
SPIRE 的高可用不是"主备切换 + leader 选举",而是真·多活(active-active)。全库无 leader 选举 / Raft / 分布式锁(grep -rniE "leader.?elect|raft|paxos|distributed.lock" pkg/server/ 零命中)。它由四层正交机制叠加而成:
| 层 | 机制 | 出处 |
|---|---|---|
| ① 共享状态 | 同一 trust domain 的多个 spire-server 读写同一个共享 SQL datastore | doc/scaling_spire.md:15 |
| ② 各自签发,统一信任 | 每个 server 维护自己独立的 CA 密钥,把自己的 CA 证书 append 到共享 bundle;所有 server 签的 SVID 都能用同一 bundle 验证 | ca/manager/manager.go:1035;doc/scaling_spire.md:19 |
| ③ 上游/嵌套统一信任根 | 经 UpstreamAuthority 让多 server 挂到同一外部 CA(Vault/AWS PCA/…)或上游 SPIRE(Nested),中间 CA 上溯同一 root | 见 §3、§4 |
| ④ Agent 侧容错 | Agent 只从本地内存缓存给 workload 发 SVID,并提前轮换,server 短时全挂也不影响存量 workload | 见 §6 |
一句话:多台 server 无主地共享一个数据库 + 各签各的 CA 汇入共享 bundle,所以既不需要 leader,也不需要共享签名密钥。
1. 共享状态存储(HA 的基石)
1.1 DataStore:多 server 共享的"单一事实源"
DataStore 接口(pkg/server/datastore/datastore.go:14)存储所有需要跨 server 共享的动态状态:
| 类别 | 内容 | 行号 |
|---|---|---|
| Bundles | 信任 bundle(X509 CA + JWT 授权公钥) | datastore.go:16-24 |
| Registration Entries | 注册条目 | datastore.go:33-41 |
| Entry / Node Events | 变更事件流(用于多 server 缓存同步,见 1.4) | datastore.go:44-67 |
| Attested Nodes | 已认证节点/agent | datastore.go:51-60 |
| Join Tokens | 一次性入网令牌 | datastore.go:75-78 |
| Federation Relationships | 联邦关系 | datastore.go:81-85 |
| CA Journals | 每 server 的 CA 轮换账本(见 2.2) | datastore.go:88-91 |
除了 CA 私钥本身(在各自 KeyManager),其余全部状态都在这个共享库里。任一 server 宕机,其他 server 从同一库读到完全相同的注册条目/节点/bundle。
1.2 支持的数据库,以及为何生产多活必须用共享 Postgres/MySQL 而非 sqlite
内置 SQL 插件(pkg/server/datastore/sqlstore/)支持的方言:mysql / postgres / sqlite3,外加带 IAM 认证的 aws_mysql / aws_postgres。
sqlite 不能用于生产多活的根本原因——它是本地单文件、单写者数据库:
// pkg/server/datastore/sqlstore/sqlstore.go:1022
// sqlite3 can only have one writer at a time. since we're in WAL mode, ...这个写锁是进程内(db.opMu.Lock()),跨不了机器/进程;连接串就是本地文件路径,两台 server 无法安全共享。因此官方要求横向扩展时"configure all servers in same trust domain to read and write to the same shared datastore"(doc/scaling_spire.md:15),即 Postgres 或 MySQL(网络多写数据库)。
多 server 对同一行的并发"读-改-写"由 withReadModifyWriteTx 保证:MySQL/Postgres 走 SELECT ... FOR UPDATE 行锁(sqlstore.go:980-999),注释还特意声明兼容 Percona XtraDB Cluster 与 PostgreSQL hot-standby。
1.3 读写分离(read-only replica):ro_connection_string + TolerateStale
明确支持,这是 SPIRE 为大规模多活设计的读副本卸载能力:
- 配置项
ro_connection_string(sqlcommonconfig;conf/server/server_full.conf:288-291;doc/plugin_server_datastore_sql.md:239,注明不适用 sqlite)。非空时插件额外打开第二条只读连接ds.roDb。 - 读一致性开关
DataConsistency枚举(datastore.go:95):RequireCurrent(默认,读主库)vsTolerateStale(允许读副本、可能略陈旧)。只有调用方传TolerateStale且ro_db已配置才走副本。 - 一个精妙的安全默认:首次全量加载缓存强制走主库,只有增量事件扫描才允许走副本——
authorized_entryfetcher_registration_entries.go:144注释// preliminary loading should not be done via read-replicas(RequireCurrent)vs 增量扫描TolerateStale。
1.4 事件驱动的多 server 缓存一致性(HA 的关键隐藏机制)
每台 server 在内存里维护一份"授权注册条目缓存"(authorizedentries.Cache)以避免每次签发都全表扫库。多台 server 如何保持这份缓存近实时一致?靠共享库里的 events 表 + 轮询:
server A 改注册条目 → 写共享库并产生一条 event
→ server B 下个轮询周期看到 event(ListRegistrationEntryEvents, GreaterThanEventID)
→ server B 刷新自己的内存缓存- 增量扫描
scanForNewEvents(authorized_entryfetcher_registration_entries.go:108-136,TolerateStale可下沉只读副本); - 初始全量
loadCache(:139-168,RequireCurrent); - 跳号/乱序事件用
eventTracker(endpoints/eventTracker.go)补偿。
这就是多活下各 server 缓存最终一致的机制,且读事件可下沉到只读副本,减轻主库压力。
1.5 托管数据库(AWS RDS / Aurora,IAM 认证)
database_type = "aws_postgres" | "aws_mysql" 接入托管库,驱动 pkg/server/datastore/sqldriver/awsrds/ 用 buildAuthToken 生成短期 IAM 令牌连库。生产上通常把共享 datastore 交给 RDS Multi-AZ / Aurora 这类自带 HA/failover/只读副本的托管服务——数据库层的 HA 由数据库自己保证,SPIRE 只管连。
2. 多 Server 实例的协调:CA 签名密钥如何共处
2.1 关键设计:各 server 独立 CA,证书汇入共享 bundle(无共享密钥、无 leader)
doc/scaling_spire.md:19 一句话点破 HA 下的 CA 模型:
In High Availability mode, each server maintains its own Certificate Authority, which may be either self-signed certificates or an intermediate certificate off of a shared root authority (i.e. when configured with an UpstreamAuthority).
即多个 server 不共享签名密钥、也不选主。每台 server:
- 在自己的 KeyManager 里生成自己的 CA 私钥(
manager.go:280); selfSignX509CA(root 模式)或upstreamSignX509CA(nested/upstream 模式);- 把这张 CA 证书 append 到共享 bundle:
// pkg/server/ca/manager/manager.go:1035
res, err := ds.AppendBundle(ctx, &common.Bundle{
TrustDomainId: m.c.TrustDomain.IDString(),
RootCas: rootCAs, // 本 server 自己的 CA 证书
JwtSigningKeys: jwtSigningKeys,
})于是共享 bundle 里累积了全部 server 各自的 CA 证书。任何 agent 从任一 server 取到的都是这个合并 bundle,所以 server A 签发的 SVID 能被"验证 server B 所签 SVID"的一方接受——统一信任、零协调。这就是不需要 leader、不需要共享私钥的根本原因。
2.2 CA journal:每 server 一份,靠 KeyManager 公钥自我定位
ca/manager/journal.go 把本 server 轮换出的 CA 密钥账本持久化到共享库(SetCAJournal)。多台 server 共用一张 ca_journals 表,如何区分哪条属于哪台 server? 靠 KeyManager 里的公钥反查(findCAJournal,journal.go:250-285):
遍历本 server KeyManager 的所有公钥 → 算 authorityID → FetchCAJournal(authorityID)
→ 匹配上 = 这条 journal 属于我含义:server 的身份 = 它 KeyManager 里的密钥。只要密钥还在,重启后就能找回自己的 CA 账本、继续用原 CA;找不到匹配则当作全新 server、重新准备 CA。→ 这直接决定了 §2.4 KeyManager 的选型。
2.3 Prepare / Activate / Rotate:每 server 自主轮换,无需协调
每台 server 各跑一个 Rotator,每 10s 检查一次自己的 CA slot(ca/rotator/rotator.go),完全自主:
- 准备提前量 = 生命周期的 1/2(上限 30 天);
- 激活/切换提前量 = 生命周期最后 1/6(上限 7 天)。
即"过半准备下一张,进入最后 1/6 才切换",中间有 current 与 next 两张 CA 都在 bundle 里的重叠期,保证轮换平滑。因为准备/激活都是往共享 bundle 追加新 CA,多台 server 各自轮换天然无冲突。PruneCAJournals(safety 14 天)清理已下线 server 的陈旧账本。
2.4 KeyManager 在多活下的选型(memory vs disk vs KMS)——重要
CA 私钥不在共享库里,而在各 server 的 KeyManager 插件中。多活下选型直接影响"server 重启/重调度后能否找回自己的 CA"(见 2.2):
| KeyManager | 私钥位置 | 多活/重启行为 |
|---|---|---|
| memory | 仅进程内存 | 重启即丢钥 → 匹配不到 journal → 每次重启生成全新 CA、bundle 膨胀。能用但不推荐 |
| disk | 本机磁盘 keys_path | 只有重挂到同一持久卷才找得回;容器漂移到别的节点且非持久卷时等同 memory |
| KMS 类(AWS KMS / GCP KMS / Azure Key Vault / HashiCorp Vault) | 外部 KMS,私钥永不出 KMS | 密钥与计算实例解耦,pod 可自由重调度仍能取回自己的钥;安全性也最好 |
对"共享 KMS 才能真多活"这一常见说法的精确修正:KMS 的价值不是让多台 server 共享同一把密钥,而是把每台 server 各自的密钥材料从具体主机上解耦,使弹性/容器环境下 server 可随意扩缩容、重调度而不丢 CA 身份。 AWS KMS 文档(
doc/plugin_server_keymanager_aws_kms.md:56-63)印证:每台 server 有独立实例标识(key_identifier_file),各自管理自己的一组 KMS 密钥(用 tagspire-server-id区分);某台永久下线后其密钥两周无刷新会被其他 server 回收。 准确表述应是:多活下推荐用 KMS/Vault 类 KeyManager,让每个 server 的持久 CA 密钥不绑死在某台主机上。
3. UpstreamAuthority 与信任链(多 server 挂同一上游 CA)
3.1 作用
当配置了 UpstreamAuthority,每台 server 的 CA 走 upstreamSignX509CA:把自己的公钥做成 CSR → upstreamClient.MintX509CA(...) → 上游签回一张中间 CA。因 X.509 链式语义,不同 server 铸出的中间 CA 都上溯到同一个上游 root,彼此签的 SVID 互相可验。上游 root 经流式订阅写进共享 bundle(upstream_client.go → SyncX509Roots → ds.AppendBundle),因共享 datastore 而对全体 server 一致。
3.2 各上游插件(共享外部 CA)
| 插件 | 目标共享 CA | 文档 |
|---|---|---|
vault | HashiCorp Vault PKI | doc/plugin_server_upstreamauthority_vault.md |
aws_pca | AWS ACM Private CA | ..._aws_pca.md |
gcp_cas | GCP Certificate Authority Service | ..._gcp_cas.md |
cert-manager | K8s cert-manager | ..._cert_manager.md |
ejbca | EJBCA | ..._ejbca.md |
awssecret | AWS Secrets Manager 里的 root CA 私钥 | ..._awssecret.md |
disk | 本地磁盘 PEM CA | ..._disk.md |
spire | 同域上游 SPIRE server(Nested 专用) | ..._spire.md |
两个实践注意点:
- JWT 发布差异:只有
spire上游插件真正实现PublishJWTKeyAndSubscribe,其余返回Unimplemented。故用了 JWT-SVID 的 Nested 拓扑不适合用 Vault 上游(doc/plugin_server_upstreamauthority_vault.md明确)。- disk 上游从本机 PEM 文件加载 root 私钥:要让多台 server 用它链到同一 root,必须把相同的 cert/key 文件分发到每台 server——这是把 root 私钥直接放盘的固有运维/安全代价,也是生产上更推荐 KMS/Vault/PCA 而非 disk 的原因。
4. Nested SPIRE(嵌套/分层)多活拓扑
命名更正:旧文档常提的
SignX509CASVID在 v1.15.2 不存在。真实名称:服务端 RPCSVID/NewDownstreamX509CA,内部方法ca.SignDownstreamX509CA(见 SVID 颁发流程 §4.1 的ServerCA接口)。
4.1 root 侧:给 downstream server 签中间 CA
root server 的 handler(pkg/server/api/svid/v1/service.go:603)带授权门:只有 caller 命中一条 Downstream==true 的注册条目才放行:
// pkg/server/api/svid/v1/service.go:603
func (s *Service) NewDownstreamX509CA(ctx, req) (...) {
...
downstreamEntries, isDownstream := rpccontext.CallerDownstreamEntries(ctx)
if !isDownstream { // :614 授权门
return ... "caller is not a downstream workload"
}
x509CASvid, err := s.ca.SignDownstreamX509CA(ctx, ca.DownstreamX509CAParams{ // :637 root 私钥签中间 CA
PublicKey: csr.PublicKey, TTL: ...,
})
// 返回 中间 CA 链 + root bundle
}门控链路完整:注册条目 Downstream bool 列(CLI spire-server entry create -downstream)→ OPA 策略 allow_downstream(policy_data.json)→ 中间件 isDownstreamViaEntries 校验 caller 的 entries。中间 CA 模板设 IsCA=true+KeyCertSign,Subject OU 打 DOWNSTREAM-<链深度>(证实可多级嵌套)。
4.2 downstream 侧:spire 上游插件 + 并置 Agent
downstream server 用 upstreamauthority "spire" 插件去 root 领中间 CA。认证方式很关键:downstream server 通过与它并置(co-located)的 SPIRE Agent 的 Workload API 取得自己的 SVID,再用它 mTLS 认证到 root——正是 doc/scaling_spire.md:45 说的"obtains credentials over the Workload API to directly authenticate with the upstream SPIRE Server"。
4.3 拓扑与故障隔离
[Root SPIRE Server(s)] ← 持 root key;本层可用"共享 DB 多实例"再做 HA
签中间 CA(NewDownstreamX509CA)
│ mTLS(经各下游并置 Agent 的 Workload API)
┌───────┼─────────┐
[Downstream A] [Downstream B] [Downstream C] ← 区域/集群级 server
独立 datastore 独立 datastore 独立 datastore 各持一张中间 CA
│ │ │
本区 Agents/WL 本区 Agents/WL 本区 Agents/WL要点(均有出处):
- 同一 trust domain、单一信任根,全域 SVID 互验(
doc/scaling_spire.md:43)。 - root 宕机不影响下游:下游只在首次/轮换时找 root 领中间 CA,拿到后用本地中间 CA 独立签本区 workload——
:49"the top tier can go down, and intermediate servers will continue to operate"。 - 下游互相隔离:每个下游有独立 datastore(
:103),单个下游或其库故障只影响本区——:53"containment strategy to segment failure domains"。 - 两级各自可再 HA,正交叠加;多云友好(下游可用不同 node attestor);并降低 root 侧库压力(昂贵的 per-agent 授权下沉到各下游库)。
5. 联邦(Federation):跨 trust domain 交换信任 bundle
联邦本质是"各 trust domain 互换信任 bundle"。配置入口 FederationConfig(pkg/server/config.go:168):BundleEndpoint(出向发布)+ FederatesWith(入向拉取)。
5.1 出向:bundle endpoint 发布本域 bundle
pkg/server/endpoints/bundle/server.go 起独立 HTTPS 服务(TLS ≥1.2,只接 GET /),从共享库取本域 bundle,序列化为 SPIFFE 标准 JWKS(X.509 标 use:"x509-svid"、JWT 标 use:"jwt-svid")。两种认证 profile:
| profile | 认证 | 特点 |
|---|---|---|
| https_spiffe(默认) | 直接用本 server 自己的 SVID 作 TLS 证书 | 需 bootstrap:先有对方 bundle 才能拉对方 bundle,首建联邦要离线交换一次初始 bundle |
| https_web(ACME / 磁盘证书) | Web PKI 证书 | 无 bootstrap 依赖,更适合跨组织/跨云松耦合 |
5.2 入向:周期刷新外域 bundle
bundle/client/Manager 每 10s 对账联邦关系,每个外域一个 goroutine 按 refresh hint(默认 5 分钟)轮询 updater.UpdateBundle:从对端取 JWKS → 与本地不同才 ds.SetBundle 落库;拉取失败保留本地旧副本——对端宕机不丢既有信任。配置源:静态 federates_with(配置文件)+ 动态 Trust Domain API,静态覆盖动态。
5.3 与多活/多区域的关系
- 正交两层:单 trust domain 内 HA(多 server 共享库/信任根)与跨 trust domain 联邦是两个正交维度。
- 多区域典型形态:每 region 一套独立 trust domain + 独立 CA + 独立库,region 间通过 bundle endpoint 互换 CA;跨区域信任只靠交换公开 bundle,不共享私钥/不共享数据库,耦合最低、爆炸半径最小。
- workload 侧落地:注册条目带
FederatesWith,agent 下发 SVID 时把对应外域 bundle 塞进WorkloadUpdate.FederatedBundles,workload 即可验证外域对端。
6. Agent 侧的高可用
6.1 连一个 server_address(DNS/L4 LB + 客户端 round-robin)
Agent 配单个 server_address(可为 DNS 名或 IP)。它被包成 gRPC dns:/// scheme 并启用客户端 round-robin,所以一个 DNS 名解析出的多台 server IP 会被 gRPC 自动轮询——这就是连接层 HA:
// pkg/agent/client/dial.go:23
roundRobinServiceConfig = `{ "loadBalancingConfig": [ { "round_robin": {} } ] }`生产上把 server_address 指向 DNS 名 / L4 LB(如 K8s Service server_address="spire-server")即可后挂多台 server。
6.2 server 不可达时,用缓存 SVID 继续服务 workload
Workload API 只读本地内存缓存,从不直连 server(handler → Manager → 纯内存 LRU cache)。因此 server 全挂期间,存量 workload 的 X509-SVID 照常发放。JWT-SVID 还有显式兜底:server 不可达则返回缓存副本(manager.go:341-343 "Unable to renew JWT; returning cached copy")。
6.3 轮换提前量(对抗 server 宕机的缓冲)
默认半寿命轮换:SVID 剩余寿命降到生命周期一半(±10% 抖动打散峰值)就提前轮换,留出约半个寿命的缓冲容忍 server 短时不可用(common/rotationutil/rotationutil.go)。可选 availability_target(仅 X509,须 ≥24h)把缓冲拉得更长,以覆盖更久的 server 停机窗口。
6.4 同步失败保留旧缓存
两个同步循环(条目 5s、SVID 500ms,失败指数退避)在拉取出错时提前 return、根本不动缓存;只有拉取成功才 UpdateEntries/UpdateSVIDs。agent 自身 SVID 轮换失败且未过期时只记日志、继续用现有 SVID。→ server 宕机期间缓存原样保留,持续发证。
7. 实践拓扑总结
综合 doc/scaling_spire.md:23-27(三种拓扑)与上述代码:
7.1 三种拓扑对比图
一句话区分:A 是同一信任域内加机器(共享库、共享信任根);B 是同一信任域内分层(root 签中间 CA 给独立库的下游,切故障域);C 是跨信任域(各自独立,只交换公开 bundle 建立互信)。
7.2 对比表
| 维度 | A · 单域多 Server | B · Nested | C · 联邦 |
|---|---|---|---|
| 信任域数 | 1 | 1 | 多(每域一个) |
| 信任根 / CA | 各 server 自签 CA 汇入共享 bundle(或共用上游) | root 持 root key,downstream 用中间 CA | 各域独立 root |
| 数据库 | 1 个共享库(所有 server) | root 1 个 + 每 downstream 各 1 个独立库 | 每域各自独立库 |
| SVID 互验 | 全域直接互验 | 全域直接互验(同一信任根) | 需先交换 bundle 才能互验 |
| 故障隔离 | 弱(共享库是关键点) | 强(下游互隔,root 挂下游继续) | 最强(完全独立) |
| 跨区/跨云 | 共享库跨区延迟高、不推荐 | 适合(下游可用不同 attestor) | 适合(松耦合) |
| 有无 leader/主从 | 无(server 对等多活;主备下沉到 DB 层) | 无(两级各自对等) | 无(各域独立) |
| 典型场景 | 同区域 HA 与横向扩展 | 大规模 / 多集群 / 故障隔离 | 多组织 / 多管理域 / staging-prod 隔离 |
7.3 三种形态的落地要点
| 形态 | 结构 | 适用场景 | 权衡 |
|---|---|---|---|
| A. 单 trust domain + 多 server + 共享 DB | N 台 server 共享 Postgres/MySQL(可加只读副本);各 server 独立 CA 汇入共享 bundle;推荐配 UpstreamAuthority 统一 root + KMS/Vault KeyManager | 单一管理域、同区域/同集群的 HA 与横向扩展 | 最简单;但共享 DB 跨地域/跨云延迟高、运维复杂;DB 是关键单点,须用 RDS/Aurora 类托管 HA + 备份做容灾 |
| B. Nested(root + 多 downstream) | 顶层 root(自身可 HA)持 root key 签中间 CA;各区域一个 downstream,各有独立 DB,经 spire 上游领中间 CA | 跨区域/跨云/大规模;需故障域隔离 | root 挂了下游继续跑;下游互相隔离、单点半径小;降低顶层库压力;代价是拓扑复杂、每个下游需并置 agent |
| C. 多 trust domain + 联邦 | 每区域/组织一个独立 trust domain(独立 root/CA/DB),经 bundle endpoint 互换 bundle | 多管理域、跨组织互通、staging/prod 隔离 | 耦合最低、爆炸半径最小、不共享私钥/DB;但跨域信任是"最终一致"(按 refresh hint 刷新),https_spiffe 首建需 bootstrap |
实战常叠加:例如每个 region 内用形态 A(多 server + 共享库 + KMS)做 HA,region 之间用形态 C 联邦;或形态 B 的 root 层用形态 A 做 HA。
doc/ 下相关文档索引
doc/scaling_spire.md— 核心:HA 模式、三拓扑、sizing 表(:107-112均从 2 Server Units 起,即默认双实例 HA)doc/plugin_server_datastore_sql.md— 共享 datastore、支持的 DB、ro_connection_string只读副本、Cloud SQL/AWS IAMdoc/plugin_server_keymanager_aws_kms.md— KMS KeyManager 的 HA 章节(:56-63);同类_gcp_kms.md/_azure_key_vault.md/_hashicorp_vault.mddoc/plugin_server_upstreamauthority_spire.md— Nested 专用spire上游插件doc/spire_server.md(federation 配置/bundle endpoint)、doc/spire_agent.md(server_address、availability_target、LB 示例)doc/multiple_instances.md— 单机用 systemd 模板跑多实例(注:是运维便利,非 HA 机制本身)
8. 辨析:没有"active-active 双活"时,是 1主1备n从吗?还是靠联邦?
这是一个常见疑问。结论先行:
SPIRE server 层不存在 master / standby / follower / leader 的概念。全库搜
master|standby|primary|leader|failover(grep -rniE ... pkg/server/)只命中一处,还是sqlstore.go:996关于 PostgreSQL "hot standby" 的注释——那说的是数据库,不是 SPIRE。
8.1 为什么 SPIRE server 没有主从
SPIRE server 是近乎无状态的对等节点(peers):所有需要共享的动态状态(注册条目、attested nodes、bundle、events、join tokens、CA journal)都在外部共享 SQL 库里(见 §1.1),server 进程本身几乎不持久化关键状态(唯一的例外是各自 KeyManager 里的 CA 私钥,见 §2.4)。既然状态不在 server 内,就没有"谁是权威副本"的问题,也就不需要选主、不需要主从复制——要么跑 1 台(单点),要么跑多台且全部 active。SPIRE 里没有"平时待命、主挂了才顶上的 standby server"。
8.2 "1主1备n从" 属于数据库层,不是 SPIRE 层
你想要的主从/主备语义,SPIRE 把它下沉给了数据库:
| 你的诉求 | SPIRE 的落地方式 |
|---|---|
| 主库 + 只读从库(读扩展) | 用 Postgres/MySQL 的 primary + read replica;SPIRE 配 ro_connection_string + TolerateStale 把读打到从库(§1.3) |
| 主库故障自动切换(failover) | 由数据库 / 托管服务负责(RDS Multi-AZ、Aurora、Patroni 等);SPIRE server 无感,它只连一个(可故障转移的)连接串 |
| server 冗余 | 多台 server 一律 active-active,连同一个(高可用的)库 |
也就是说:"1 master + 1 standby + n follower" 是你给数据库选的拓扑,SPIRE server 永远是对等多实例。一个常见的生产组合就是:3 台 spire-server(active-active) + 1 主 + 1 备 + N 只读副本的 Postgres。
8.3 联邦不是用来做同域高可用的
- 联邦解决的是"跨信任域互信"(不同 root/CA 的两个 trust domain,如何让 X 域的 workload 认证 Y 域的 workload),手段是互换公开 trust bundle(§5)。
- 同一信任域内的冗余,永远是"多 server 共享库"(§1、§2),而不是联邦。把联邦拿来做同域 HA 是概念错配:联邦两端是两套独立的信任根与数据库,并不共享注册表,彼此签发的身份要显式交换 bundle 才互认。
8.4 三句话总结
- 想要 server 冗余 → 多台 active-active + 共享库(无主从,§1)。
- 想要"主/备"语义 → 那是数据库的事(primary/standby + failover),SPIRE server 仍是多活对等。
- 想跨信任域认证 → 用联邦(交换 bundle),这与"高可用"是正交的两件事。
附:一句话回答"SPIRE 怎么做多活"
同一 trust domain 内,多台 server 无主地共享一个 SQL 数据库存放全部注册/节点/bundle/事件状态(sqlite 因单文件单写者不可用,须 Postgres/MySQL,可加只读副本卸载读);每台 server 各签各的 CA 并把证书汇入共享 bundle(靠 KeyManager 公钥认领各自的 CA journal,推荐 KMS/Vault 让密钥不绑主机),故无需 leader、无需共享签名密钥;跨区域/跨故障域再用 Nested(root 签中间 CA 给独立库的下游,root 挂下游继续跑)或联邦(独立 trust domain 互换 bundle)扩展;Agent 侧经 DNS/LB round-robin 连多 server,并从本地缓存 + 提前半寿命轮换持续给 workload 发证,扛得住 server 短时全挂。