ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

一次生产故障彻底讲清kube-apiserver、kube-proxy、Calico的关系

一次生产故障彻底讲清kube-apiserver、kube-proxy、Calico的关系 一次生产故障让我彻底把这三个组件的关系想通了。事情的经过很简单某天客户报障说集群里跨节点访问 Service 不通但同节点 Pod 之间互访正常。我上去排查先看 kube-apiserver 日志正常再看 kube-proxy 的 iptables 规则Service 的 DNAT 链也都在最后把目光移到 Calico 的 BGP 邻居状态发现calico/node is not ready进一步看calicoctl node status输出里赫然写着number of node(s) with bgp peering established 0。那一刻我才意识到很多 Kubernetes 使用者其实根本没搞明白这三个组件各管哪一段、彼此怎么配合。说实话这三个词几乎每个搞 K8s 的人日常都会碰到但真要问一句“kube-apiserver、kube-proxy、Calico 到底是什么关系”能立刻说清楚的人并不多。这篇文章就从这个角度切入把三者的职责边界、协作链路、常见故障逻辑讲透顺便把那句让人头疼的bgp peering established 0也一并解决掉。适合刚学 Kubernetes 网络原理的读者也适合集群出了问题不知道从哪里下手的运维同学。1. 先搞清楚三个组件各自干的是什么活很多人栽跟头不是因为某个组件多难而是因为一开始就把它们的功能混在一起了。所以第一步先把边界划清楚。1.1 kube-apiserver集群的“总协调员”kube-apiserver 是 Kubernetes 控制面的入口也是唯一一个直接跟 etcd 打交道的组件。你执行kubectl get pods、kubectl apply请求最终都会打到 apiserver 上。它负责认证、鉴权、准入控制然后把数据落到 etcd 里同时把变化通过 watch 机制广播给其他组件。你可以把它理解成公司前台所有对外对内的正式请求都得走它这扇门它记录每个人的状态并通知相关部门。关键点在于kube-apiserver 不参与实际的数据包转发。它只处理“配置流”不处理“数据流”。也就是说Pod 之间跑业务流量时apiserver 是不会插手的。这也是 Kubernetes 能水平扩展、性能瓶颈不在这儿的底层原因。1.2 kube-proxyService 的落地执行者kube-proxy 解决的核心问题是集群里 Service 的 ClusterIP 是怎么做到负载均衡的。Service 是一个虚拟概念没有真实的网络设备对应它。当你在 YAML 里定义了一个 Service 并指定 selector控制器会创建 Endpoints新版本叫 EndpointSlice但真正让流量能够从 ClusterIP 转发到某个 Pod IP 的是每个节点上的 kube-proxy。kube-proxy 的工作模式有三种userspace 模式最早的模式性能差现在已经基本淘汰。iptables 模式默认模式利用 DNAT 规则把 ClusterIP 映射到 Pod IP。IPVS 模式基于内核的 LVS 实现规则多、性能好大集群建议使用。无论哪种模式kube-proxy 干的事本质都一样维护节点上的转发规则让访问 Service 的流量能落到后端的某个 Pod 上。它不负责 Pod 之间直接通信——那是 CNI 的活。1.3 CalicoPod 网络的实现者Calico 是 CNIContainer Network Interface插件负责两件大事给 Pod 分配 IPIPAM并把 Pod 的 veth 网卡接入宿主机网络。维护路由规则保证不同节点上的 Pod 能互相通信。Calico 的实现思路是“纯三层路由”。它不依赖 overlay 隧道虽然也支持 IPIP 和 VXLAN 模式而是把每个节点当成一台路由器通过 BGP 协议把各节点的 Pod 网段广播出去让流量直接走物理网络路由过去。这也是为什么 Calico 在网络性能上往往优于 Flannel 的 VXLAN 模式——因为它少了一层封装开销。1.4 三者的职责边界对比用一张表可以看得很清楚组件所属平面解决什么问题是否参与数据转发kube-apiserver控制面API 入口、配置下发、状态存储否kube-proxy数据面节点维度Service - Pod 的负载均衡与转发是通过规则间接参与Calico数据面网络维度Pod IP 分配、跨节点路由、NetworkPolicy是直接参与三层路由我见过不少新手把 kube-proxy 和 Calico 混为一谈以为装了 Calico 就不需要 kube-proxy或者反过来。这是完全错误的。两者解决的问题正交——一个管“Pod 与 Pod 怎么通”一个管“Service 这个抽象 IP 怎么转发到 Pod”。2. 三者之间的协作逻辑边界清楚了接下来看它们怎么配合。这一节是整个 Kubernetes 网络模型的核心理解以后基本不会再被网络问题绕晕。2.1 配置流的完整链路先从一条新的 Service 创建开始看你执行kubectl apply -f service.yaml。kube-apiserver 校验资源写入 etcd。kube-apiserver 通过 watch 机制通知 kube-proxy有新的 Service。kube-proxy 监听 Service 和 EndpointSlice 的变化在节点上更新 iptables / IPVS 规则。Pod 创建时kubelet 调用 CNI 插件Calico由 Calico 分配 IP、配置网络。这里有个容易被忽略的点kube-apiserver 是通过 watch 主动推送还是靠各组件轮询答案是 watch。kube-proxy、kubelet、controller-manager 都是 apiserver 的客户端它们与 apiserver 建立长连接通过 watch 接口监听自己关心的资源变化。这种设计保证了配置变更能在毫秒级内到达各节点。做个简单实验验证你在大集群里kubectl create service然后立刻iptables -t nat -S KUBE-SERVICES | grep ClusterIP大概率规则已经存在了这就是 watch 推送的功劳。2.2 数据流的完整路径理解了配置流再来看数据流。假设集群有三个节点Node1 上有一个 Pod A访问 Node2 上的 Pod B。请求的路径大致是Pod A 的流量从 veth 进入 Node1 的 root 网络命名空间。查路由表命中 Calico 下发的路由条目ip route里能看到10.244.2.0/24 via 192.168.1.12 dev eth0 proto 80这样的条目。数据包发到 Node2 的物理网卡。Node2 上 Calico 配置的路由规则把包导到 Pod B 的 veth。如果中间启用了 IPIP那么步骤 3 实际是把原始包封装在一个新的 IP 包里面到达 Node2 后再解封装。如果你访问的是一个 Service那么在这个链路中间嵌入了 kube-proxy 的 DNAT 步骤当数据包匹配到 Service 的 ClusterIP:Port 时iptables 的 DNAT 规则把目标地址改写为某个后端 Pod IP之后的路由查找就完全交给 Calico 负责了。2.3 关键认知kube-proxy 和 Calico 在 iptables 里“共存”这是最容易产生问题的地方。kube-proxy 在 nat 表里插入规则负责 Service 的 DNAT/SNAT。Calico 在 filter 表和 nat 表里都有规则负责 Pod 网络策略NetworkPolicy和流量放行。两条链路要协同工作任何一个组件的规则错了都可能导致 Service 访问失败。这也是很多故障排查难的原因——你光看某一个组件的规则没用要全链路一起看。一句话总结kube-apiserver 是发令员kube-proxy 是小区门口保安Calico 是城市交通道路系统。保安只负责把你引到正确的楼栋但楼与楼之间的路怎么修、怎么通是交通系统的事。3. 从 Service 到 Pod 的一次完整数据流纸上谈兵没用。我们实际在一个节点上用 tcpdump 和 iptables 验证一下这个链路。3.1 实验环境我这边测试集群是 kubeadm 装的版本 1.28Calico v3.27单节点上运行着一个 Nginx 和一个测试 Pod。创建 Servicekubectl expose deployment nginx --port80 --target-port80 --namenginx-svc kubectl get svc nginx-svc得到的 ClusterIP 假设是10.98.171.24。去任意节点上看 iptablesiptables -t nat -L KUBE-SERVICES | grep 10.98.171.24会看到类似这样的输出KUBE-SVC-XXXX tcp -- 0.0.0.0/0 10.98.171.24 tcp dpt:80这条规则的语义是凡是目标地址是 ClusterIP 且端口是 80 的 TCP 包跳到对应的KUBE-SVC-XXXX自定义链。继续深入iptables -t nat -L KUBE-SVC-XXXX -n会看到后面挂着跳转到KUBE-SEP-XXXX的规则这就是负载均衡的体现。iptables 的 random 模式让每个包的落点不一样真正做到了按概率分发。3.2 路由决策阶段如果 DNAT 成功数据包的 dest IP 已经被改写成了 Pod IP比如10.244.1.5。此时系统会重新做一次路由查找这时候就轮到 Calico 登场。ip route | grep 10.244输出里能看到类似这样一行10.244.1.5 dev cali-xxxx scope link这个条目不是 kube-proxy 建的是 Calico 在 Pod 创建时写入主机路由表的。它的作用是告诉内核这个 IP 直接通过某个 veth 设备访问。如果是跨节点的 Pod路由表里会是10.244.2.0/24 via 192.168.1.12 dev eth0 proto 80目标 IP 是 Node2 的物理网卡 IP数据包就会从本机 eth0 出去经网络的 BGP 路由到达 Node2。3.3 用 tcpdump 验证全过程在 Node1 上打开两个终端终端 Atcpdump -n -i any port 80 -e终端 Bkubectl exec -it test-pod -- curl 10.98.171.24正常输出里应该能看到请求先以 ClusterIP 为目标经过 NAT 之后出现在 Node2 上的包目标地址已经变成了 Pod IP。这中间的 NAT 动作就是 kube-proxy Calico 配合的实证。3.4 特别说明链路里还有 conntrackiptables 的 NAT 是“有状态”的靠 conntrack 跟踪连接状态。这意味着同一个 TCP 连接里的回包会自动做反向 NAT不需要再走一次 DNAT 匹配。这也是为什么大量长连接场景下iptables 规则性能问题不明显但新建连接特别多时conntrack 表满了容易丢包。常见现象节点上dmesg | tail看到nf_conntrack: table full, dropping packet。这不是 Calico 的锅也不是 kube-proxy 的锅而是连接跟踪表容量不够。我一般会这样调sysctl -w net.netfilter.nf_conntrack_max524288高并发集群建议把 65536 的默认值调大并且配合nf_conntrack_buckets一起调整否则容易链表过长导致 CPU 飙升。4. Calico 部署与 BGP peering 为 0 的排查实录文章开头提到的那个故障想必大家还有印象。这一节就专门讲 Calico 的部署和“BGP peering established 0”这个高频问题。4.1 标准安装流程我推荐用 Operator 方式安装升级、回滚都省事。也可以直接 apply 官方提供的 manifestkubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/tigera-operator.yaml curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/custom-resources.yaml改一下custom-resources.yaml里的cidr保证与集群--pod-network-cidr一致比如192.168.0.0/16然后应用kubectl apply -f custom-resources.yaml安装完成后看 Pod 状态kubectl get pods -n calico-system如果所有 Pod 都是 Running再用calicoctl看节点状态calicoctl node status正常情况下应该看到类似IPv4 BGP status ----------------------------------------------------------------- | PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO | ----------------------------------------------------------------- | 192.168.1.11 | node-to-node mesh | up | 04:32:11 | Established | -----------------------------------------------------------------4.2 热点报错BGP peering established 0如果你执行calicoctl node status看到的是number of node(s) with bgp peering established 0同时calico/node容器一直 CrashLoopBackOff或者报calico/node is not ready: BIRD is not ready: BGP not established不要慌。按照下面的排查顺序来。第一步看 calico-node 日志kubectl logs -n calico-system -l k8s-appcalico-node -c calico-node --tail200重点搜索关键词BGP、bird、error、failed。第二步检查节点 IP 是否被正确识别有些服务器有多网卡Calico 默认使用第一个网卡的 IP 作为 BGP 对等地址。如果抓错了网卡节点之间 BGP 永远建不连。这时候在custom-resources.yaml的配置里指定spec: calicoNetwork: nodeAddressAutodetectionV4: interface: eth0第三步检查 BGP 端口是否被防火墙拦住BGP 使用 TCP 179 端口。如果你在节点之间开启了防火墙或安全组记得放行 179 端口否则对等关系建立不了。telnet 192.168.1.12 179能通说明网络没问题。第四步检查 Pod 网段是否冲突Calico 宣告的 Pod CIDR 必须和 kube-apiserver 传给 kubelet 的--pod-network-cidr一致。冲突会导致路由错乱BGP 邻居即便建立了业务也不通。kubectl get node -o wide | awk {print $6} kubectl get ippool -o yaml对比两边网段是否一致。第五步确认 IPIP 和 BGP 配置项默认 Calico 开启的是IPIP跨子网封装 node-to-node mesh。如果自定义配置里设错了ipipMode或disabled都会导致异常。一个比较稳妥的最小配置apiVersion: projectcalico.org/v3 kind: IPPool metadata: name: default-ipv4-ippool spec: cidr: 192.168.0.0/16 ipipMode: Always natOutgoing: true这里ipipMode: Always的好处是跨节点通信无论是否同网段都走 IPIP 封装兼容性最好代价是多一层封装头。4.3 为什么 BGP peering 为 0 会卡住 node ready很多人问BGP 对等关系跟“节点 Ready”有什么关系原因在于 Calico 的calico/node容器启动时BIRD 进程要等 BGP 会话建立成功才认为网络就绪。如果不就绪它会持续重试导致 Pod 状态一直不健康。这不是 Kubernetes 本身的问题而是 Calico 的健康检查机制比较严格。也就是说 BGP peering 0 不会阻塞 K8s 节点上报 Ready但它会让 Calico 组件无法正常工作从而影响该节点上的 Pod 网络。真正的坑在于这种故障早期没有明显表象Pod 能调度但网络不通。所以我把calicoctl node status列为了日常巡检的必查项。5. 三者的性能影响与调优方向讲完关系再来点实际的生产环境下这三个组件的性能怎么调优。5.1 kube-apiserver 性能apiserver 的性能瓶颈一般在 etcd 和内存。kube-apiserver默认的--max-requests-inflight是 400读请求多了容易排队。大集群可以调高一些同时把 etcd 的磁盘换成 NVMe启用压缩。注意不要盲目调大否则 apiserver 内存会吃紧反而拖垮节点。核心指标还是看 etcd 的db大小超过 2GB 就要留意历史版本堆积可以开启压缩etcdctl compaction $(etcdctl endpoint status --write-outjson | jq -r .[0].Status.header.revision)5.2 kube-proxy 模式选型如果你集群规模不大Pod 数几百iptables 模式完全够用。如果 Pod 数千以上Service 数量很多建议切换到 IPVS 模式。切换方法kubectl edit configmap -n kube-system kube-proxy把mode: 改成mode: ipvs然后重启 kube-proxy 的 Pod。IPVS 模式下iptables 规则数量大幅减少长连接场景也更稳定。但注意IPVS 模式默认RR轮询算法很多场景希望保持会话亲和同一个客户端访问同一个后端需要开启apiVersion: v1 kind: Service metadata: name: nginx-svc spec: sessionAffinity: ClientIP sessionAffinityConfig: clientIP: timeoutSeconds: 108005.3 Calico 模式选择Calico 有三种主要数据面模式模式优点缺点适用场景IPIP跨子网兼容性好配置简单有封装开销性能略低云环境、跨VPCVXLAN同 IPIP 类似内核支持更广泛封装开销略高无 BGP 支持的环境eBPF性能高、延迟低、可观测性好对内核版本要求高5.7大规模生产集群我目前生产环境用的还是 IPIP 模式因为兼容性最好运维简单。但如果你追求极致性能且内核满足要求eBPF 模式绝对值得尝试。它把 kube-proxy 那套 iptables 逻辑用 eBPF 程序替代CPU 占用降低明显延迟也能减少 20% 左右。eBPF 模式开启前要确认三件事内核版本 5.7kube-proxy 换成kube-proxy replacement模式节点上不能有老的 iptables 残留规则。6. 常见的坑和相应对策最后把我在实际运维中遇到的、网上不太容易搜到的坑集中列一下。坑一kube-proxy 规则被覆盖如果你自定义过 iptables 规则或者装过其他网络组件kube-proxy 每次同步规则时会清掉一部分不属于自己的链。解决办法是把自定义规则放在KUBE-*链之后或者用 NetworkPolicy / NPWG 的机制实现而不是直接改 iptables。坑二Calico 的 iptables 规则太多导致延迟高Pod 数量上千时calico-*链会变得很长。性能敏感的节点启用BPF数据面或者换成VPP模式会好很多。如果没有条件至少把日志级别调到warning减少日志写入的 IO 开销。坑三Pod 启动后第一秒网络不通这是 Calico 的一个老问题Pod 刚创建时Felix 还没把它的路由信息同步到主机路由表数据包可能找不到路由而丢包。可以通过给 Pod 加readinessProbe缓解或者升级到 Calico v3.20它的路由学习速度已经有明显提升。坑四网络策略生效慢Calico 的 NetworkPolicy 依赖 Felix 的同步周期默认是 10 秒一次。大集群里策略数量多可以用--max-arrows、调整 Felix 的syncInterval来优化但不建议设成太短的间隔否则 Felix 会频繁重新计算全量规则。坑五conntrack 表被打满前文提到了nf_conntrack: table full再补一句如果 kube-proxy 用的 IPVS 模式它不经过 conntrack 吗不是的负载均衡本身还是依赖 conntrack 做会话保持。所以 conntrack 的调优无论哪种模式都值得做。7. 几个排查命令的记忆口诀被问了很多次这个问题后我发现大家最主要的困难不是命令不会敲而是不知道先敲哪个。整理一个自己常用的排查顺序看集群状态kubectl get pods -A -o wide优先确认 calico-system 和 kube-system 里的组件都正常。看服务解析kubectl get svc -A确认 ClusterIP 存在且 selector 匹配到端点。看端点kubectl get endpoints svc如果没有就说明 controller-manager 没给你生成 Endpoints大概率是 label 写错。看节点路由ip route | grep PodCIDR确认 Calico 路由条目是否正确。看 BGPcalicoctl node status确认 BGP 状态都是 Established。看 iptablesiptables -t nat -L KUBE-SERVICES -n | grep ClusterIP对照服务是否生成了链。按照这个顺序大多数网络问题十分钟内能定位到具体组件而不是在一堆日志里抓瞎。最后再分享一个小技巧排查完网络问题之后把calicoctl node status的输出保存下来做基线后续再比对就很容易发现是路由问题还是 BGP 问题。这个文件我也会定期备份尤其是在调整过 Calico 配置之后。
返回列表