一、引言:NetworkPolicy 拦得住 IP,却回答不了"谁在调用谁"
《NetworkPolicy 网络微隔离》解决了 Kubernetes 东西向流量的第一层问题:哪些 Pod 可以访问哪些 IP、端口。但真实业务继续演进后,仅靠它仍有几件事做不了:
- 一个订单服务被多个调用方访问,如何按服务身份限制"只有订单前端可以调用,其他服务一律拒绝"?
- Pod IP 经常变化,如何确认对方确实是声明的服务,而不是同一网段里的冒名者?
- Pod 之间的 HTTP/gRPC 流量如何默认加密,而不是依赖每个应用自己配置证书?
- 新版本发布时,如何按 90%/10% 切流,并统一配置超时、重试和故障注入?
- 一次跨服务调用慢在哪里,能否不改业务代码就获得流量指标和链路信息?
这些能力的共同答案,是 Service Mesh(服务网格)。它在业务进程之外增加一层服务通信基础设施,把身份认证、传输加密、服务鉴权、流量治理和通信可观测性从应用中剥离出来。
开篇结论:Service Mesh 不是 NetworkPolicy 的替代品,也不是"装上就零信任"。NetworkPolicy 管网络可达性,mTLS 管通信身份与加密,AuthorizationPolicy 管服务调用授权;三者叠加,才构成完整的东西向零信任链路。
二、原理解析:Sidecar、控制平面与工作负载身份
2.1 Service Mesh 的本质:把通信能力移出业务进程
以传统 Sidecar 模式为例,每个应用 Pod 中除业务容器外,还会注入一个代理容器。Istio 默认使用 Envoy:
┌──────────────────────────────┐
│ Istiod 控制平面 │
│ 配置下发 · 证书签发 · 服务发现 │
└──────────────┬───────────────┘
│ xDS / 证书
┌──────────────┴───────────────┐
│ │
┌─────────▼─────────┐ ┌─────────▼─────────┐
│ order-client Pod │ │ order-api Pod │
│ ┌───────────────┐ │ │ ┌───────────────┐ │
│ │ 业务容器 │ │ │ │ 业务容器 │ │
│ └──────┬────────┘ │ │ └──────▲────────┘ │
│ │ │ │ │ │
│ ┌──────▼────────┐ │ mTLS │ ┌──────┴────────┐ │
│ │ Envoy Sidecar │──┼──────────┼▶│ Envoy Sidecar │ │
│ └───────────────┘ │ │ └───────────────┘ │
└────────────────────┘ └────────────────────┘
业务容器通常仍访问普通的 Service 地址;流量被透明转发给 Sidecar,由代理执行加密、鉴权、路由、重试和指标采集。业务代码无需为每个语言重复实现一套通信中间件。
控制平面 Istiod 不转发业务数据,主要负责:
- 将 Kubernetes Service、Endpoint 与网格配置转换为代理配置;
- 通过 xDS 协议向 Envoy 下发监听器、路由、集群和证书配置;
- 为工作负载签发和轮换身份证书;
- 校验网格配置并暴露运行状态。
2.2 mTLS 不是"开个 HTTPS":核心是双向身份验证
普通 TLS 通常只由客户端验证服务端;mTLS(双向 TLS)则要求双方出示并验证证书:
客户端 Sidecar 服务端 Sidecar
│ │
│ 1. 出示客户端证书 + 验证服务端证书 │
│─────────────────────────────────────────▶│
│ 2. 服务端验证客户端证书,并出示自身证书 │
│◀─────────────────────────────────────────│
│ 3. 双方确认身份后建立加密通道 │
│═════════════════════════════════════════▶│
Istio 使用类似 SPIFFE 的工作负载身份,将证书身份与 Kubernetes 命名空间、ServiceAccount 关联,例如:
spiffe://cluster.local/ns/mesh-demo/sa/order-client
这带来一个重要变化:授权依据从易变化的 Pod IP,变成稳定且可验证的工作负载身份。
2.3 四层控制不要混为一谈
控制层
主要对象
回答的问题
局限
NetworkPolicy
Pod、Namespace、IP、端口
这个 Pod 能否连到目标端口?
不理解 HTTP 方法、路径和加密身份
PeerAuthentication
工作负载、命名空间、网格
服务间是否必须使用 mTLS?
不决定谁有业务调用权限
AuthorizationPolicy
服务身份、方法、路径等
谁被允许调用哪个接口?
前提是身份和流量能被正确识别
Kubernetes RBAC
用户/ServiceAccount、API 资源
谁能操作 Kubernetes API?
不管业务服务之间的 HTTP/gRPC 调用
关键纠偏:PeerAuthentication: STRICT 只代表"必须加密并验证身份",不表示"所有有身份的调用方都被允许"。最小权限仍需 AuthorizationPolicy;而 Kubernetes RBAC 不能替代它。
2.4 流量治理:Service 负载均衡之上的 L7 能力
Kubernetes Service 主要提供连接级负载均衡;Service Mesh 则能在请求级执行更精细的策略:
- 按权重进行金丝雀发布;
- 按 Header、路径或调用方身份路由;
- 配置超时、有限重试与熔断;
- 注入延迟或错误,验证系统韧性;
- 按版本(subset)组织同一服务的不同工作负载。
但重试不是越多越好:在超时、重试和上游多级调用叠加时,可能形成重试风暴。生产环境必须为幂等性、最大重试次数和整体超时预算负责。
2.5 Sidecar 与 Ambient:两种数据平面取舍
模式
优势
代价
更适合
Sidecar
生态成熟、每工作负载隔离清晰、L7 能力完整
Pod 容器数增加,资源与升级成本较高
需要完整 L7 治理、成熟方案的团队
Ambient
不为每个 Pod 注入 Sidecar,节点级代理降低接入成本
架构较新;Waypoint 与节点级组件带来新的运维边界
希望逐步采用 mTLS/L4 能力、降低 Sidecar 成本
本文选择更成熟、便于观察的 Istio Sidecar 模式。无论采用哪种模式,身份、mTLS 和授权的基本思想一致。
三、实战演示:订单服务 mTLS + 最小权限 + 金丝雀发布
3.1 场景与目标
模拟订单系统: - order-client:订单调用方,使用独立 ServiceAccount;
- order-api-v1/v2:订单 API 两个版本;
- 客户端只允许以 GET 方法调用订单 API;
- 服务之间强制 mTLS;
- 90% 流量走 v1,10% 走 v2;
- 设置 2 秒超时与有限重试。
3.2 前置条件 - Kubernetes 集群,建议至少 4 vCPU、8 GiB 可用内存;
- kubectl 可管理集群;
- 已安装与集群版本兼容的 istioctl(版本应参考 Istio 官方支持矩阵);
- 集群具备拉取 Istio 与示例镜像的网络条件。
安装控制平面:
istioctl install --set profile=default -y
kubectl get pods -n istio-system
安装后先做静态检查
istioctl analyze --all-namespaces
生产环境不应直接照搬 demo 安装命令:需要独立规划控制平面副本、资源、升级策略、证书根、Gateway 高可用与配置审计。
3.3 步骤一:创建命名空间并启用 Sidecar 注入
kubectl create namespace mesh-demo
kubectl label namespace mesh-demo istio-injection=enabled
创建两个工作负载身份与订单服务:
order-demo.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: order-client
namespace: mesh-demo
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-client
namespace: mesh-demo
spec:
replicas: 1
selector:
matchLabels:
app: order-client
template:
metadata:
labels:
app: order-client
spec:
serviceAccountName: order-client
containers:
- name: curl
image: curlimages/curl:8.10.1
command: "sh", "-c"
args: "while true; do sleep 3600; done"
apiVersion: v1
kind: Service
metadata:
name: order-api
namespace: mesh-demo
spec:
selector:
app: order-api
ports:
- name: http
port: 8080
targetPort: 8080
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api-v1
namespace: mesh-demo
spec:
replicas: 1
selector:
matchLabels:
app: order-api
version: v1
template:
metadata:
labels:
app: order-api
version: v1
spec:
containers:
- name: api
image: hashicorp/http-echo:latest
args: "-listen=:8080", "-text=order-v1"
ports: - containerPort: 8080
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api-v2
namespace: mesh-demo
spec:
replicas: 1
selector:
matchLabels:
app: order-api
version: v2
template:
metadata:
labels:
app: order-api
version: v2
spec:
containers:
- name: api
image: hashicorp/http-echo:latest
args: "-listen=:8080", "-text=order-v2"
ports:
- containerPort: 8080
部署并验证注入结果:
kubectl apply -f order-demo.yaml
kubectl rollout status deployment/order-client -n mesh-demo
kubectl rollout status deployment/order-api-v1 -n mesh-demo
kubectl rollout status deployment/order-api-v2 -n mesh-demo
kubectl get pods -n mesh-demo
每个 Pod 的 READY 应为 2/2:一个业务容器,一个 Istio Sidecar。若仍为 1/1,不要继续排业务,先检查注入是否生效。
3.4 步骤二:强制 mTLS,并显式声明网格内部 TLS 模式
mesh-security.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: order-api-mtls
namespace: mesh-demo
spec:
selector:
matchLabels:
app: order-api
mtls:
mode: STRICT
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: order-api
namespace: mesh-demo
spec:
host: order-api.mesh-demo.svc.cluster.local
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
kubectl apply -f mesh-security.yaml
CLIENT_POD=$(kubectl get pod -n mesh-demo
-l app=order-client -o jsonpath='{.items0.metadata.name}')
确认客户端到订单 API 的 TLS 配置被网格识别
istioctl authn tls-check "$CLIENT_POD.mesh-demo"
order-api.mesh-demo.svc.cluster.local
网格内调用应成功
kubectl exec -n mesh-demo "$CLIENT_POD" -c curl --
curl -sS http://order-api:8080/
tls-check 输出应体现 ISTIO_MUTUAL,状态正常。PERMISSIVE 可同时接收明文和 mTLS,适合迁移期;生产稳定后,敏感服务应收紧为 STRICT。
3.5 步骤三:先默认拒绝,再按身份与方法放行
order-authorization.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: mesh-demo-default-deny
namespace: mesh-demo
spec: {} # 空规则:拒绝该命名空间内所有入站请求
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-order-client-get
namespace: mesh-demo
spec:
selector:
matchLabels:
app: order-api
action: ALLOW
rules:
-
from:
-
source:
principals:
- cluster.local/ns/mesh-demo/sa/order-client
to:
- operation:
methods: "GET"
kubectl apply -f order-authorization.yaml
已授权身份 + GET:应返回 order-v1 或 order-v2
kubectl exec -n mesh-demo "$CLIENT_POD" -c curl --
curl -sS http://order-api:8080/
同一身份 + POST:方法不在允许列表,应返回 403
kubectl exec -n mesh-demo "$CLIENT_POD" -c curl --
curl -sS -o /dev/null -w '%{http_code}\n' -X POST http://order-api:8080/
默认拒绝是高影响操作。生产变更顺序应为:盘点真实调用关系 → 配置允许规则 → 启用默认拒绝 → 观察拒绝日志与业务指标。不要先封死再补规则。
3.6 步骤四:配置 90/10 灰度、超时与有限重试
order-traffic.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: order-api
namespace: mesh-demo
spec:
hosts:
- order-api.mesh-demo.svc.cluster.local
http:
-
route:
-
destination:
host: order-api.mesh-demo.svc.cluster.local
subset: v1
weight: 90
- destination:
host: order-api.mesh-demo.svc.cluster.local
subset: v2
weight: 10
timeout: 2s
retries:
attempts: 2
perTryTimeout: 800ms
retryOn: connect-failure,refused-stream,unavailable
kubectl apply -f order-traffic.yaml
istioctl analyze -n mesh-demo
连续请求并统计版本分布
for i in (seq130);dokubectlexec−nmesh−demo"(seq 1 30); do kubectl exec -n mesh-demo "(seq130);dokubectlexec−nmesh−demo"CLIENT_POD" -c curl --
curl -sS http://order-api:8080/
echo
done | sort | uniq -c
结果应接近 v1 多、v2 少,但 30 次样本不保证恰好 27:3。流量比例应以较大样本或网格指标评估,不要用几次手工请求判断权重是否准确。
3.7 步骤五:检查身份、路由与通信指标
查看客户端代理持有的工作负载证书信息
istioctl proxy-config secret "$CLIENT_POD.mesh-demo"
查看客户端实际获得的路由与上游集群配置
istioctl proxy-config routes "CLIENTPOD.mesh−demo"istioctlproxy−configclusters"CLIENT_POD.mesh-demo" istioctl proxy-config clusters "CLIENTPOD.mesh−demo"istioctlproxy−configclusters"CLIENT_POD.mesh-demo" | grep order-api
检查 Sidecar 配置同步状态
istioctl proxy-status
生产环境还应采集 Envoy/Istio 指标,并在日志中保留 traceparent 等链路上下文,供《可观测性三支柱》中的 Metrics → Tracing → Logging 接力使用。
3.8 清理实验环境
kubectl delete namespace mesh-demo
四、排错实战:把"网络、身份、授权、路由"分层定位
4.1 Sidecar 没有注入,Pod 一直是 1/1
排查顺序:
kubectl get ns mesh-demo --show-labels
kubectl get pods -n mesh-demo
kubectl describe pod -n mesh-demo
kubectl get pods -n istio-system
常见原因:
- Namespace 没有 istio-injection=enabled;
- 标签是在 Pod 创建后才添加,旧 Pod 不会自动补 Sidecar,需要滚动重建;
- 准入 Webhook 或 Istiod 不可用;
- Pod 或命名空间存在豁免配置。
4.2 开启 STRICT 后连接失败:000、重置或超时
先分清是对端没有代理,还是 TLS/路由配置不匹配:
istioctl authn tls-check "CLIENTPOD.mesh−demo" order−api.mesh−demo.svc.cluster.localistioctlproxy−configclusters"CLIENT_POD.mesh-demo" \ order-api.mesh-demo.svc.cluster.local istioctl proxy-config clusters "CLIENTPOD.mesh−demo" order−api.mesh−demo.svc.cluster.localistioctlproxy−configclusters"CLIENT_POD.mesh-demo" | grep order-api
kubectl get peerauthentication,destinationrule -n mesh-demo
高频根因:对端未接入网格却要求 STRICT、DestinationRule 与 PeerAuthentication 模式不一致、Service 端口未命名或命名不规范、配置尚未同步。迁移期可临时 PERMISSIVE,但必须有最终收紧计划。
4.3 返回 403:区分"授权拒绝"与"应用拒绝"
出现 RBAC: access denied 时,通常指 Istio AuthorizationPolicy,而不是 Kubernetes API 的 RBAC。检查:
kubectl get authorizationpolicy -n mesh-demo -o yaml
istioctl proxy-config listeners "$CLIENT_POD.mesh-demo"
重点确认:
- 默认拒绝是否先命中;
- 调用方 ServiceAccount 是否与 principals 完全一致;
- trust domain 是否确为 cluster.local;
- 方法、路径、端口条件是否把正常请求排除;
- 多 ALLOW 策略是叠加放行,不是相互覆盖。
4.4 灰度不生效:始终只看到一个版本
按链路逐层核对:
kubectl get endpointslices -n mesh-demo -l kubernetes.io/service-name=order-api
kubectl get destinationrule,virtualservice -n mesh-demo -o yaml
istioctl proxy-config routes "$CLIENT_POD.mesh-demo"
常见原因包括:Service selector 只圈中了单版本、Pod 缺少 version 标签、DestinationRule subset 与标签不一致、VirtualService host 与实际访问地址不一致,或客户端没有经过网格代理。
4.5 指标、日志或链路数据缺失
先确认流量确实经过 Sidecar,再检查指标端口和采集配置。链路追踪尤其需要注意:Sidecar 可以生成 span,但跨服务 trace 连续性通常仍依赖应用传递 traceparent/b3 等上下文 Header。 "零代码接入"不等于业务完全不用配合。
4.6 资源升高或延迟增大
Sidecar 会消耗 CPU/内存,并增加代理处理链路。应同时观察业务容器与代理容器的资源、代理配置同步、重试次数和上游延迟。若延迟在启用重试后放大,先检查是否发生重试风暴,而不是简单提高超时。
4.7 一图速查
调用失败
│
├─ Pod 只有 1/1? ── 是 → 查注入标签、Webhook、旧 Pod 是否重建
│ 否
├─ 000 / reset? ─────── → 查 STRICT、对端是否入网格、TLS 与配置同步
│
├─ 403? ─────────────── → 查 AuthorizationPolicy、身份、方法/路径条件
│
├─ 路由不符? ────────── → 查 Endpoint、version 标签、DR subset、VS host
│
└─ 延迟/资源异常? ───── → 查 Sidecar 资源、重试、超时与配置规模
五、总结:零信任不是一张策略,而是一条身份链
Service Mesh 把服务通信从"同网段可连"提升为"工作负载有身份、流量默认加密、调用按身份授权、路由按版本治理"。其中: - 控制平面负责配置与证书,数据平面负责真实通信;
- mTLS解决传输加密和双向身份验证,不等于自动完成最小权限;
- AuthorizationPolicy按服务身份和 L7 属性授权,与 Kubernetes RBAC 不是一回事;
- NetworkPolicy + mTLS + AuthorizationPolicy分别覆盖网络可达、通信身份与调用权限;
- 灰度、超时和重试必须与业务幂等性、超时预算一起设计;
- Sidecar 与 Ambient 是部署形态取舍,不是零信任目标本身的差异。