同事问我想搭一套K8s环境练手有没有不折腾的方案。我第一个反应就是kubeadm Docker flannel这个组合。它是Kubernetes官方提供的集群引导工具配合flannel这个轻量级网络插件可以在三台普通Linux服务器上快速拉起一个可用集群。这篇文章就把我完整搭建三节点集群的过程、参数选择逻辑和踩过的坑分享出来适合准备入门K8s、或者在公司内网搭建测试环境的开发者参考。1. 方案选型与版本搭配1.1 三节点集群到底能做什么三节点指的是1个master节点和2个worker节点。这是K8s集群最小的“标准配置”之一既能跑控制面组件也能承担真实的业务负载调度。这个规模有几个实际用途验证Deployment滚动更新、测试Pod跨节点通信、试验NodeSelector和污点调度、跑小型中间件集群。如果再往上加节点就是横向扩容三节点已经可以把你从“单机Docker”的思维拽到“分布式调度”的语境里。K8s控制面组件本身占内存不高但加业务Pod后需要余量。我实测下来master节点2核4G勉强够用worker节点2核4G可以跑轻量应用如果要多跑几个数据库之类的服务建议每台4核8G起步。1.2 为什么选kubeadm而不是二进制或工具链搭建K8s常见三条路二进制方式、kubeadm方式、自动化工具方式。二进制方式最透明每个组件kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy自己下载、自己配systemd单元适合想深挖原理的人。但代价是你要手工处理证书签发、kubeconfig配置、组件启动顺序第一次搭基本要半天起步出错还不好排查。kubeadm把这一大堆脏活自动完成——证书、kubeconfig、静态Pod清单、控制面组件编排全给你生成好worker节点只需要执行一条join命令就能接入。它不隐藏细节生成的yaml都写在/etc/kubernetes/manifests里你可以直接查看。自动化工具比如各种开源部署平台上手快但出了问题容易变成“黑盒”对学习K8s本身帮助有限。所以我一直推荐测试环境用kubeadm生产环境等理解了原理之后再用自动化工具。1.3 版本兼容性这件头等大事这里必须说一个很多新手踩进去的坑Docker和K8s的兼容关系在1.24版本之后发生了根本变化。K8s 1.24之前kubelet默认通过dockershim调用Docker作为运行时。1.24版本正式移除了dockershimDocker不能再作为kubelet直接的CRI运行时。现在主流的做法是用containerd作为容器运行时Docker CLI留着用来构建镜像和本地调试。这套方案也符合“kubeadm使用Docker”这个标题的常见语义——保留Docker生态但运行时换成containerd。实际生产里绝大多数集群也是这么跑的。如果你确实想让Docker直接作为k8s运行时比如是1.23及更早的版本需要额外部署cri-dockerd适配器。但这个方案已经不算主流我不建议新环境这么干。我本次以Kubernetes 1.28.x为演示版本配套版本归纳一下组件版本建议说明kubeadm / kubelet / kubectl1.28.x三件套版本要一致containerd1.7.x替代docker作为运行时Docker CLI24.x或更新仅做镜像构建、本地调试flannelv0.24.x网络插件配合kube-flannel.yml内核4.15满足overlay和iptables模块需求2. 环境准备与系统初始化2.1 硬件与网络规划准备三台纯净的Linux服务器操作系统我用的是CentOS 7.9兼容内核的环境Ubuntu 22.04/20.04同样可行命令上用systemctl管理服务即可。IP规划我习惯单独划出来比如主机名IP角色k8s-master192.168.10.10控制面与调度k8s-node1192.168.10.11worker节点k8s-node2192.168.10.12worker节点三台机器必须在同一二层网络内防火墙或者安全组需要放行K8s相关端口6443apiserver、2379-2380etcd、10250kubelet、10255kube-proxy、8472flannel VXLAN。如果内网环境条件允许我建议测试环境直接封掉防火墙省事生产环境再按最小权限制定策略。主机名建议现在就改好避免后面kubelet证书、节点名称对不上# 以master为例node1/node2对应修改 hostnamectl set-hostname k8s-master echo 192.168.10.10 k8s-master /etc/hosts echo 192.168.10.11 k8s-node1 /etc/hosts echo 192.168.10.12 k8s-node2 /etc/hosts所有节点都要执行hosts配置保证彼此可以通过主机名互访。2.2 关闭swap、加载内核模块K8s从早期版本开始就要求关闭swap因为kubelet的QoS、内存隔离假设建立在物理内存管理之上。直接关掉swapoff -a sed -i s/.*swap.*/#/ /etc/fstabsed这行是把fstab里的swap挂载注释掉否则重启后又回弹。接下来加载两个关键内核模块cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilteroverlay是容器镜像分层存储依赖的文件系统驱动br_netfilter让iptables规则对bridge网桥上的流量也生效K8s的Service转发依赖它。然后配置sysctl参数cat EOF | tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl --system千万别跳过很多集群搭起来之后跨节点不通就是net.ipv4.ip_forward没有置1。2.3 安装containerd并修改关键配置系统一般会自带一个旧版containerd建议用Docker官方源安装一套完整组件包含containerd和Docker CLIyum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y docker-ce docker-ce-cli containerd.io这里本质上安装的就是docker-ce只是我们不启用Docker作为运行时而是用docker命令来构建镜像和打tag。containerd默认配置需要手动生成和调整mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml调整两处第一处SystemdCgroup必须改为true。K8s官方建议kubelet与容器运行时使用同一个cgroup驱动否则会错过cgroup相关报错sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml第二处sandbox_imagepause镜像默认地址在国内网络环境可能拉不到可以改成可访问的镜像仓库地址。这里的操作方式是把pause镜像的地址换成你的网络环境可以访问的地址这一点非常关键——很多kubeadm init卡住都是因为pause镜像拉取失败。改完成之后重启containerdsystemctl daemon-reload systemctl enable --now containerd systemctl status containerd顺手验证一下crictl能否连接crictl version如果能正常输出版本信息说明运行时已经就绪。3. 核心组件安装与集群初始化3.1 安装kubeadm、kubelet、kubectlK8s官方源在国内访问不稳定一般会替换为镜像源。配置yum源指向可用的镜像仓库地址然后安装三件套cat EOF | tee /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck0 EOF安装并锁定版本yum install -y kubeadm-1.28.2 kubelet-1.28.2 kubectl-1.28.2 systemctl enable --now kubelet注意这里的yum配置地址其实是某个公共镜像地址实际执行时根据你的网络环境替换。kubelet现在状态是failed也不用管它要等kubeadm init之后拿到配置才能正常启动——这是正常现象别慌。3.2 初始化master节点在master节点上执行先拉取镜像再正式init。先检查镜像拉取kubeadm config images pull如果镜像地址不通可以在init命令里指定--image-repository参数换成能访问的镜像仓库地址。这一步的目的是把pause、etcd、apiserver等镜像提前拉到本地。然后执行初始化kubeadm init \ --apiserver-advertise-address192.168.10.10 \ --control-plane-endpointk8s-master \ --pod-network-cidr10.244.0.0/16 \ --kubernetes-versionv1.28.2几个参数说明--apiserver-advertise-address指定API Server对外通告的IP其他节点要能访问这个地址。--control-plane-endpoint尽量用主机名而不是IP后续如果要做高可用可以在这里填一个VIP或者负载均衡地址现在用master主机名即可。--pod-network-cidr10.244.0.0/16是flannel的默认网段。这个值必须与flannel配置一致否则节点之间Pod网段对不上网络直接不通。我见过不少案例是这里写错了然后Pod能创建但跨节点访问永远超时。整个init过程大概2到5分钟如果卡住最可能的原因就是镜像拉不下来。先检查crictl images确认镜像是否齐全不要反复重复init要先reset再做kubeadm reset -finit成功后会输出join命令记得保存。如果不小心丢了可以这样重新生成kubeadm token create --print-join-command这个命令会输出包含token和discovery-token-ca-cert-hash的完整join命令直接复制到worker节点执行即可。初始化完成后按提示配置kubectlmkdir -p $HOME/.kube cp /etc/kubernetes/admin.conf $HOME/.kube/config验证控制面状态kubectl get nodes kubectl get pods -n kube-system此时master节点大概率是NotReady这是正常的——网络插件还没安装。继续往下走。3.3 worker节点接入集群在node1和node2上执行刚刚保存的join命令kubeadm join 192.168.10.10:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxxworker节点无须再执行init它会自动拉取必要的镜像注册kubelet证书请求。如果join失败多半是token过期或者hash值不对用kubeadm token create重新生成。加入后回到master查看kubectl get nodes双节点会以NotReady状态出现依然没安装CNI这不影响我们接下来部署flannel。4. flannel部署与集群网络验证4.1 flannel到底解决了什么问题K8s集群里每个Pod都要有独立的IP而且跨节点也要能互通。这个需求不能靠docker0网桥实现因为docker0是单机网络跨节点就乱了。flannel用VXLAN隧道解决跨节点通信。简单理解flannel给每台机器分配一个子网比如master用10.244.0.0/24node1用10.244.1.0/24。Pod的所有流量先走进flannel虚拟网桥再被封装进VXLAN报文经过物理网络送到目标节点最后解封装路由到目标Pod。VXLAN技术上对组网要求低只要三层可达就能跑不需要改交换机配置。这也是它适合测试环境的原因。4.2 部署flannel先下载flannel的部署清单从官方发布页获取kube-flannel.yml然后直接applykubectl apply -f kube-flannel.yml如果网络环境无法访问官方发布页可以换一台能访问的机器提前下载再拷贝过来。反正yaml是静态文件不涉及运行时联网。部署完成后观察flannel Pod状态kubectl get pods -n kube-system -o wide正常情况每个节点会有一个flannel Pod状态为Running。如果出现CrashLoopBackOff多半是VXLAN隧道创建失败或者flannel版本与内核不兼容。先查看日志kubectl logs -n kube-system -l appflannelflannel跑起来后再回头看节点状态kubectl get nodes此时master、node1、node2应该全部变成Ready。4.3 验证跨节点Pod通信光看节点Ready还不够还要验证数据面真实连通。我在测试时习惯创建几个跨节点的Pod并在每个节点上验证。先部署测试Podkubectl run nginx-test --imagenginx kubectl scale deploy/nginx-test --replicas3 kubectl get pods -o wide确认三个Pod分布在不同节点然后从master上的Pod去ping另一个节点的Podkubectl exec -it nginx-test-xxxxx -- ping 10.244.1.x如果Ping通说明VXLAN数据面已经OK。另外还可以验证Service的ClusterIPkubectl expose pod nginx-test-xxxxx --port80 kubectl get svc curl ClusterIP:80能通就说明kube-proxy的iptables或IPVS规则正常。我测试时通常会从master节点、worker节点分别curl Service避免出现过路通、跨节点不通的假象。5. 常见问题与排错实录5.1 问题速查表搭建过程我前后踩过不少坑整理成一张速查表你后面对着查就行现象根因处理方式init卡在拉镜像镜像地址不通改--image-repository或提前离线导入crictl ps能跑但kubelet起不来cgroup驱动不一致检查SystemdCgrouptruekubelet侧配置也为systemdflannel CrashLoopBackOffCNI插件版本不匹配或VXLAN端口不通看flannel日志放通8472端口节点长时间NotReadyCNI未部署或flannel没Ready等flannel跑起来或检查kubelet日志跨节点Pod ping不通安全组/防火墙挡了VXLAN放通8472/UDP确认ip_forward1join时报token过期token有效期默认24小时重新生成join命令kube-proxy 日志报错IPVS模块未加载加载ip_vs相关内核模块5.2 两个最值得讲的排查故事第一个是cgroup驱动不一致。kubelet日志会显示failed to run Kubelet: misconfiguration: kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs。这个报错在我看来就是配置不一致导致的。如果你kubelet的启动参数或kubelet.yaml里没指定默认用systemd而containerd默认生成配置是cgroupfs两边对不上就会一直报错。我统一把containerd的SystemdCgroup改成true之后这个问题就消失了。第二个是公有云环境下flannel Pod起来了但跨节点不通。查了一圈发现是安全组没有放行8472端口VXLAN的UDP报文直接被丢了。flannel数据面是走UDP封装的安全组和网络ACL缺一不可。排查时先看本机抓包tcpdump -i eth0 udp port 8472如果能收到报文但无法互通再检查路由表看是不是flannel的子网路由没下发。5.3 按需放行端口与failed启动排查这一点容易被忽略新版kubelet启动时如果没有配置会处于“failed”状态很多新手以为装坏了。实际上kubelet要等到init完成才会生成 /etc/kubernetes/kubelet.conf然后才能正常启动。判断方法systemctl status kubelet journalctl -u kubelet -f如果你看到类似Failed to load kubelet config file的日志这不是故障是还没有初始化。把kubeadm init执行完kubelet会自动重启并加载配置。还有一个小细节在公有云环境测试时如果放了防火墙除了要放行6443和8472master节点上的10250kubelet指标接口也必须让worker节点可以访问否则kubelet注册会失败。5.4 清理与重建重来几次就熟了测试环境经常需要推倒重来。完整清理命令kubeadm reset -f rm -rf /etc/cni/net.d ~/.kube rm -rf /var/lib/etcd在master上执行reset之后worker节点也要执行否则重新搭建时会因为旧证书冲突而join失败。关于镜像我是这样处理的把要用到的K8s镜像和flannel镜像全部用docker pull拉到本地然后通过docker save导出成tar包拷贝到每一台机器再docker load进去。kubeadm从本地加载镜像时不会再触发远程拉取这种方式在离线环境或者内网环境也能给你省去大量排错时间。6. 实际操作中的体会这套集群我前后搭了不止十次最大的感受是kubeadm确实把复杂度控制在了可接受范围内但对K8s的基础知识有一定的要求依然很重要。你至少得明白Pod、Deployment、Service、CNI这四件事不然出了问题会不知道从哪查起。我个人建议新同学拿到集群之后不要急着删了重来先在上面做完这几件事跑一个多副本Deployment、手动删掉一个Pod看调度器怎么补、给节点打标签后用nodeSelector把Pod调度到指定节点、再看一下flannel网卡和路由表。这些操作会把抽象概念变成具象记忆比看十篇文档都有用。还有一个小技巧每创建一个网络相关资源后用ip route和ip addr检查一下路由和网卡变化很快就能建立起K8s网络模型的空间感。以后再遇到网络问题你会比很多人更快定位到原因。