目录

Kubernetes-05 网络详解

Kubernetes 网络详解

系列第五篇。K8s 网络是最复杂但也最重要的部分之一。本篇从 Pod 网络、Service、Ingress 到网络策略,系统讲解 K8s 的网络体系。


目录

  1. K8s 网络基础模型
  2. Pod 网络
  3. Service
  4. Ingress
  5. DNS 服务发现
  6. NetworkPolicy(网络策略)
  7. CNI 插件
  8. 实战:Go 微服务通信
  9. 小结

1. K8s 网络基础模型

K8s 的网络模型有三个基本要求:

  1. 所有 Pod 之间可以不经 NAT 直接通信(跨节点也可以)
  2. 所有 Node 可以与所有 Pod 直接通信(不经 NAT)
  3. Pod 自己看到的 IP 与其他人看到的 IP 相同(没有 IP 映射)
K8s 网络层次:

┌─────────────────────────────────────────────────────────────┐
│                     集群外部(Internet)                     │
└───────────────────────────┬─────────────────────────────────┘
                            │ 80/443
┌───────────────────────────▼─────────────────────────────────┐
│               Ingress / LoadBalancer Service                 │
│                   (7层路由 / 4层负载均衡)                   │
└───────────────────────────┬─────────────────────────────────┘
                            │ ClusterIP
┌───────────────────────────▼─────────────────────────────────┐
│                         Service                             │
│               (稳定 VIP + 负载均衡到 Pod)                   │
└─────────┬─────────────────┬──────────────────┬─────────────┘
          │                 │                  │
    ┌─────▼────┐      ┌─────▼────┐      ┌─────▼────┐
    │  Pod A   │      │  Pod B   │      │  Pod C   │
    │10.244.1.2│      │10.244.2.3│      │10.244.3.4│
    └──────────┘      └──────────┘      └──────────┘
       Node 1            Node 2            Node 3
   (192.168.1.1)     (192.168.1.2)    (192.168.1.3)

2. Pod 网络

2.1 Pod IP

每个 Pod 在创建时都会分配一个集群内唯一的 IP(来自 Pod CIDR,如 10.244.0.0/16)。

kubectl get pods -o wide
# NAME          READY   STATUS    IP            NODE
# go-api-7d...  1/1     Running   10.244.1.42   node-1
# go-api-8f...  1/1     Running   10.244.2.15   node-2

Pod IP 的特点:

  • Pod 重启后 IP 可能变化
  • Pod 迁移到其他节点后 IP 一定变化
  • 因此,不要直接使用 Pod IP,应使用 Service

2.2 同 Pod 内容器通信

同一个 Pod 内的容器共享网络命名空间,通过 localhost 直接通信:

// 容器 A(主应用)访问容器 B(sidecar)
resp, err := http.Get("http://localhost:9090/metrics")

2.3 Pod 间通信

// 容器 A(Pod1: 10.244.1.42)访问容器 B(Pod2: 10.244.2.15)
// 可以直接访问,但不推荐(IP 会变)
resp, err := http.Get("http://10.244.2.15:8080/api")

// 推荐使用 Service DNS
resp, err := http.Get("http://service-b:8080/api")

3. Service

Service 是 K8s 中的网络抽象层,提供稳定的访问端点来访问一组 Pod。

3.1 Service 的四种类型

ClusterIP    → 只在集群内部访问(默认)
NodePort     → 通过节点 IP + 端口访问(开发测试)
LoadBalancer → 通过云厂商负载均衡器访问(生产外部访问)
ExternalName → DNS CNAME,将服务映射到外部 DNS 名

3.2 ClusterIP(默认)

apiVersion: v1
kind: Service
metadata:
  name: go-api
  namespace: production
spec:
  type: ClusterIP           # 默认值,可省略
  selector:
    app: go-api             # 选择后端 Pod
  ports:
  - name: http
    port: 80                # Service 端口(ClusterIP:80)
    targetPort: 8080        # Pod 端口(容器监听的端口)
    protocol: TCP
  - name: grpc
    port: 9000
    targetPort: 9000
# 查看 Service
kubectl get service go-api
# NAME     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
# go-api   ClusterIP   10.96.100.150   <none>        80/TCP    5m

# 集群内可以通过 ClusterIP 或 DNS 访问
curl http://10.96.100.150:80
curl http://go-api.production.svc.cluster.local:80
curl http://go-api:80  # 同 Namespace 内可简写

ClusterIP 工作原理:

客户端 Pod → 请求 10.96.100.150:80
    ↓ kube-proxy 的 iptables/IPVS 规则
随机选择一个后端 Pod(10.244.x.x:8080)
    ↓
到达目标 Pod

Headless Service(无 ClusterIP):

