Files
Qiniu/technical/k8s/k8s-05-service-and-networking.md

19 KiB
Raw Permalink Blame History

tags, create time
tags create time
k8s
service
networking
devops
2026-07-07 15:48

Kubernetes Service 与网络

概述

Kubernetes 中 Pod 的生命周期是短暂的——它们随时可能被销毁重建,每次重建都会获得新的 IP 地址。如果服务之间直接通过 Pod IP 通信,任何一次 Pod 重启都会导致调用方断连。Service 正是为了解决这个问题而生:它为一组动态变化的 Pod 提供一个稳定的访问入口(虚拟 IP),配合 DNS 和负载均衡,让服务发现变得透明且可靠。

K8s 网络模型

Kubernetes 对集群网络有三个核心要求,任何 CNI(Container Network Interface)插件都必须满足:

原则 说明
Pod-to-Pod 集群内任意两个 Pod 可以直接通过 IP 通信,无需 NAT
Pod-to-Service Pod 通过 Service 的虚拟 IP(ClusterIP)访问后端 Pod,由 kube-proxy 实现负载均衡
External-to-Service 集群外部流量可以通过 NodePort、LoadBalancer 或 Ingress 进入集群

CNI 插件简介

CNI 插件负责为 Pod 分配 IP 地址并配置网络连通性。常见实现:

  • Calico:基于 BGP 路由,支持 NetworkPolicy,性能优秀
  • Flannel:简单轻量,使用 VXLAN overlay,适合小规模集群
  • Cilium:基于 eBPF,高性能,支持 L7 网络策略
  • Weave Net:自带加密,配置简单

[!tip] 如何选择 小规模或学习环境用 Flannel 足够;生产环境推荐 Calico 或 Cilium,它们对 NetworkPolicy 的支持更完善。

Service 详解

ClusterIP

默认类型,为 Service 分配一个集群内部虚拟 IP,仅集群内可访问。

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
  namespace: default
spec:
  type: ClusterIP          # 默认值,可省略
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80              # Service 端口
      targetPort: 8080      # Pod 端口

流量路径:Client Pod → ClusterIP(虚拟 IP)→ kube-proxy → 后端 Pod:8080

ClusterIP 是一个"虚拟 IP"——它不存在于任何网卡上,只存在于 iptables/IPVS 规则中。

NodePort

在 ClusterIP 的基础上,在每个节点上开一个端口(默认范围 30000-32767),外部流量可通过 <NodeIP>:<NodePort> 访问。

apiVersion: v1
kind: Service
metadata:
  name: my-app-nodeport
spec:
  type: NodePort
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80              # Service 端口(集群内部访问)
      targetPort: 8080      # Pod 端口
      nodePort: 30080       # 节点端口(可选,不指定则自动分配)

[!info] iptables 规则链路 当外部请求到达 <NodeIP>:30080 时,kube-proxy 在每个节点上创建的 iptables 规则会将流量 DNAT 到后端 Pod 的 IP:Port。这意味着即使请求落在没有运行目标 Pod 的节点上,流量也会被正确转发。

flowchart LR
    A[External Client] -->|NodeIP:30080| B[Node]
    B -->|iptables DNAT| C[Pod on Node A]
    B -->|iptables DNAT| D[Pod on Node B]
    B -->|iptables DNAT| E[Pod on Node C]

LoadBalancer

在 NodePort 的基础上,向云厂商申请一个外部负载均衡器(如 AWS ELB、GCP CLB),自动将外部流量分发到各节点的 NodePort。

apiVersion: v1
kind: Service
metadata:
  name: my-app-lb
  annotations:
    # 云厂商特定注解,例如 AWS:
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

[!warning] 继承关系 LoadBalancer = ClusterIP + NodePort + 云厂商 LB。如果没有云厂商支持(如裸金属环境),LoadBalancer 类型的 Service 会一直停留在 <pending> 状态。此时可以考虑 MetalLB 作为替代方案。

ExternalName

将 Service 映射到一个外部 DNS 名称(CNAME 记录),不涉及任何代理和负载均衡。

apiVersion: v1
kind: Service
metadata:
  name: external-db
  namespace: default
spec:
  type: ExternalName
  externalName: db.example.com   # 映射到外部域名

适用场景:

  • 集群内应用需要访问外部数据库或 API
  • 跨命名空间/跨集群服务映射
  • 迁移过程中临时将内部 Service 指向外部服务

