首页 / 文章 / Kubernetes 网络详解:从 ClusterIP 到 Cilium Service Mesh
← 返回
IT技术

Kubernetes 网络详解:从 ClusterIP 到 Cilium Service Mesh

✍️ zhirenhun 📅 2026/8/24 👁 142 阅读 ⏱ 54 分钟
Kubernetes 网络详解:从 ClusterIP 到 Cilium Service Mesh

大多数 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。

目录

你将学到的内容

让我们开始吧。

先决条件

在开始之前,您应该具备:

知识:

工具和访问:

关于 CNI 的说明:第 1–3 部分适用于任何 Kubernetes 集群,无论使用哪种 CNI。第 4–6 部分使用 Cilium 特定的资源(CiliumNetworkPolicy、Hubble)。如果您使用不同的 CNI,概念是相同的,只有 YAML 语法有所不同。

第一部分:Pod IP 和容器网络模型

1.1 每个 Pod 为何拥有自己的 IP

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 网络,与节点网络分离。

1.2 CNI 插件实际做了什么

容器网络接口(CNI)是负责使 Kubernetes 网络模型工作的插件。当一个新 Pod 在节点上被调度时,Kubernetes kubelet 会调用 CNI 插件,它会执行四个操作:

  1. 为 Pod 创建一个新的网络命名空间:一个隔离的网络环境

  2. 创建一对虚拟以太网(veth):一端在 Pod 的命名空间内,另一端在节点上

  3. 将集群 Pod CIDR 中的 IP 地址分配给 veth 对的 Pod 端

  4. 添加路由规则,使节点知道如何到达集群中的每个 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 合规证据

1.3 验证 Pod 间通信

最基本的网络测试是:进入一个 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 插件使这两点都成立。

第二部分:服务 — ClusterIP、NodePort 和 LoadBalancer

2.1 问题:Pod IP 不稳定

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 硬编码的应用程序崩溃。

2.2 Services 如何解决稳定性问题

Kubernetes Service 提供了 Pod IP 所不具备的两项功能:一个在 Service 存在期间永不变化的稳定 IP 地址(ClusterIP),以及其他 Pod 可以使用的、与 IP 无关的稳定 DNS 名称。

创建 Service 时,Kubernetes 会从 service CIDR(例如 10.100.0.0/16)分配一个虚拟的 ClusterIP,在 CoreDNS 中创建形如 ..svc.cluster.local 的 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 会自动更新这些规则。

2.3 何时使用每种服务类型

类型 DNS 名称 可访问来源 使用场景
ClusterIP redis.production.svc.cluster.local 仅在集群内部 数据库、缓存、内部 API
NodePort :30000–32767 节点 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 会自动处理稳定性、负载均衡和健康检查。

第三部分:Ingress —— 外部流量路由

3.1 问题:每个微服务一个 LoadBalancer 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

3.2 入口控制器如何解决此问题

入口控制器是运行在您集群内部的 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 的方式仅适用于早期原型阶段。

Part 4: Network Policies — Micro-Segmentation

4.1 默认情况:每个 Pod 都可以与其他 Pod 通信

开箱即用的 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

如果在您的集群上此操作成功,则表明没有网络分段。

4.2 解决方案:默认拒绝 + Cilium 网络策略

正确的做法是采用默认拒绝:先阻止 Pod 之间的所有流量,然后仅放行应用程序所需的特定通信路径。

步骤 1 — 应用默认拒绝策略:

# 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 命令中应用下面的允许规则,或先应用允许规则。

步骤 2 — 添加命名空间级隔离:

# 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

步骤 3:为每项服务添加允许规则

# 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

4.3 使用 Hubble 验证策略并收集 SOC2 证据

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 提供了它正在工作的证据。

Part 5: CNI 比较 — Cilium vs Calico vs AWS VPC CNI

选择正确的 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 可以一起提供。

Part 6: 服务网格 — Cilium vs Istio vs Linkerd

6.1 服务网格提供的功能

服务网格为您的集群网络添加了 Kubernetes 原生不具备的三项功能。

mTLS(相互 TLS)会加密服务之间每一对通信,并验证双方身份。若没有 mTLS,您的支付服务与数据库之间的流量将在集群内以明文形式传输。

流量可观测性会追踪每一次服务间调用的请求速率、延迟百分位和错误率,从而为您的应用提供实时性能图谱。

流量管理决定流量如何流转:失败时重试、超时、下游服务降级时进行熔断,以及用于金丝雀部署的流量拆分。

6.2 边车问题

传统的服务网格(如 Istio 和 Linkerd)会在每个 Pod 中注入一个 sidecar 代理容器。该 sidecar 会拦截所有网络流量并应用网格策略。问题在于资源开销:Istio 的 Envoy sidecar 每个 Pod 大约会增加 128MB 内存以及 5–10% 的延迟开销。

在拥有 200 个 Pod 的集群中,Istio sidecar 会额外增加 25.6GB 内存开销,并对每次服务调用产生可测量的延迟。

Cilium 采用不同的方式解决此问题。它在内核层通过 eBPF 实现服务网格,完全不需要 sidecar。

6.3 完整对比

功能 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 在更高运营复杂度的代价下提供更多控制。

Kubernetes 网络最佳实践

做: 使用 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 网络是安全的。在您的第一个企业客户要求查看网络分段图之前,应先实施默认拒绝。

资源

——

🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

← 上一篇
量化的真实权衡:FP16、INT8 与 GGUF 在生产中按模型大小的实际分歧
下一篇 →
使用 DigitalOcean 批量推理对 250,000 条记录分类,成本仅 7.04 美元

📌 相关推荐

GraphRAG 是推理问题,而非数据库问题
2026/8/30
构建市场时光机:使用 Python 和 WebSocket 重放交易会话
2026/8/30
如何自行基准测试LLM推理:值得信赖的数字设计标准
2026/8/30
← 返回文章列表