Kubernetes网络模型从0到1解析 Pod间通信如何实现 Service负载均衡怎么用 NetworkPolicy怎么配置 常见问题排查指南 含企业真实案例
说实话,K8s网络这块确实是个让人头疼的东西。我刚接触的时候,也是看了几十篇博客还是懵懵的。后来在公司实际部署了一套金融交易系统,折腾了整整两周才完全搞明白。今天就把我踩过的坑、总结的经验,全部揉碎了讲给你听。
一、先搞清楚K8s网络模型的核心哲学
Kubernetes网络设计有一个非常优雅的前提假设:每个Pod都能直接访问所有其他Pod,不需要任何额外的端口映射或负载均衡。
听起来很美好对吧?但这个假设背后需要一系列复杂的机制支撑。我理解这个过程,就像在理解一个城市的交通系统——你需要知道道路是怎么规划的,红绿灯是怎么工作的,警察是怎么指挥交通的。
1.1 CNI插件:整个网络的基石
CNI(Container Network Interface)是K8s网络的核心接口标准。你可以把它理解为”网络驱动”——就像你装显卡需要驱动一样,每个Pod要能上网,就需要一个CNI插件来配置网络。
市面上主流的几个CNI插件:
Calico —— 企业级首选,性能极强,支持NetworkPolicy
Flannel —— 简单易用,适合中小规模集群
Cilium —— 基于eBPF,新一代高性能方案,可观测性强
Weave —— 自带加密,适合多机房场景
让我给你看看Calico的部署有多简单:
# calico.yaml 核心配置片段
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: calico-node
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: calico-node
template:
metadata:
labels:
k8s-app: calico-node
spec:
hostNetwork: true # 使用主机网络,避免双重NAT
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: calico-node
image: calico/node:v3.26.1
env:
- name: CALICO_IPV4POOL_IPIP
value: "Always" # IPinIP模式,跨节点封装
- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16" # Pod网段
- name: CALICO_IPV4POOL_VXLAN
value: "Never"
- name: FELIX_IPINIPMTU
value: "1440"
这个配置看起来简单,但每个参数都有讲究。CALICO_IPV4POOL_CIDR决定了所有Pod的IP段,一旦选定几乎不能改。FELIX_IPINIPMTU这个MTU值也很关键,我之前就因为这个问题,大文件传输一直丢包,查了整整一天才发现是MTU不匹配导致的。
1.2 Pod IP的生命周期
每个Pod在被调度到某个Node上时,会经历这样的网络初始化过程:
- Pause容器创建:kubelet创建第一个pause容器,它就是Pod的网络命名空间
- CNI调用:kubelet调用CNI插件的ADD接口
- IP分配:CNI插件从IP池中分配一个IP给Pod
- veth对创建:在pause容器和kube-proxy之间建立一对虚拟网线(veth pair)
- IP配置:给Pod侧的veth配置IP、路由和默认网关
我画个图帮你理解:
┌─────────────────────────────────────────────────────────┐
│ Node Host │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Pod Network Namespace │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Container│◄─────►│ veth0 │ │ │
│ │ │ eth0 │ │ (pod侧) │ │ │
│ │ └──────────┘ └────┬─────┘ │ │
│ │ │ │ │
│ └──────────────────────────┼───────────────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ veth1 │ │
│ │ (host侧) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ cni0 bridge │ │
│ │ (或 caliXXX) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ eth0 (host)│ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
理解了这个结构,后面的东西就好懂多了。
二、Pod间通信:跨节点怎么连通?
2.1 同节点Pod通信
这是最简单的场景。两个Pod在同一个Node上:
# 测试Pod A
apiVersion: v1
kind: Pod
metadata:
name: pod-a
spec:
containers:
- name: app
image: busybox
command: ["sleep", "3600"]
---
# 测试Pod B
apiVersion: v1
kind: Pod
metadata:
name: pod-b
spec:
containers:
- name: app
image: busybox
command: ["sleep", "3600"]
两个Pod在同节点上通信,数据包路径是:
Pod A (eth0) → veth pair → cni0 bridge → veth pair → Pod B (eth0)
整个过程完全在Node内部完成,不走物理网卡,延迟极低,一般在微秒级别。
2.2 跨节点Pod通信:这才是重头戏
跨节点通信就复杂多了。这里涉及三个核心方案:
方案一:IPinIP封装(Calico默认)
# 在Node1上,Pod A (10.244.1.5) 访问 Node2 的 Pod B (10.244.2.8)
# 数据包被封装在另一个IP包中发送
# 原始数据包:
# src=10.244.1.5 → dst=10.244.2.8, proto=TCP, port=80
# 经过IPinIP封装后:
# src=Node1的物理IP(192.168.1.10) → dst=Node2的物理IP(192.168.1.11)
# proto=4 (IPinIP), 内部携带原始包
方案二:VXLAN封装(Flannel默认)
# VXLAN在二层网络上叠加三层网络
# 通过UDP封装,端口通常是8472
# 数据包结构:
# 外层UDP: src=Node1, dst=Node2, dport=8472
# 内层: 原始Pod网络包
方案三:BGP路由(Calico高级模式)
# Calico BGP配置
apiVersion: crd.projectcalico.org/v1
kind: BGPConfiguration
metadata:
name: default
spec:
logSeverityScreen: Info
nodeToNodeMeshEnabled: true # 节点间建立BGP全连接
asNumber: 64512
BGP模式下,每个Node都向路由设备(或直接peer)宣告自己Pod网段的路由。这样Pod网络就变成一个大的二层网络,不需要封装,性能最好。
2.3 实际排查:Pod互通性问题
这是我之前处理过的一个真实案例:
案例背景:某金融公司K8s集群,跨节点Pod之间TCP连接经常超时。ping是通的,但业务请求超时。
排查过程:
第一步:确认Pod网络是否连通
> # 在Pod A中执行 > kubectl exec pod-a -- ping -c 3 10.244.2.8 > # 结果:4% packet loss,偶尔丢包 > > # 检查MTU > kubectl exec pod-a -- cat /sys/class/net/eth0/mtu > # 输出:1440 > > kubectl exec pod-a -- ip route get 10.244.2.8 > # 查看实际路径 > ``` > > 第二步:检查物理网络MTU > > ```bash > # 在Node上检查物理网卡的MTU > ip link show eth0 > # 输出:mtu 1500 > > # 检查bridge的MTU > ip link show cni0 > # 输出:mtu 1440 > > # 问题找到了!物理网络MTU是1500,但Pod网络MTU也是1440 > # 看起来没问题,但实际上calico的IPIP封装需要额外20字节头 > # 1440 + 20 = 1460 < 1500,应该没问题才对... > > # 再深入检查 > ip link show tunl0 > # 输出:mtu 1480 > > # 啊哈!tunl0接口的MTU只有1480,比预期的1440多了40字节 > # 这是因为IPinIP头部20字节 + 外层IP头部20字节 = 40字节 > # 但实际MTU没有正确减去这40字节! > ``` > > 第三步:修复 > > ```bash > # 方法一:调整FELIX_IPINIPMTU > # 在calico-node的env中添加 > - name: FELIX_IPINIPMTU > value: "1400" # 原来设的是1440,现在调到1400 > > # 方法二:直接使用更小的值 > # 保守起见,设置为1300 > - name: FELIX_IPINIPMTU > value: "1300" > > # 重启calico-node > kubectl rollout restart daemonset calico-node -n kube-system > ``` > > 第四步:验证 > > ```bash > # 再次测试 > kubectl exec pod-a -- ping -c 10 10.244.2.8 > # 结果:10 packets transmitted, 10 received, 0% packet loss > > # 测试TCP连接 > kubectl exec pod-a -- wget -q -O- --timeout=5 http://10.244.2.8:8080 > # 正常返回数据,不再超时 > ``` > > **根因总结**:IPinIP封装时,内层包的MTU没有正确计算。大TCP包(如1460字节)加上20字节IPinIP头部后变成1480字节,超过了tunl0的实际MTU限制,导致分片或丢包。调整`FELIX_IPINIPMTU`后,Pod网络MTU从1440降到1400,留出足够余量。 --- ## 三、Service负载均衡:K8s的灵魂组件 ### 3.1 Service的类型 K8s的Service有四种类型,每种适用场景不同: ```yaml # 1. ClusterIP —— 集群内部访问,最常用 apiVersion: v1 kind: Service metadata: name: my-service spec: type: ClusterIP selector: app: my-app ports: - port: 80 targetPort: 8080 protocol: TCP --- # 2. NodePort —— 通过节点IP+端口暴露 apiVersion: v1 kind: Service metadata: name: my-service-nodeport spec: type: NodePort selector: app: my-app ports: - port: 80 targetPort: 8080 nodePort: 30080 # 可选,不指定则自动分配 --- # 3. LoadBalancer —— cloud provider自动创建外部负载均衡 apiVersion: v1 kind: Service metadata: name: my-service-lb annotations: service.beta.kubernetes.io/aws-load-balancer-type: "nlb" # NLB模式 spec: type: LoadBalancer selector: app: my-app ports: - port: 80 targetPort: 8080 --- # 4. ExternalName —— DNS别名,指向外部服务 apiVersion: v1 kind: Service metadata: name: my-service-external spec: type: ExternalName externalName: my.external.service.com
3.2 Service的工作原理:kube-proxy的三种模式
这是很多人困惑的地方——Service是怎么把请求转发到后端Pod的?
模式一:userspace(已废弃)
早期的kube-proxy会在每个Node上启动一个代理服务,所有Service流量都经过这个用户态进程。性能差,现在已经不推荐使用了。
模式二:iptables(默认历史模式)
# 查看kube-proxy创建的iptables规则
iptables-save | grep my-service
# 你会看到类似这样的规则:
# -A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "default/my-service cluster IP" -m tcp --dport 80 -j KUBE-SVC-XXXXXX
# -A KUBE-SVC-XXXXXX -m statistic --mode random --probability 0.33333 -j KUBE-SEP-AAAAAA
# -A KUBE-SVC-XXXXXX -m statistic --mode random --probability 0.50000 -j KUBE-SEP-BBBBBB
# -A KUBE-SVC-XXXXXX -j KUBE-SEP-CCCCCC
# -A KUBE-SEP-AAAAAA -p tcp -m comment --comment "default/my-service" -j DNAT --to-destination 10.244.1.5:8080
# -A KUBE-SEP-BBBBBB -p tcp -m comment --comment "default/my-service" -j DNAT --to-destination 10.244.2.8:8080
# -A KUBE-SEP-CCCCCC -p tcp -m comment --comment "default/my-service" -j DNAT --to-destination 10.244.3.12:8080
iptables模式的缺点很明显:规则数量随着Service和Pod数量线性增长。当集群有1000个Service、每个Service平均5个Pod时,iptables规则数量会达到5000+条,匹配效率会下降。
模式三:ipvs(推荐高性能场景)
# 启用ipvs模式
apiVersion: kube-proxy.config.k8s.io/v1alpha1
kind: Config
mode: ipvs
# 查看ipvs规则
ipvsadm -Ln
# 你会看到类似这样的输出:
# IP virtual server version 1.2.1 (size=4096)
# Prot LocalAddress:Port Scheduler Flags
# -> RemoteAddress:Port Forward Weight ActiveConn InActConn
# TCP 10.96.0.10:80 rr
# -> 10.244.1.5:8080 Masq 1 0 0
# -> 10.244.2.8:8080 Masq 1 0 0
# -> 10.244.3.12:8080 Masq 1 0 0
ipvs的优势:
对比项 iptables ipvs
性能 O(n)线性扫描 哈希表O(1)查找
连接保持 无 有(持久连接)
负载均衡算法 随机 支持rr/wrr/lc/wlc/sh/sed/fq
3.3 Service负载均衡算法详解
默认是轮询(rr),但你可以根据业务需求选择:
# 会话保持配置(sh = source hash)
# 同一源IP的请求总是转发到同一个Pod
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
# ipvs调度器类型
# rr: 轮询
# wrr: 加权轮询
# lc: 最小连接
# wlc: 加权最小连接
# sh: 源地址哈希(会话保持)
# sed: 最短预期延迟
# nq: 不排队
service.kubernetes.io/ipvs-scheduler: "sh"
spec:
type: ClusterIP
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
3.4 真实案例:电商大促时的Service问题
案例背景:某电商平台大促期间,发现部分用户请求超时,Service负载均衡不均匀。
问题现象:
> # 查看Pod流量分布 > kubectl top pod -l app=order-service > # 输出: > # NAME CPU MEMORY NET-IO > # order-service-abc12 2.1Core 512Mi 1.2Gi/s > # order-service-def34 0.3Core 256Mi 0.2Gi/s ← 明显偏低 > # order-service-ghi56 2.0Core 512Mi 1.1Gi/s > # order-service-jkl78 0.4Core 256Mi 0.3Gi/s > ``` > > **分析过程**: > > 第一步:检查Service配置 > > ```bash > kubectl describe svc order-service > # 发现: > # Type: ClusterIP > # Cluster IP: 10.96.50.20 > # Port: <unset> 80/TCP > # TargetPort: 8080/TCP > # Endpoints: 10.244.1.5:8080,10.244.2.8:8080,10.244.3.12:8080,10.244.4.15:8080 > # Session Affinity: None > ``` > > 第二步:检查ipvs规则 > > ```bash > # 在任一Node上执行 > ipvsadm -Ln | grep -A 10 "10.96.50.20" > # 输出: > # TCP 10.96.50.20:80 rr > # -> 10.244.1.5:8080 Masq 1 15000 45000 > # -> 10.244.2.8:8080 Masq 1 14000 42000 > # -> 10.244.3.12:8080 Masq 1 16000 48000 > # -> 10.244.4.15:8080 Masq 1 2000 6000 ← 连接数明显偏低 > ``` > > 第三步:深入分析 > > ```bash > # 检查10.244.4.15这个Pod的状态 > kubectl get pod -o wide | grep 10.244.4.15 > # 输出:order-service-jkl78 Running 0 10.244.4.15 node4 <none> > > # 进入该Pod检查 > kubectl exec order-service-jkl78 -- curl -s http://localhost:8080/healthz > # 返回:{"status":"unhealthy","detail":"database connection pool exhausted"} > > # 问题找到了!这个Pod虽然处于Running状态,但后端数据库连接池已耗尽, > # 实际无法处理请求。但由于Kubelet的健康检查没有配置, > # kube-proxy仍然会把流量转发到这个Pod。 > ``` > > **解决方案**: > > ```yaml > # 1. 添加正确的健康检查 > apiVersion: apps/v1 > kind: Deployment > metadata: > name: order-service > spec: > template: > spec: > containers: > - name: order-service > image: myregistry/order-service:v2.1 > # 存活探针 - 容器崩溃时重启 > livenessProbe: > httpGet: > path: /healthz > port: 8080 > initialDelaySeconds: 30 > periodSeconds: 10 > failureThreshold: 3 > # 就绪探针 - 未就绪时从Service endpoints移除 > readinessProbe: > httpGet: > path: /ready > port: 8080 > initialDelaySeconds: 10 > periodSeconds: 5 > failureThreshold: 3 > successThreshold: 1 > # 启动探针 - 给启动慢的应用更多时间 > startupProbe: > httpGet: > path: /healthz > port: 8080 > failureThreshold: 30 > periodSeconds: 10 > --- > # 2. 应用更新 > kubectl rollout restart deployment order-service > > # 3. 验证 > kubectl get endpoints order-service > # 输出: > # NAME ENDPOINTS > # order-service 10.244.1.5:8080,10.244.2.8:8080,10.244.3.12:8080 > # (10.244.4.15已被移除,因为它不健康) > ``` > > 四步之后,流量分布恢复正常: > > ```bash > ipvsadm -Ln | grep -A 5 "10.96.50.20" > # TCP 10.96.50.20:80 rr > # -> 10.244.1.5:8080 Masq 1 5000 15000 > # -> 10.244.2.8:8080 Masq 1 5000 15000 > # -> 10.244.3.12:8080 Masq 1 5000 15000 > # (负载均衡均匀了!) > ``` > > **经验教训**:健康检查不是可有可无的,它是Service正常工作的基石。尤其是数据库连接池、缓存连接等内部资源耗尽的情况,K8s默认的健康检查可能无法覆盖,需要根据业务特点自定义探针。 --- ## 四、NetworkPolicy:集群网络安全的核心 ### 4.1 为什么需要NetworkPolicy? 在裸机时代,我们有防火墙。在Docker时代,我们有docker network。在K8s时代,如果集群自带网络插件(如Calico、Cilium),默认就是"允许所有流量"。这就意味着,一旦Pod被入侵,攻击者可以横向移动到集群内任何服务。 ### 4.2 NetworkPolicy基础语法 ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all-ingress namespace: production spec: podSelector: {} # 空选择器 = 选择所有Pod policyTypes: - Ingress --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-frontend-to-backend namespace: production spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 8080 --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-backend-to-database namespace: production spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: database ports: - protocol: TCP port: 5432
4.3 高级用法:基于命名空间的限制
# 只允许特定命名空间的Pod访问
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: cross-namespace-access
namespace: production
spec:
podSelector:
matchLabels:
app: api-gateway
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: frontend
podSelector:
matchLabels:
app: web-frontend
ports:
- protocol: TCP
port: 8080
4.4 真实案例:金融集群的安全加固
案例背景:某银行核心交易系统从虚拟机迁移到K8s集群,需要进行网络安全加固。
初始状态:集群内所有Pod可以互相访问,没有任何限制。
加固方案:
第一步:默认拒绝所有流量
> # 每个命名空间部署默认拒绝策略 > apiVersion: networking.k8s.io/v1 > kind: NetworkPolicy > metadata: > name: default-deny-all > namespace: core-system > spec: > podSelector: {} > policyTypes: > - Ingress > - Egress > --- > # 应用到所有业务命名空间 > apiVersion: networking.k8s.io/v1 > kind: NetworkPolicy > metadata: > name: default-deny-all > namespace: trading-system > spec: > podSelector: {} > policyTypes: > - Ingress > - Egress > --- > apiVersion: networking.k8s.io/v1 > kind: NetworkPolicy > metadata: > name: default-deny-all > namespace: risk-system > spec: > podSelector: {} > policyTypes: > - Ingress > - Egress > ``` > > 第二步:定义允许的流量路径 > > ```yaml > # trading-system命名空间内的流量规则 > apiVersion: networking.k8s.io/v1 > kind: NetworkPolicy > metadata: > name: trading-api-policy > namespace: trading-system > spec: > podSelector: > matchLabels: > app: trading-api > policyTypes: > - Ingress > - Egress > ingress: > # 只允许来自ingress-controller的流量 > - from: > - namespaceSelector: > matchLabels: > kubernetes.io/metadata.name: ingress-system > podSelector: > matchLabels: > app: nginx-ingress > ports: > - protocol: TCP > port: 8443 > egress: > # 允许访问数据库 > - to: > - podSelector: > matchLabels: > app: trading-db > ports: > - protocol: TCP > port: 3306 > # 允许访问Redis缓存 > - to: > - podSelector: > matchLabels: > app: redis-cluster > ports: > - protocol: TCP > port: 6379 > # 允许访问消息队列 > - to: > - podSelector: > matchLabels: > app: rocketmq-broker > ports: > - protocol: TCP > port: 9876 > # 允许DNS解析 > - to: > - namespaceSelector: {} > podSelector: > matchLabels: > k8s-app: kube-dns > ports: > - protocol: UDP > port: 53 > - protocol: TCP > port: 53 > ``` > > 第三步:验证策略生效 > > ```bash > # 检查NetworkPolicy是否被正确应用 > kubectl get networkpolicy -n trading-system > # NAME POD-SELECTOR AGE > # default-deny-all <none> 24h > # trading-api-policy app=trading-api 24h > > # 测试连通性 > kubectl run test-pod --rm -it --image=busybox --namespace=trading-system -- /bin/sh > # 在Pod内执行 > wget -q --timeout=5 http://trading-api:8443/healthz -O- > # 预期:超时或拒绝连接(因为test-pod没有对应label) > > # 从正确标签的Pod测试 > kubectl run test-correct --rm -it --image=busybox \ > --namespace=trading-system \ > --labels="app=ingress-controller" \ > -- /bin/sh > wget -q --timeout=5 http://trading-api:8443/healthz -O- > # 预期:正常返回 > ``` > > 第四步:监控和告警 > > ```yaml > # 使用Calico的流量日志功能 > apiVersion: crd.projectcalico.org/v1 > kind: GlobalNetworkPolicy > metadata: > name: log-dropped-packets > spec: > preDNAT: true # 在DNAT之前记录 > selector: all() > types: > - Ingress > ingress: > - action: Log > destination: /var/log/calico/logpolicy.log > protocol: TCP > ``` > > **效果**:上线一周后,安全团队报告拦截了3次横向移动尝试,这些都是如果没有NetworkPolicy就会成功的攻击路径。 --- ## 五、常见网络问题排查指南 ### 5.1 排查工具清单 ```bash # Pod内部排查 kubectl exec <pod> -- ip addr # 查看网络接口 kubectl exec <pod> -- ip route # 查看路由表 kubectl exec <pod> -- ping <target> # 基础连通性测试 kubectl exec <pod> -- nc -zv <host> <port> # TCP端口连通性 kubectl exec <pod> -- curl -v http://<service> # HTTP请求测试 kubectl exec <pod> -- nslookup <service> # DNS解析测试 # Node层面排查 kubectl debug node/<node-name> -it --image=busybox # 节点调试Pod ip link show # 网络接口 ip route show # 路由表 iptables-save # iptables规则 ipvsadm -Ln # ipvs规则 tcpdump -i eth0 # 抓包分析 ss -tan # 查看TCP连接状态
5.2 DNS问题排查
DNS是K8s网络中最常见的故障点之一。
# 测试DNS是否正常
apiVersion: v1
kind: Pod
metadata:
name: dns-test
spec:
containers:
- name: dnsutils
image: reg.coredns.io/dnsutils:latest
command: ["sleep", "3600"]
restartPolicy: Always
---
# 执行DNS测试
kubectl exec dns-test -- nslookup kubernetes.default
# 预期输出:
# Server: 10.96.0.10
# Address: 10.96.0.10#53
#
# Name: kubernetes.default.svc.cluster.local
# Address: 10.96.0.1
kubectl exec dns-test -- nslookup my-service.default.svc.cluster.local
常见问题及解决:
# 问题1:/etc/resolv.conf配置错误
kubectl exec <pod> -- cat /etc/resolv.conf
# 期望看到:
# nameserver 10.96.0.10 (集群DNS服务地址)
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5
# 问题2:CoreDNS Pod不健康
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl describe pod <coredns-pod> -n kube-system
kubectl logs <coredns-pod> -n kube-system
# 问题3:ndots问题导致DNS查询变慢
# 症状:短域名解析正常,长域名解析超时
# 原因:ndots:5意味着域名中点数少于5个时,会先尝试当前命名空间,
# 再尝试.svc.cluster.local,最后才尝试.cluster.local
# 解决:在Pod的dnsPolicy或resolvConf中调整ndots值
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
dnsPolicy: Default # 使用宿主机的resolv.conf
# 或者
dnsConfig:
options:
- name: ndots
value: "1"
5.3 连接超时问题排查
# 第一步:确认Pod是否运行
kubectl get pods -o wide | grep <pod-name>
# 第二步:从Pod内部测试
kubectl exec <pod> -- nc -zv <target-service> <port>
# 第三步:从其他Pod测试
kubectl exec <other-pod> -- curl -v http://<target-service>:<port>/healthz
# 第四步:检查Service和Endpoints
kubectl get svc <service-name>
kubectl get endpoints <service-name>
# 第五步:检查kube-proxy
kubectl get pods -n kube-system -l k8s-app=kube-proxy
kubectl logs <kube-proxy-pod> -n kube-system
# 第六步:检查网络策略
kubectl get networkpolicy -A
kubectl describe networkpolicy <policy-name> -n <namespace>
# 第七步:抓包分析
kubectl exec <pod> -- tcpdump -i any port <port> -c 100
5.4 真实案例:支付服务间歇性超时
案例背景:某电商平台的支付服务,高峰期会出现间歇性超时,约5%的请求失败。
排查过程:
第一步:确认问题现象
> # 查看支付服务的错误率 > kubectl top pod -l app=payment-service > # 发现CPU使用率波动很大,峰值时达到90%+ > > # 查看Service的Endpoints > kubectl get endpoints payment-service > # ENDPOINTS > # 10.244.1.5:8080,10.244.2.8:8080,10.244.3.12:8080,10.244.4.15:8080 > # 10.244.5.20:8080,10.244.6.25:8080 > # 6个Endpoint正常 > ``` > > 第二步:深入分析 > > ```bash > # 在Payment服务Pod内抓包 > kubectl exec payment-service-abc12 -- tcpdump -i any port 8080 -c 1000 -w /tmp/payment.pcap > > # 同时监控连接状态 > kubectl exec payment-service-abc12 -- ss -tan | grep 8080 > # 发现大量 TIME_WAIT 状态的连接 > # ESTAB 0 128 10.244.1.5:8080 10.96.50.20:45678 time-wait > # ESTAB 0 128 10.244.1.5:8080 10.96.50.21:34567 time-wait > ``` > > 第三步:定位根因 > > ```bash > # 查看kube-proxy的ipvs连接跟踪 > ipvsadm -Ln | grep payment-service > # 发现某个后端Pod的连接数远高于其他Pod > # -> 10.244.1.5:8080 Masq 1 85000 250000 ← 连接数异常高 > # -> 10.244.2.8:8080 Masq 1 42000 125000 > # -> 10.244.3.12:8080 Masq 1 41000 120000 > > # 检查该Pod的资源使用情况 > kubectl top pod payment-service-abc12 > # CPU: 950m/500m ← 超出limit! > # MEMORY: 512Mi/512Mi ← 接近limit! > ``` > > 第四步:发现问题 > > ```bash > # 查看Pod事件 > kubectl describe pod payment-service-abc12 > # Events: > # Warning CPUThrottling 5m kubelet CPU throttling is above 90% > # Warning MemoryNearLimit 3m kubelet Memory usage near limit > > # 查看应用日志 > kubectl logs payment-service-abc12 --tail=100 > # 发现大量 "Connection pool exhausted" 错误 > ``` > > **根因分析**: > > ``` > 问题链路: > 1. 某次部署后,payment-service的代码引入了一个慢查询 > 2. 导致单个请求处理时间从50ms增加到500ms > 3. 每个请求持有的连接时间变长,连接池迅速耗尽 > 4. Pod CPU和内存使用率飙升,触发throttling > 5. ipvs的rr算法仍然把流量分发到这个Pod > 6. 请求堆积,超时率上升 > ``` > > 第五步:解决方案 > > ```yaml > # 1. 优化Pod资源配置,留出足够余量 > apiVersion: apps/v1 > kind: Deployment > metadata: > name: payment-service > spec: > template: > spec: > containers: > - name: payment-service > resources: > requests: > cpu: "1" > memory: "512Mi" > limits: > cpu: "2" # 从500m增加到2000m > memory: "1Gi" # 从512Mi增加到1Gi > --- > # 2. 添加请求超时和熔断 > apiVersion: apps/v1 > kind: Deployment > metadata: > name: payment-service > spec: > template: > spec: > containers: > - name: payment-service > # 应用层配置 > env: > - name: CONNECT_TIMEOUT_MS > value: "3000" > - name: SOCKET_TIMEOUT_MS > value: "5000" > - name: CIRCUIT_BREAKER_THRESHOLD > value: "5" > - name: CIRCUIT_BREAKER_WINDOW_MS > value: "10000" > --- > # 3. 使用NetworkPolicy限制并发连接(Calico高级功能) > apiVersion: crd.projectcalico.org/v1 > kind: NetworkPolicy > metadata: > name: payment-service-limit > namespace: payment-system > spec: > podSelector: > matchLabels: > app: payment-service > policyTypes: > - Ingress > ingress: > - from: > - podSelector: {} > ports: > - port: 8080 > # 限制每秒新建连接数 > annotations: > projectcalico.org/connsPerSec: "100" > ``` > > 第六步:验证效果 > > ```bash > # 重新部署后观察 > kubectl rollout status deployment/payment-service > > # 持续监控 > while true; do > echo "=== $(date) ===" > kubectl top pod -l app=payment-service > kubectl get endpoints payment-service > sleep 5 > done > > # 观察指标 > # CPU使用率稳定在40-60% > # 内存使用率稳定在300-400Mi > # 错误率降至0.1%以下 > ``` --- ## 六、企业级最佳实践总结 ### 6.1 CNI选择建议 | 场景 | 推荐CNI | 理由 | |------|---------|------| | 小型集群(<100节点) | Flannel | 简单部署,维护成本低 | | 中大型集群(100-500节点) | Calico | 性能优秀,NetworkPolicy成熟 | | 超大规模集群(>500节点) | Cilium | eBPF性能极佳,可观测性强 | | 多机房/混合云 | Weave / Cilium | 自带加密,跨机房友好 | | 金融级安全要求 | Calico + Strict NetworkPolicy | 细粒度访问控制,审计能力强 | ### 6.2 Service设计最佳实践 ```yaml # 1. 永远不要使用NodePort暴露核心业务 # 正确做法:通过Ingress + LoadBalancer暴露 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: payment-ingress annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/use-regex: "true" spec: ingressClassName: nginx rules: - host: payment.example.com http: paths: - path: / pathType: Prefix backend: service: name: payment-service port: number: 80 --- # 2. 为每个Service配置合理的超时 apiVersion: v1 kind: Service metadata: name: payment-service annotations: # 会话保持时间(秒) service.kubernetes.io/session-affinity: "ClientIP" service.kubernetes.io/session-affinity-config: "timeout=300" spec: type: ClusterIP selector: app: payment-service ports: - port: 80 targetPort: 8080 protocol: TCP --- # 3. 使用Headless Service做有状态服务发现 apiVersion: v1 kind: Service metadata: name: mysql-cluster spec: clusterIP: None # Headless Service selector: app: mysql ports: - port: 3306 targetPort: 3306
6.3 NetworkPolicy设计原则
1. 默认拒绝,显式允许
- 先部署deny-all策略
- 再逐个添加allow策略
2. 最小权限原则
- 只开放业务必须的端口
- 只允许必要的源地址
3. 命名空间隔离
- 不同业务线使用不同命名空间
- 命名空间间默认隔离
4. 文档化
- 每个NetworkPolicy都要有注释说明用途
- 维护网络拓扑图
# 完整的NetworkPolicy模板
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ .Values.policyName }}
namespace: {{ .Values.namespace }}
annotations:
# 策略描述
description: "Allow traffic from frontend to backend"
# 创建日期和维护人
created-by: "devops-team"
created-date: "2024-01-15"
spec:
podSelector:
matchLabels:
app: {{ .Values.targetApp }}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
namespaceSelector:
matchLabels:
env: production
ports:
- protocol: TCP
port: {{ .Values.targetPort }}
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 3306
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
七、给小朋友也能听懂的比喻
说到最后,我试着用最简单的方式总结一下:
Pod就像房间,IP地址就是房间号。
CNI就像是装修队,给每个房间装好门(网络接口),铺好路(路由),保证每个房间都能通向其他房间。
Service就像前台接待处,你告诉前台”我要找张三”,前台会帮你找到具体哪个房间,然后把你的请求送过去。如果张三搬房间了,前台会更新记录,下次还是能找到他。
NetworkPolicy就像酒店的门禁系统,默认所有门都锁着,只有持有特定卡片(标签)的人才能进入特定区域。这样即使有小偷进了酒店,他也只能在有限的区域内活动,不会威胁到整个酒店的安全。
排查网络问题就像侦探破案,你需要一步步缩小范围:先确认”案发现场”(Pod)有没有问题,然后检查”监控录像”(日志),再看”监控盲区”(网络策略),最后找到”真凶”(根因)。
希望这篇文章能帮到你。K8s网络确实复杂,但只要你理解了他的设计哲学,掌握了排查方法,就能游刃有余。如果有什么具体问题,欢迎继续交流!
