Kubernetes部署这事儿,网上教程一抓一大把,但大多数要么只讲单机体验,要么直接甩一堆命令让人照着敲,完全不解释为什么。我第一次在生产环境搭多节点集群的时候,光是搞明白网络插件选型、镜像源切换、cgroup驱动匹配这几个问题就折腾了一整周。这篇是Kubernetes基础系列的第四篇,前几篇已经把Pod、控制器、Service这些核心概念讲清楚了,这篇就专注一件事:把一个真正能用的多节点Kubernetes集群,从零到一完整部署起来,并且讲明白每一步背后的原理。整套流程走完,你可以得到一个生产可用的集群骨架,后续无论是跑业务应用还是继续研究集群运维,都有了一个靠谱的基础。
先说清楚这篇指南适合谁:你已经理解Pod、Deployment、Service这些基础概念,但还没亲手搭过集群;或者你之前只在minikube里玩过,想搞清楚真正的多节点集群是怎么组织的;再或者你已经照着某篇教程搭到一半卡住了,想搞清楚排查思路。如果你是完全没有Kubernetes概念的小白,建议先回去看基础系列前三篇,把这篇文章当成实操手册来用会顺畅很多。
1. 动手前先做三个决定:搭建方式、版本策略和节点规划
1.1 为什么我推荐用kubeadm而不是minikube或二进制部署
搭建Kubernetes集群的主流方式有三条路:minikube、kubeadm、纯手工二进制部署。很多人一上来就纠结选哪个,其实这三者的定位和适用场景差异非常大,选错了后面会很难受。
minikube解决的是“我在笔记本上体验Kubernetes”的需求,它本质上是把整个集群塞进一个虚拟机或者容器里,适合学习API用法、验证YAML清单,但不适合作为多节点集群的搭建方案。它的网络、存储、节点管理都是为单机环境设计的,你没法通过它理解真实集群中节点间的交互逻辑。
kubeadm是Kubernetes官方提供的集群引导工具,它的定位非常明确:把控制平面的核心组件以静态Pod的方式跑起来,把节点加入、令牌管理、证书分发这些繁琐的引导工作自动化。生产环境里绝大多数团队都是基于kubeadm往上叠自己的运维体系,这也是我推荐它作为学习多节点集群搭建路径的原因——它能让你看清楚集群引导过程的全貌,又不需要像纯手工部署那样手动维护每一个组件的启动参数。
纯手工二进制部署确实能让你对每个组件理解得最深,但那更像是一个“研究项目”而非“部署方案”。你需要手动下载所有二进制文件、自己写systemd unit文件、手动生成证书、手动配置etcd集群,一套流程走下来费时费力,而且很容易因为某个参数写错导致控制平面起不来。除非你是在深入研究Kubernetes内部原理,否则不必在生产环境选择这条路径。
1.2 版本选型不是越新越好
很多人在搭集群时喜欢装最新版本,我早期也踩过这个坑。Kubernetes的版本策略是每个小版本支持期大约14个月,大版本之间的升级需要逐级进行,不能跨大版本跳跃。你在部署时选的版本,决定了后续一年内的升级路径和社区生态兼容性。
我建议选择当前处于稳定期中间段的小版本。比如Kubernetes v1.28.x系列,既不是刚发布的“新鲜版本”,也不是快进入EOL的“老版本”,社区反馈已经充分,周边组件(CNI插件、Ingress Controller、监控组件)都完成了兼容性验证。用这类版本搭建集群,遇到未知问题的概率会小很多。
配套的容器运行时我推荐containerd。Docker作为运行时虽然仍然可用,但从Kubernetes 1.24版本之后就已经移除了对Docker的默认支持,需要额外安装适配组件。containerd是CNCF旗下的容器运行时,和Kubernetes的兼容性最好,也是当前生产环境的事实标准。
1.3 节点规划需要提前画好的草图
部署三节点集群是最常见的起步配置,也是理解Kubernetes集群架构的最小规模:一个控制平面节点(master)加两个工作节点(worker)。
控制平面节点运行kube-apiserver、kube-controller-manager、kube-scheduler、etcd这四个核心组件,以及容器运行时和kubelet。工作节点运行容器运行时、kubelet、kube-proxy,以及真正承载业务Pod。
在硬件配置上,控制平面节点的CPU和内存需求其实不低,因为kube-apiserver是所有请求的总入口,etcd要持久化集群状态。单节点的控制平面在测试环境或中小规模业务下够用,但生产环境建议至少三个控制平面节点组成高可用。这篇文章先把单控制平面节点的部署流程讲透,后续再单独展开高可用控制平面的搭建方案。
网络规划要提前想清楚三个网段:集群中Pod的网段(Pod CIDR)、Service的网段(Service CIDR)、宿主机自身的物理网络网段。这三个网段必须互相不重叠。我常用的一套规划是这样的:物理网段192.168.x.0/24、Pod网段10.244.0.0/16、Service网段10.96.0.0/12。Pod网段分配给CNI插件管理,Service网段由kube-apiserver启动参数里的service-cluster-ip-range指定,这两个网段的选择会在后面初始化集群时直接写进配置文件里。
2. 基础环境准备:系统初始化和容器运行时配置的细节
2.1 所有节点都要执行的系统初始化操作
拿到三台干净的服务器之后,别急着装Kubernetes,先把系统基础环境统一处理好。我这里的操作基于Ubuntu 22.04 LTS,但这套思路基本适用于所有systemd系Linux发行版。
第一步是关闭swap。Kubernetes的kubelet在默认配置下要求swap必须关闭,因为Kubernetes的Pod内存配额机制依赖于宿主机内存的直接管理,swap的存在会让内存回收行为变得不可控,Pod内存超限后kubelet无法准确判断是否应该触发OOM Kill。执行swapoff -a只能临时关闭,还需要编辑/etc/fstab把swap那一行注释掉,否则重启后swap又回来了。
第二步是加载必要的内核模块并调整内核参数。Kubernetes集群的网络组件依赖br_netfilter模块来处理桥接流量的iptables规则,overlay文件系统也需要对应模块支持。内核参数方面,最关键的是net.ipv4.ip_forward=1,它决定了宿主机能否转发跨节点流量。还需要把net.bridge.bridge-nf-call-iptables和net.bridge.bridge-nf-call-ip6tables设为1,确保经过Linux网桥的流量也能被iptables规则处理。
第三步是同步hosts解析。我这里说的不只是三台机器的/etc/hosts,因为Kubernetes集群节点的互访既可以通过IP也可以使用主机名。为了让后续join操作和kubectl访问更加稳定,我习惯在每台节点的/etc/hosts里写入三台机器的IP与主机名对应关系。这里的坑在于,如果你没有统一配置主机名解析,kubelet和kube-apiserver之间的很多操作日志会变得难以排查,因为日志里显示的都是IP或随机生成的主机名。
2.2 containerd安装与配置:cgroup驱动是第一个大坑
安装containerd的方式非主流其实是发行版自带的包管理工具,Ubuntu直接用apt install containerd就能装上。但装完之后不能直接用,必须生成默认配置并改两个关键参数。
生成默认配置的命令是containerd config default > /etc/containerd/config.toml。这个命令会把containerd所有可配置项的默认值输出到配置文件里,之后我们只需要在这个文件基础上做修改。
第一个必须改的是SystemdCgroup参数。containerd支持两种cgroup驱动:cgroupfs和systemd。Kubernetes的kubelet默认使用systemd作为cgroup驱动,如果containerd这边还使用cgroupfs,两者的资源管理逻辑就会不一致,轻则Pod的资源配额不生效,重则kubelet启动直接失败。在config.toml里找到[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]这一段,把SystemdCgroup设为true即可。
第二个是sandbox镜像地址。Kubernetes的每个Pod都有一个基础沙箱容器Pause容器,这个镜像在初始化集群时会被自动拉取。但默认配置指向的是国外镜像仓库,在国内网络环境下非常容易拉取失败。需要在config.toml里把sandbox_image改成国内可访问的镜像地址。
改完配置后执行systemctl restart containerd重启服务,然后执行systemctl is-active containerd确认运行状态。这一步如果出错,后面的kubeadm init基本没有成功的可能,所以值得多花几分钟检查清楚。
2.3 安装kubeadm、kubelet和kubectl
三个核心命令行工具的安装版本必须一致,这一点非常重要。kubeadm负责集群引导,kubelet是每个节点上都会运行的代理进程,kubectl是集群客户端。如果它们的版本不一致,集群运行时很可能会出现意想不到的兼容问题。
安装时换用国内镜像源能避免很多痛苦。Kubernetes官方源放在Google的服务器上,国内访问很慢甚至超时。我这边习惯配置阿里云镜像源,速度和稳定性都不错。安装完成后执行kubeadm version、kubelet --version、kubectl version --client三个命令确认版本一致。
还有一个细节需要处理:给kubelet设置cgroup驱动为systemd。最简单的方式是在/etc/sysconfig/kubelet(如果是CentOS)或/etc/default/kubelet(Ubuntu)里写入KUBELET_EXTRA_ARGS="--cgroup-driver=systemd"。这个参数的作用是让kubelet的cgroup驱动和containerd保持一致,避免后面初始化集群时kubelet反复报错。另外还需要执行systemctl enable kubelet提前把kubelet注册成开机自启服务,虽然此时kubelet会因为缺少集群配置而反复重启失败,但这是正常现象,kubeadm init成功之后它就会稳定下来。
3. 控制平面部署:kubeadm init的完整流程与核心参数
3.1 提前写好kubeadm配置文件而不是用命令行参数
kubeadm init支持直接传参和配置文件两种方式。我强烈建议使用配置文件,因为命令行参数只能覆盖一小部分配置项,而且后续如果要升级、重建或迁移集群,配置文件可以直接复用。
配置文件通常叫kubeadm-config.yaml,一个最基本的控制平面初始化配置长这样:
apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.10 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.2 controlPlaneEndpoint: 192.168.1.10:6443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12InitConfiguration这里的advertiseAddress写的是控制平面节点自身的内网IP,kube-apiserver会把这个IP广播给集群其他组件。nodeRegistration.criSocket告诉kubeadm使用哪个容器运行时,必须和上一步配置的containerd一致。
ClusterConfiguration里的kubernetesVersion用于指定要安装的版本,controlPlaneEndpoint是控制平面的访问入口。单控制平面节点时它和advertiseAddress相同,但在高可用场景下这个地址会是负载均衡器的IP。networking是整份配置里最关键的段落,podSubnet要和你选择的CNI插件要求的网段一致,serviceSubnet则决定了Service虚拟IP的分配范围。
3.2 镜像预拉取与init执行过程
执行kubeadm init之前,建议先执行一次kubeadm config images pull,这个命令会把控制平面所需的全部镜像提前拉取到本地。在镜像拉取阶段就能提前发现网络问题,不用等到init跑一半卡住才知道拉不下来的哪个镜像。
如果拉取超时,可以先用kubeadm config images list查看需要的镜像列表,然后把每一个镜像重新打上可访问的仓库标签。
一切就绪后执行初始化命令:
kubeadm init --config kubeadm-config.yaml --upload-certs--upload-certs这个参数在高可用场景下用于把控制平面证书上传到集群,后续加入的新控制平面节点可以直接从集群拉取证书,单控制平面节点写上也没问题。
init过程一般会持续两三分钟,核心经历这几个阶段:检查和准备环境、生成证书并分发到各组件、生成Kubernetes控制平面组件的静态Pod清单、等待apiserver健康检查通过、生成kubeconfig认证文件并保存到当前用户目录。最终输出的内容里有一段kubeadm join示例命令,这段命令要保存好,后面添加工作节点时会用到。
初始化的最后一步是配置kubeconfig,否则kubectl无法访问集群。操作其实就一行命令:
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config如果没有执行这一步,执行任何kubectl命令时会报“connection refused”或“no configuration has been provided”之类的错误,不要误以为集群没起来。
3.3 kube-apiserver不是普通进程:静态Pod与自愈机制
初始化完成后,如果你在控制平面节点上执行docker ps或crictl ps,会发现kube-apiserver、kube-controller-manager、kube-scheduler等组件都在以容器方式运行。这些容器并不是通过Deployment或ReplicaSet管理,而是由kubelet直接根据/etc/kubernetes/manifests目录下的静态Pod清单启动的。
理解静态Pod机制对后续运维很重要:控制平面组件的生命周期由kubelet全权接管,如果apiserver容器崩溃了,kubelet会自动根据清单文件把它重新拉起来。这意味着你在排查控制平面问题时,不需要也不应该手动去启动或停止这些组件,只需要修改/etc/kubernetes/manifests目录下的YAML文件,kubelet会检测到变更并自动重启对应组件。
etcd也同样以静态Pod方式运行,它的数据目录默认在/var/lib/etcd。后续做备份时,需要到etcd容器里执行etcdctl snapshot save,或者直接备份这个数据目录,二者选其一即可。
4. 工作节点接入与CNI网络插件:让集群真正成为集群
4.1 使用join令牌添加工作节点
控制平面初始化完成后,它像是一个孤零零的大脑,没有任何工作节点可以调度Pod。添加工作节点的核心是使用kubeadm init输出的kubeadm join命令,命令大致长这样:
kubeadm join 192.168.1.10:6443 --token xxxxxx.yyyyyyyy --discovery-token-ca-cert-hash sha256:zzzzzzzz如果当时忘了保存,可以在控制平面节点上用kubeadm token create --print-join-command重新生成一条完整的join命令。
在工作节点上执行join命令后,它会经历这么几个流程:通过token向apiserver发起加入请求、拿到集群CA证书进行验证、安装必要组件并注册为Ready状态。整个过程一般不到一分钟。
这里有一个很容易踩的坑:工作节点上的容器运行时配置必须和控制平面节点保持一致,尤其是cgroup驱动和sandbox镜像地址。如果工作节点的containerd没改SystemdCgroup,或者镜像仓库地址不对,join大概率会卡在“failed to pull pause image”或者kubelet反复报错无法注册。我在帮别人排查集群加入失败问题时,十有八九是工作节点的基础环境没做好。
加入成功后,回到控制平面节点执行kubectl get nodes,如果新节点状态还是NotReady,不着急,原因通常是网络插件还没部署,这正是下一步要做的事。
4.2 CNI网络插件选型:Calico还是Flannel
CNI插件负责实现Pod之间的跨节点通信。Kubernetes本身就规定了Pod之间必须能直接通信,但具体的网络方案完全交给第三方CNI插件来实现。常见的选型有Flannel、Calico、Cilium等,选型逻辑其实很清晰。
Flannel的优点是简单轻量,它通过VXLAN隧道技术实现跨节点通信,配置和维护成本极低,几十个节点以内的集群用它都很舒服。缺点也很明显:它没有内置网络策略能力,如果你需要在集群里做精细的流量隔离,Flannel提供不了什么帮助。
Calico基于BGP协议实现纯三层路由,性能比VXLAN隧道模式好,同时内置了完整的NetworkPolicy能力,可以精细化控制Pod之间的访问关系。如果你做的系统涉及多租户隔离、微服务间按需开放端口、生产环境安全合规要求高,Calico基本是不二之选。代价是它的组件比Flannel多、排错时涉及的概念也更多,学习曲线稍微陡一点。
如果集群规模不大、暂时不需要网络策略、目标是尽快跑起来,Flannel值得优先选择。如果你准备以后跑生产业务、需要网络隔离能力,就直接上Calico。两种插件的Pod网段要求不同,Flannel通常占用10.244.0.0/16,Calico则没有强绑定但需要写入IP池配置。这也是为什么我在kubeadm配置文件中把podSubnet设置成10.244.0.0/16的原因——方便切换。
4.3 部署Calico的完整流程
以Calico为例,部署流程相当简洁。从官方仓库下载manifest文件后直接应用即可:
curl -L https://raw.githubusercontent.com/projectcalico/calico/v3.27/manifests/calico.yaml -o calico.yaml kubectl apply -f calico.yamlCalico的组件会以DaemonSet的方式运行在每个节点上,DaemonSet能确保集群扩容后新节点自动运行网络组件。
部署完成后,回到控制平面节点执行kubectl get pods -n kube-system,会看到calico-operator和一组calico-node Pod陆续进入Running状态。当所有Calico Pod都稳定运行后,再执行kubectl get nodes,所有节点都应该变成Ready状态。
这里有一个反复出现的经验:如果节点长时间处于NotReady状态,第一件事不是重启节点,而是执行kubectl describe node <节点名>查看节点状态里的Condition部分,然后从控制平面节点去检查工作节点上systemctl status kubelet的运行日志。绝大多数网络插件部署失败或者节点不可达的问题都能从这两个日志里找到线索。
5. 集群部署完成后的验证清单与常用运维命令
5.1 用nginx验证集群端到端链路是否打通
节点全部Ready并不代表集群真正可用,因为Pod的调度、Service的负载均衡、kube-proxy的规则转发这些核心链路有没有问题,只有实际跑一个应用才能验证出来。我验证集群最常用的方式就是部署一个简单的nginx Deployment。
先创建Deployment,把Pod副本数设为2,确保两个Pod能调度到不同节点上;然后创建Service,把端口暴露到集群内部网络;最后用kubectl get endpoints确认Service后端关联到两个Pod的IP。
接下来验证跨节点通信。进入其中一个Pod里执行curl <另一个Pod的IP>,如果能通,说明Pod网络层面的跨节点通信没问题。再在宿主机上执行curl <Service的ClusterIP>,如果集群内部服务发现与负载均衡正常工作,这里应该能返回nginx的默认页面。
最后一个验证点是从集群外部访问应用。这个入口通常是Ingress Controller或NodePort,但在基础集群搭建阶段,可以先用临时命令把本地端口转发到Service端口来测试外网通路:
kubectl port-forward svc/nginx-service 8080:80 --address 0.0.0.0然后在任一能访问到这台控制平面节点IP的设备上用浏览器访问http://控制平面IP:8080,只要能看到nginx欢迎页,整个集群的核心数据链路就算完全跑通了。这个验证流程同时覆盖了容器运行时、kubelet调度、Service负载均衡、kube-proxy转发、CNI网络通信,整整走完了一遍集群的数据面,比单纯看节点状态可靠得多。
5.2 日常运维需要掌握的十个高频kubectl命令
集群搭好只是一个开始,日常运维才是长久的事。这里分享我几乎每天都会用到的一组kubectl高频命令组合,按需要对照着查就行。
# 查看节点状态和详细信息 kubectl get nodes -o wide kubectl describe node <节点名> # 查看所有namespace的Pod及所在节点 kubectl get pods -A -o wide # 查看事件流(排查问题的第一入口) kubectl get events -A --sort-by=.lastTimestamp # 查看某个Pod的实时日志 kubectl logs -f <Pod名> -n <namespace> # 进入Pod内部排查 kubectl exec -it <Pod名> -n <namespace> -- /bin/sh # 查看Service和Endpoints的对应关系 kubectl get svc -A kubectl get endpoints -A # 查看集群组件的健康状态 kubectl get componentstatuses排查集群问题的标准顺序我一般是这样:先看节点是否Ready,再看Pod是否Running,再看Service Endpoints是否正常,最后看事件流里有没有异常记录。如果事件流里出现FailedScheduling,说明资源不足或污点约束导致Pod无法调度;出现FailedPullImage,说明镜像地址错误或网络无法访问镜像仓库;出现Unhealthy,说明探针配置有问题或者容器内服务没有按预期启动。按照这个顺序一步步过滤,绝大多数问题都能在几分钟内定位。
6. 踩过的一些坑,以及对应的排查思路
6.1 cgroup驱动不一致:apiserver能起来但kubelet反复挂掉的经典现场
印象很深的一次排错经历是:控制平面节点初始化完成后,etcd和apiserver这些静态Pod都正常运行了,但kubelet这个二进制进程一直处于CrashLoopBackOff状态,日志里反复出现“failed to ensure that the cgroup is a systemd cgroup”之类的报错。排查到最后确定就是cgroup驱动不匹配:kubelet用了systemd驱动,containerd还在用cgroupfs。解法就是回到第2.2节所说的,把containerd的SystemdCgroup改为true并重启containerd。
这个问题值得多说一句排查思路而不是直接给答案:遇到kubelet起不来时,先用journalctl -u kubelet -f看实时日志,而不是反复重启kubelet。这种“看日志找线索”的排错方式,比盲目重启要高效得多,也是遇到绝大多数集群故障时的第一反应。
6.2 Docker镜像拉取不了:换镜像源是必修课
国内环境下拉取Kubernetes相关镜像超时基本是常态。我踩过一次比较典型的坑是,用kubeadm config images pull时卡在pause镜像下载上,一直报“failed to pull pause image”的错误。这个问题处理方式其实是修改位于/etc/containerd/config.toml里的sandbox_image配置项,将其改为国内可访问的镜像地址。改完并重启containerd后,重新执行拉取镜像命令就正常了。
更通用的处理思路是直接修改kubeadm的镜像仓库地址。在kubeadm init的命令行加上--image-repository参数,或者kubeadm init --config kubeadm-config.yaml配置文件里加imageRepository配置项,就能把默认镜像源整体替换成国内镜像仓库地址,这个方法在初始化之前提前设置最省事。
6.3 Pod一直Pending的排查链路
Pod一直处于Pending状态通常意味着调度没有完成,最直接的原因是节点资源不足或者节点上有污点导致Pod没有容忍度。排查链路是这样的:
先用kubectl describe pod <Pod名>查看Events部分,如果出现FailedScheduling事件,后面通常会跟着具体原因,例如“Insufficient cpu”“Insufficient memory”或者“0/3 nodes are available”这类描述。如果是资源不足,处理方式要么扩容节点,要么调整Pod的requests配置。如果是污点导致无法调度,则需要检查节点上的taints配置,确认业务是否应该容忍这些污点。
需要补充说明的是,不要一看到Pending就去查代码改配置,先把describe输出看完是最省事的做法,事件里往往已经把原因写得足够清晰。
6.4 节点重启后kubelet无法自动恢复
物理节点重启后,kubelet需要重新注册到集群。正常情况下,因为kubelet已经注册为systemd服务并设置了开机自启,节点起来后它会自动向apiserver重新注册并恢复Ready状态。常见的问题场景是containerd依赖了自定义配置但启动顺序不对,或者镜像仓库地址失效导致沙箱镜像拉取失败,kubelet就会反复尝试重启。
遇到节点重启后NotReady的情况,依然是先查看kubelet状态和日志,确认是网络问题、镜像问题还是证书过期问题。结合之前配置cgroup时设置的KUBELET_EXTRA_ARGS内容逐项排查。这类问题基本都是积年累月的配置问题叠加,处理完一次之后,最好把示例节点的完整初始化脚本固化下来,后续节点重建时直接跑脚本,能极大减少重复踩坑的概率。
7. 最后再分享一个小技巧:把标准化脚本固化下来
搭集群这件事,和写代码有个相通之处:一次性脚本写完就丢了是最亏的。把整个初始化流程固化成一套可重复执行的脚本,价值很大。
我习惯把环境准备部分的全部操作整理到一个prepare-node.sh脚本中,包含模块加载、内核参数配置、swap关闭、hosts写入、containerd安装配置、kubeadm相关工具安装这几大步。每台新节点加入集群之前,先跑一遍脚本再执行join命令,基本都能顺利注册成功。
控制平面节点的初始化则对应一个init-control-plane.sh,把kubeadm配置文件、镜像预拉取和init命令串联起来。这套脚本在集群出问题需要重建恢复的时候价值尤为明显——重新初始化一个干净的控制平面,成本从一小时压缩到几分钟。
如果你走的也是这条基于kubeadm的部署路线,我的具体版本建议是参考上一轮验证过的稳定版本组合,比如kubeadm 1.28.x配Calico 3.27、containerd 1.7.x。这套组合在多个模拟项目中都验证过稳定性,作为学习或中小规模的业务起步配置,是一个省心且足够稳妥的起点。