使用kubeadm从零搭建Kubernetes集群:关键步骤与排错指南 1. 动手前的整体认知与环境决策1.1 先搞清楚Kubernetes到底解决了什么问题Kubernetes这个项目从诞生到现在基本已经成了容器编排的事实标准。但很多人在第一次接触它的时候都会被一大堆抽象概念吓住节点、Pod、Deployment、Service、Namespace、etcd、CRI…… 我记得自己第一次装Kubernetes的时候纯粹是照着网上的命令脚本复制粘贴装完之后集群显示Ready了我却完全不知道这些命令背后的逻辑是什么后面出了问题也不知道从哪里排查。所以在写安装步骤之前我想先帮大家建立一个整体认知。Kubernetes的核心工作其实很简单帮助你把一堆容器管起来包括部署在哪里、挂掉怎么重启、如何扩容缩容、如何暴露给外部访问。你可以把它理解成一个“自动化的容器酒店前台”每个节点是房间Pod是住进去的客人kubelet是楼层的管家而控制平面就是酒店的管理系统。这篇博文不讨论Kubernetes的所有功能只聚焦一件事怎么从一个全新的Linux环境一步一步搭建出一个可用的Kubernetes集群。我会把每个关键步骤背后的原理讲清楚而不是只给命令。1.2 安装方式怎么选kubeadm、二进制、minikube还是kind第一次安装Kubernetes的人通常会卡在第一个问题上用哪种方式装网上教程五花八门有说用minikube的、有说用kind的、有说直接上kubeadm的甚至有人直接从GitHub拉二进制包手工配置。我个人的建议是学习阶段用kubeadm测试快速体验用minikube或者kind生产环境用kubeadm加自动化运维工具而不是手工二进制部署。可能有人会觉得手工二进制部署更“底层”、更厉害但实际上Kubernetes组件之间的配置关系非常复杂手工方式需要自己管理证书、token、服务文件、etcd集群任何一个环节失误都会导致组件间通信失败排查起来非常痛苦。kubeadm最大的价值在于它把这些复杂的初始化工作自动化了包括证书生成、token创建、组件配置文件生成、健康检查等。它不是一个黑盒子你完全可以通过kubeadm init --dry-run查看它将要执行的内容学习成本低可控性也高。如果你只是想在笔记本电脑上跑一个单机集群来玩minikube会更合适如果是在CI流程里临时起一个集群做测试kind更轻量。但论最贴近生产环境、最能帮你理解组件关系的安装方式kubeadm是第一选择。下面所有内容都基于kubeadm展开。1.3 版本选型与组件兼容性Kubernetes的安装有一个让新手很头疼的特点版本兼容性非常严格。kubelet版本不能高于控制平面版本太多kubectl也因为兼容性做了矩阵限制CNI插件、容器运行时也各有适配版本。如果不先看兼容矩阵就直接装经常会出现装完以后组件报奇怪的错。我的建议是选一个相对新的稳定版本比如1.28或者1.29这条线。不要追最新版本因为最新版刚发布时配套的教程、镜像、插件适配往往还没完全跟上也不要选太老的版本因为老版本在容器运行时接口上跟新版本差异很大排查问题时参考文档也少。选稳定版本后把kubeadm、kubelet、kubectl三个组件锁定到同一个小版本然后在你的环境里一次性装好。这里还要注意一个容易踩的坑容器运行时和Kubernetes的CRI版本适配。Kubernetes 1.24之后默认就没法直接用Docker作为运行时了需要走containerd或者其他符合CRI标准的运行时。我这次选择containerd原因很简单它比Docker轻和Kubernetes的集成是原生级别的而且网上主流资料都在用遇到问题解决起来容易。2. 环境准备网络、内核与容器运行时2.1 节点规划与最低硬件我这次搭建的是一套两节点集群一个控制平面节点加一个工作节点。从纯学习角度来说单节点集群也能跑但那样你看不到调度、Pod分配到节点这些核心逻辑所以有条件的话还是至少两个节点比较舒服。从硬件角度看控制平面节点至少需要2核CPU和4GB内存工作节点最好也是2核起步。资源如果再低集群能跑起来但碰到稍大的容器镜像或并发操作时各种超时和OOM会频繁发生你很难判断是配置问题还是资源问题。我的建议是两台虚拟机每台分配2核4GB内存磁盘40GB以上操作系统选择任意的Debian系Linux发行版即可。节点规划时可以做个简单的表格节点角色主机名建议配置参考用途控制平面节点master-node2核4GB40GB磁盘运行kube-apiserver、kube-controller-manager、kube-scheduler、etcd工作节点worker-node2核4GB40GB磁盘运行kubelet、kube-proxy、实际工作负载这里要提前说一句kubeadm支持把控制平面和工作负载同时跑在一个节点上但前提是在kubeadm init时给控制平面节点去掉污点或者把单节点初始化配置写清楚。生产环境不要这么干学习环境为了省资源可以。2.2 操作系统基础配置在安装Kubernetes之前系统层面有几项配置必须提前做好否则后面会连环报错。第一项是关闭swap。Kubernetes的kubelet从设计上就不希望在启用swap的环境里工作因为kubelet需要通过内存请求和限制来管理Pod的内存使用如果swap存在容器可以逃逸到swap里内存配额就形同虚设。关闭swap使用swapoff -a同时编辑分区表文件把swap行注释掉保证重启后依然生效。第二项是配置主机名和hosts解析。集群内的节点之间通信非常依赖主机名解析最好不要写死IP。控制平面节点和执行节点都要修改hosts文件把各节点的IP和主机名对应关系写进去。很多初学的人忽略这一步结果控制平面初始化以后、工作节点加入时频繁出现token校验通过但节点间连接失败的问题。第三项是加载内核模块和系统参数。Kubernetes依赖iptables做流量转发还依赖br_netfilter模块来保证桥接流量能够被iptables处理。没有这个模块Pod之间的通信和Service的转发都会出问题。常见的做法是创建/etc/modules-load.d/k8s.conf写入br_netfilter和overlay再通过sysctl设置net.bridge.bridge-nf-call-iptables1和net.ipv4.ip_forward1。这些参数最好一开机就生效而不是每次手动执行。第四项是时间同步。Kubernetes组件之间基于证书通信证书校验依赖系统时间时间偏移过大会直接导致认证失败。多数云主机默认带时间同步服务但虚拟机自己装的话很容易忽略推荐安装chrony或systemd-timesyncd确保时钟一致。2.3 容器运行时为什么选containerd而不是Docker在Kubernetes早期版本里Docker是默认的容器运行时kubelet通过一个叫dockershim的中间层来对接Docker。但Docker本身的设计并不是为了服务Kubernetes这种编排系统的它多了一层守护进程还额外占用系统资源。从Kubernetes 1.24开始dockershim被正式移除Kubernetes统一通过CRIContainer Runtime Interface来对接各种符合标准的容器运行时。CRI你可以理解成一个标准化插座kubelet只负责按这个插座的协议发命令至于底层的容器怎么创建、怎么隔离都由运行时来管。containerd是目前最主流的CRI运行时因为它轻量、稳定而且Docker本身底层的容器创建能力也是通过containerd实现的。你在使用kubectl exec进入容器、查看日志、收集指标的时候实际上都是在和containerd打交道。所以现在装Kubernetes我不建议大家再去装Docker或者装完Docker再装containerd。直接用containerd就可以省去一层多余的依赖排查问题也更容易。2.4 安装并初始化containerdcontainerd的安装分两步安装二进制包和生成默认配置。不同Linux发行版的包管理器不大一样但思路相同。装好之后必须先执行containerd config default生成一份默认配置文件然后把它放到指定路径。如果你不初始化配置直接启动containerd它可能用内置的默认参数运行最后出现cgroup驱动和Pod镜像源不匹配的问题。拿到默认配置后有两个地方一定要改。第一个是systemd的cgroup驱动把SystemdCgroup设置为true。为什么要改因为Kubernetes的kubelet默认使用systemd作为cgroup驱动如果容器运行时的cgroup驱动不一致两个组件之间无法共同管理Pod的资源限制kubelet会把节点标记为NotReady并且报failed to run kubelet之类的错。第二个是sandbox_image这个参数决定了每个Pod启动时使用的pause镜像地址。默认地址在部分网络环境下拉不下来所以要改成你实际能访问的镜像源地址。这里多说一句很多人改完以后没有重启containerd导致配置一直没有生效排查了半天才发现是旧配置还在跑。每次改完配置文件一定记得systemctl restart containerd。3. kubeadm安装控制平面与工作节点3.1 安装kubeadm、kubelet、kubectl容器运行时准备好之后接下来安装Kubernetes的三个核心工具。kubeadm负责初始化集群和管理tokenkubelet是在每个节点上运行的核心代理组件负责跟控制平面通信并管理本节点的Podkubectl是给管理员使用的命令行客户端。这部分有两个注意事项。第一是安装源建议使用Kubernetes官方维护的软件源而不是从第三方源直接下载。官方源里能拿到与版本对应的完整依赖第三方源虽然方便但偶尔会出现包版本混乱、依赖缺失的问题。第二是版本锁定安装完成后要执行一个锁版本的命令防止后续系统在更新软件包时把kubelet或kubectl升级到不兼容的版本。生产环境或者长期学习环境都应养成这个习惯。安装完成以后可以通过kubeadm version和kubelet --version检查当前版本。这里我还想强调一点kubelet和kubeadm的版本要尽量保持一致如果kubelet版本比控制平面的版本落后太多注册节点时会出现版本不匹配的报错这个问题在排查阶段非常常见。3.2 初始化控制平面节点控制平面节点是集群的大脑所有组件的核心都在这里运行。kubeadm init命令就是用来完成初始化的它做的事情很多生成证书、创建etcd、启动kube-apiserver、生成kubeconfig文件、创建token等。你不需要手工做这些事但必须知道它生成的哪些文件在哪个位置因为它们之后会用于节点接入和日常管理。在初始化之前建议先想好两个参数--apiserver-advertise-address和--pod-network-cidr。第一个参数是控制平面API Server对外暴露的IP地址一般来说就是你控制平面节点的IP。如果你有多个网卡一定要手动指定否则kubeadm可能选了错误的IP工作节点永远连不上控制平面。第二个参数是Pod的网段它给集群内的每个Pod分配一个虚拟IP。这个网段不能和你的物理网络网段重叠否则数据包路由会混乱。我这次选择的是10.244.0.0/16因为后续的CNI插件我准备使用Flannel这个网段是Flannel的默认规划。如果你打算用Calico则可以选择10.20.0.0/16或者自定义的其他网段。关键是这个网段一旦定下来后面尽量不要改改起来非常麻烦。初始化命令执行成功后控制台会输出一段提示包括如何配置kubectl、如何把工作节点加入集群、以及join命令中需要的token和证书哈希。很多人第一次装的时候没有保存这段输出后面需要用token找回命令时非常被动。我的建议是把关键输出直接复制保存到本地文件里之后再慢慢研究。3.3 配置kubectl初始化完成后kubectl还不能立刻使用因为当前用户还没有集群的访问权限。kubeadm在初始化过程中生成了/etc/kubernetes/admin.conf这个文件包含管理员的所有权限信息你的任务就是把它放到用户目录下让kubectl能识别。具体流程很简单创建~/.kube目录把admin.conf复制成~/.kube/config然后设置好文件权限。接下来你可以执行kubectl get nodes这时候会看到控制平面节点处于NotReady状态这是正常的因为网络插件还没装。不要看到NotReady就觉得失败继续往下走。这里我还想多说一个点如果你用root用户操作把config文件放到root家目录没问题如果你用普通用户操作记得把用户名和组名对应修改。很多人用普通用户执行kubectl时一直提示无权限查到最后发现是config文件权限不对或者配置文件里的用户信息对不上。3.4 给集群装上网络插件网络插件是很多入门者的噩梦因为Pod网段规划、CNI插件选择、防火墙策略这些因素混在一起稍不留神就出现Pod一直处于ContainerCreating或者节点报路由错误的情况。从原理上讲Kubernetes集群里的每个Pod都有一个集群内唯一的IP但Pod可能部署在任何节点上不同节点上的Pod之间如何通信这本身是个复杂的网络工程问题。CNI插件就是负责解决这个问题的。Flannel和Calico是两种常见方案Flannel更轻、配置更简单适合学习和中小规模集群Calico功能更强、支持网络策略适合对安全隔离有要求的环境。我这次选择Flannel因为学习阶段它最直观部署方式就是一个YAML文件。执行kubectl apply -f kube-flannel.yml后Flannel会在集群里创建DaemonSet每个节点上都会运行一个Flannel Pod负责维护这个节点的Pod网络。安装完成后过一两分钟再执行kubectl get nodes节点状态应该会从NotReady变成Ready。如果长时间没有变化多半是Pod网段规划冲突、或者防火墙拦截了UDP/TCP端口。如果你选择了Calico安装过程类似但Calico对环境的要求更高比如需要开启IP_FORWARD、有时候需要调整mtu参数。还有一点Calico的默认数据面会把Pod流量封装成IPIP模式如果底层网络不允许这种封装则需要切换到BGP模式或者VXLAN模式。相比之下Flannel默认使用VXLAN或者host-gw模式改动更少适合初学者。3.5 工作节点加入集群控制平面Ready之后接下来把工作节点纳入集群。这一步本质上是让工作节点上的kubelet以一个可信身份去连接API Server并且接收API Server下发的Pod调度任务。kubeadm把整个认证过程封装成了kubeadm join命令你需要提供API Server地址、token、以及CA证书的哈希值。token的默认有效期是24小时如果你是在初始化当天就加入节点直接用初始化输出保存的join命令就可以。如果过了有效期或者命令丢了可以在控制平面节点上重新创建token用kubeadm token create --print-join-command生成新的join命令。证书哈希值也可以用openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2/dev/null | openssl dgst -sha256 -hex类似的方式自行校验但我个人建议优先用kubeadm生成好的完整命令省得手动拼装出错。在工作节点执行join命令时需要确认三件事第一是工作节点的kubelet和kubeadm已经安装完成并且版本一致第二是系统基础配置已经OK尤其是swap关闭、内核模块加载第三是工作节点和控制平面节点之间的网络是通的比如通过ping确认能够访问API Server的6443端口。加入完成后回到控制平面节点执行kubectl get nodes你会看到两个节点但工作节点大概率还是NotReady。不要慌观察一段时间Flannel的Pod会被调度到新节点上等网络插件在这个节点上也运行成功后节点状态就会变成Ready。3.6 验证集群是否真的可用节点显示Ready并不代表一切都完美有一种常见的假象是节点状态正常但实际调度Pod时才发现问题。所以节点全部就绪后建议立刻部署一个测试应用来验证整个链路。最省事的做法是创建一个Nginx的Deployment并给它设置一个明确的副本数比如3个。执行之后观察Pod的调度和运行状态正常情况下三个副本会被分散到不同的节点上。然后再创建一个Service把这些Pod暴露出来并尝试从节点上访问Service的ClusterIP和端口确认流量能到达后端的某个Pod。这个验证过程虽然简单但能发现很多问题。比如如果Pod一直处于ContainerCreating通常说明镜像拉不下来或者CNI网络配置异常通过kubectl describe pod能看到具体报错如果Service创建后访问不通可能是kube-proxy没运行或者iptables规则出了问题排查思路跟普通网络排查类似。做完这一步你的集群才算真正可用。4. 实测高频问题和排错锦囊4.1 镜像拉取失败是最常见的问题我在多次安装中发现新手遇到最多的一个问题就是镜像拉取失败。具体表现是kubectl get pods -A时一堆Pod处于ImagePullBackOff错误信息通常包含Failed to pull image或者registry.k8s.io连接超时。原因是Kubernetes的核心组件镜像托管在官方的镜像仓库里这个仓库在某些网络环境下的访问速度很不稳定。解决办法分两类一类是给containerd配置镜像加速或白名单代理另一类是直接修改sandbox_image和组件容器里的镜像地址替换成可访问的第三方镜像源。这里有一个细节替换镜像地址不能只改前面的一级域名还要保持镜像的版本标签不变。你可以先用kubeadm config images list查看kubeadm认为需要哪些镜像然后逐一替换成第三方源里的对应镜像版本。改完之后再执行kubeadm init时就不会因为镜像问题卡住了。4.2 token过期或join命令丢失怎么办token有效期问题很典型。如果你初始化完集群第二天才想加入工作节点立刻就会遇到token过期的问题。别着急重置token非常容易在控制平面节点上执行kubeadm token create --print-join-command它会直接输出一条可以直接复制到工作节点执行的join命令。有时候你会看到输出里的token有效时间还是24小时这也是正常的你可以通过--ttl0让token不过期不过生产环境不建议这么干。另外如果你需要手动拼接join命令还要知道CA证书的哈希值但在绝大多数情况下没必要手动拼直接让kubeadm帮你生成完整命令是最稳的。4.3 节点NotReady的排查路径节点从NotReady变成Ready是安装过程中最让人焦虑的阶段。不要一上来就重启服务而是按照下面这个顺序排查。第一步执行kubectl describe node 节点名看节点状态里有没有类似KubeletNotReady的提示以及底层原因。第二步登录对应节点执行journalctl -u kubelet -f看kubelet日志里是否持续报错。第三步检查容器运行时状态执行crictl ps看能否正常获取容器列表如果不能说明kubelet和containerd的cgroup驱动或者socket通信出了问题。我遇到最多的情况是cgroup驱动不一致kubelet使用systemdcontainerd使用cgroupfs两边的管理方式对不上节点就永远NotReady。解决办法是把containerd配置里的SystemdCgroup改成true重启containerd后再重启kubelet。这种问题在日志里不会直接说“cgroup不一致”而是报failed to run Kubelet或者misconfiguration所以新手很容易被绕进去。4.4 节点重启后集群组件没起来集群搭建完成以后你可能会把虚拟机重启一次来验证开机自启。如果重启后kubectl get nodes显示节点报错或者Pod起不来通常是两个原因一个是containerd和kubelet没有设置开机自启另一个是网络插件在启动顺序上有问题。第一个原因最容易解决执行systemctl enable containerd和systemctl enable kubelet就行。第二个原因稍微复杂一些因为kubelet本身不会在启动时自动创建Flannel或者Calico的Pod这些Pod是由API Server负责调度的而API Server又依赖etcd和kubelet。如果系统的服务启动顺序错乱可能会出现“先启动的kubelet等不到控制平面”的情况但大多数情况下只要底层服务都是开启状态等几分钟就能恢复正常。如果你发现重启后节点依然NotReady最直接的排查方式是挨个查看集群Pod的状态。比如用kubectl get pods -n kube-system查看核心组件再看kubectl logs -n kube-system pod名称。通常日志会告诉你到底是哪个组件起不来是数据库文件损坏、还是证书过期、还是端口被占用。这里特别提醒不要随便执行kubeadm reset它的作用是清空整个集群状态一旦执行所有配置和etcd数据都会丢失只能重新初始化。4.5 常见问题速查表我把上面提到的问题整理成速查表方便排查时对照现象可能原因排查命令解决办法Pod一直ImagePullBackOff镜像拉取失败kubelet相关日志替换镜像源或配置加速节点NotReadycgroup驱动不一致kubectl describe nodecontainerd启用SystemdCgroup网络插件无法启动Pod网段冲突或端口被占用kubectl logs网络Pod调整网段或防火墙规则join时报token过期token超出有效期kubeadm token create重新生成join命令kubectl无法访问集群kubeconfig配置错误kubectl config view检查admin.conf路径和权限节点重启后无法恢复containerd或kubelet未自启systemctl status开启enable自启动4.6 证书过期问题Kubernetes集群里的组件间通信大量依赖TLS证书kubeadm生成的证书有效期通常是一年。如果你长时间使用一套集群某一天突然发现apiserver的体积日志中出现证书过期的报错那就需要处理了。处理证书并不难执行kubeadm certs renew all可以统一续期所有证书然后重启相关组件让新证书生效。但有一点容易忽略如果你用的是kubeadm管理的kubeconfig文件可能也需要同步更新否则你通过kubectl访问集群时依然会报证书错误。学习环境可以这样做生产环境应该把证书有效期纳入监控提前续期。我个人的经验是不管你用哪种方式搭建Kubernetes都不要只把它当成一锤子买卖的安装过程。装完以后多花点时间观察日志、理解组件关系后面再遇到类似问题时你的排查速度会比直接搜报错快很多。如果时间允许建议再去玩一下存储卷、Ingress、RBAC这些功能它们会帮助你更全面地理解整个系统。