目录

Kubernetes-05 网络详解

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