服务网格工作负载纳管:从 Sidecar 注入到 Ambient、eBPF 与 VM
凌晨发布后,client 调用 orders 开始间歇性超时。平台页面显示控制面全部 Ready,mesh-lab 命名空间也带着注入标签,值班同学因此先去查业务线程池。抓取 Pod 以后却发现,旧的 orders-v1 没有代理,新滚动出来的 orders-v2 有 sidecar;同一个 Service 后面混着两种数据路径,只有部分请求接受网格身份、策略与遥测。
另一次迁移更隐蔽:团队把命名空间改成 ambient,以为“没有 sidecar”就等于流量不再经过代理。应用容器确实没有新增进程,但节点上的 ztunnel 已接管 L4;需要 HTTP 身份匹配的策略却没有 waypoint 执行。一次成功的 200 同时掩盖了两个事实:请求已经换路,而 L7 策略仍没有落点。纳管真正要管理的不是标签,而是一条从选择到退出的状态链。
先选数据面,再决定怎样加入
服务网格把控制面生成的身份、路由和策略交给数据面执行,但数据面不只有“每个 Pod 一个代理”。选型时先画出真实请求经过哪里:
sidecar 让故障域和资源随工作负载分散,能在源、目标两侧做细粒度处理,代价是每个 Pod 都增加代理、连接池、内存和升级对象。Istio ambient 把 L4 安全隧道交给每节点 ztunnel,只有需要 L7 策略的流量才经过 waypoint;应用 Pod 没有 sidecar,不代表没有代理。Cilium 的 L3/L4 主路径由 CNI、agent 与 eBPF 程序处理,L7 流量仍可能被送入 Envoy。VM 或裸机既没有 Kubernetes admission,也没有天然 Pod 生命周期,通常要显式注册身份、启动代理并安装宿主机透明转发规则。
判断用哪一种,不能只比较平均延迟。还要问:团队是否需要每工作负载故障隔离,节点级代理故障会影响多少服务,L7 策略占比多大,是否已有 CNI/eBPF 运维能力,VM 比例多高,代理和证书由谁升级,以及退出时能否恢复原始网络。没有一个形态能同时把资源、隔离、能力和迁移成本都降到最低。
在隔离命名空间建立直连基线
先准备一个可删除的 Kubernetes 测试集群、kubectl、目标产品 CLI 或 Helm 3,以及创建 namespace、Deployment、Service、查看 Pod 日志和执行端口转发的权限。安装网格控制面还需要集群级 CRD、webhook、ClusterRole 与 CNI 权限,应由平台账号执行;应用账号只获得 mesh-lab 内的工作负载和策略权限。版本必须从目标产品的 release 与 Kubernetes 支持矩阵锁定,镜像最好固定 digest,不能把 latest 当团队合同。
下面的骨架先在没有纳管标签的 mesh-lab 中部署 client、orders-v1 和 orders-v2。orders 两个版本应返回可区分的版本字符串,并回显调用方生成的 X-Request-Id;Service 只选择 app: orders。项目仓库里应把这些 Kubernetes 对象放在 deploy/mesh-lab/,由 Kustomize 或 Helm 管理,而不是让开发者手工修改线上 Deployment。
kubectl create namespace mesh-lab
kubectl -n mesh-lab apply -f deploy/mesh-lab/orders-v1.yaml
kubectl -n mesh-lab apply -f deploy/mesh-lab/orders-v2.yaml
kubectl -n mesh-lab apply -f deploy/mesh-lab/client.yaml
kubectl -n mesh-lab rollout status deploy/orders-v1
kubectl -n mesh-lab rollout status deploy/orders-v2
kubectl -n mesh-lab rollout status deploy/client
kubectl -n mesh-lab exec deploy/client -- sh -c `
'for i in 1 2 3 4 5; do wget -qO- --header="X-Request-Id: baseline-$i" http://orders:8080/version; echo; done'预期证据不是固定的 v1/v2 比例,而是五次请求都能解析 orders、连接 8080 并返回 orders-v1 或 orders-v2,同时三个 Pod 都没有网格代理容器。保存 Pod UID、容器列表、Service endpoints、请求 ID 与应用日志。这是退出后的比较基线;若直连都不稳定,继续启用网格只会增加变量。
Sidecar:标签只影响下一次 Pod 创建
以 Istio 为例,先按安装指南核对目标 release、平台要求与 profile,再在隔离集群安装。实验环境可以使用 CLI 生成清单,生产环境应把 profile、revision、Hub、tag 与 digest 固化到受评审的 values 中:
istioctl version
istioctl install --set profile=minimal --set revision=mesh-lab-r1 --skip-confirmation
kubectl get pods -n istio-system
istioctl analyze --all-namespacesprofile 决定安装的组件集合,revision 决定 webhook 与控制面的升级轨道;它不是普通说明标签。revision 名写错会让新 Pod 找不到预期 injector,混用 istio-injection=enabled 与 revision label 还会产生选择优先级问题。具体规则和手工注入入口应以Istio sidecar injection 文档为准。
先用 revision label 选择整个 mesh-lab namespace,但不重建任何 Pod。这样选择状态已经变化,旧 Pod 仍可作为“尚未经过准入”的对照。不要使用 kubectl label deployment:它只修改 Deployment 自身的 metadata,injector 看不到这个标签。
kubectl label namespace mesh-lab istio.io/rev=mesh-lab-r1 --overwrite
istioctl experimental check-inject -n mesh-lab deploy/client
kubectl -n mesh-lab get pod -l app=client `
-o jsonpath='{range .items[*]}{.metadata.name}{" containers="}{.spec.containers[*].name}{"\n"}{end}'check-inject 应显示究竟由哪个 webhook、哪个 revision 命中,以及未命中的原因;若同时保留 istio-injection 与 istio.io/rev,应先消除多重选择,而不是靠一次滚动碰运气。预期现有 Pod 仍看不到 istio-proxy,Deployment template 也不会因为 namespace 标签而变化;admission webhook 只在新 Pod 创建时改写提交给 API Server 的 Pod。现在执行滚动并保存准入后的 Pod JSON:
kubectl -n mesh-lab rollout restart deployment/client
kubectl -n mesh-lab rollout status deployment/client
kubectl -n mesh-lab get pod -l app=client -o yaml > client-admitted.yaml
istioctl proxy-status正向判据至少有四层:Pod spec 出现代理及重定向相关 init/CNI 配置;代理与控制面建立连接并同步;代理能看到 orders cluster 或 endpoint;带请求 ID 的真实请求出现在代理访问日志并到达应用。仅看到多一个容器,不能证明流量经过它。
接着制造未纳管反例:保持 orders-v1 和 orders-v2 旧 Pod 不动,只重建 client。在默认允许迁移流量的配置下,请求可能仍然成功,但这一跳不能据此称为双向网格 mTLS。检查源代理的连接安全状态与目标应用日志,再滚动两个 orders Deployment,让新 Pod 经过准入:
kubectl -n mesh-lab rollout restart deployment/orders-v1 deployment/orders-v2
kubectl -n mesh-lab rollout status deployment/orders-v1
kubectl -n mesh-lab rollout status deployment/orders-v2
istioctl proxy-status把自动注入接进项目时,标签应落在 Deployment 的 Pod template 或由平台管理的 namespace/revision,而不是只执行一次命令。CI 可渲染 manifest 后检查 injection 选择器、排除端口和 ServiceAccount;部署后再检查新 ReplicaSet 的 Pod UID、代理同步与请求证据。这样版本回滚会同时恢复业务镜像和纳管声明。
流量接管决定了什么会被看见
sidecar 常用 init container 修改 Pod 网络命名空间里的 iptables,或由 CNI 在 Pod 创建期间完成重定向。核心逻辑是把匹配的入站、出站 TCP 流量送到代理 listener,同时排除代理自身 UID、loopback、控制面、DNS、健康探针和显式端口。任何一个排除字段都同时影响可用性与安全:排除过少会形成递归代理或探针失败,排除过多会留下绕过路径。
遇到“代理存在但没有遥测”,按包的方向检查,而不是先重装控制面:
用 Pod UID 确认请求发自新 Pod,而不是仍存活的旧 ReplicaSet。检查 Service endpoint 是否指向预期 Pod IP,避免把无端点误诊为代理问题。在允许的调试环境读取 Pod 网络规则或 CNI 状态,确认 8080 是否落在捕获集合。
查看代理 listener、cluster、endpoint 与配置 ACK/NACK,区分“包没进代理”和“代理没有上游配置”。用同一 request ID 关联源代理、目标代理和应用日志;只有源端日志常意味着目标未纳管或目标入站旁路。
健康探针要单独验证。kubelet 发起的探针来源和网络路径可能与业务请求不同,部分实现会重写 HTTP probe。若注入后 Pod 永远不 Ready,比较准入前后的 probe 字段、目标端口和代理就绪状态,不要简单把探针端口加入全局排除;那会让业务也可能绕过入站策略。hostNetwork、UDP、同 Pod loopback、Job/CronJob、headless Service 与直接 Pod IP 都应进入接入评审,因为它们不一定服从普通 ClusterIP 的路径假设。
Ambient:没有 Pod Sidecar,仍要证明 ztunnel 与 waypoint
Istio ambient 的接入入口和限制以工作负载加入 ambient为准。下面的安装与前面的 sidecar 实验应使用另一套可删除集群;不要把第二次 istioctl install 直接覆盖到同一控制面。安装时必须启用与目标版本匹配的 ambient profile、CNI 与 ztunnel,之后给 namespace 或 Pod 选择数据面:
istioctl install --set profile=ambient --skip-confirmation
kubectl label namespace mesh-lab istio.io/dataplane-mode=ambient
kubectl -n istio-system get daemonset,pods
istioctl ztunnel-config workloads -n istio-system | Select-String 'mesh-lab'这里 istio.io/dataplane-mode=ambient 选择的是数据面模式;它不会给应用 Pod 增加容器,也不要求为了纳管而重建现有 Pod。ztunnel-config workloads 中目标 workload 应出现 HBONE 协议,且实际请求应同时留下源/目标身份、连接安全属性和应用 request ID。若标签存在但 workload 没进入 ztunnel 视图,优先检查 CNI 事件、节点 ztunnel、Pod 级 istio.io/dataplane-mode=none 覆盖和 host network,而不是滚动业务。然后创建一个明确不在 ambient 中的 Pod 作为反例,验证迁移策略下它的行为与受管 Pod 不同。
HTTP 路由、JWT 或基于方法/路径的授权需要 L7 执行点,通常要部署 waypoint 并让目标服务使用它。waypoint 创建成功不等于任何流量会使用它,必须显式绑定目标:
istioctl waypoint apply -n mesh-lab --name orders-waypoint
kubectl -n mesh-lab wait gateway/orders-waypoint `
--for=condition=Programmed --timeout=120s
kubectl -n mesh-lab label service orders `
istio.io/use-waypoint=orders-waypoint --overwrite
kubectl -n mesh-lab get service orders `
-o jsonpath='{.metadata.labels.istio\.io/use-waypoint}{"\n"}'默认 waypoint 处理发往 Service 的流量;若调用方直接访问 Pod IP 或 VM IP,则要把 waypoint 的 istio.io/waypoint-for 设为 workload 或 all,并在对应 workload 上绑定。正向实验要在 waypoint 访问日志中找到同一 request ID;反向实验访问只靠 L4 无法判断的 /admin,预期由 waypoint 拒绝且应用无业务日志。若策略状态已接受但 /admin 仍成功,检查原始目标类型、istio.io/use-waypoint 的生效层级、Gateway Programmed 状态和数据面配置。不能从 namespace 已加入 ambient 推出 L7 已启用。
sidecar 与 ambient 混合迁移时,要显式列出每个 Deployment 的模式。当前 Istio 规则下同时命中时 sidecar 优先,但 sidecar 来源流量在渐进迁移中可能绕过目标 waypoint;已有 L7 策略不能只做对象等价转换,还要验证真实路径。需要连续 L7 执行时,先部署 waypoint 和迁移策略,激活 waypoint,再启用 ambient,确认 ztunnel 纳管后才移除 sidecar 注入标签,最后重建 Pod。没有 L7 约束的隔离实验可以先验证 ambient L4,再按需绑定 waypoint,但不能把这个顺序直接复制到生产。任何批次都先选无状态服务,不要一次给整个共享 namespace 改标签。
eBPF:接管发生在节点,L7 仍可能进入 Envoy
Cilium 由 CNI 创建 endpoint,agent 把身份和策略编译到 eBPF map;因此没有 admission 注入也能接管 L3/L4。先从Cilium Service Mesh 文档核对 Kubernetes、内核、CNI chaining、kube-proxy replacement 与 Envoy 前提。纳管证据应包含 CiliumEndpoint 状态、security identity、BPF policy verdict 与 Hubble flow,而不是查 sidecar 容器。
当配置 HTTP 可见性、L7 网络策略或 Gateway API GAMMA 时,流量可能被重定向到节点级 Envoy,具体路径见L7 traffic management。于是“sidecar-free”只说明业务 Pod 里没有代理,不表示全程没有用户态 L7 代理。反向实验可以给 orders 增加基于路径的拒绝规则:预期 /version 允许、/admin 被数据面拒绝,并在 flow verdict 与 Envoy 访问证据中看到执行点。若只有策略对象存在而请求全放行,检查 endpoint identity、selector、L7 proxy readiness 与重定向端口。
Cilium mutual authentication 与透明链路加密也不是一个开关。前者涉及工作负载身份和对等认证,能力成熟度、SPIRE 依赖与集群限制要按目标 stable 文档复核;后者保护节点间传输但不自动赋予服务级身份。选型时把“谁是谁”和“链路是否加密”分开验证。
VM 与外部工作负载:注册、代理和宿主机规则由不同对象持有
VM 没有 Pod admission,纳管通常拆成四件事:控制面登记工作负载或服务;节点 agent/attestor 判断它是否有资格取得身份;代理取得 bootstrap、证书与 xDS;root 权限安装透明代理规则。SPIRE 的workload registration只建立 selector 到 SPIFFE ID 的映射,不会自动截获应用流量。
以 Kuma Universal 为例,需要创建 Dataplane/Workload、为 kuma-dp 提供短期 token 和受信控制面 CA,再以专用系统用户运行代理。transparent proxy 由 root 修改 iptables,业务进程必须使用另一个 UID,避免命中 owner bypass。Consul VM 则通常由 client agent 管理服务注册、健康检查、证书与代理;Kubernetes 当前 dataplane 拓扑不能机械套到 VM。两者都要分别证明 registration、代理 xDS、证书、端口捕获和真实请求。
VM 接入前先保存 iptables-save,列出 SSH、DNS、控制面、metadata、监控、管理端口及同机其他服务。透明规则重启后是否持久取决于实现与宿主机配置。正向实验验证 client 到 orders-v1 被代理且身份正确;反向实验用未注册进程或错误系统用户请求,预期无法取得 SVID,或在 strict 身份策略下被拒绝。若它仍能直连,就要检查宿主机路由、防火墙和 owner/port bypass,不能把“代理启动了”当封口。
注册 token、bootstrap、ACL token、私钥、trust bundle、Envoy config dump、真实服务标签与节点地址都是敏感资产。token 通过短期 Secret 分发并限制读取者,私钥不得写入 Git、镜像层或普通 CI 日志;诊断包要脱敏 SPIFFE ID、证书序列号、拓扑与内部地址,并设置保留期限。
身份和策略是后续状态,不是注入副作用
代理 Ready 以后还要确认它取得了预期 workload identity,信任正确根,并收到了与当前 generation 对应的策略。mTLS 只能证明双方身份与传输保护,不能替代授权。一个错误 ServiceAccount 即使能完成 mTLS,也可能不该访问 orders 的管理操作。
最小正反矩阵至少包含:正确身份访问 /version 成功;错误身份访问 /admin 被拒绝;未纳管来源在 strict 迁移完成后不能降级为明文;控制面暂时断开时既有连接、既有配置与新 Pod 的表现分别记录。访问日志中的 200、策略状态、证书状态和 xDS 同步要用 request ID 与 Pod UID 关联,避免把不同副本的证据拼在一起。
接入后若出现 503,先按第一证据分型:没有 listener 多半是捕获或代理配置问题;没有 cluster/endpoint 多半是发现或 selector 问题;TLS handshake 失败检查双方身份、trust domain、时间与信任包;明确 RBAC deny 才进入授权策略;连接在应用前被 reset 再看连接池、节点代理和 CNI。按层排查比反复 rollout 更容易保留现场。
安全退出按依赖的反方向进行
去掉 namespace 标签不会改写已经运行的 sidecar Pod,VM 上停止代理也不会自动恢复 iptables。安全退出先恢复非网格路径,再撤销策略与接管,最后才卸载控制面:
冻结新的网格策略变更,导出声明对象、安装 values、Pod template、数据面状态和必要的脱敏证据。为 client -> orders 建立可验证的直连 DNS、网络和应用安全路径,确认超时、TLS 与业务鉴权不依赖代理隐式补齐。分批移除 Deployment/namespace 的纳管选择并滚动,持续比较基线;ambient 则移除 workload/waypoint 使用关系并验证节点路径。
VM 先排空连接、停止代理,再使用产品提供的 uninstall 子命令移除其拥有的透明规则;禁止全局 flush iptables。确认 Pod 不再有 sidecar,节点不再为这些 workload 保留网格专用的 L7 重定向、mutual-auth 要求或注册对象,临时凭证已撤销。若 Cilium 仍是集群 CNI,CiliumEndpoint 与基础 L3/L4 数据路径仍由 CNI 持有,不能为了退出 service mesh 而删除。
所有业务都退出后,才按官方卸载顺序处理 webhook、CNI、控制面、CRD 与 CA;共享 CRD、根密钥和持久卷必须单独审批。
实验环境可在确认上下文和资源归属后删除:
kubectl config current-context
kubectl delete namespace mesh-lab
kubectl get mutatingwebhookconfigurations
kubectl get crd | Select-String 'istio|cilium|consul|kuma'最后两条是残留检查,不是看到名称就删除。共享集群里 CRD、webhook 与 CNI 可能仍服务其他 namespace。
用容量和治理决定推广速度
sidecar 的成本随 Pod 数、每代理连接池、listener/cluster 数和遥测量增长;ambient 的 ztunnel 成本随节点和连接增长,waypoint 随选中的 L7 流量增长;eBPF 还受 map 容量、事件速率和节点 Envoy 约束;VM 增加 agent、代理、宿主机规则与凭证生命周期。容量测试应覆盖平均值之外的 Pod 批量启动、证书同时轮换、策略大批更新、节点故障与遥测后端变慢。
团队不要照搬一组固定 CPU/内存数字。先记录未纳管基线,再对同一请求模型测代理 CPU、内存、连接、P50/P99、错误率、配置收敛与证书更新时间;扩大副本和策略数量后,观察这些量是否近似线性以及节点故障的影响半径。预算还要包含控制面、镜像、日志索引、Trace 采样、跨区带宽和工程值班,而不只是业务 Pod 多出的容器 requests。
长期维护需要一份可查询的纳管清册:每个 workload 的 owner、数据面模式、ServiceAccount、端口捕获与排除、策略 owner、目标 revision、证书来源、退出路径和最后一次演练结果。平台在准入层阻止冲突标签、特权 bypass 与未经批准的 hostNetwork;应用团队负责业务超时、鉴权和幂等;安全团队负责根信任、凭证和策略审计。每次升级先做一个 revision/canary 批次,证明新旧数据面互通、身份不断档、策略不降级且可以回切,再扩大选择集合。
纳管完成的判据最终很朴素:能指出请求经过的执行点,能证明双方身份与策略版本,能用反例证明绕过被发现或阻止,也能在不破坏其他网络规则的前提下恢复直连。标签只是起点,证据链和可逆性才是工程能力。
