大多数 Kubernetes 教程不会告诉你:大多数工程师能够运行 kubectl expose,但不到 10% 的人真正明白他们这么做时会发生什么。
我在超过 10 家公司调试过 Kubernetes 网络问题。同样的知识盲点反复出现。工程师不了解 ClusterIP 在底层是如何工作的。他们不明白为什么不同命名空间的 Pod 默认可以相互通信。他们也不清楚 CNI 插件在内核层面到底做了什么。
本教程正是为了填补这一空白。你将从底层了解 Kubernetes 网络是如何工作的:Pod IP 是如何分配的以及为什么它们能够跨节点通信;kube-proxy 如何利用 iptables 规则实现 ClusterIP;Ingress 控制器如何通过单一负载均衡器路由外部流量;Network Policy 如何为 SOC2 合规实施微分段;以及 Cilium 如何利用 eBPF 用更快、更可观察、更安全的方案取代上述所有机制。
读完本指南后,你将能够调试 “为什么我的 Pod 无法访问该服务?” 问题,实现符合 SOC2 CC6.1 的默认拒绝 Network Policy,并有信心为自己的集群选择合适的 CNI。
Pod IP、容器网络模型以及 CNI 如何分配地址
kube-proxy 如何利用 iptables 实现 ClusterIP,以及为什么 eBPF 更快
Ingress 控制器:通过单一负载均衡器路由所有外部流量
Network Policy:默认拒绝以及按服务放行的规则,以实现零信任网络
CNI 对比:Cilium vs Calico vs AWS VPC CNI 以及各自的适用场景
服务网格:Cilium、Istio、Linkerd 在 mTLS 和可观测性方面的比较
让我们开始吧。
在开始之前,您应该具备:
知识:
基本的 Kubernetes 熟悉度:您可以部署一个 Pod 并创建一个 Service
基本的 Linux 网络概念:您知道 IP 地址和端口是什么
对负载均衡器的作用有一般的了解
工具和访问:
一个运行中的 Kubernetes 集群(EKS、GKE,或通过 kind 创建的本地集群)
kubectl 已配置并指向您的集群
helm 3 已安装(用于在第 4 部分安装 Cilium)
从第 4 部分开始:在您的集群上安装 Cilium(helm install cilium cilium/cilium)
关于 CNI 的说明:第 1–3 部分适用于任何 Kubernetes 集群,无论使用哪种 CNI。第 4–6 部分使用 Cilium 特定的资源(CiliumNetworkPolicy、Hubble)。如果您使用不同的 CNI,概念是相同的,只有 YAML 语法有所不同。
Kubernetes 网络模型有一条基本规则:每个 Pod 拥有自己的唯一 IP 地址,并且每个 Pod 可以使用这些 IP 地址与其他所有 Pod 进行通信——无需网络地址转换(NAT)。
这与 Docker 默认的工作方式不同,Docker 默认情况下容器共享主机网络或使用端口映射。在 Kubernetes 中,Pod 之间没有端口映射。IP 地址为 10.244.1.2 的 Pod A 可以直接访问位于不同节点的 IP 地址为 10.244.2.3 的 Pod B,并且源 IP 地址会被保留。
在您的集群上验证这一点:
# List all pods across all namespaces with their IP addresses and node placement
kubectl get pods -o wide --all-namespaces
预期输出:
NAMESPACE NAME READY STATUS IP NODE
production payment-api-5d6b8d8c4f-abc12 1/1 Running 10.244.1.2 node-1
production user-api-5d6b8d8c4f-def34 1/1 Running 10.244.2.3 node-2
production redis-master-0 1/1 Running 10.244.1.4 node-1
每个 Pod 都有一个唯一的 IP。node-1 上的 payment-api 和 node-2 上的 user-api 可以直接通过这些 IP 互相访问。请注意,这些 IP 来自 10.244.0.0/16 CIDR:这是 Pod 网络,与节点网络分离。
容器网络接口(CNI)是负责使 Kubernetes 网络模型工作的插件。当一个新 Pod 在节点上被调度时,Kubernetes kubelet 会调用 CNI 插件,它会执行四个操作:
为 Pod 创建一个新的网络命名空间:一个隔离的网络环境
创建一对虚拟以太网(veth):一端在 Pod 的命名空间内,另一端在节点上
将集群 Pod CIDR 中的 IP 地址分配给 veth 对的 Pod 端
添加路由规则,使节点知道如何到达集群中的每个 Pod IP
没有 CNI,Pod 将没有网络连通性。有了 CNI,扁平的 Pod 网络模型就成为现实。
检查您的集群中安装了哪个 CNI 插件:
# List the CNI binaries installed on a node
ls /opt/cni/bin/
以下是一些常见的 CNI 插件及适用场景:
| CNI | 默认启用? | 主要使用场景 |
|---|---|---|
| AWS VPC CNI | 是的 (EKS) | Pod 能获得真实的 VPC IP,最适合 AWS 原生集成。 |
| Calico | 否 | 支持 BGP 路由的高级网络策略 |
| Cilium | 否 | 基于 eBPF 的网络、第七层策略、服务网格及 SOC2 合规证据 |
最基本的网络测试是:进入一个 Pod,通过 IP ping 另一个 Pod。
# Step 1: Get the IP of a target pod
TARGET_IP=$(kubectl get pod redis-master-0 -o jsonpath='{.status.podIP}')
echo "Target IP: $TARGET_IP"
# Step 2: Exec into another pod and ping the target
kubectl exec -it payment-api-5d6b8d8c4f-abc12 -- ping -c 3 $TARGET_IP
预期输出:
PING 10.244.1.4 (10.244.1.4): 56 data bytes
64 bytes from 10.244.1.4: icmp_seq=0 ttl=62 time=0.8ms
64 bytes from 10.244.1.4: icmp_seq=1 ttl=62 time=0.7ms
64 bytes from 10.244.1.4: icmp_seq=2 ttl=62 time=0.9ms
如果此操作成功,则 CNI 工作正常。如果失败,请检查是否有 Network Policy 阻止了 ICMP 流量(第 4 部分会介绍此内容)。
需要记住的一条规则:每个 Pod 都会分配到一个 IP。Pods 可以直接使用这些 IP 进行通信。CNI 插件使这两点都成立。
Pod IP 每次 Pod 重启时都会变化。如果您部署了支付 API 的新版本,旧 Pod 会被删除,新 Pod 会获得新 IP 并被创建。任何配置为调用旧 IP 的服务现在都包含失效的引用。
下面是错误的做法:硬编码 Pod IP。
# Bad: Direct Pod IP in application configuration
# This IP will stop working the next time the database Pod restarts
apiVersion: v1
kind: Pod
metadata:
name: payment-api
spec:
containers:
- name: api
env:
- name: DATABASE_HOST
value: "10.244.1.4" # Pod IP — will change on next restart
在开发环境中这是脆弱的,在生产环境中则是灾难性的。例行的 Pod 重启——无论是节点排空、OOM 杀死还是部署回滚——都会导致任何将旧 IP 硬编码的应用程序崩溃。
Kubernetes Service 提供了 Pod IP 所不具备的两项功能:一个在 Service 存在期间永不变化的稳定 IP 地址(ClusterIP),以及其他 Pod 可以使用的、与 IP 无关的稳定 DNS 名称。
创建 Service 时,Kubernetes 会从 service CIDR(例如 10.100.0.0/16)分配一个虚拟的 ClusterIP,在 CoreDNS 中创建形如 的 DNS 记录,并在每个节点上配置 kube-proxy,添加 iptables 规则以将流量从 ClusterIP 负载均衡到其后健康的 Pod IP。
下面是正确的实现方式:一个 ClusterIP Service。
# Good: ClusterIP Service provides a stable IP and DNS name
# redis.production.svc.cluster.local always resolves to 10.100.0.1
# regardless of which Redis pods are running behind it
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: production
spec:
selector:
app: redis
role: master # Only pods with these labels receive traffic
ports:
- port: 6379 # Port the Service listens on
targetPort: 6379 # Port the Pod actually runs on
type: ClusterIP # Default: accessible only inside the cluster
kube-proxy 如何使用 iptables 实现负载均衡:当 Service 被创建时,kube-proxy 会在集群中的每个节点上添加 iptables 规则。这些规则会拦截发往 ClusterIP 的流量,并将其重定向到健康的 Pod IP 之一。在节点上运行以下命令以查看规则的实际效果:
# View the iptables rules kube-proxy created for the redis Service
# Each KUBE-SEP entry represents one Pod endpoint
sudo iptables -t nat -L KUBE-SERVICES | grep redis
预期输出:
Chain KUBE-SVC-REDIS (1 references)
target prot source destination
KUBE-SEP-AAA all anywhere anywhere /* production/redis */ statistic mode random probability 0.50
KUBE-SEP-BBB all anywhere anywhere /* production/redis */
流量通过这些 iptables 规则在两个 Pod 端点之间以 50/50 的比例分配给 Redis ClusterIP。当 Pod 重启并获得新 IP 时,kube-proxy 会自动更新这些规则。
| 类型 | DNS 名称 | 可访问来源 | 使用场景 |
|---|---|---|---|
| ClusterIP | redis.production.svc.cluster.local |
仅在集群内部 | 数据库、缓存、内部 API |
| NodePort | |
节点 IP + 端口 | 本地开发、调试 |
| LoadBalancer | AWS ELB DNS 名称 | 互联网(通过云负载均衡器) | 外部 API、Web 应用 |
验证 Service 是否正确路由流量:
# Describe a Service to see its endpoints (the actual Pod IPs behind it)
kubectl describe service redis -n production
预期输出:
Name: redis
Namespace: production
Type: ClusterIP
IP: 10.100.0.1
Port: 6379/TCP
TargetPort: 6379/TCP
Endpoints: 10.244.1.4:6379,10.244.2.5:6379
Session Affinity: None
如果 Endpoints 显示 ,Service 选择器与任何正在运行的 Pod 不匹配。这是 Kubernetes 中“连接被拒绝”错误的最常见原因。
需要记住的一条规则是,Pod 应该始终连接到 Service DNS 名称,而永不直接连接到 Pod IP。Service 会自动处理稳定性、负载均衡和健康检查。
每个 LoadBalancer Service 会创建一个专用的云负载均衡器。在 AWS 上,每个 Application Load Balancer 的费用大约为 \(0.008/LCU-hour plus \)0.0225/小时基础费用。这相当于每个负载均衡器每月大约 16–27 美元。
对于 20 个微服务而言,这仅负载均衡器费用就达到了每月 320–540 美元,此外每处理一个请求还需额外支付 0.008/LCU。
下面是每个微服务一个 LoadBalancer 的错误做法:
# Bad: This creates a new AWS ALB every time it is applied
# 20 microservices = 20 ALBs = $300-500/month before any traffic charges
apiVersion: v1
kind: Service
metadata:
name: payment-api
spec:
type: LoadBalancer # Creates a dedicated ALB
ports:
- port: 80
targetPort: 8080
入口控制器是运行在您集群内部的 Pod,它会监视 Ingress 资源,并配置一个外部负载均衡器,根据主机名和 URL 路径将流量路由到多个服务。
例如,AWS Load Balancer Controller 为所有 Ingress 资源创建一个 ALB,并配置其监听器规则,将 api.company.com/payments 路由到支付服务,将 api.company.com/users 路由到用户服务,所有流量均通过同一个负载均衡器。
正确的实现方式是:为所有服务使用一个入口。
# Good: One Ingress resource routes all external traffic
# One ALB is created total, regardless of how many services are listed
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shared-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
alb.ingress.kubernetes.io/ssl-redirect: "443"
spec:
rules:
- host: api.company.com
http:
paths:
- path: /payments
pathType: Prefix
backend:
service:
name: payment-service
port:
number: 8080
- path: /users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 8080
- host: dashboard.company.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dashboard-service
port:
number: 3000
tls:
- hosts:
- api.company.com
- dashboard.company.com
secretName: tls-wildcard-cert
确认Ingress已配置完成,且ALB DNS名称已分配:
# Watch until the ADDRESS column shows the ALB DNS name (typically 2-3 minutes)
kubectl get ingress shared-ingress -n production -w
预期输出:
NAME CLASS HOSTS ADDRESS PORTS
shared-ingress alb api.company.com,dashboard.company.com k8s-prod-sharedin-abc123.us-east-1.elb.amazonaws.com 80, 443
成本差异:
| 方法 | 负载均衡器 | 月费用 |
|---|---|---|
| 每个微服务一个 LoadBalancer Service(20 个服务) | 20 ALBs | ~$400/month |
| 单个 Ingress 控制器 | 1 ALB | ~$27/month |
需要记住的一点是:一个使用基于路径的路由的 Ingress 控制器通过单个负载均衡器为所有服务提供服务。按服务分配 LoadBalancer 的方式仅适用于早期原型阶段。
开箱即用的 Kubernetes 默认不对 Pod 之间施加任何网络限制。前端 Pod 可以直接向数据库 Pod 发起 API 调用。分析服务可以查询支付数据库。被攻破的 Pod 可以扫描集群中的其他所有 Pod。
这不够安全。为了符合 SOC2 CC6.1(逻辑访问控制)、HIPAA 以及大多数企业安全框架,您需要能够证明网络流量仅限于必要范围。
验证目前是否存在无限制的流量:
# Without Network Policies, this call from the frontend to the payment DB will succeed
# It should not be allowed in a secure cluster
kubectl exec -it frontend-pod -n production -- \
curl http://payment-postgres.production.svc.cluster.local:5432
如果在您的集群上此操作成功,则表明没有网络分段。
正确的做法是采用默认拒绝:先阻止 Pod 之间的所有流量,然后仅放行应用程序所需的特定通信路径。
# This policy applies to all pods in the namespace (empty endpointSelector matches all)
# It blocks all ingress and egress traffic by default
# Warning: apply this and all pod-to-pod communication immediately stops
# Have your allow rules ready before applying this in production
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
description: "Block all inter-pod traffic by default — zero-trust baseline"
endpointSelector: {} # Matches all pods in this namespace
ingress:
- {} # Empty ingress rule = deny all inbound
egress:
- {} # Empty egress rule = deny all outbound
应用默认拒绝策略将立即中断命名空间内所有 pod 之间通信。请在同一条 kubectl apply 命令中应用下面的允许规则,或先应用允许规则。
# Allow pods to communicate within the same namespace
# Block cross-namespace traffic by default
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-same-namespace
namespace: production
spec:
endpointSelector: {}
ingress:
- fromEndpoints:
- matchLabels:
io.kubernetes.pod.namespace: production
egress:
- toEndpoints:
- matchLabels:
io.kubernetes.pod.namespace: production
# Grant the payment service only the specific network access it needs
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: payment-service-network-policy
namespace: production
spec:
endpointSelector:
matchLabels:
app: payment-service
egress:
# Allow: payment-service → postgres on port 5432
- toEndpoints:
- matchLabels:
app: postgres-db
toPorts:
- ports:
- port: "5432"
protocol: TCP
# Allow: payment-service → Stripe API externally
- toFQDNs:
- matchName: "api.stripe.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
Cilium 包含 Hubble,一个网络可观测性工具,能精准显示哪些流量被 Network Policies 允许,哪些被阻止。Hubble 也是您证明网络分段正常运行的 SOC2 证据。
# Install the Hubble CLI
export HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-amd64.tar.gz
tar xzvf hubble-linux-amd64.tar.gz
sudo mv hubble /usr/local/bin/
# Port-forward to the Hubble relay
kubectl port-forward -n kube-system svc/hubble-relay 4245:80 &
# Show all flows in the production namespace from the last hour
hubble observe --namespace production --since 1h
# Show only dropped flows — proves policies are blocking unauthorised traffic
hubble observe --namespace production --verdict DROPPED --since 1h
以下为Hubble输出示例,显示一次被阻止的连接尝试:
Apr 19 03:17:41.234 DROPPED TCP 10.244.1.5:52341 → 10.244.1.4:5432 policy-deny
Apr 19 03:17:41.235 ALLOWED TCP 10.244.1.2:43211 → 10.244.1.4:5432 allow-same-namespace
第一行显示被阻止的未授权连接尝试。第二行显示被允许的合法连接。每日将此日志导出到您的 SOC2 证据存储桶。
记住的一条规则:默认拒绝是零信任基线。然后为每个所需的通信路径添加显式允许规则。Hubble 提供了它正在工作的证据。
选择正确的 CNI 是一个难以逆转的决定。在 CNI 之间迁移需要排空并替换集群中的每个节点。请一次性做出正确的决定。
以下是针对生产 EKS 集群重要能力的真实比较:
| 能力 | AWS VPC CNI | Calico | Cilium |
|---|---|---|---|
| 来自 VPC CIDR 的 Pod IP | ✅ 是 | ❌ 否(覆盖网络) | ❌ 否(覆盖网络) |
| 基本网络策略 | ✅ 是(Kubernetes 标准) | ✅ 是 | ✅ 是 |
| 第 7 层策略(HTTP 路径、gRPC 方法) | ❌ 否 | ❌ 否 | ✅ 是 |
| eBPF 数据平面 | ❌ 否 | ❌ 否 | ✅ 是 |
| Hubble 流可观测性 | ❌ 否 | ❌ 否 | ✅ 是 |
| 服务网格(无 sidecar 的 mTLS) | ❌ 否 | ❌ 否 | ✅ 是 |
| 内置 SOC2 网络证据 | ❌ 否 | ❌ 否 | ✅ 是(Hubble) |
| 性能开销 | 低 | 中 | 非常低(eBPF 绕过 iptables) |
| AWS 原生集成 | ✅ 最佳 | 中 | 中 |
推荐矩阵:
| 您的情况 | 推荐 CNI |
|---|---|
| 简单的 EKS 集群,AWS 原生工具,无高级策略 | AWS VPC CNI |
| 需要网络策略但不需要第 7 层或可观测性 | Calico |
| 需要带有 pod 级隔离证据的 SOC2 合规性 | Cilium |
| 需要无 sidecar 代理开销的服务网格 | Cilium |
| 需要第 7 层网络策略(允许 GET /health,拒绝 POST /admin) | Cilium |
记住的一条规则:为了 EKS 上的 SOC2 合规和零信任网络,Cilium 是正确的选择。它提供 pod 级隔离、第 7 层策略以及可作为审计证据的 Hubble 流日志。这些能力没有其他 CNI 可以一起提供。
服务网格为您的集群网络添加了 Kubernetes 原生不具备的三项功能。
mTLS(相互 TLS)会加密服务之间每一对通信,并验证双方身份。若没有 mTLS,您的支付服务与数据库之间的流量将在集群内以明文形式传输。
流量可观测性会追踪每一次服务间调用的请求速率、延迟百分位和错误率,从而为您的应用提供实时性能图谱。
流量管理决定流量如何流转:失败时重试、超时、下游服务降级时进行熔断,以及用于金丝雀部署的流量拆分。
传统的服务网格(如 Istio 和 Linkerd)会在每个 Pod 中注入一个 sidecar 代理容器。该 sidecar 会拦截所有网络流量并应用网格策略。问题在于资源开销:Istio 的 Envoy sidecar 每个 Pod 大约会增加 128MB 内存以及 5–10% 的延迟开销。
在拥有 200 个 Pod 的集群中,Istio sidecar 会额外增加 25.6GB 内存开销,并对每次服务调用产生可测量的延迟。
Cilium 采用不同的方式解决此问题。它在内核层通过 eBPF 实现服务网格,完全不需要 sidecar。
| 功能 | Cilium | Istio | Linkerd |
|---|---|---|---|
| 是否需要 sidecar | ❌ 否(eBPF 内核) | ✅ 是(Envoy,~128MB/ Pod) | ✅ 是(Rust 代理,~10MB/ Pod) |
| 每个 Pod 的内存开销 | 0 MB | ~128 MB | ~10 MB |
| 延迟开销 | <1% | 5–10% | 2–3% |
| mTLS | ✅ 是 | ✅ 是 | ✅ 是 |
| 流量管理(金丝雀、熔断) | 有限 | ✅ 完整 | ✅ 完整 |
| 内置流量可观测性(Hubble) | ✅ 是 | ❌ 需要 Kiali | ❌ 需要 Buoyant Cloud |
| 原生 SOC2 证据 | ✅ 是 | ❌ 需要额外工具 | ❌ 需要额外工具 |
| 部署复杂度 | 低 | 高 | 中等 |
推荐矩阵:
| 您的情况 | 推荐的服务网格 |
|---|---|
| 需要 mTLS 和 SOC2 证据,且资源开销最小 | Cilium |
| 需要高级流量管理:金丝雀、熔断、加权路由 | Istio |
| 需要轻量级 mTLS,且不想承受 Istio 的运维复杂度 | Linkerd |
| 在运行数百个 Pod 的集群中,sidecar 开销是预算考量 | Cilium |
启用 Cilium 的服务网格模式(无需 sidecar):
# Upgrade your Cilium installation to enable service mesh features
helm upgrade cilium cilium/cilium \
--namespace kube-system \
--reuse-values \
--set envoy.enabled=true \
--set ingressController.enabled=true
# Verify the service mesh is active
cilium status | grep "Service Mesh"
记住这一条规则:Cilium 提供零 sidecar 开销的 mTLS 和 SOC2 证据。对于需要高级流量管理或复杂金丝雀发布模式的团队,Istio 在更高运营复杂度的代价下提供更多控制。
✅ 做: 使用 Service,而非 Pod IP。Pod IP 在每次重启后会变化。Service 的 DNS 名称永不变化。
✅ 做: 使用基于路径的单一 Ingress 控制器。一个 ALB 可为所有服务提供服务,相比每个服务使用 LoadBalancer,每月可节省 300–400 美元。
✅ 做: 使用 Cilium 实施默认拒绝的 Network Policy。这是 SOC2 CC6.1 所要求的技术控制。
✅ 做: 将 Hubble 流日志作为 SOC2 证据使用。将每日丢弃流日志导出到您的证据存储桶。
✅ 做: 使用 Cilium 启用 mTLS 以实现服务间加密通信。无需 sidecar。
✅ 做: 使用感知拓扑的路由,使流量保持在同一可用区内,以降低跨可用区数据传输成本。
❌ 不要: 为每个微服务创建一个 LoadBalancer Service。使用 Ingress 进行外部路由。
❌ 不要: 仅依赖 Security Groups 进行 pod 级隔离。Security Groups 作用于节点层面。同一节点上的所有 pod 共享该节点的安全组。Network Policies 作用于 pod 级别。
❌ 不要: 假设默认的 "allow all" pod 网络是安全的。在您的第一个企业客户要求查看网络分段图之前,应先实施默认拒绝。
Cilium 文档: 官方 Cilium 安装指南、CiliumNetworkPolicy 参考以及 Hubble 可观测性文档
Cilium Service Mesh 指南: 如何在不使用 sidecar 的情况下启用 mTLS 和 Layer 7 策略
Kubernetes Network Policy 文档: 标准的 Kubernetes NetworkPolicy API 参考
AWS Load Balancer 控制器: 用于从 Kubernetes Ingress 资源预配 AWS ALB 的 Ingress 控制器的官方文档
Hubble CLI 安装: 安装 Hubble CLI 以观察 Cilium 网络流量
kube-proxy iptables 模式: 解释 kube-proxy 如何使用 iptables 实现 Service 路由的 Kubernetes 文档
Kubernetes CNI 插件规范: 所有 CNI 插件实现的 CNI 接口规范
AWS VPC CNI 插件 GitHub: 默认 EKS 网络插件的源代码和文档
配套仓库: 本指南中的 CiliumNetworkPolicy 清单和 Hubble 证据导出脚本
——
一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。