当 Pod 查询 external-db.default.svc.cluster.local 时,CoreDNS 会返回 db.example.com 的 CNAME 记录,Pod 再对 db.example.com 发起真正的 DNS 查询。

Service 类型对比

类型 ClusterIP NodePort LoadBalancer ExternalName
默认 是 否 否 否
集群内访问 支持 支持 支持 通过 CNAME
集群外访问 不支持 支持(NodeIP:Port) 支持(外部 IP) 不适用
端口范围 任意 30000-32767 任意(LB 端口) 无端口
典型场景 内部微服务互调 开发测试暴露 生产环境对外服务 访问外部服务
云厂商依赖 无 无 需要 无

Endpoints 与 EndpointSlices

Endpoints

每个 Service 背后都有一个同名的 Endpoints 对象,记录了当前所有健康 Pod 的 IP:Port 列表。当 Pod 就绪(Readiness Probe 通过)时自动加入,Pod 删除时自动移除。

# 查看 Service 对应的 Endpoints
kubectl get endpoints my-app-svc
# NAME          ENDPOINTS                                AGE
# my-app-svc    10.244.1.5:8080,10.244.2.8:8080         5m

EndpointSlices

Kubernetes 1.21 引入 EndpointSlices 作为 Endpoints 的替代方案。

[!info] 为什么需要 EndpointSlices? 在大规模集群中,一个 Service 可能关联成百上千个 Pod。Endpoints 对象会将所有 Pod IP 存储在单个对象中,导致:

  1. 更新风暴:任何一个 Pod 变化都要更新整个 Endpoints 对象
  2. 内存压力:kube-proxy 和所有节点的 kubelet 都要 watch 这个大对象
  3. API Server 压力:每次更新都要全量推送

EndpointSlices 将后端列表拆分成多个小切片(默认每个切片最多 100 个端点),增量更新,大幅降低开销。

# 查看 EndpointSlices
kubectl get endpointslices -l kubernetes.io/service-name=my-app-svc
flowchart TB
    subgraph Endpoints 旧方式
        S1[Service] --> E1[Endpoints<br/>全部 Pod IP 在一个对象中]
    end
    subgraph EndpointSlices 新方式
        S2[Service] --> ES1[EndpointSlice-0<br/>Pod 1-100]
        S2 --> ES2[EndpointSlice-1<br/>Pod 101-200]
        S2 --> ES3[EndpointSlice-2<br/>Pod 201-300]
    end

DNS 解析机制

CoreDNS

Kubernetes 使用 CoreDNS 作为集群内置 DNS 服务(1.12 之前使用 kube-dns)。每个 Service 创建后,CoreDNS 会自动为其注册 DNS 记录。

Service DNS 格式

资源类型 DNS 格式 示例
普通 Service <svc>.<ns>.svc.cluster.local my-app.default.svc.cluster.local
Headless Service <svc>.<ns>.svc.cluster.local → 解析到所有 Pod IP 直接返回 Pod IP 列表
Pod <pod-ip-dash>.<ns>.pod.cluster.local 10-244-1-5.default.pod.cluster.local

[!tip] 同命名空间内可以省略后缀 在 default 命名空间内的 Pod 访问同命名空间的 Service,可以直接用 my-app-svc,无需写全限定域名。

Headless Service

当 Service 的 clusterIP 设置为 None 时,它成为一个 Headless Service。此时 DNS 查询不会返回一个虚拟 IP,而是直接返回所有后端 Pod 的 IP 列表。

apiVersion: v1
kind: Service
metadata:
  name: my-app-headless
spec:
  clusterIP: None          # Headless
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080

典型应用场景:

  • StatefulSet:有状态应用(如数据库集群)需要知道每个 Pod 的独立地址
  • 自定义负载均衡:客户端自己实现负载均衡逻辑
  • 服务发现:客户端需要获取所有后端地址
# 查询 Headless Service 的 DNS
nslookup my-app-headless.default.svc.cluster.local
# Name:    my-app-headless.default.svc.cluster.local
# Address: 10.244.1.5
# Address: 10.244.2.8
# Address: 10.244.3.12

kube-proxy 深入

kube-proxy 是运行在每个节点上的网络代理,负责实现 Service 的负载均衡。它有三种工作模式:

iptables 模式(默认)

kube-proxy 监听 Service 和 Endpoints 的变化,在每个节点上维护 iptables 规则链。

