Kubernetes核心组件实战:etcd高可用部署与Pod网络通信全解析
写这篇东西之前先聊聊为什么值得花时间看它。Kubernetes 整个集群的大脑是 API Server而 API Server 所有的状态、配置、集群元数据都存在 etcd 里。可以这么说etcd 一挂整个集群就算还活着也等于失忆了kubectl 根本拿不到任何响应。另一边Pod 通信则是集群真正能跑起分布式应用的血管系统Pod 之间怎么找到对方、流量怎么过去、跨节点怎么通全看网络插件这套机制搞得清不清楚。这次就把这两块硬骨头放在一起啃从 etcd 的单节点部署讲到三节点高可用再把 Pod 通信从同节点容器互通到跨节点 Overlay 网络整个链路用实操串一遍。适合谁来参考刚入手 Kubernetes 不久、想自己从零搭一套集群的开发者或者已经在用托管集群但一直对 etcd 和 CNI 网络原理比较模糊的人。我只讲能落地的东西所有配置都是可以直接拿来用的坑也都替你踩过了。1. etcd 部署前必须想清楚的几个问题1.1 先理解 etcd 在 K8s 里到底扮演什么角色etcd 是一个分布式的强一致性 KV 存储底层用的是 Raft 共识算法。Kubernetes 选它做后端存储是因为它能在多节点之间保证数据一致而且支持 Watch 机制——API Server 可以通过 Watch 实时感知数据变化不需要频繁轮询。你可以把 etcd 理解成飞机的黑匣子几乎集群里所有“应该被记住的事”都存在这里节点状态、Pod 调度信息、Service 的 Endpoint 列表、ConfigMap、Secret、各种资源的期望状态和实际状态全部都是等价的键值对写入 etcd。唯一不走 etcd 的是业务容器本身的数据流量那一层走的是网络和存储跟 etcd 没直接关系。所以部署 etcd 时的核心关注点是第一不能单点不然整个集群的控制平面就是单点第二数据必须落盘不能只靠内存第三网络必须稳定低延迟因为每次 API Server 的读写都会打到这里来网络抖动直接变成 API 延迟。1.2 版本选择和拓扑规划一开始就要定好Kubernetes 各版本对 etcd 的版本支持有明确区间部署前一定要查官方文档里的 etcd 版本矩阵不要随便拿一个 etcd 最新版就去配合老的 K8s 版本。比如常见搭配里K8s 1.22 大致对应 etcd 3.5.x更老的可能要 etcd 3.4.x。API Server 和 etcd 之间走的是 gRPC版本差太多会出现兼容性报错最典型的就是Etcdserver: no leader或者 watch 断流。拓扑上我的建议是如果条件允许etcd 和 K8s Master 节点分开部署。原因是 etcd 对磁盘 IO 要求很敏感如果 Master 节点上同时跑着大量控制面组件和业务负载磁盘竞争会把 etcd 的 fsync 延迟拉高而 etcd 的写性能恰恰是跟 fsync 延迟强相关的。但如果只是测试环境或者学习环境单机二进制部署一个 etcd 其实完全够用。生产环境才建议三台独立机器组成三节点 etcd 集群。注意 Raft 集群要求奇数节点三节点能容忍挂一台五节点能容忍挂两台再多意义不大因为写放大和选举开销都会增加。1.3 数据目录和磁盘选型最容易忽略的细节etcd 的数据目录建议单独放在一块盘上最好 SSD。你可以用fio简单测一下目标盘的 fsync 性能如果延迟在个位数毫秒级别问题不大如果经常跳到几十甚至上百毫秒那等流量上来之后etcd 的写请求会频繁超时K8s 里你能看到的现象是kubectl get nodes时灵时不灵Pod 调度卡住。还有一个细节是 etcd 默认的--snapshot-count是 100000 条事务打一次快照如果数据变化很快磁盘空间消耗会很明显。建议给 etcd 数据目录留出至少 10GB 以上的空间并开启定期做碎片整理defrag的习惯。这个放后面排查部分细说。2. 手动部署一个单节点 etcd把步骤吃透2.1 获取二进制与基础参数配置最简单的方式是直接下载 etcd 官方发布的二进制压缩包。解压后能看到两个核心程序etcd和etcdctl。etcd是服务端etcdctl是命令行客户端日常用它检查集群状态非常方便。启动一个最基础的单节点 etcd命令长这样etcd \ --name etcd-1 \ --data-dir /var/lib/etcd \ --listen-client-urls http://0.0.0.0:2379 \ --advertise-client-urls http://192.168.1.10:2379 \ --listen-peer-urls http://0.0.0.0:2380 \ --initial-advertise-peer-urls http://192.168.1.10:2380 \ --initial-cluster etcd-1http://192.168.1.10:2380 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster-state new这里每个参数都是有讲究的。--listen-client-urls是 API Server 或者 etcdctl 访问的地址--advertise-client-urls是把这个节点的客户端地址“广播”给其他成员如果有多块网卡这两个一定不能乱填否则别的节点可能拿着广播地址根本连不上。--listen-peer-urls和--initial-advertise-peer-urls则是用于 etcd 节点之间内部通信的。--initial-cluster-token是集群的唯一标识主要用来避免两个不同时间段的集群配置文件相互干扰类似命名空间的作用。启动之后用etcdctl验证etcdctl --endpointshttp://192.168.1.10:2379 endpoint health看到healthy输出就说明服务正常。再写入一个 key 测试一下etcdctl put /test hello etcdctl get /test能正常写读这个单节点 etcd 就能给 K8s 用了。2.2 把 etcd 接入 Kubernetes 的 API ServerK8s 的 kube-apiserver 连接 etcd 是通过一堆启动参数来指定的。最核心的是--etcd-servers这是必填的。在单节点场景下假设 etcd 监听在192.168.1.10:2379那么 kube-apiserver 启动时需要加--etcd-servershttp://192.168.1.10:2379如果你启用了 etcd 的 TLS那么还需要额外指定 CA 证书和客户端证书--etcd-cafile/etc/kubernetes/pki/etcd/ca.crt \ --etcd-certfile/etc/kubernetes/pki/etcd/server.crt \ --etcd-keyfile/etc/kubernetes/pki/etcd/server.key这里有一个容易踩的坑如果你已经有现成的 K8s 集群改成手动指向外部 etcd需要先把 kube-apiserver 里旧的数据清掉或者直接用一个全新的数据目录。否则 API Server 启动时会因为 etcd 里已有旧集群数据而出现资源冲突最典型的表现就是 Pod 调度过去之后状态一直不对。2.3 验证 API Server 是否真正写入了 etcd这一步很多教程不提但我觉得很值得做。启动完 kube-apiserver 后用 etcdctl 看看里面的 keyetcdctl --endpointshttp://192.168.1.10:2379 get / --prefix --keys-only | head -50你会看到大量以/registry/...开头的 key比如/registry/nodes、/registry/pods、/registry/services等。这是 Kubernetes 在 etcd 里的通用存储路径约定。看到这些 key 出现才说明 API Server 和 etcd 之间真的打通了而不是单纯 etcd 自己能跑而已。3. 三节点 etcd 高可用集群部署生产环境的标准姿势3.1 配置生成与静态拓扑生产环境我习惯用静态拓扑也就是手动填好三个节点的地址和名称这样最清晰不会像动态发现那样需要额外依赖 DNS 或者发现服务。假设三台机器的 IP 分别是192.168.1.10、192.168.1.11、192.168.1.12节点名分别是etcd-1、etcd-2、etcd-3。三台机器都要执行安装 etcd 这一步。注意节点名在整个集群里必须唯一这个唯一性会写进每个节点的 member list 里如果不唯一后面加入集群时会报name conflicts。每台机器的启动配置其实结构一样只是 IP 和 name 不同。拿etcd-1为例etcd \ --name etcd-1 \ --data-dir /var/lib/etcd \ --listen-client-urls http://192.168.1.10:2379,http://127.0.0.1:2379 \ --advertise-client-urls http://192.168.1.10:2379 \ --listen-peer-urls http://192.168.1.10:2380,http://127.0.0.1:2380 \ --initial-advertise-peer-urls http://192.168.1.10:2380 \ --initial-cluster etcd-1http://192.168.1.10:2380,etcd-2http://192.168.1.11:2380,etcd-3http://192.168.1.12:2380 \ --initial-cluster-token etcd-k8s-cluster \ --initial-cluster-state new第二台和第三台除了节点名和 IP其他结构完全相同。三个节点几乎同时启动是最省事的因为 Raft 集群需要选主如果你一个一个启动第一个节点会因为找不到其他节点而反复等待但不用担心它会在其他节点加入后自动完成选举和同步。3.2 启动顺序和自举过程三节点 etcd 集群的启动顺序其实没有严格要求但建议在短时间内依次启动避免某个节点等太久。启动完成后立刻查看成员列表etcdctl --endpointshttp://192.168.1.10:2379,http://192.168.1.11:2379,http://192.168.1.12:2379 member list正常情况下你会看到三个节点都是started状态且 leader 只有一个。如果发现某个节点显示unstarted大概率是--initial-cluster里的某个地址写错了或者防火墙把 2380 端口拦了。3.3 用 etcdctl 三节点验证集群健康状态生产环境我建议每次部署完后都执行一次完整健康检查命令如下etcdctl --endpointshttp://192.168.1.10:2379,http://192.168.1.11:2379,http://192.168.1.12:2379 endpoint health --cluster注意这个--cluster参数它会让 etcdctl 检查所有集群成员的健康状态而不是只检查你列出来的 endpoints。如果所有节点都返回healthy说明集群本身没问题。接着还可以看下选举状态etcdctl endpoint status --cluster -w table输出里有IS LEADER列能一眼看出谁是当前 leader。这对后面排查网络分区问题非常有用因为如果 leader 频繁切换说明集群内部网络或者磁盘稳定性出了问题。3.4 配置 kube-apiserver 连接三节点 etcdkube-apiserver 那边很简单直接把所有 etcd 地址都填进去--etcd-servershttp://192.168.1.10:2379,http://192.168.1.11:2379,http://192.168.1.12:2379API Server 自带 etcd client 的故障转移逻辑某一个节点不可用时它会自动切换到另一个。但注意这不代表你不需要 keepalived 或者 LB。API Server 对 etcd 的 failure tolerance 是 Raft 级别的只要多数节点活着就能继续服务三节点集群里挂掉一台完全没问题但如果你同时挂掉两台etcd 就退化成只读或者干脆拒绝了因为无法达成多数派。这些是 Raft 算法的基本属性运维时要心里有数。3.5 etcd 集群证书与安全配置别裸奔我第一次搭生产环境时图省事用了 HTTP 明文结果被安全扫描直接打回。其实 etcd 配置 TLS 没有想象中复杂总结下来就四类证书CA 证书、服务端证书、客户端证书、Peer 证书。Peer 证书是节点之间互信用的客户端证书是 API Server 访问 etcd 用的。下面是一个带 TLS 的典型配置片段etcd \ --name etcd-1 \ --data-dir /var/lib/etcd \ --client-cert-auth \ --trusted-ca-file/etc/etcd/pki/ca.crt \ --cert-file/etc/etcd/pki/etcd-1.crt \ --key-file/etc/etcd/pki/etcd-1.key \ --peer-client-cert-auth \ --peer-trusted-ca-file/etc/etcd/pki/ca.crt \ --peer-cert-file/etc/etcd/pki/etcd-1-peer.crt \ --peer-key-file/etc/etcd/pki/etcd-1-peer.key \ --initial-cluster etcd-1https://192.168.1.10:2380,etcd-2https://192.168.1.11:2380,etcd-3https://192.168.1.12:2380注意配置 TLS 之后--initial-cluster里的协议也要从http改成https。否则节点之间的 peer 通信会拿 HTTPS 协议去连 HTTP 端口连不上。这个坑我见过很多次排查到最后就是这里改漏了。API Server 侧需要配置--etcd-servershttps://192.168.1.10:2379,https://192.168.1.11:2379,https://192.168.1.12:2379 \ --etcd-cafile/etc/kubernetes/pki/etcd/ca.crt \ --etcd-certfile/etc/kubernetes/pki/etcd/server.crt \ --etcd-keyfile/etc/kubernetes/pki/etcd/server.key这里我强烈建议用 etcdctl 先手动验证一遍 TLS 是否通不要让 kube-apiserver 直接替你做测试不然报错时日志又多又乱etcdctl --endpointshttps://192.168.1.10:2379 \ --cacert/etc/etcd/pki/ca.crt \ --cert/etc/etcd/pki/etcd-1.crt \ --key/etc/etcd/pki/etcd-1.key \ endpoint health注意我们刚才用了两套证书etcd-1.crt 是 etcd 服务端证书API Server 用的 server.crt 是客户端证书。服务端证书必须包含 etcd 节点的 IP 或者域名作为 SAN否则客户端在校验证书时直接失败。这个 SAN 配置在生成证书的时候就要想好之后改很麻烦。4. Pod 通信的本质先搞清楚数据平面和控制平面4.1 Pod 网络的核心问题是“IP 从哪来、包怎么走”Kubernetes 网络模型规定每个 Pod 都有自己独立的 IPPod 内所有容器共享这个 IPPod 之间可以直接通过 IP 通信且不需要 NAT。这个模型在设计上很干净但实现它需要解决两个核心问题IP 怎么分配流量怎么路由。第一件事由 IPAMIP Address Management模块负责通常集成在 CNI 插件里。Kubernetes 默认的 Pod CIDR 范围是为每个节点分配一个子网段的比如10.244.0.0/24给 node110.244.1.0/24给 node2。节点上的 Pod 从自己所属的子网里拿地址。第二件事就复杂了因为 Pod 分布在不同节点而物理网络的拓扑千差万别于是各家 CNI 插件就出来解决“如何把 Pod 的包送到对端 Pod”的问题。4.2 从容器到 Podpause 容器与网络命名空间先看最底层的模型。每个 Pod 里其实有一个“什么业务都不干”的 pause 容器它的唯一作用就是持有网络命名空间network namespace。Pod 里的其他业务容器通过--networkcontainer:pause这种方式把自己的网卡放进同一个命名空间里。这样做的好处是业务容器无论怎么重启、被杀死、重新创建Pod 的网络栈都不会变。因为 Pod 的 IP 和端口绑定在 pause 容器持有的网络命名空间上只要 pause 容器还活着Pod 的 IP 就一直有效。所以在抓包时你看到的 veth 对一端在 Pod 的网络命名空间里另一端在节点的根命名空间里流量从 Pod 出去之后先到节点再走路由决定下一跳。4.3 同节点 Pod 通信与跨节点 Pod 通信的差异同节点上的两个 Pod它们的 veth 对都挂着同一个 Linux 网桥一般是cbr0或者docker0CNI 不同名字不同。Pod A 发往 Pod B 的包从 veth 出来到网桥网桥通过 MAC 地址学习直接转发到 Pod B 的 veth——这个过程不离开宿主机所以延迟极低。跨节点就有意思了。物理网络上节点之间通过路由器和交换机连接Pod IP 是虚拟网段物理网络设备根本不知道 10.244.0.x 该怎么路由。于是 CNI 插件会做一件事在每个节点上建立一条通向“Pod 网段”的路由下一跳指向对端节点的物理 IP或者用一种叫 Overlay 的封装技术把 Pod 的包重新包一层在物理网络上传输。如果 CNI 用 BGP 或者纯路由模式比如 Flannel 的host-gw模式与 Calico 的 BGP 模式那么每个节点都直接声明自己能路由哪些 Pod 子网包在物理网络层就送到对端节点。如果 CNI 用 Overlay 封装比如 Flannel 的vxlan模式则包会被封成 UDP 报文到达对端再解封装性能会略差一些但在三层网络不互通的环境有很强的穿透力。5. 动手实现 Pod 通信从 CNI 插件安装开始5.1 选择一个合适的 CNI 插件最常见的两个选择是 Flannel 和 Calico。Flannel 的好处是配置简单默认 VXLAN 模式对网络设备要求低三层能通就能跑很适合学习和内部测试环境。Calico 性能更好支持网络策略默认用 BGP 做路由不封包适合生产环境。如果你的集群未来要上复杂的 NetworkPolicy那就直接选 Calico。我下面用 Flannel 的 VXLAN 模式作为例子因为它最能体现 Overlay 网络原理排查起来也更直观。5.2 Flannel VXLAN 模式的完整配置流程在 kubeadm 初始化的集群上安装 Flannel 其实只有一个命令kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml但要注意kubeadm 初始化集群时需要给 Pod 网络指定 CIDR。如果你不加这个参数默认的--pod-network-cidr是空的Flannel 装上去也分配不到地址范围。所以初始化时肯定要加kubeadm init --pod-network-cidr10.244.0.0/16同时kube-flannel.yml文件里默认的 net-conf.json 配置也写好了Network: 10.244.0.0/16两边一定要一致不然 Flannel 起来后会因为 IPAM 子网冲突或者找不到对应配置而反复报错。安装完成后检查关键组件kubectl get pods -n kube-system | grep flannel kubectl get nodes如果 Flannel pod 处于CrashLoopBackOff先看日志。最常见的两个问题一是节点没有加载flannel.1这个网卡接口二是--pod-network-cidr和 Flannel 配置不一致。5.3 从节点侧观察 veth 与路由表节点上 Flannel 运行起来后你会看到多了一个叫flannel.1的虚拟网卡MTU 默认是 1450比物理网卡的 1500 少 50。这 50 字节就是 VXLAN 封装的头部开销。如果物理网络 MTU 受限这个值还需要相应调小。这是很多 MTU 类问题的源头。再看节点路由表大概率会看到类似这样的条目10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 10.244.1.0/24 via 192.168.1.11 dev flannel.1 onlink 10.244.2.0/24 via 192.168.1.12 dev flannel.1 onlink第一条是本节点的 Pod 网段走 cni0 网桥。后两条是其他节点的 Pod 网段全部走 flannel.1 这个 VXLAN 隧道设备从隧道出去之后经过 VXLAN 封装转到对应节点的物理 IP。这就是 Overlay 网络的典型路由形态。5.4 实际创建两个跨节点 Pod打通通信链路我用两个 Deployment 来测试。先创建第一个kubectl create deployment nginx-a --imagenginx kubectl scale deployment nginx-a --replicas2由于调度器默认会把两个副本尽量分布在不同节点如果节点充足的话这天然就是一个跨节点通信的测试场景。然后创建第二个kubectl create deployment nginx-b --imagenginx kubectl scale deployment nginx-b --replicas1获取 Pod 的 IPkubectl get pods -o wide观察输出里的IP列如果 nginx-a 的一个 Pod 在 node1 拿到10.244.0.2nginx-b 的 Pod 在 node2 拿到10.244.1.3这两个 Pod 之间直接 pingkubectl exec -it pod-name -- ping -c 3 10.244.0.2能 ping 通就说明基本网络通了。但 nginx 容器里默认可能没带 ping 命令这时候直接用 wget 测更实用kubectl exec -it nginx-b-pod -- wget -qO- http://10.244.0.2返回 nginx 的欢迎页 HTML跨节点 Pod 通信就完全验证通过了。5.5 tcpdump 抓包验证 VXLAN 封装细节如果你好奇心强一点可以在 node2 的物理网卡上抓包看看tcpdump -i eth0 udp port 8472 -XX你会看到源 IP 是 node2 的物理 IP目标 IP 是 node1 的物理 IPUDP 目标端口 8472这是 VXLAN 的默认端口。把包展开看还能看到内层其实是原始的 Pod IP 报文。这个过程就像把一封信装进另一个更大的信封里寄出去收件箱拆开后才能看到真正的信纸。这个比喻能帮你理解 Overlay 网络为什么能穿透三层网络。如果抓包只看到 UDP 包发出但没有回应问题大概率在另一个节点的 VXLAN 响应路径上常见的可能是 flannel.1 设备没起来或者路由表缺条目。6. etcd 和网络链路打通后常见问题与排查实录6.1 etcd 频繁选举导致 API Server 间歇性不可用现象kubectl get nodes时好时坏etcd 日志里出现大量leader changed。排查时先看 etcd 的指标etcdctl --endpointshttp://192.168.1.10:2379 endpoint status --cluster -w table如果 leader 字段频繁变化先检查节点之间网络延迟。用ping看延迟用ss -tnp看是否有丢包或重传。也可以直接看 etcd 的--metrics指标重点关注etcd_network_peer_round_trip_time_seconds。我遇到过一个典型案例三节点跨机房部署两两之间网络延迟稳定但其中一台机器磁盘 IO 过载导致它发心跳的 goroutine 被卡住其他节点认为它已故障于是触发重选举。后来把数据目录迁移到裸 SSD问题彻底消失。6.2 Pod 网段与其他网络冲突导致跨节点不通很多内部网都有保留网段。如果 Pod 的 CIDR 恰好跟公司内部某个 VLAN 网段重叠路由就会打架。排查方法很直接在节点上ip route show看有没有指向物理网络的路由被 Pod 网段路由覆盖。解决方法规划 Pod CIDR 时远离现有物理网段比如选用10.244.0.0/16或10.42.0.0/16这类常见内部网段时要先确认机房内没有冲突。6.3 Flannel 隧道接口 MTU 问题导致大包不通、小包能通现象很经典Pod 之间 ping 小包没问题但传输大文件或者跑数据库迁移时直接断流。原因就是物理网络 MTU 小于 1500比如常见的内网 MTU 是 1400而 Flannel VXLAN 的默认 MTU 是 1450数据包到物理网卡时超过 1400 会被丢弃IP 层又不会自动分片DF 标志位导致大包发不出去。解决方案是在 Flannel 配置里调整vniMTU为1350或更小同时保证节点物理网卡的实际 MTU 足够大。最简单粗暴的测试方法是用ping -M do -s 1472检查节点间最大可用包大小。说白了网络问题很多时候不是功能性的而是 MTU 这类细节在作怪。6.4 kube-apiserver 报 etcd 连接超时但 etcd 本身健康这种情况遇到过多次etcdctl endpoint health全军健康但 API Server 日志里疯狂刷context deadline exceeded。排查后发现是 etcd 节点上的文件描述符不够用或者 API Server 与 etcd 之间的并发连接数超过了 etcd 的--max-request-bytes与 gRPC 连接数限制。可以通过调大ulimit -n并重启 etcd 进程解决。还有一种场景是 etcd 需要 compaction因为历史版本数据不断积累每次查询都扫描全量历史导致 API Server 请求变慢。可以用etcdctl compact revision配合etcdctl defrag释放碎片空间。注意 defrag 操作会短暂阻塞读写最好在业务低峰期做。6.5 快速排查小工具etcd 端到端链路自检最后分享一个我常用的自检套路。etcd 部署完先验服务端再用模拟 client 验证读写最后再交给 API Server 去做连接。如果 API Server 之后出问题不要第一反应怀疑 etcd先把这些基础项逐条过一遍# 1. 成员状态 etcdctl member list # 2. 健康状态 etcdctl endpoint health --cluster # 3. 写入读取测试 etcdctl put /health/test ok etcdctl get /health/test # 4. 检查磁盘 IO iostat -x 1 # 5. 检查网络连接 ss -tnp | grep 2379这套流程走完百分之八十的“etcd 问题”其实都能定位到具体原因大部分时候不是 etcd 本身挂了而是网络、磁盘、连接数这些外围环境出问题。我个人在实际操作中的体验是etcd 和 Pod 网络这两块东西一旦把原理层面打通了平时踩的那些坑基本都能顺着思路猜出个大概。特别是 VXLAN 链路只要记住“外层物理 IP 找设备内层 Pod IP 找路由”再加一个 MTU 的克制网络问题就基本有了清晰的排查框架。如果后面你的集群服务规模在涨还可以继续往 Calico 的 BGP 模式和 eBPF 网络方向研究那一层还有更多有意思的东西可以折腾。