19 KiB
tags, create time
| tags | 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,仅集群内可访问。
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 存储在单个对象中,导致:
- 更新风暴:任何一个 Pod 变化都要更新整个 Endpoints 对象
- 内存压力:kube-proxy 和所有节点的 kubelet 都要 watch 这个大对象
- 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]
规则链路详解:
KUBE-SERVICES:总入口,匹配目标 IP:Port 确定 ServiceKUBE-SVC-XXX:Service 级别链,使用statistic模块做随机负载均衡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 访问不通时,按以下顺序排查:
-
确认 Pod 运行正常
kubectl get pods -l app=my-app -o wide kubectl logs <pod-name> kubectl describe pod <pod-name> -
确认 Endpoints 已注册
kubectl get endpoints my-app-svc # 如果 ENDPOINTS 为 <none>,说明没有健康的 Pod 匹配 selector -
确认 selector 匹配
# 对比 Service selector 和 Pod labels kubectl get svc my-app-svc -o jsonpath='{.spec.selector}' kubectl get pods --show-labels -
确认端口正确
# 检查 targetPort 是否与 Pod 实际监听端口一致 kubectl get svc my-app-svc -o yaml | grep -A5 ports kubectl exec <pod-name> -- ss -tlnp -
确认 DNS 解析
kubectl run test --rm -it --image=busybox -- nslookup my-app-svc -
确认网络策略未阻断
kubectl get networkpolicy -n <namespace> -
确认 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:确保滚动更新和节点维护时服务可用性
延伸阅读
- k8s-01-overview — Kubernetes 基础概念总览
- k8s-04-deployment-and-replicaset — Deployment 与 ReplicaSet 详解