flowchart TB
    A[请求到达 ClusterIP] --> B[KUBE-SERVICES 链]
    B --> C{匹配 Service?}
    C -->|是| D[KUBE-SVC-XXX 链]
    D --> E[概率匹配 -m statistic --mode random]
    E -->|概率 50%| F[KUBE-SEP-AAA 链 → Pod A]
    E -->|概率 50%| G[KUBE-SEP-BBB 链 → Pod B]
    C -->|否| H[REJECT]

规则链路详解:

  1. KUBE-SERVICES:总入口,匹配目标 IP:Port 确定 Service
  2. KUBE-SVC-XXX:Service 级别链,使用 statistic 模块做随机负载均衡
  3. KUBE-SEP-XXX:Endpoint 级别链,执行 DNAT 将目标地址转换为 Pod IP:Port

[!warning] iptables 模式的局限

  • 规则数量与 Service × Pod 数量成正比,大规模集群下规则链很长
  • 线性遍历匹配,时间复杂度 O(n)
  • 不支持连接追踪以外的负载均衡算法

IPVS 模式

IPVS(IP Virtual Server)是 Linux 内核中的四层负载均衡器,性能远优于 iptables。

# kube-proxy 配置启用 IPVS
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
  scheduler: "rr"    # 负载均衡算法:rr / lc / sh / sed / nq

支持的负载均衡算法:

算法 全称 说明
rr Round Robin 轮询(默认)
lc Least Connections 最少连接数
sh Source Hashing 源地址哈希(会话保持)
sed Shortest Expected Delay 最短预期延迟
nq Never Queue 永不排队

iptables vs IPVS 对比

维度 iptables IPVS
性能 规则多时下降明显 哈希表查找,O(1)
最大 Service 数 约 5000 开始明显退化 可支撑数万
负载均衡算法 仅随机 rr / lc / sh / sed / nq
连接保持 有限支持 原生支持
依赖 iptables(用户态工具) ipvs 内核模块
调试 iptables -L -n -t nat ipvsadm -Ln

[!tip] 生产建议 节点 Service 数超过 1000 时,强烈建议切换到 IPVS 模式。

Ingress 基础

Service(NodePort/LoadBalancer)工作在四层(TCP/UDP),而 Ingress 工作在七层(HTTP/HTTPS),提供基于域名和路径的路由能力。

Ingress Controller

Ingress 资源本身只是一份声明式配置,真正执行路由逻辑的是 Ingress Controller。常见的 Ingress Controller:

  • Nginx Ingress Controller:最广泛使用,社区版 + F5 版
  • Traefik:自动服务发现,配置简单
  • HAProxy:高性能,企业级特性丰富
  • Istio Gateway:Service Mesh 生态中的网关

Path-based 路由

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: path-based-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-svc
                port:
                  number: 80
          - path: /web
            pathType: Prefix
            backend:
              service:
                name: web-svc
                port:
                  number: 80

Host-based 路由

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: host-based-ingress
spec:
  ingressClassName: nginx
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-svc
                port:
                  number: 80
    - host: web.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-svc
                port:
                  number: 80

TLS 终止

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls-secret    # 存放证书的 Secret
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-svc
                port:
                  number: 80

证书需要提前创建为 Kubernetes Secret:

kubectl create secret tls app-tls-secret \
  --cert=tls.crt \
  --key=tls.key
flowchart LR
    A[Client] -->|HTTPS| B[Ingress Controller<br/>TLS 终止]
    B -->|HTTP| C[Service: my-app-svc]
    C --> D[Pod A]
    C --> E[Pod B]

NetworkPolicy

默认情况下,Kubernetes 集群中所有 Pod 之间可以互相通信。NetworkPolicy 允许你定义精细的网络访问控制规则。

[!warning] 前提条件 NetworkPolicy 需要 CNI 插件支持。Flannel 不支持 NetworkPolicy,Calico 和 Cilium 支持。

默认全通策略

如果不创建任何 NetworkPolicy,所有流量都是放行的:

flowchart LR
    A[Pod A] -->|允许| B[Pod B]
    B -->|允许| A
    A -->|允许| C[Pod C]
    C -->|允许| B

限制 Pod 间通信

示例:只允许前端 Pod 访问后端 Pod 的 8080 端口

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: backend              # 策略作用于 backend Pod
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend     # 只允许来自 frontend Pod
      ports:
        - protocol: TCP
          port: 8080            # 只允许访问 8080 端口

示例:限制出站流量,只允许访问指定 DNS

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-egress
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: my-app
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              name: kube-system   # 允许访问 kube-system 命名空间
      ports:
        - protocol: UDP
          port: 53                # 允许 DNS 查询
    - to:
        - ipBlock:
            cidr: 10.0.0.0/8     # 允许访问内网
      ports:
        - protocol: TCP
          port: 443

