跳转至

服务与网络

Pod 的 IP 是临时的,Service 才是稳定的访问入口。再往上,Ingress 负责七层路由,NetworkPolicy 负责「谁能访问谁」。这一章把三层网络讲清楚。

难度 · 进阶 预计 60 分钟 前置:Pod、工作负载

你将学到

  • Kubernetes 的 Pod 网络模型与 CNI 的职责
  • ClusterIP、NodePort、LoadBalancer、ExternalName、Headless 的差异
  • Service、Endpoints、EndpointSlice 的关系
  • CoreDNS 的解析规则与常见 DNS 问题
  • Ingress 的路由与 TLS
  • NetworkPolicy 的默认拒绝与精确放行

Pod 网络模型

Kubernetes 对网络有三条基本要求:

  1. 每个 Pod 有独立 IP;
  2. 所有 Pod 之间可以直接通信,不需要 NAT;
  3. 节点上的进程可以直接访问所有 Pod。

这套「扁平网络」由 CNI 插件实现。CNI 负责:

  • 给 Pod 分配 IP;
  • 配置 veth、路由、网桥;
  • 实现跨节点通信(VXLAN / IPIP / BGP / 原生路由);
  • 部分 CNI 还实现 NetworkPolicy。
flowchart LR
  subgraph Node1
    P1[Pod A<br/>10.244.1.10] --> V1[veth]
    V1 --> B1[cni0 / 路由]
  end
  subgraph Node2
    P2[Pod B<br/>10.244.2.20] --> V2[veth]
    V2 --> B2[cni0 / 路由]
  end
  B1 <-->|VXLAN / BGP / 路由| B2

图:跨节点 Pod 通信由 CNI 负责;具体封装或路由方式取决于插件配置。

Service

为什么需要 Service

Pod 会被重建,IP 会变化。Service 提供:

  • 稳定的 ClusterIP 和 DNS 名称;
  • 基于标签选择器自动维护后端;
  • 负载均衡(iptables / IPVS / eBPF)。
service-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  name: demoapp-svc
spec:
  type: ClusterIP
  selector:
    app: demoapp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: http
字段 含义
port Service 暴露的端口
targetPort 后端 Pod 的端口(可以是名字)
selector 选中哪些 Pod
type ClusterIP / NodePort / LoadBalancer / ExternalName

四种类型

类型 访问范围 实现 适用
ClusterIP 集群内 虚拟 IP + kube-proxy 内部服务
NodePort 集群外(节点 IP + 端口) 每个节点开一个端口 实验、临时暴露
LoadBalancer 集群外 云 LB 或 MetalLB 生产入口
ExternalName 集群内 CNAME 到外部域名 访问外部服务
service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: demoapp-nodeport
spec:
  type: NodePort
  selector:
    app: demoapp
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080
service-externalname.yaml
apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  type: ExternalName
  externalName: db.example.com

Headless Service

clusterIP: None 不为 Service 分配虚拟 IP,DNS 直接返回所有 Pod IP。StatefulSet 依赖它提供稳定身份。

service-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: demoapp-headless
spec:
  clusterIP: None
  selector:
    app: demoapp
  ports:
    - port: 80
      targetPort: 80
kubectl run -it --rm dns --image=busybox:1.36 --restart=Never -- \
  nslookup demoapp-headless

预期返回多个 Pod IP,而不是一个 ClusterIP。

Endpoints 与 EndpointSlice

Service 通过 selector 找到 Pod,把就绪的 Pod IP 写入 EndpointSlice。kube-proxy 根据 EndpointSlice 编程转发规则。

kubectl get endpoints demoapp-svc
kubectl get endpointslice -l kubernetes.io/service-name=demoapp-svc

如果 Endpoints 为空:

  • selector 不匹配;
  • Pod 没有 Ready(readiness 失败);
  • Pod 在别的 namespace。

kube-proxy 模式

模式 原理 特点
iptables 规则匹配 + DNAT 成熟,规则多时性能下降
IPVS 内核四层负载均衡 大规模性能好,需要 ipvs 模块
eBPF Cilium 替换 kube-proxy 性能与可观测性强,复杂度高

DNS

CoreDNS 为 Service 提供 DNS:

名称 解析结果
demoapp-svc 同 namespace 的 ClusterIP
demoapp-svc.demo 指定 namespace
demoapp-svc.demo.svc 加上 svc
demoapp-svc.demo.svc.cluster.local FQDN
demoapp-headless 所有 Pod IP(Headless)
demoapp-0.demoapp-headless.demo.svc.cluster.local StatefulSet 单 Pod

Pod 的 /etc/resolv.conf:

nameserver 10.96.0.10
search demo.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