spec:
  clusterIP: None    # 设为 None 即为 Headless

Headless Service 不做负载均衡,DNS 查询会直接返回所有 Pod 的 IP 列表。常用于 StatefulSet。

3.3 NodePort

apiVersion: v1
kind: Service
metadata:
  name: go-api-nodeport
spec:
  type: NodePort
  selector:
    app: go-api
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30080       # 节点端口范围:30000-32767(不指定则随机分配)
# 通过任意节点 IP + NodePort 访问
curl http://192.168.1.1:30080
curl http://192.168.1.2:30080  # 任意节点都可以

流量路径:

外部请求 → 任意节点 IP:30080
    ↓ iptables DNAT
Service ClusterIP:80
    ↓
随机选择一个后端 Pod(可能在任何节点)

缺点:

  • 端口范围固定(30000-32767),难以记忆
  • 每个 Service 占用一个 NodePort,数量有限
  • 只有 4 层(TCP/UDP),无法基于域名/路径路由

适用场景: 开发测试、临时对外暴露服务

3.4 LoadBalancer

apiVersion: v1
kind: Service
metadata:
  name: go-api-lb
  annotations:
    # AWS 特定注解
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
spec:
  type: LoadBalancer
  selector:
    app: go-api
  ports:
  - port: 80
    targetPort: 8080
  # 限制来源 IP(可选)
  loadBalancerSourceRanges:
  - "10.0.0.0/8"
  - "172.16.0.0/12"
kubectl get service go-api-lb
# NAME         TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)
# go-api-lb    LoadBalancer   10.96.1.100    52.1.2.3        80:31234/TCP

云厂商的 cloud-controller-manager 会自动创建一个真实的负载均衡器(AWS ELB、阿里云 SLB 等),并将 EXTERNAL-IP 填写进去。

本地测试 LoadBalancer:

# minikube
minikube tunnel  # 在另一个终端运行,为 LoadBalancer Service 分配本地 IP

3.5 ExternalName

apiVersion: v1
kind: Service
metadata:
  name: external-db
  namespace: production
spec:
  type: ExternalName
  externalName: prod-db.us-east-1.rds.amazonaws.com

集群内访问 external-db 时,DNS 会返回 prod-db.us-east-1.rds.amazonaws.com 的 CNAME。

使用场景: 将集群外部的服务映射为集群内的 Service 名称,方便迁移时切换。

3.6 Endpoints 和 EndpointSlice

Service 通过 Selector 自动维护一个 Endpoints 对象:

kubectl get endpoints go-api
# NAME     ENDPOINTS                                      AGE
# go-api   10.244.1.42:8080,10.244.2.15:8080,10.244.3.7:8080   10m

当 Pod 不满足 readinessProbe 时,会自动从 Endpoints 中移除(不再接收流量)。

自定义 Endpoints(无 Selector 的 Service):

# Service(不设置 selector)
apiVersion: v1
kind: Service
metadata:
  name: external-mysql
spec:
  ports:
  - port: 3306
---
# 手动维护 Endpoints
apiVersion: v1
kind: Endpoints
metadata:
  name: external-mysql   # 必须与 Service 同名
subsets:
- addresses:
  - ip: 192.168.100.50   # 外部 MySQL 的 IP
  ports:
  - port: 3306

4. Ingress

Ingress 是7层(HTTP/HTTPS)的路由规则,将外部请求路由到不同的 Service。

外部请求
    ↓
Ingress Controller(如 Nginx Ingress Controller)
    ↓ 根据 Host/Path 路由
各个 Service → 各个 Pod

4.1 为什么需要 Ingress

如果每个服务都用 LoadBalancer,需要多个公网 IP 和多个 LB(费用高)。Ingress 用一个 LB 处理所有 HTTP 流量,根据域名/路径分发。

4.2 安装 Ingress Controller

# Nginx Ingress Controller(最常用)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml

# 验证
kubectl get pods -n ingress-nginx

# minikube
minikube addons enable ingress

4.3 基本 Ingress 配置

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  namespace: production
  annotations:
    kubernetes.io/ingress.class: "nginx"
    # Nginx 特定配置
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "5"
spec:
  # TLS 配置
  tls:
  - hosts:
    - api.myapp.com
    - admin.myapp.com
    secretName: myapp-tls-secret   # 包含 tls.crt 和 tls.key

  rules:
  # 基于域名路由
  - host: api.myapp.com
    http:
      paths:
      - path: /v1/users
        pathType: Prefix
        backend:
          service:
            name: user-service
            port:
              number: 80
      - path: /v1/orders
        pathType: Prefix
        backend:
          service:
            name: order-service
            port:
              number: 80
      - path: /                    # 默认路由
        pathType: Prefix
        backend:
          service:
            name: go-api
            port:
              number: 80

  - host: admin.myapp.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: admin-service
            port:
              number: 80

