传统边界防御建立在"内网可信、外网不可信"的假设上——但云原生与微服务时代这个假设已经崩塌:攻破一个 Pod 就能在内网横向移动,容器数量暴增让防火墙规则无法管理。网络微分段(Micro-segmentation)把安全边界从"机房围墙"下沉到每个工作负载,让"谁到谁"的通信必须经过身份验证与授权。本指南从零信任网络原理出发,系统覆盖微分段的策略模型、K8s NetworkPolicy、服务网格 mTLS 与 SPIFFE 身份体系的完整落地。
一、从边界防御到零信任:范式转变
1.1 传统边界模型的四大失效
| 假设 | 现实 | 后果 |
|---|
| 内网可信 | 攻破一个 Pod 即进入内网 | 横向移动如入无人之境 |
| 防火墙能管 | 容器数量是 VM 的 10-100 倍 | 规则爆炸、无法维护 |
| 网络即身份 | IP 会漂移、可伪造 | 凭 IP 授权不可靠 |
| 静态部署 | 容器秒级伸缩、频繁重建 | 规则跟不上变化 |
ℹ️ 核心洞察:零信任网络的公式是 “身份验证 + 按需授权"取代"网络位置信任”。通信双方不再因为"在同一内网"就互信,而是每次通信都验证"你是谁、你是否被授权访问这个服务"。
1.2 东西向 vs 南北向流量
南北向(North-South):外部 ↔ 集群/数据中心(入口防护:WAF/网关)
东西向(East-West): 集群内服务 ↔ 服务(微分段主场)
攻击场景:攻击者通过南北向入口攻破一个服务,
东西向流量没有隔离 → 可横向访问数据库/内部 API
1.3 微分段的粒度演进
安全粒度演进:
机房/边界(粗) → 子网/VLAN → 主机 → 容器/工作负载(细)
策略对象:IP 段 → 防火墙 → 标签 → 工作负载身份(SPIFFE)
←—— 更可控、更复杂 ——←
二、策略模型:默认拒绝 + 显式允许
2.1 微分段的三大原则
| 原则 | 内容 |
|---|
| 默认拒绝 | 没有显式允许的通信一律拒绝 |
| 按身份授权 | 授权对象是工作负载身份,不是 IP |
| 最小互通 | 只开放业务真实需要的路径 |
2.2 策略设计的两步法
第一步:绘制应用依赖图(谁调用谁)
前端 → 订单服务 → 数据库
前端 → 用户服务 → 用户数据库
第二步:逐条转换为显式允许规则
allow 前端 → 订单服务 (HTTP 80)
allow 订单服务 → 数据库 (TCP 3306)
allow 前端 → 用户服务 (HTTP 80)
allow 用户服务 → 用户数据库 (TCP 3306)
deny 其余全部
2.3 依赖图发现工具
# 用 service 地图/可观测性数据自动发现依赖
# 示例:基于网络抓包/审计日志统计服务间调用
kubectl get pods -o wide # 先看清工作负载
# 生产建议:基于 eBPF/Cilium Hubble 的服务图自动生成依赖
# dependency_graph.py — 从访问日志构建依赖图
def build_dependency_graph(access_logs: list[dict]) -> dict:
"""解析访问日志 (src, dst, port),构建服务间依赖图。"""
graph = {}
for log in access_logs:
src, dst, port = log["src"], log["dst"], log["port"]
graph.setdefault(dst, []).append({"from": src, "port": port})
return graph
def generate_policies_from_graph(graph, default="deny") -> list[dict]:
"""依赖图 → 显式允许策略集(其余默认拒绝)。"""
return [{"src": e["from"], "dst": dst, "port": e["port"]}
for dst, edges in graph.items() for e in edges]
三、Kubernetes NetworkPolicy:原生的微分段
3.1 默认拒绝所有入站/出站
# 1. 先立"默认拒绝"基线(每个命名空间)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: backend
spec:
podSelector: {} # 匹配命名空间全部 Pod
policyTypes: ["Ingress", "Egress"]
ingress: [] # 拒绝所有入站
egress: [] # 拒绝所有出站
3.2 显式允许业务路径
# 2. 显式放行:前端 → 订单服务
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-orders
namespace: backend
spec:
podSelector:
matchLabels: {app: orders} # 目标服务
policyTypes: ["Ingress"]
ingress:
- from:
- namespaceSelector:
matchLabels: {app: frontend-ns}
podSelector:
matchLabels: {app: frontend}
ports:
- protocol: TCP
port: 8080 # 仅业务端口
3.3 出口隔离(防数据外泄)
# 3. 出站隔离:数据库只允许访问特定 DNS 与 K8s API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-egress-only
namespace: data
spec:
podSelector:
matchLabels: {app: postgres}
policyTypes: ["Egress"]
egress:
- to:
- namespaceSelector: {} # K8s API/DNS 所在
podSelector:
matchLabels: {k8s-app: kube-dns}
ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}]
# 其余出站默认拒绝 → 即使被攻破也难以外泄数据
3.4 NetworkPolicy 的局限与 CNI 增强
| 能力 | K8s 原生 NetworkPolicy | Cilium/K8s 增强 |
|---|
| L3/L4 规则 | ✅ | ✅ |
| L7 规则(HTTP 方法) | ❌ | ✅(CiliumNetworkPolicy) |
| DNS 策略 | 部分 | ✅ |
| 身份认证 | ❌ | ✅(配合 mTLS) |
| 可观测性 | 有限 | ✅(Hubble) |
# Cilium 的 L7 策略:只允许 GET /api/orders
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: orders-l7-allow
namespace: backend
spec:
endpointSelector:
matchLabels: {app: orders}
ingress:
- fromEndpoints:
- matchLabels: {app: frontend}
toPorts:
- ports:
- port: "8080"
rules:
http:
- method: GET
path: "/api/orders/**"
四、服务网格 mTLS:身份级的微分段
4.1 服务网格解决的四个问题
| 问题 | 传统解法 | 服务网格(Istio/Linkerd) |
|---|
| 服务间加密 | 应用代码实现 | 透明双向 TLS(mTLS) |
| 身份 | IP/主机名 | SPIFFE 身份证书 |
| 授权 | 防火墙规则 | 策略控制(AuthorizationPolicy) |
| 可观测 | 无 | 全链路遥测 |
4.2 mTLS 的工作原理
mTLS(双向 TLS):
客户端 ✓ ✓ 服务端
│ 证书 │
├─────────┤ 双方都验证对方证书
│ 证书 │
│ 协商密钥 │
│ 加密通信 │
身份来源:SPIFFE 证书(每个工作负载一个身份)
身份格式:spiffe://cluster/ns/backend/sa/orders
4.3 Istio 强制严格 mTLS
# PeerAuthentication:强制命名空间内所有流量走 mTLS
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: strict-mtls
namespace: backend
spec:
mtls:
mode: STRICT # 拒绝非 mTLS 流量(PERMISSIVE 是过渡)
# AuthorizationPolicy:身份级授权(替换 IP 白名单)
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: orders-allow-frontend
namespace: backend
spec:
selector:
matchLabels: {app: orders}
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/frontend/sa/frontend-sa"]
to:
- operation:
methods: ["GET", "POST"]
4.4 渐进式迁移:从宽到严
迁移步骤:
阶段 1 透明加密(PERMISSIVE):mTLS 开启但不拒绝明文——先建立加密基线
阶段 2 身份可达(STRICT 试点):高价值命名空间强制 mTLS
阶段 3 全量 STRICT:所有命名空间强制 mTLS
阶段 4 策略收紧:按 AuthorizationPolicy 做身份级授权
监控:确保迁移不破坏业务——观察 5xx 错误率与调用图
五、SPIFFE/SPIRE:云原生的工作负载身份体系
5.1 身份的问题:IP 不可靠、密钥难管理
微服务身份不能靠 IP(会变)也不能靠证书分发(难管理)。SPIFFE(Secure Production Identity Framework for Everyone)提供了标准化的工作负载身份:
SPIFFE 身份格式:
spiffe://trust-domain/namespace/pod-name
SPIRE 实现:
Agent 在每个节点验证工作负载 → 签发短期证书(SVID)
证书自动轮换(分钟级)→ 无静态密钥
应用通过本地 socket 获取身份证书
5.2 用 SPIRE 为工作负载签发身份
# 注册工作负载条目:身份绑定到 k8s 服务账号
apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterSpiffeID
metadata:
name: orders-spiffe
namespace: backend
spec:
spiffeID: "spiffe://cluster/ns/backend/sa/orders-sa"
podSelector:
matchLabels: {app: orders}
// Go 应用中获取 SPIFFE 身份
import (
"context"
"crypto/tls"
"github.com/spiffe/go-spiffe/v2/workloadapi"
)
func getSVID(ctx context.Context) (tls.Certificate, error) {
// 从本地 Agent socket 获取自动轮换的 SVID
src, err := workloadapi.NewX509Source(ctx, workloadapi.WithClientOptions(
workloadapi.WithAddr("unix:///run/spire/agent.sock")))
if err != nil { return tls.Certificate{}, err }
return src.GetX509Certificate(), nil
}
5.3 身份在数据库层的应用
数据库层也用工作负载身份做访问控制(替代 IP 白名单):
-- PostgreSQL:以 SPIFFE 身份做访问控制
-- 应用每次连接提供 mTLS 证书(含 SPIFFE 身份)
CREATE ROLE orders_sa LOGIN;
GRANT SELECT, INSERT ON orders TO orders_sa;
-- 认证走 mTLS,授权走数据库角色 —— 不靠客户端 IP
六、微分段的实施路径与工具选型
6.1 工具选型矩阵
| 方案 | 能力 | 适用 | 复杂度 |
|---|
| K8s NetworkPolicy | L3/L4 段 | 纯 K8s、入门 | 低 |
| Cilium | L3-L7 + eBPF + 可观测 | 生产级云原生 | 中 |
| Istio | mTLS + 身份 + 策略 | 需要身份级微隔离 | 高 |
| Linkerd | 轻量 mTLS | 想简单获得 mTLS | 低 |
| NSX-T / 硬件 | 虚拟机段 | 混合/VM 环境 | 高 |
| 云原生防火墙 | 云环境段 | 云工作负载 | 中 |
6.2 自建 vs 平台能力
决策:
· 纯容器 + 想控制复杂度 → Cilium(eBPF 原生、L7 能力)
· 需要 mTLS + 身份授权 → 服务网格(Istio 全面 / Linkerd 轻量)
· 混合 VM + 容器 → 云厂商微分段 + CNI 结合
· 已有服务网格 → 优先用网格策略,避免双栈
6.3 实施节奏建议
第 1 步(基线):启用可观测性,绘制完整依赖图(1-2 周)
第 2 步(默认拒绝):关键命名空间立 deny-all(1 周)
第 3 步(显式允许):按依赖图逐条放行(2-4 周)
第 4 步(验证):对比放行前后流量,确认无断链
第 5 步(加严):全命名空间默认拒绝 + 定期策略评审
七、微分段的运维与排障
7.1 策略生效验证
# 验证:从一个 Pod 测试到目标的连通性
kubectl exec -n frontend deploy/frontend -- \
curl -sv http://orders.backend.svc:8080/api/orders
# 应看到 TLS 握手 + 200;若 403/超时则检查策略
# policy_test.py — 自动验证策略矩阵
def verify_policy_matrix(allow_pairs, deny_pairs, exec_fn):
"""对期望允许/拒绝的路径逐一验证。"""
failures = []
for src, dst in allow_pairs:
if not exec_fn(src, dst): # 期望通
failures.append(f"应允许却不通: {src}→{dst}")
for src, dst in deny_pairs:
if exec_fn(src, dst): # 期望断
failures.append(f"应拒绝却连通: {src}→{dst}")
return failures
7.2 排障工具链
| 场景 | 工具 |
|---|
| 服务连通性 | kubectl exec curl / nc |
| 策略是否生效 | kubectl describe networkpolicy |
| 流量可视化 | Cilium Hubble / Istio Kiali |
| 认证失败 | 网格访问日志(查看 principal 是否匹配) |
| DNS 问题 | 检查出口 DNS 策略 |
7.3 常见踩坑
| 坑 | 规避 |
|---|
| 忘了 DNS 出口 | 出站默认拒绝后 Pod 无法解析域名 → 先放行 DNS |
| healthcheck 被拦 | kubelet 探针源 IP 可能被策略拦 → 放行探针或走本地 |
| 太急全量加严 | 未验证就全命名空间拒绝 → 断链事故 |
| 策略堆积 | 只加不放、规则膨胀 |
八、微分段与零信任的完整架构
8.1 纵深防线:南北向 + 东西向
┌────────────────────────────────────────────────────┐
│ 南北向(外部 → 内) │
│ WAF → API 网关 → 身份验证 → 限流 → 接入应用 │
├────────────────────────────────────────────────────┤
│ 东西向(服务 → 服务) │
│ mTLS 加密 + SPIFFE 身份 + 策略授权 + L7 控制 │
├────────────────────────────────────────────────────┤
│ 数据层 │
│ 工作负载身份访问控制 + 数据加密 + 审计 │
└────────────────────────────────────────────────────┘
8.2 跨云的多集群微分段
# 多集群:统一信任域 + 跨集群身份
# 每个集群的 SPIRE 注册到同一信任域
# Istio 多集群 mesh:mTLS 跨集群工作负载互通,策略统一
trustDomain: cluster.example.com
# 跨集群策略示例:payments 集群的 svc 可访问 orders 集群
# (按 SPIFFE 身份授权,而非集群内 IP)
8.3 零信任的五大支柱落地
| 支柱 | 微分段贡献 |
|---|
| 身份 | SPIFFE/SPIRE 工作负载身份 |
| 设备 | 节点/工作负载证书验证 |
| 应用 | L7 策略(方法/路径级) |
| 网络 | mTLS + 策略驱动隔离 |
| 数据 | 加密 + 身份访问控制 |
九、评测与持续改进
9.1 微分段有效性评估
# segmentation_assessment.py
def assess_segmentation(cluster_api) -> dict:
"""评估微分段覆盖度与有效性。"""
workloads = cluster_api.list_pods()
protected = sum(1 for p in workloads if has_network_policy(p))
coverage = protected / len(workloads)
# 其他维度:
mTLS_ratio = mTLS_enabled_services() / total_services()
default_deny_ns = deny_all_namespaces() / total_namespaces()
return {
"policy_coverage": coverage,
"mtls_ratio": mTLS_ratio,
"default_deny_ratio": default_deny_ns,
}
9.2 攻防演练:验证隔离是否真有效
红队演练场景:
1. 攻破前端 Pod → 尝试横向访问订单/数据库(应被拒绝)
2. 攻破无策略命名空间的 Pod → 尝试访问高价值服务
3. 尝试从数据库 Pod 向公网外泄数据(应被出口策略拦截)
4. 伪造 IP 尝试访问 → 应被身份验证拦截(mTLS)
9.3 持续改进闭环
| 阶段 | 动作 |
|---|
| 发现 | 依赖图 + 流量可观测 |
| 收紧 | 新增服务即默认拒绝,仅放行业务路径 |
| 验证 | 策略矩阵自动化测试 |
| 监控 | 拦截日志 + 告警(被拦截的异常尝试) |
| 迭代 | 定期评审 + 红队演练 |
总结:微分段的落地框架
| 层 | 手段 | 防什么 |
|---|
| 入口(南北向) | WAF + 网关 + 身份 | 外部攻击 |
| 网络(东西向) | NetworkPolicy 默认拒绝 | 横向移动 |
| 身份 | SPIFFE/SPIRE + mTLS | IP 伪造、无身份访问 |
| 应用 | L7 策略(方法/路径) | 业务越权 |
| 数据 | 加密 + 身份访问 | 数据外泄 |
网络微分段的本质,是把"安全边界"从静态的机房围墙,进化为每个工作负载自带的、基于身份的、按需开放的边界。它通过"默认拒绝 + 显式允许 + 身份验证"的组合,把云原生时代失控的平面网络重新变成可治理的隔离空间。落地时记住三步走:先用依赖图看清流量,再立默认拒绝基线,最后按身份逐条放行——加上持续的红队验证,你的微服务网络就能实现真正的零信任隔离。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。