[!info] 策略叠加规则

  • 一旦某个 Pod 被任何 NetworkPolicy 选中,未被策略明确允许的流量将被拒绝
  • 多个 NetworkPolicy 取并集(任一策略允许即放行)
  • 只定义 policyTypes: [Ingress] 不影响出站,反之亦然
flowchart TB
    subgraph 策略生效后
        A[frontend Pod] -->|允许: TCP 8080| B[backend Pod]
        C[other Pod] -->|拒绝| B
        B -->|未定义 Egress 策略<br/>默认放行| D[任意目标]
    end

常见陷阱与最佳实践

Service 无法访问排查清单

当 Service 访问不通时,按以下顺序排查:

  1. 确认 Pod 运行正常

    kubectl get pods -l app=my-app -o wide
    kubectl logs <pod-name>
    kubectl describe pod <pod-name>
    
  2. 确认 Endpoints 已注册

    kubectl get endpoints my-app-svc
    # 如果 ENDPOINTS 为 <none>,说明没有健康的 Pod 匹配 selector
    
  3. 确认 selector 匹配

    # 对比 Service selector 和 Pod labels
    kubectl get svc my-app-svc -o jsonpath='{.spec.selector}'
    kubectl get pods --show-labels
    
  4. 确认端口正确

    # 检查 targetPort 是否与 Pod 实际监听端口一致
    kubectl get svc my-app-svc -o yaml | grep -A5 ports
    kubectl exec <pod-name> -- ss -tlnp
    
  5. 确认 DNS 解析

    kubectl run test --rm -it --image=busybox -- nslookup my-app-svc
    
  6. 确认网络策略未阻断

    kubectl get networkpolicy -n <namespace>
    
  7. 确认 kube-proxy 正常

    kubectl get pods -n kube-system -l k8s-app=kube-proxy
    # 检查 iptables 规则是否存在
    iptables -t nat -L KUBE-SERVICES | grep my-app-svc
    

Session Affinity

默认情况下 Service 使用轮询负载均衡。如果需要会话保持(同一客户端始终访问同一 Pod),可以启用 Session Affinity:

apiVersion: v1
kind: Service
metadata:
  name: sticky-svc
spec:
  selector:
    app: my-app
  sessionAffinity: ClientIP       # 基于客户端 IP 保持会话
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 1800        # 会话超时时间(默认 10800 秒)
  ports:
    - port: 80
      targetPort: 8080

[!warning] Session Affinity 注意事项

  • 在 iptables 模式下,基于源 IP 的会话保持可能在 NAT 环境下失效
  • IPVS 模式支持更精细的会话保持(sh 算法)
  • 对于 HTTP 应用,更推荐使用 Cookie-based Affinity(在 Ingress 层实现)

External Traffic Policy

控制外部流量如何路由到 Pod,只对 NodePort 和 LoadBalancer 类型 Service 生效:

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local    # 或 Cluster(默认)
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080
策略 行为 优点 缺点
Cluster(默认) 流量可能被转发到其他节点上的 Pod 负载均衡更均匀 多一跳转发,丢失源 IP
Local 流量只转发到本节点上的 Pod 保留源 IP,减少转发延迟 Pod 分布不均时负载不均衡
flowchart TB
    subgraph "externalTrafficPolicy: Cluster"
        A1[Client] -->|请求| B1[Node 1]
        B1 -->|可能转发| C1[Node 2 的 Pod]
        B1 -->|本地| D1[Node 1 的 Pod]
    end
    subgraph "externalTrafficPolicy: Local"
        A2[Client] -->|请求| B2[Node 1]
        B2 -->|仅本地| D2[Node 1 的 Pod]
        B2 -.->|不转发| C2[Node 2 的 Pod]
    end

其他最佳实践

  • 使用命名空间隔离 Service:避免命名冲突,便于权限管理
  • 合理设置 readinessProbe:Pod 未就绪时不会被加入 Endpoints,避免流量打到未准备好的实例
  • 避免使用 ExternalName 映射到集群内部 Service:可能导致 DNS 解析循环
  • 大规模集群启用 EndpointSlices:减少 API Server 和 kube-proxy 的压力
  • 生产环境使用 IPVS 模式:在 Service 数量较多时性能显著优于 iptables
  • 为关键 Service 配置 PodDisruptionBudget:确保滚动更新和节点维护时服务可用性

延伸阅读