4.4 路径类型

pathType 描述
Exact 精确匹配,/foo 不匹配 /foo/
Prefix 前缀匹配,/foo 匹配 /foo/foo/bar
ImplementationSpecific 由 Ingress Controller 决定

4.5 TLS 证书配置

# 手动创建 TLS Secret
kubectl create secret tls myapp-tls-secret \
  --cert=tls.crt \
  --key=tls.key \
  -n production

使用 cert-manager 自动续签(Let’s Encrypt):

# 安装 cert-manager 后,只需添加 annotation
metadata:
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - api.myapp.com
    secretName: myapp-tls-secret   # cert-manager 自动创建和续签

4.6 高级 Nginx Ingress 配置

metadata:
  annotations:
    # 限流
    nginx.ingress.kubernetes.io/limit-rps: "100"
    nginx.ingress.kubernetes.io/limit-connections: "50"

    # 认证(Basic Auth)
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"

    # CORS
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://myapp.com"

    # 会话保持(基于 Cookie)
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "INGRESSCOOKIE"

    # WebSocket 支持
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"

    # 重写路径
    nginx.ingress.kubernetes.io/rewrite-target: /$2

5. DNS 服务发现

K8s 内置 DNS 服务(CoreDNS),为 Service 和 Pod 提供 DNS 解析。

5.1 Service DNS 规则

<service-name>.<namespace>.svc.cluster.local

# 示例:
go-api.production.svc.cluster.local
mysql.default.svc.cluster.local

同 Namespace 内可简写:

// 完整 DNS
http.Get("http://go-api.production.svc.cluster.local:80")

// 同 Namespace 内简写
http.Get("http://go-api:80")
http.Get("http://go-api.production:80")  // 跨 Namespace 简写(带 Namespace 名)

5.2 Pod DNS 规则

<pod-ip>.<namespace>.pod.cluster.local
# 如:10-244-1-42.production.pod.cluster.local(IP 中的点换成横线)

# StatefulSet Pod:
<pod-name>.<service-name>.<namespace>.svc.cluster.local
# 如:mysql-0.mysql.default.svc.cluster.local

5.3 DNS 查询示例

# 在 Pod 内查询 DNS
kubectl exec -it go-api-7d9b4c-xxxxx -- nslookup go-api
kubectl exec -it go-api-7d9b4c-xxxxx -- nslookup go-api.production.svc.cluster.local

# 查看 DNS 配置
kubectl exec -it <pod> -- cat /etc/resolv.conf
# nameserver 10.96.0.10       ← CoreDNS 的 ClusterIP
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5

5.4 CoreDNS 配置

kubectl get cm coredns -n kube-system -o yaml

可以添加自定义解析、上游 DNS 等配置。


6. NetworkPolicy(网络策略)

默认情况下,K8s 中所有 Pod 可以互相通信。NetworkPolicy 用于限制 Pod 间的网络流量

注意: NetworkPolicy 需要 CNI 插件支持(Calico、Cilium 等)。Flannel 不支持。

6.1 概念

NetworkPolicy 是白名单机制:
- 一旦为 Pod 定义了 NetworkPolicy,所有不匹配规则的流量都会被拒绝
- 没有 NetworkPolicy 的 Pod 允许所有流量

6.2 拒绝所有入站流量(默认拒绝)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
  namespace: production
spec:
  podSelector: {}      # 空选择器:匹配所有 Pod
  policyTypes:
  - Ingress            # 控制入站流量
  # 没有 ingress 规则 = 拒绝所有入站

6.3 只允许特定服务访问数据库

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: mysql         # 限制对象:mysql Pod

  policyTypes:
  - Ingress              # 控制入站(谁可以访问 mysql)

  ingress:
  # 允许来自 go-api 的访问
  - from:
    - podSelector:
        matchLabels:
          app: go-api
    ports:
    - protocol: TCP
      port: 3306

  # 允许来自 admin Namespace 的任何 Pod
  - from:
    - namespaceSelector:
        matchLabels:
          name: admin
    ports:
    - protocol: TCP
      port: 3306

6.4 限制出站流量

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: go-api-egress
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: go-api

  policyTypes:
  - Egress              # 控制出站(go-api 可以访问谁)

  egress:
  # 允许访问 mysql
  - to:
    - podSelector:
        matchLabels:
          app: mysql
    ports:
    - port: 3306

  # 允许访问 Redis
  - to:
    - podSelector:
        matchLabels:
          app: redis
    ports:
    - port: 6379

  # 允许 DNS 查询(必须!否则无法解析域名)
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: UDP
      port: 53

  # 允许访问外部 API(如支付网关)
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 10.0.0.0/8      # 排除集群内部网络
    ports:
    - port: 443

