Consul Service Mesh:从 Connect 接管到多数据中心治理
发布窗口刚开始,client 对 orders-v1 的调用却出现了一个很别扭的现象:应用日志里一部分请求是 200,另一部分是连接重置;Consul 控制台中的服务全是绿色,Pod 也全部 Ready。值班同学先重启应用,错误率短暂下降,随后又恢复。真正的线索藏在请求路径里:旧 Pod 仍直连 Kubernetes Service,新 Pod 已被透明代理接管,而一条新 intention 只到达了部分 Envoy。三个“健康”界面描述的是三种不同状态,没有一个能单独回答请求到底经过了哪里。
另一次事故更危险。团队把 intentions 从默认允许收紧为默认拒绝,验证了一次 client -> orders-v1 返回 200 便宣布完成。几小时后,未纳管的批处理仍能明文直连 orders-v1,而已建立的长连接在 deny 生效后继续传输。mTLS、身份、授权、连接生命周期和底层网络边界被揉成了一个绿色结论。Consul Connect 真正需要治理的,正是这些看似相邻、实际独立的状态。
先把一次请求拆成两条链
Consul 的 control plane 保存服务注册、健康、配置条目、CA、ACL 和授权目标状态;它不转发 client 的业务字节。Data plane 才是承载流量的 Envoy sidecar 与各种 gateway。Kubernetes 的当前形态通常由 consul-dataplane 在 sidecar 内管理 Envoy,它不是另一层业务代理;VM 则常由本机 Consul client agent 缓存发现、证书和 intention 信息并向 Envoy 提供配置。控制面架构与数据面架构把这两条链分得很清楚。
控制链是“声明 -> Consul server -> 配置生成 -> xDS 下发 -> Envoy ACK”;请求链是“应用 socket -> iptables 或显式 upstream -> 源 Envoy -> mTLS -> 目标 Envoy -> intention -> 应用端口”。前者成功不保证后者被接管,后者偶尔成功也不保证身份与策略正确。因此后面的每一步都保留四类证据:声明对象及版本、代理实际配置、身份与证书、带 request ID 的正反请求。
Consul 2.0 起采用 IBM Version-Modification-Fix 生命周期语义,不应只按普通 SemVer major 理解。Kubernetes 安装还要同时固定 Consul、consul-k8s/Helm chart、Kubernetes 与 consul-dataplane/Envoy 组合;目标组合应从Consul on Kubernetes 兼容页和Envoy 兼容矩阵核对。下面用 2.0.2 展示对象形态,真正落地时还应固定 chart 版本与镜像 digest,而不是让 latest 漂移。
许可也会改变架构决策。Consul 1.17.0 及以后源码采用 Business Source License 1.1,Additional Use Grant 通常允许组织内部生产使用,但限制向第三方提供与 IBM 付费产品竞争的托管或嵌入式产品;各版本在各自 Change Date 后转为 MPL 2.0。SaaS、再分发、嵌入和商业支持场景应让法务基于官方 LICENSE判断,不能把“可下载”写成 OSI 开源或无限制商用。
在隔离集群启用 Connect
先准备一个可丢弃的 Kubernetes 集群、Helm、kubectl 和一个不承载真实业务的 mesh-lab namespace。下面的三 server 示例还要求至少三个可调度节点、可动态供给的持久卷和满足反亲和的拓扑;资源不足的单节点实验应明确改用一个 server,不能把它的可用性结论外推到生产。执行者需要创建 CRD、MutatingWebhookConfiguration、ClusterRole、StatefulSet、Secret,以及在显式暴露网关时创建 LoadBalancer 的权限。ACL bootstrap token、gossip key、CA 私钥与 Helm release Secret 都是高敏资产,不应出现在 shell history、Git、工单截图或 CI 明文日志中。
创建 consul-values.yaml:
global:
name: consul
datacenter: mesh-lab-dc1
image: hashicorp/consul:2.0.2
tls:
enabled: true
acls:
manageSystemACLs: true
server:
replicas: 3
storage: 10Gi
connectInject:
enabled: true
default: false
transparentProxy:
defaultEnabled: truedatacenter 进入发现、身份和跨数据中心语义,部署后随意改名不是无损操作。server.replicas: 3 提供 Raft 多数派,仍需反亲和、持久卷和快照;把它改成 1 只能得到实验控制面。manageSystemACLs 让 chart 管理系统 ACL 材料,便利的代价是安装者和 bootstrap Secret 获得高价值权限。default: false 则把接入变成显式选择,避免整个共享集群在一次升级时同时改变网络路径。这里的 global.tls.enabled 保护 Consul agent/API/RPC 通信,不是 Connect 工作负载证书的开关;Connect CA 是服务器维护的另一套签发状态,二者的根、轮换和恢复材料必须分开盘点。
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
export CONSUL_CHART_VERSION='<从兼容矩阵固定的版本>'
helm upgrade --install consul hashicorp/consul \
--namespace consul --create-namespace \
--version "$CONSUL_CHART_VERSION" \
--values consul-values.yaml
kubectl -n consul get pods
kubectl get mutatingwebhookconfigurations | grep consulHelm 安装指南是安装入口。预期 server 形成多数派,injector/controller 可用,PVC 已绑定;这些只能证明控制面具备工作条件。不要把 token 打印到终端。需要诊断 ACL 时,使用临时环境变量或受控 Secret 投递,并确保命令追踪关闭、日志脱敏、会话结束后撤销。
让 mesh-lab 的请求真正进入数据面
创建 namespace 并显式打开注入。下面假定已经有 client、orders-v1、orders-v2 三个虚构 Deployment,以及分别名为 client、orders 的 Service,其中 orders 选择两个目标版本;应用响应体应回显版本与 X-Request-ID,这样才能区分代理本地错误和真正的上游响应。ACL 开启时,默认 binding rule 不接受 default ServiceAccount,且 Consul 服务名应与 Pod 的 ServiceAccount 名一致,所以先把调用方和两个目标版本绑定到专用身份。
connectInject.default: false 表示每个 Pod 都必须显式选择注入。namespace 只能通过 k8sAllowNamespaces、k8sDenyNamespaces 或 namespaceSelector 决定“允许 injector 处理哪些 namespace”,不能代替 Pod template 上的 consul.hashicorp.com/connect-inject: "true"。透明代理则可以由 namespace label 设置默认值,再被 Pod annotation 覆盖。这两个入口长得相似,却控制不同阶段。
kubectl create namespace mesh-lab --dry-run=client -o yaml | kubectl apply -f -
kubectl -n mesh-lab create serviceaccount client --dry-run=client -o yaml | kubectl apply -f -
kubectl -n mesh-lab create serviceaccount orders --dry-run=client -o yaml | kubectl apply -f -
kubectl -n mesh-lab patch deployment client --type merge -p \
'{"spec":{"template":{"spec":{"serviceAccountName":"client"}}}}'
kubectl -n mesh-lab patch deployment orders-v1 --type merge -p \
'{"spec":{"template":{"spec":{"serviceAccountName":"orders"}}}}'
kubectl -n mesh-lab patch deployment orders-v2 --type merge -p \
'{"spec":{"template":{"spec":{"serviceAccountName":"orders"}}}}'
kubectl label namespace mesh-lab consul.hashicorp.com/transparent-proxy=true
for workload in client orders-v1 orders-v2; do
kubectl -n mesh-lab patch deployment "$workload" --type merge -p \
'{"spec":{"template":{"metadata":{"annotations":{"consul.hashicorp.com/connect-inject":"true"}}}}}'
done
kubectl -n mesh-lab rollout restart deploy/client deploy/orders-v1 deploy/orders-v2
kubectl -n mesh-lab rollout status deploy/client
kubectl -n mesh-lab rollout status deploy/orders-v1
kubectl -n mesh-lab rollout status deploy/orders-v2
kubectl -n mesh-lab get pods
kubectl -n mesh-lab get pod -l app=client -o jsonpath='{range .items[*].spec.containers[*]}{.name}{"\n"}{end}'Pod template annotation 和 namespace label 都只影响之后经过 admission 的 Pod,旧 Pod 不会凭空长出 sidecar;所以必须 rollout。预期 Pod spec 出现应用容器与 Consul dataplane/Envoy 相关容器、init 或注解,但这仍不是流量证据。若 rollout 卡住,先看 injector webhook、Pod event、init container 与 dataplane 日志;不要通过删除 webhook 或把 failurePolicy 改成 Ignore 让未纳管 Pod 静默进入生产。接着在 client 内发一组带唯一 request ID 的请求:
kubectl -n mesh-lab exec deploy/client -c client -- sh -c '
for n in 1 2 3 4 5; do
curl -sS -D- -H "X-Request-ID: mesh-lab-$n" http://orders:8080/version
done'期望应用响应能看到 orders-v1 与 orders-v2,源代理访问日志包含相同 request ID 和目标 cluster,目标代理日志包含来源服务身份,orders 应用日志也包含该 ID。若应用返回 503 而目标应用没有日志,优先判断为代理本地无健康 upstream、TLS 或 intention 拒绝;若目标应用明确返回 500,责任在上游。单次 200 不能说明所有 Pod、长短连接和配置版本均已收敛。
透明代理依靠流量重定向,让应用继续访问原有 Service/DNS。透明代理说明指出,这并不会自动处理 hostNetwork、特殊 DNS、UDP、loopback、直连 Pod IP、headless/StatefulSet 和管理端口。DialedDirectly 一类按实例直连配置会改变处理方式,并可能绕开依赖 HTTP route 的 L7 能力。任何 excluded inbound/outbound port 都是一条潜在旁路:排除过少会让探针或控制连接失败,排除过多会让敏感流量不受身份与 intention 约束。
CA、证书与 intention 是三个不同开关
Connect CA 为服务实例签发 SPIFFE-compatible X.509 证书。证书证明“对端携带了由可信 CA 签发的服务身份”,mTLS 提供双向认证和传输加密;是否允许该身份访问目标服务,由 intention 单独决定。Connect 机制和intentions 文档是这条边界的权威入口。
先建立最小允许关系,再用低优先级的 wildcard intention 显式拒绝其余调用。这样实验不依赖集群当前的 acl.default_policy,而精确的 client -> orders allow 会优先于 wildcard deny。Kubernetes CRD 的具体字段应与已安装 chart 同步:
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceIntentions
metadata:
name: orders
namespace: mesh-lab
spec:
destination:
name: orders
sources:
- name: client
action: allow
---
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceIntentions
metadata:
name: mesh-default-deny
namespace: mesh-lab
spec:
destination:
name: "*"
sources:
- name: "*"
action: denykubectl apply -f orders-intention.yaml
kubectl -n mesh-lab get serviceintentions orders mesh-default-deny -o yaml创建 intention 的授权按 destination 判定。项目交付身份若只应管理 orders,就给它目标服务对应的最小 service:write/intention 能力,不要发放 management token;再用一个只有 service:read 或无目标写权限的身份提交同样变更,预期 API/控制器拒绝且既有对象 generation 不变。Kubernetes RBAC 允许创建 CR 并不代表 Consul ACL 接受配置,反过来持有 Consul token 也不代表能写 Kubernetes CR,两条权限链都要留下拒绝证据。
正向实验从 client 新建连接访问 orders,预期成功。反向实验临时创建同样已注入、身份为 example-test 的 Pod 发请求,预期在目标 Envoy 被拒绝,orders 应用没有对应 request ID。若反例仍成功,依次检查默认策略、来源身份、目标 service 名、对象 generation、controller 状态和代理实际 RBAC 配置;不要先放大通配 allow。
L4 intention 在连接建立时判定。把 allow 改成 deny 后,旧 TCP 连接可能继续传输,新连接才被拒绝;因此紧急撤权还要配置连接排空、最大连接寿命或主动终止。L7 permission 可以按 method、path、header 判断,但前提是服务 protocol 正确声明为 HTTP 类协议。把 HTTP 错标为 TCP 会失去请求级授权与路由;把私有二进制协议错标为 HTTP 则会在解析处失败。
渐进迁移中的 permissive mTLS 更容易产生假安全:明文请求没有 mTLS identity,官方行为是它不受 intentions 保护。它只能作为短暂迁移态。切回 strict 后应同时证明正确身份成功、错误身份拒绝、未注入明文失败,并检查是否存在可绕过 sidecar 的 NetworkPolicy、路由或主机网络路径。CA 状态也要单独取证:记录当前 root ID、active root、leaf 有效期与续签失败指标;轮换演练先让新旧根重叠,等待各代理接收新 trust bundle,再签发新 leaf,最后撤销旧根。只看到新 root 已创建,不能证明旧代理、离线 VM 和长连接已经跨过轮换窗口。
用配置条目做版本路由,而不是让重试掩盖故障
Consul 将不同流量职责拆成不同配置条目:service-router 处理 L7 匹配和 destination,service-splitter 做权重分流,service-resolver 管 subset、failover 与部分超时,service-defaults.upstreamConfig 管连接、并发和被动健康检查。Intention 只负责授权,不能替代路由或重试。
可以先把 orders-v1、orders-v2 注册成 orders 的两个 subset,再将 90%/10% 流量分配给它们。验证不能只读配置对象:连续发送足够请求,按响应版本统计趋势,同时把代理 cluster/route 配置与对象版本对应起来。反向实验把 v2 endpoint 置为故障响应,观察 passive health check 是否暂时摘除、重试是否换到 v1、总调用次数是否增加。
这里最容易制造“恢复了但更慢”的事故。客户端重试 2 次、源代理再重试 2 次,并不一定是总共 4 次;在每层都重新发起的情况下可能放大为 9 次尝试。request timeout、idle timeout、connect timeout 也不是同一个时钟。团队应先定义一次业务请求的总 deadline,再为连接、单次尝试和退避分配预算;只有幂等操作才允许自动重试,并监控 attempt_count、reset、被摘除 endpoint 与上游实际 QPS。
VM 和 Kubernetes 混合接入
VM 生产拓扑通常由 3 或 5 个 server agent 与每台机器的 client agent 组成。可用的 mesh 还需要持久化 data dir、TLS/gossip encryption、ACL bootstrap、CA、服务注册、sidecar proxy 和 DNS/API 可达性;consul agent -dev 只适合本机观察对象,不是生产模板。
VM 上先以服务定义注册 orders-v1 及其 sidecar,再用 consul connect envoy -sidecar-for orders-v1 生成并启动 Envoy。生产环境不要让应用进程持有 management token;为 agent、服务注册、sidecar 与运维读取分别签发最小 ACL token,通过文件权限严格的 Secret store 投递。Envoy bootstrap、leaf certificate、ACL token 和 gossip key 都不得进入制品或普通诊断包。
Kubernetes 当前默认的 Consul Dataplane 不要求每个节点运行 client agent。它减少 gossip/client agent,却把更多 xDS 生成与连接负载集中到 server。混合接入时还要打通 server RPC/gRPC、必要的 gossip 或 mesh gateway 路径,正确分发 CA 与 ACL,并验证 Kubernetes Service identity 与 VM service registration 对齐。仅仅两边安装相同版本不会自动互通。
xDS 解释了为什么控制面断线后行为不一致
Consul 根据 catalog、upstream、证书、intentions 和配置条目生成 listener、cluster、route、secret 与 RBAC filter,再通过 xDS 发给 Envoy。VM agent 会缓存 CA root、leaf、intentions 和 upstream discovery;Kubernetes dataplane 也会保留代理最后收到的配置。因此控制面短暂不可达时,既有代理和既有连接可能继续工作。
这不是无限容灾承诺。新 Pod 需要注入、bootstrap、证书与首次 xDS;旧证书最终需要续签;策略修复也无法下发。断开控制面的反向实验要分别观察:已有连接、新连接、已有 Pod 新请求、新 Pod、证书临近轮换和策略变更。预期“旧请求仍成功但新 Pod无法就绪”是一种合理的部分失效,不应被概括成 mesh 正常。
排障时按请求链倒推:
应用是否真的访问 orders,request ID 和目标端口是什么。Pod spec、iptables 与排除端口是否让流量进入源 Envoy。源 Envoy 是否有对应 listener、cluster、endpoint 和 secret,最近一次 xDS 是否 ACK。
双端证书的服务身份、信任根、有效期和时钟是否一致。目标 Envoy 的 intention/RBAC 是 allow 还是 deny,拒绝发生在连接还是 HTTP 请求层。目标应用是否收到请求并返回响应。
DNS 失败、无 endpoint、TLS verify、policy deny、upstream reset 和应用 500 的第一证据不同。拓扑图只能提示发生过遥测,不能证明当前配置;Trace 缺失还可能只是应用没有传播 W3C/B3 context。访问日志、指标和 Trace 是三条独立证据链,生产中应对 service、namespace、path 等标签控制基数,并对证书、token、内部地址与请求头脱敏。
多数据中心先选一致性模型,再选网关
每个 Consul datacenter 都有自己的 catalog 与 Raft 控制面。WAN federation 和 cluster peering 不是同义词:前者有 primary datacenter,权威 mesh 配置、ACL 和根 CA 由 primary 协调,secondary 可用本地 intermediate CA 签 leaf;后者连接相对独立的控制面并共享选定资源。先决定是否需要共同治理域,再决定网络连接方式。
Mesh gateway 可以承载跨 datacenter/partition 的控制面或数据面流量,但它也成为吞吐、连接和故障传播边界。演练至少要覆盖 primary 断连、mesh gateway 不可用、远端服务删除、错误 CA、重叠地址和跨区误路由,分别观察既有连接与新连接。跨区成功请求还要保留源身份、目标身份、gateway 日志和远端应用 request ID,不能只看 WAN members。
根 CA 私钥、replication ACL token、gossip key 和跨区凭据是最高等级资产。根信任迁移应采用旧根与新根重叠、双端刷新、逐批验证、最后撤销旧根的过程。把 CA 私钥导出到仓库或普通 CI 变量,即使网络隔离完善,也会把所有服务身份变成可伪造资产。
容量与成本要测三次放大
数据面成本随 RPS、并发连接、payload、TLS、L7 filter、重试和日志/Trace 采样变化;控制面成本随 service、proxy、endpoint、config 数和变更率变化;遥测成本随标签基数、采样率和保留期变化。Consul Dataplane 把 server 与每个代理的 xDS 关系变得更直接,所以不能只测 Envoy CPU。
压测应先保存无 mesh 基线,再测仅 mTLS、加入 intention、加入 L7 route/retry、故障恢复和证书轮换。固定硬件、版本、镜像 digest、连接模型和采样配置,比较 p50/p99、error/reset、Envoy CPU/RSS、server CPU/RSS、xDS 收敛、活动连接、日志字节和 active series。停止条件应来自 SLO 与资源预算;某个网上的单代理数字不能替代真实服务图。
长期成本还包括三类副本:每 Pod sidecar 的资源预留,3/5 server 与跨区 gateway 的高可用,日志指标 Trace 的存储查询。高变更率发布会触发配置风暴,CA 轮换会同时触发 secret 更新,故障恢复又会造成连接重建;只测平均稳态会错过最危险的长尾。
升级和退出都是状态迁移
升级前导出 server snapshot、Helm values、CRD/配置条目、CA/ACL 恢复材料、webhook、PVC 与 gateway 地址,并核对升级总览、版本特定说明和目标兼容矩阵。VM 逐个升级 server,确认 quorum、leader 与健康后再继续 client 和 Envoy;Kubernetes 还要按 partition 推进 server,并 rollout workload/gateway 才能取得新 sidecar。旧 client-agent 架构迁移到 Dataplane 时,必须等所有工作负载重新注入并验证后才删除 client DaemonSet。
回滚不等于把镜像标签改回去。旧版本必须能读取升级后的 Raft 状态、配置条目和 CRD,Envoy 与证书仍在兼容窗内。一次演练应验证旧到旧、旧到新、新到旧、新到新四种请求方向,并把“流量回切”“配置回退”“二进制回退”“状态恢复”分别定义。
退出时先给 client -> orders 恢复非 mesh 的 TLS、认证、负载均衡、timeout/retry 和 NetworkPolicy,做正反验证;再按 cohort 移除注入标记并 rollout,确认 Pod spec 无代理且请求不再命中旧 Envoy。随后注销 VM service/proxy、删除 intentions 与 route entries、停止跨区导出、撤销仅属于退网对象的 ACL/CA 材料,最后才卸控制面和 CRD;共享根 CA 不能按单服务退网直接销毁。
# 先恢复并验证非 mesh 安全路径,再让新 Pod 不再注入或重定向
kubectl label namespace mesh-lab consul.hashicorp.com/transparent-proxy-
for workload in client orders-v1 orders-v2; do
kubectl -n mesh-lab patch deployment "$workload" --type=json \
-p='[{"op":"remove","path":"/spec/template/metadata/annotations/consul.hashicorp.com~1connect-inject"}]'
done
kubectl -n mesh-lab rollout restart deploy/client deploy/orders-v1 deploy/orders-v2
kubectl -n mesh-lab get pods -o jsonpath='{range .items[*].spec.containers[*]}{.name}{"\n"}{end}'
# 确认无注入容器、旧代理连接和残留 CR 后再清理实验 namespace
kubectl delete namespace mesh-lab
# 控制面卸载是独立且更高风险的动作
consul-k8s uninstall -namespace consulKubernetes 卸载文档说明默认会保留 Secret 与 PVC;-wipe-data=true 才会删除安装数据。这个参数不能作为普通清理命令。controller 先停还可能让 CRD finalizer 无法完成,所以应在卸载前清空 CR、检查 webhook、ServiceAccount、PVC、Secret、LoadBalancer 和 DNS。退出的最终证据是:业务已有替代安全路径、所有 workload 无代理、旧控制面无连接、跨区 gateway 流量归零、旧凭据与 CA 已撤销、替代告警正常、存储与云资源完成核销。
Consul Connect 适合需要跨 Kubernetes 与 VM 的统一服务身份、Envoy 数据面、配置化流量治理和多数据中心连接,并愿意承担 Consul 控制面及其许可治理的团队。若系统只运行在单一 Kubernetes 集群、调用图简单、NetworkPolicy 与应用 TLS 已满足需求,额外的 sidecar、CA、xDS 和遥测成本可能大于收益。选型的关键不是功能框数量,而是团队能否长期拥有证书轮换、策略评审、容量压测、升级兼容和退出恢复这五条责任链。