ndots:5 意味着「名字里少于 5 个点就优先尝试 search 域」,这也是跨 namespace 访问时偶尔多一次查询的原因。

测试:

kubectl run -it --rm dns --image=busybox:1.36 --restart=Never -- sh
# 容器内
nslookup kubernetes.default
nslookup demoapp-svc.demo
cat /etc/resolv.conf

Ingress

Service 是四层(TCP/UDP),Ingress 是七层(HTTP/HTTPS),提供基于域名和路径的路由。

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demoapp
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - demo.lab
      secretName: demo-tls
  rules:
    - host: demo.lab
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-svc
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-svc
                port:
                  number: 80
pathType 行为
Exact 精确匹配
Prefix 按路径段前缀匹配
ImplementationSpecific 由 Ingress Controller 决定

Ingress 本身只是规则,真正生效需要 Ingress Controller(如 ingress-nginx)。没有 Controller 时,Ingress 对象不会产生任何流量。

NetworkPolicy

默认情况下,Pod 之间全部互通。NetworkPolicy 用来实现「默认拒绝 + 按需放行」。

deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
  namespace: demo
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

这条策略选中 namespace 内所有 Pod,拒绝所有入站和出站流量。然后按需放行:

allow-web.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-web
  namespace: demo
spec:
  podSelector:
    matchLabels:
      app: demoapp
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 80

NetworkPolicy 需要 CNI 支持

Flannel 默认不支持 NetworkPolicy;Calico、Cilium 支持。写了策略但没有生效,先确认 CNI 是否实现了 networking.k8s.io/v1 的 NetworkPolicy。

NetworkPolicy 是「允许列表」的叠加:一旦某个 Pod 被至少一条策略选中,未被允许的流量默认拒绝。

实验:Service 与 NetworkPolicy

1. 部署两个后端

kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deploy web --image=nginx:1.27 --replicas=2 -n demo
kubectl expose deploy web --port=80 --name=web -n demo
kubectl get endpoints web -n demo

2. 验证 ClusterIP

kubectl run curl --image=curlimages/curl:8.10.1 --rm -it --restart=Never -n demo -- \
  curl -s http://web/

3. 验证 Headless

kubectl expose deploy web --port=80 --name=web-headless --clusterip=None -n demo
kubectl run dns --image=busybox:1.36 --rm -it --restart=Never -n demo -- \
  nslookup web-headless

4. 默认拒绝

kubectl apply -f deny-all.yaml
kubectl run curl --image=curlimages/curl:8.10.1 --rm -it --restart=Never -n demo -- \
  curl -m 3 -s http://web/

预期超时。然后应用 allow-web.yaml,再次访问。注意:策略中的 namespaceSelector 要匹配实际来源 namespace。

5. 观察 EndpointSlice

kubectl get endpointslice -n demo -w

删除一个 Pod,观察 EndpointSlice 如何更新。

排错表

现象 原因 处理
Service 访问超时 没有 Endpoints / 网络策略 / CNI 异常 get endpoints、describe svc
curl 返回 503 后端未 Ready get pods、readiness 探针
DNS 解析失败 CoreDNS 异常 / 网络策略阻断 kubectl get pods -n kube-system
跨 namespace 解析失败 用了短名 用 svc.ns 或 FQDN
NodePort 访问不通 防火墙 / externalTrafficPolicy 检查节点端口与安全组
Ingress 404 host/path 不匹配 curl -H 'Host: ...',检查 pathType
Ingress 502 后端端口错 / 未 Ready get endpoints,检查 targetPort
NetworkPolicy 不生效 CNI 不支持 换 Calico/Cilium
源 IP 丢失 SNAT / externalTrafficPolicy: Cluster 改 Local 或用 X-Forwarded-For

进阶挑战

  1. 用 externalTrafficPolicy: Local 部署 NodePort,观察源 IP 变化。
  2. 给 StatefulSet 配置 Headless Service,验证 pod-0.svc 的 DNS。
  3. 写一条只允许 frontend 访问 api 的 NetworkPolicy,并用 kubectl exec 验证。
  4. 用 kubectl get endpointslice -o yaml 观察 endpoints 与 ports 结构。
  5. 把 CoreDNS 的 ConfigMap 改错,观察集群 DNS 故障,再恢复。

小结

  • Pod 网络由 CNI 实现,Service 提供稳定入口,Ingress 提供七层路由。
  • Service 的 selector 与 Pod 标签不匹配,Endpoints 就为空。
  • DNS 名称有固定格式:svc.namespace.svc.cluster.local。
  • NetworkPolicy 必须由支持的 CNI 实现,默认拒绝要配合精确放行。

延伸阅读