6.5 完整的微服务网络策略示例

# frontend 只能被 Ingress Controller 访问
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-policy
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: ingress-nginx

# backend 只能被 frontend 访问
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend

# database 只能被 backend 访问
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database-policy
spec:
  podSelector:
    matchLabels:
      app: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - port: 5432

7. CNI 插件

CNI(Container Network Interface)是 K8s 的网络插件标准,负责:

  • 为 Pod 分配 IP
  • 配置 Pod 的网络接口
  • 实现跨节点的 Pod 互通

主流 CNI 对比

CNI 类型 NetworkPolicy 性能 适用场景
Flannel Overlay(VXLAN) 不支持 一般 简单环境、学习
Calico BGP/Overlay 支持 大多数生产环境
Cilium eBPF 支持(增强版) 最高 高性能/安全要求高
Weave Overlay 支持 一般 混合云

Calico(最常用):

  • 使用 BGP 路由,性能好(不需要封包/解包)
  • 支持 NetworkPolicy
  • 支持全局网络策略

Cilium(新一代):

  • 基于 eBPF,在内核级别处理网络,性能最好
  • 支持 L7(HTTP、gRPC)级别的网络策略
  • 提供更丰富的可观测性

8. 实战:Go 微服务通信

8.1 场景

两个 Go 服务:user-serviceorder-service,order 需要调用 user 获取用户信息。

8.2 user-service 部署

# user-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: myregistry.io/user-service:v1.0.0
        ports:
        - containerPort: 8080
          name: http
        - containerPort: 9090
          name: grpc
---
apiVersion: v1
kind: Service
metadata:
  name: user-service
  namespace: production
spec:
  selector:
    app: user-service
  ports:
  - name: http
    port: 80
    targetPort: 8080
  - name: grpc
    port: 9090
    targetPort: 9090

8.3 Go 代码中的服务发现

package client

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

// K8s 环境下,直接使用 Service DNS 名
const userServiceURL = "http://user-service.production.svc.cluster.local"

type UserClient struct {
    httpClient *http.Client
}

func NewUserClient() *UserClient {
    return &UserClient{
        httpClient: &http.Client{
            Timeout: 5 * time.Second,
            Transport: &http.Transport{
                MaxIdleConns:        100,
                MaxIdleConnsPerHost: 10,
                IdleConnTimeout:     90 * time.Second,
            },
        },
    }
}

type User struct {
    ID    int64  `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func (c *UserClient) GetUser(ctx context.Context, userID int64) (*User, error) {
    url := fmt.Sprintf("%s/users/%d", userServiceURL, userID)

    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return nil, fmt.Errorf("create request: %w", err)
    }

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("do request: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("user service returned %d", resp.StatusCode)
    }

    var user User
    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
        return nil, fmt.Errorf("decode response: %w", err)
    }

    return &user, nil
}

8.4 使用环境变量配置 Service 地址

K8s 会为每个 Service 自动注入环境变量:

# 在 Pod 内查看
env | grep USER_SERVICE
# USER_SERVICE_SERVICE_HOST=10.96.100.150
# USER_SERVICE_SERVICE_PORT=80
# USER_SERVICE_PORT=tcp://10.96.100.150:80

但更推荐直接用 DNS 名,不要依赖这些环境变量(顺序问题、名称复杂)。

8.5 跨 Namespace 访问

// 从 default Namespace 访问 production Namespace 的 user-service
const userServiceURL = "http://user-service.production.svc.cluster.local"

// DNS 格式:<service>.<namespace>.svc.cluster.local

9. 小结

网络组件速查

组件 层级 作用
Pod 网络 L3 Pod 间直接通信
Service (ClusterIP) L4 集群内负载均衡
Service (NodePort) L4 通过节点端口外部访问
Service (LoadBalancer) L4 云 LB 外部访问
Ingress L7 HTTP 路由,域名/路径分发
NetworkPolicy L3/L4 网络访问控制
CoreDNS DNS 服务发现
CNI L2/L3 底层网络实现

网络故障排查

# 检查 Service 的 Endpoints(为空则说明没有匹配的就绪 Pod)
kubectl get endpoints <service-name>

# 在 Pod 内测试 DNS
kubectl exec -it <pod> -- nslookup <service-name>

# 在 Pod 内测试连通性
kubectl exec -it <pod> -- curl http://<service-name>:<port>

# 测试网络策略
kubectl exec -it <pod-a> -- curl http://<pod-b-ip>:8080

# 查看 kube-proxy 模式
kubectl get cm kube-proxy -n kube-system -o yaml | grep mode

# 临时创建网络测试 Pod
kubectl run netshoot --image=nicolaka/netshoot -it --rm -- bash