Kubernetes Service Mesh 与 mTLS 实战:从网络可达走向零信任服务通信

一、引言: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 不转发业务数据,主要负责:
  1. 将 Kubernetes Service、Endpoint 与网格配置转换为代理配置;
  2. 通过 xDS 协议向 Envoy 下发监听器、路由、集群和证书配置;
  3. 为工作负载签发和轮换身份证书;
  4. 校验网格配置并暴露运行状态。
    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

常见原因:

  1. Namespace 没有 istio-injection=enabled;
  2. 标签是在 Pod 创建后才添加,旧 Pod 不会自动补 Sidecar,需要滚动重建;
  3. 准入 Webhook 或 Istiod 不可用;
  4. 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 是部署形态取舍,不是零信任目标本身的差异。
相关推荐
叼烟扛炮1 小时前
Linux 学习笔记(六十):单线程的逆袭——epoll 高并发服务器实战
linux·服务器·网络·笔记·学习·算法
weirong2 小时前
跟原生 HPA 做对照压测:五轮里前四轮全废,最后一个结论也被我自己降级
kubernetes
沫璃染墨3 小时前
从零入门计算机网络系列(十五):TCP可靠传输机制详解——从序号与确认应答到乱序处理
服务器·网络·c++·tcp/ip·tcp
xiaoye-duck4 小时前
《C++ 项目:手写大模型接入 SDK》本地 Ollama 部署与适配
开发语言·网络·c++·语言模型
叼烟扛炮5 小时前
Linux 学习笔记(六十一):网络编程毕业设计——Reactor 反应堆模式
linux·网络·笔记·学习·课程设计
2401_868534785 小时前
【网络安全
网络·python
xyzzklk5 小时前
变相解决华为Mate 8锁屏后无法建立入站网络连接
linux·运维·服务器·网络·华为·go·安卓
叼烟扛炮7 小时前
Linux 学习笔记(五十九):epoll 的生死抉择——LT 与 ET 模式
linux·运维·服务器·网络·笔记·学习·php
xixingzhe215 小时前
AI 对话打字机效果:SSE 和 HTTP 的区别与适用场景
网络·网络协议·http