--- tags: [k8s, service, networking, devops] create time: 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,仅集群内可访问。 ```yaml 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),外部流量可通过 `:` 访问。 ```yaml 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 规则链路 > 当外部请求到达 `:30080` 时,kube-proxy 在每个节点上创建的 iptables 规则会将流量 DNAT 到后端 Pod 的 IP:Port。这意味着即使请求落在没有运行目标 Pod 的节点上,流量也会被正确转发。 ```mermaid 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。 ```yaml 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 会一直停留在 `` 状态。此时可以考虑 MetalLB 作为替代方案。 ### ExternalName 将 Service 映射到一个外部 DNS 名称(CNAME 记录),不涉及任何代理和负载均衡。 ```yaml 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 删除时自动移除。 ```bash # 查看 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 个端点),增量更新,大幅降低开销。 ```bash # 查看 EndpointSlices kubectl get endpointslices -l kubernetes.io/service-name=my-app-svc ``` ```mermaid flowchart TB subgraph Endpoints 旧方式 S1[Service] --> E1[Endpoints
全部 Pod IP 在一个对象中] end subgraph EndpointSlices 新方式 S2[Service] --> ES1[EndpointSlice-0
Pod 1-100] S2 --> ES2[EndpointSlice-1
Pod 101-200] S2 --> ES3[EndpointSlice-2
Pod 201-300] end ``` ## DNS 解析机制 ### CoreDNS Kubernetes 使用 **CoreDNS** 作为集群内置 DNS 服务(1.12 之前使用 kube-dns)。每个 Service 创建后,CoreDNS 会自动为其注册 DNS 记录。 ### Service DNS 格式 | 资源类型 | DNS 格式 | 示例 | |----------|----------|------| | 普通 Service | `..svc.cluster.local` | `my-app.default.svc.cluster.local` | | Headless Service | `..svc.cluster.local` → 解析到所有 Pod IP | 直接返回 Pod IP 列表 | | Pod | `..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 列表。 ```yaml apiVersion: v1 kind: Service metadata: name: my-app-headless spec: clusterIP: None # Headless selector: app: my-app ports: - port: 80 targetPort: 8080 ``` **典型应用场景**: - **StatefulSet**:有状态应用(如数据库集群)需要知道每个 Pod 的独立地址 - **自定义负载均衡**:客户端自己实现负载均衡逻辑 - **服务发现**:客户端需要获取所有后端地址 ```bash # 查询 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 规则链。 ```mermaid 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。 ```yaml # 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 路由 ```yaml 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 路由 ```yaml 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 终止 ```yaml 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: ```bash kubectl create secret tls app-tls-secret \ --cert=tls.crt \ --key=tls.key ``` ```mermaid flowchart LR A[Client] -->|HTTPS| B[Ingress Controller
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,所有流量都是放行的: ```mermaid flowchart LR A[Pod A] -->|允许| B[Pod B] B -->|允许| A A -->|允许| C[Pod C] C -->|允许| B ``` ### 限制 Pod 间通信 **示例:只允许前端 Pod 访问后端 Pod 的 8080 端口** ```yaml 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** ```yaml 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]` 不影响出站,反之亦然 ```mermaid flowchart TB subgraph 策略生效后 A[frontend Pod] -->|允许: TCP 8080| B[backend Pod] C[other Pod] -->|拒绝| B B -->|未定义 Egress 策略
默认放行| D[任意目标] end ``` ## 常见陷阱与最佳实践 ### Service 无法访问排查清单 当 Service 访问不通时,按以下顺序排查: 1. **确认 Pod 运行正常** ```bash kubectl get pods -l app=my-app -o wide kubectl logs kubectl describe pod ``` 2. **确认 Endpoints 已注册** ```bash kubectl get endpoints my-app-svc # 如果 ENDPOINTS 为 ,说明没有健康的 Pod 匹配 selector ``` 3. **确认 selector 匹配** ```bash # 对比 Service selector 和 Pod labels kubectl get svc my-app-svc -o jsonpath='{.spec.selector}' kubectl get pods --show-labels ``` 4. **确认端口正确** ```bash # 检查 targetPort 是否与 Pod 实际监听端口一致 kubectl get svc my-app-svc -o yaml | grep -A5 ports kubectl exec -- ss -tlnp ``` 5. **确认 DNS 解析** ```bash kubectl run test --rm -it --image=busybox -- nslookup my-app-svc ``` 6. **确认网络策略未阻断** ```bash kubectl get networkpolicy -n ``` 7. **确认 kube-proxy 正常** ```bash 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: ```yaml 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 生效: ```yaml 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 分布不均时负载不均衡 | ```mermaid 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**:确保滚动更新和节点维护时服务可用性 ## 延伸阅读 - [[k8s-01-overview]] — Kubernetes 基础概念总览 - [[k8s-04-deployment-and-replicaset]] — Deployment 与 ReplicaSet 详解