☰
CentOS7上基于kubeadm搭建K8S集群:从环境初始化到故障排查完整指南
2026/9/26 4:43:12 网站建设 项目流程

在HoRain云的ECS实例上,用CentOS7从零搭建一套可用的K8S集群,这件事我前后折腾了整整三天。从环境初始化到集群初始化,再到网络插件、节点加入、故障排查,每一步都藏着坑。这篇指南我按实际操作的顺序来写,把每个命令、每个参数为什么这么设都讲清楚,争取让你照着做能一次跑通。

先说背景:我这边用的是三台HoRain云服务器,一主两从,系统镜像CentOS 7.6,内核3.10。这套配置跑一个学习型K8S集群完全够用,生产负载的话建议节点规格往上提。下面所有命令都在干净系统上实测过,节点规格和系统版本一致的前提下,直接复制基本能跑通。

1. 搭建前的核心准备与架构选型思路

1.1 版本选型:为什么是CentOS7和1.23

集群搭建最怕的不是命令敲错,而是版本组合在你不知不觉时已经过时或冲突。这次选CentOS7,主要看中它的存量资料多,遇到问题基本都能搜到成熟解法。CentOS7默认内核是3.10,跑K8S没有硬性问题,但需要把内核模块和系统参数提前配好,后面小节会专门讲。

K8S版本这里我推荐1.23.x。原因很实在:1.24版本开始,容器运行时不再默认使用Docker,如果你习惯docker ps这套操作,又不打算额外装cri-dockerd,选1.23.x能省掉一堆配置纠结。想体验新特性的话1.24到1.28也能装,但得提前搞明白containerd和cri-dockerd这套体系,不然初始化到一半容易卡住。

组件版本严格一致是铁律。kubeadm、kubelet、kubectl三个二进制版本必须完全一样,混装会在集群初始化时直接报版本不匹配。我这边统一用的是1.23.17,三个组件全部锁到这个版本。

1.2 节点规划与资源评估

我这次用了三台云服务器,一主两从。直接给出一份可参考的规格表:

节点角色主机名建议配置系统盘内网IP示例
Masterk8s-master2核4G50G SSD10.0.0.10
Worker1k8s-node12核4G50G SSD10.0.0.11
Worker2k8s-node22核4G50G SSD10.0.0.12

内存是硬门槛。K8S系统组件加Calico这些基础Pod,1G内存根本跑不动,2G只是勉强学习用,想跑业务应用至少4G起步。磁盘方面系统盘建议给到50G以上,镜像和日志增长非常快,我实际遇到过磁盘写满导致节点异常的情况。

公网IP不是必须的,但如果节点都在内网,后续想从本地电脑访问Dashboard或应用,就得规划端口转发或跳板机。另外安全组至少要放行6443(kube-apiserver)、10250(kubelet)和NodePort端口段。我在云平台控制台忘记放行10250,导致Worker节点死活注册不上,这是后话。

1.3 环境初始化:每个节点都必须完成的五件事

三台节点打好镜像后,环境初始化要在每台机器上重复执行。第一步是设置主机名和hosts解析。主机名不能随便起,节点注册时用的就是这个名字,我统一用k8s-master、k8s-node1、k8s-node2。hosts文件里加上三台机器的内网IP和主机名对应关系,避免组件之间通过主机名解析失败。

第二步是关闭防火墙和SELinux。很多教程说生产环境不建议关防火墙,但对快速搭建来说,这一步能省掉大量排查白名单的时间。CentOS7上执行systemctl stop firewalld && systemctl disable firewalld,SELinux在 /etc/selinux/config 里改成disabled,然后重启。内网测试环境直接关掉没问题,生产环境建议后续用安全组做精细控制。

第三步是关闭swap。K8S默认不支持swap,开着swap节点初始化会直接报错。swapoff -a只是临时生效,要永久关闭得注释掉 /etc/fstab 里的swap行,不然重启后swap又回来了,这个坑我踩过。

第四步是加载内核模块和调整系统参数。K8S依赖ip_vs、overlay等内核模块,还要把桥接的iptables转发打开。标准配置是这样:

cat <<EOF > /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 vm.swappiness = 0 EOF sysctl --system modprobe br_netfilter

net.bridge.bridge-nf-call-iptables这个参数如果不开,Pod跨节点通信会出现诡异问题,例如有的服务通有的不通。ip_forward是转发用的,不开的话Pod流量出不去。K8S的Service和Pod网络依赖iptables做DNAT/SNAT,而iptables对网桥流量默认不生效,所以必须主动开启桥接转发开关。

第五件事是配置软件源和时钟同步。云服务器自带源有时候速度波动大,我习惯先备份原来的CentOS-Base.repo,再替换成国内镜像源。时钟同步依赖chrony,K8S组件对时间偏差很敏感,时间差超过几百毫秒,集群健康检查就会出现异常。

环境初始化完成后,建议reboot重启一次,确保内核参数和SELinux配置都生效。

2. Docker运行时安装与配置

2.1 Docker的安装和版本锁定

K8S本身不直接运行容器,它需要一套容器运行时。CentOS7时代最常用的就是Docker。安装前先装yum-utils,然后添加Docker官方yum源:

yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

国内环境访问这个官方源速度波动大,但一般还是能装上,不行就找发行版自带的Docker源。重点是版本,我推荐锁定在20.10.x,这个版本和K8S 1.23兼容性最好,也是社区验证最多的组合。

安装命令:

yum install -y docker-ce-20.10.21 docker-ce-cli-20.10.21 containerd.io

containerd.io要一起装,Docker的运行依赖它。安装完成后先别急着启动,下面还有关键配置要改。

2.2 daemon.json里最重要的两个配置

Docker装好后,必须修改/etc/docker/daemon.json。最关键的是cgroup驱动。K8S官方要求容器运行时的cgroup驱动必须和kubelet保持一致,否则kubelet会报failed to run Kubelet这类错误。CentOS7上典型的daemon.json是这样:

{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "storage-driver": "overlay2" }

exec-opts里的native.cgroupdriver=systemd就是关键。Docker默认的cgroup driver是cgroupfs,而kubelet初始化时默认也是cgroupfs,但实际部署中推荐都用systemd,这样在systemd管理的进程树中能保持一致,避免资源统计混乱。另外把日志文件限制到100M,防止容器日志把磁盘吃满,这一步强烈建议加上。

很多人忘记重启Docker或没检查,后面初始化集群时就莫名报错。改完配置执行systemctl daemon-reload && systemctl restart docker,然后通过docker info确认输出里的Cgroup Driver: systemd。

确认没问题后,把Docker设为开机自启:systemctl enable --now docker。到这一步运行时基础就打好了。

2.3 通过简单容器验证环境

配置无误后,用最简单的容器验证Docker能否跑起来:

docker run --rm hello-world

如果拉取不到这个测试镜像,可以换成已存在的镜像,或者用docker pull nginx试试。只要能拉取、能启动、能看到容器状态,说明Docker就绪。这一步也是在间接验证daemon.json配置有没有低级错误,JSON语法不对的话Docker服务根本起不来。

补充一点,Docker 20.10版本默认使用的containerd组件,后续排查镜像问题时可能会用到 crictl 命令。crictl需要单独安装,是K8S社区推荐的容器运行时命令行工具,提前装好后面排查会方便很多。

3. K8S核心组件安装与集群初始化

3.1 添加K8S软件源并锁定版本

接下来进入正题。三台机器上都要安装kubeadm、kubelet和kubectl。这三个组件通过YUM安装时,需要先配置K8S软件源:

cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=0 EOF yum clean all && yum makecache

用国内镜像源的原因很简单:默认的软件源地址在本地环境基本不可达,镜像源能让你少等很长时间。gpgcheck设为0,省去导入GPG密钥的步骤,测试环境完全够用。

安装时直接指定版本号,而不是装latest:

yum install -y kubelet-1.23.17 kubeadm-1.23.17 kubectl-1.23.17

为了防手滑升级,可以在/etc/yum.conf里加上exclude=kubelet kubeadm kubectl,或者每次yum update时加 --exclude 参数。不加的话,某次yum update就可能把集群组件版本搞乱,这是非常现实的教训。

装完后用kubeadm version确认,输出里的版本信息应该显示v1.23.17。

3.2 初始化前的镜像准备

kubeadm init时会拉取一系列镜像,包括apiserver、controller-manager、scheduler、etcd、coredns等。默认情况下,这些镜像来自k8s.gcr.io或registry.k8s.io,国内环境拉取很容易超时。解决办法是更换镜像仓库。

先查看当前版本需要哪些镜像:

kubeadm config images list --kubernetes-version v1.23.17

然后在init命令里用--image-repository参数指定阿里云容器镜像服务地址,具体是registry.aliyuncs.com/google_containers。这个镜像仓库覆盖了K8S核心组件的镜像,实测速度和可靠性都不错。

也可以提前把镜像拉取到本地:

kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers

这一步能全部成功的话,后续init基本不会卡在镜像阶段。我当时就是在init过程中卡了十几分钟,手动拉取才发现是网络问题。

3.3 用kubeadm init初始化Master节点

准备工作做完,开始真正初始化Master。这里给出实测可用的完整命令:

kubeadm init \ --apiserver-advertise-address=10.0.0.10 \ --image-repository registry.aliyuncs.com/google_containers \ --kubernetes-version v1.23.17 \ --pod-network-cidr=10.244.0.0/16

参数拆开讲一下。--apiserver-advertise-address是Master的内网IP,apiserver会监听这个地址对外提供服务。如果你写的是公网IP或写错网卡IP,后续节点加入会把地址搞乱。--pod-network-cidr是Pod网段,10.244.0.0/16是Flannel插件的默认网段,选Calico的话也可以用这个段,但必须与后面安装的CNI插件配置保持一致,这是无数人踩过的坑。

初始化成功后会输出一大段信息,包括控制面初始化完成的提示、配置kubectl的命令、以及一条kubeadm join命令。这段输出很重要,丢了就麻烦了,建议直接复制保存到本地文件。

然后配置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 get nodes,能看到Master节点,状态是NotReady。这是正常的,网络插件还没装。

3.4 Worker节点加入集群

Worker节点上装好kubeadm等组件后,执行初始化输出里的kubeadm join命令:

kubeadm join 10.0.0.10:6443 --token xxxxx.xxxxxxxxxxxx \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxx

如果token过期了,在Master上重新生成一条join命令:

kubeadm token create --print-join-command

token有效期默认24小时,隔天再添加节点大概率过期,重新生成即可。Worker节点加入后,kubectl get nodes能看到三台节点,但状态都是NotReady,直到CNI网络插件运行起来。

3.5 安装Calico网络插件

网络插件决定Pod之间如何通信。我选的是Calico,相比Flannel,Calico支持NetworkPolicy网络策略,性能也更好,生产环境用得更多。安装过程:

kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.25/manifests/calico.yaml

要注意,Calico的yaml中默认的IP池网段是192.168.0.0/16,如果前面init时用的Pod网段不是这个,需要修改calico.yaml里的CALICO_IPV4POOL_CIDR环境变量。最省事的方案是把init时的--pod-network-cidr也设成192.168.0.0/16,这样Calico默认配置就能直接用。为了和Flannel兼容而用10.244.0.0/16的话,那就得改yaml。无论哪种方式,保证两边CIDR一致是铁律。

apply完成后,watch kubectl get pods -n kube-system,等所有Pod变成Running。calico相关镜像比较大,首次拉取加上创建时间可能需要几分钟。全部Running后,kubectl get nodes,三台节点应该都变成Ready。

到这里,一个最基础的1主2从K8S集群就已经可用了。

4. 集群高可用与故障转移实践

4.1 单Master集群的边界与生产环境要求

上面搭起来的单Master集群,用来学习和验证完全没问题,但如果是生产场景,单Master存在单点故障风险。Master挂了,意味着apiserver、controller-manager、scheduler全部不可用,整个集群控制面瘫痪。Worker节点上已有容器还能继续跑,但没人调度、没人管理,Deployment异常也就没人修复。

所以生产环境至少三台Master,同时配合负载均衡器把请求分散到多个apiserver上。这是K8S高可用架构里最常见的一类方案,也是面试里经常被问到的"三台Master怎么保证高可用"。

4.2 三Master高可用的标准架构

生产级高可用方案通常分两层。第一层是控制面组件多副本:每台Master上都有独立的kube-apiserver、kube-controller-manager和kube-scheduler进程,controller-manager和scheduler通过选主机制保证同一时刻只有一个leader在干活。第二层是接入层的负载均衡:在Master前面架一台负载均衡器,常见的软件方案是HAProxy配合keepalived做VIP漂移,也可以用云平台提供的负载均衡服务,对外暴露一个虚拟IP或域名,所有kubeadm join和kubectl请求都指向这个虚拟地址。

etcd这里也要规划。堆叠模式下etcd跟着Master走,三台Master上各跑一个etcd实例,数据自动同步;条件允许的话,也可以把etcd单独部署在三台独立机器上,控制面和存储互不影响。堆叠模式部署简单,单机房场景够用;独立部署更适合跨机房容灾。

搭建多Master集群时,kubeadm init后还需要用kubeadm join --control-plane把其他Master加入控制面,这一步比Worker加入多一个etcd和证书处理,参数上要指定 --control-plane。整个流程对操作顺序要求很高,建议先在一台Master上init通过,再把另外两台Master以控制面方式加入,最后再添加Worker。

4.3 证书续签:一个容易被忽视的故障点

K8S控制面证书默认有效期是一年。很多集群跑了大半年后突然apiserver报证书过期、kubectl连不上,根源就是这个。kubeadm管理的集群,用下面命令在Master节点上手动续签所有证书:

kubeadm certs renew all

续签完成后,需要重启kube-apiserver、kube-controller-manager、kube-scheduler这些静态Pod组件。最简单的办法是重启节点,或者用systemctl restart kubelet让kubelet重新拉起静态Pod。

实际操作中,我更建议把这个做成自动任务。每台Master上写一个定时任务:

30 3 * * * /usr/bin/kubeadm certs renew all > /var/log/kube-cert-renew.log 2>&1 && systemctl restart kubelet

每天早上3点半自动续签一次。但要注意,这个任务只处理Master上的现有证书,如果Master节点数量有变化,或者续签后组件没有正常重启,集群可能出现健康检查异常。所以高可用环境里,证书续签一定要和监控报警联动,而不是只靠定时任务盲跑。理论上证书续签对集群影响很小,但实际执行时务必观察节点状态。

5. 常见问题排查与避坑实录

5.1 镜像拉取失败的几种处理方式

初始化或运行中拉取镜像失败,是最常见的问题。现象一般就是Pod一直ContainerCreating,事件提示Failed to pull image。

如果是因为默认镜像仓库网络不通,解决办法已经讲过:用--image-repository换国内镜像仓库。如果是运行中的某个应用镜像拉不到,先确认镜像tag是否存在、是否私有仓库需要认证。还有一种情况是磁盘空间不足导致镜像层写入失败,用docker system df或df -h一看便知。

K8S 1.23环境里还可以用crictl images直接查看节点上的容器镜像,比docker images的维度更贴近K8S视角。遇到部署缓慢,先用crictl pull测试能不能拉下来,能拉下来再排查yaml问题。

5.2 kubelet起不来:先看journalctl

很多节点加入失败,表面看是join命令报错,实际上问题出在kubelet。K8S的报错信息经常是笼统的error execution phase preflight,真正的细节藏在kubelet日志里。排查命令:

journalctl -xeu kubelet -f

常见原因有:swap没关彻底、cgroup驱动不一致、kubelet没配systemd驱动。swap的问题前面已经说了,这里重点说cgroup驱动不一致。如果daemon.json里写的是systemd,但kubelet启动时用的还是cgroupfs,两者就会打架。解决办法是在/etc/sysconfig/kubelet里加上:

KUBELET_EXTRA_ARGS="--cgroup-driver=systemd"

然后重启kubelet。

还有一类问题:kubelet无法访问apiserver。报错通常是connection refused或timeout。检查节点的hosts解析是否正确,以及安全组是否放行了6443端口。我在实际环境里遇到过节点注册时用主机名解析到了错误IP,导致join阶段校验失败,改完hosts马上就好。

5.3 CoreDNS一直CrashLoopBackOff

集群初始化完成后,kube-system里的CoreDNS状态是CrashLoopBackOff。多数情况下,这是在网络插件装好之前CoreDNS无法运行导致的,等Calico或Flannel Pod跑起来后会自动恢复。如果网络插件已经就绪还是Crash,看日志:

kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50

日志里如果出现no servers found这类DNS解析失败的错误,通常是Pod网段的DNS配置没正确写入 /etc/resolv.conf。还有一种少见但存在的场景:宿主机本身的resolv.conf有问题,或者CoreDNS Pod被调度到了某个网络不通的节点。排查时可以kubectl describe pod -n kube-system -l k8s-app=kube-dns看事件。

5.4 节点一直NotReady怎么查

节点NotReady,我按这个顺序排查:先kubectl describe node xxxx看Conditions里有没有提示;再kubectl get pods -n kube-system看网络插件Pod是否Running;然后上到节点机器看kubelet日志;最后检查网络模块和路由表。

Calico Pod反复重启是典型问题,原因经常是Pod网段和Calico配置不一致,或者kubelet的cgroup驱动配置有问题。还有一个容易被忽略的坑:节点的内网IP地址变了。云主机如果被迁移或网卡重建,内网IP变化会导致kubelet注册信息过期,节点状态变成NotReady或直接消失。这种情况要把旧的kubelet证书和kubeconfig删掉,重新join或重启kubelet。

5.5 常见问题速查表

整理一张速查表,遇到问题先对照一遍:

现象常见原因处理思路
kubeadm init卡在Pulling镜像仓库访问慢使用国内镜像仓库拉取
kubelet报cgroup driverDocker与kubelet驱动不一致统一为systemd并重启
节点NotReadyCNI未起或Pod网段不一致检查calico/flannel状态
CoreDNS CrashLoop网络插件未就绪或DNS配置错等待CNI或修复resolv.conf
join时token过期token 24小时有效kubeadm token create重新生成
6443连接超时安全组未放行或hosts错在云控制台放行6443/10250
Pod长时间Pending资源不足或污点未容忍describe pod查看调度事件
证书过期一年有效期kubeadm certs renew all

6. 集群验证与后续扩展思路

6.1 用Deployment快速验证集群

集群Ready只是开始,建议立刻部署一个简单的应用验证整个链路的可用性。我用Nginx做冒烟测试:

kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --port=80 --type=NodePort kubectl get svc

kubectl get svc会看到Service的NodePort端口,比如80:31000/TCP。这时候在任意Worker节点上执行curl localhost:31000,能返回Nginx欢迎页说明集群的网络链路、调度、kube-proxy转发都正常。

再验证一下副本调度和自愈能力:

kubectl scale deployment nginx --replicas=3 kubectl get pods -o wide

把其中一个Pod删掉,kubectl delete pod nginx-xxxxx,几秒钟后控制器会重新拉起一个,这就是K8S的自愈能力。这一步验证通过,集群基本就达到可用标准了。

6.2 生产化方向:监控、日志、存储与Ingress

能用的K8S集群和能上生产的K8S平台,差距主要在配套组件上。监控优先做,用Prometheus Operator加Grafana是最主流的方案,能覆盖Node、Pod、容器的指标采集。第一次做建议先装kube-prometheus整体方案,自带告警规则,比从零拼装省太多时间。

日志方面,轻量选择是Loki,重量级选择是EFK。Loki资源占用小很多,更适合小集群;EFK在日志检索和大规模场景下更成熟。存储这块,如果要在集群里跑数据库或有状态应用,建议上Rook-Ceph。Rook可以在K8S集群内部直接编排Ceph存储集群,部署流程稍长,但跑起来后StatefulSet的持久化存储问题就彻底解决了。

最后是Ingress Controller。默认的Service类型是ClusterIP和NodePort,NodePort端口一般很大,不适合直接对外暴露。装一个nginx-ingress-controller后,可以用域名和路径做路由,再配合cert-manager自动管理证书,对外发布服务方便得多。

6.3 维护集群的常用命令清单

日常维护高频命令,建议收藏:

kubectl get nodes # 查看节点状态 kubectl get pods -A # 查看所有namespace的Pod kubectl logs -f pod_name -n namespace # 跟踪日志 kubectl describe pod pod_name # 定位问题核心 kubectl get svc -A # 查看Service kubectl top nodes # 查看节点资源使用率

这一套组合下来,日常巡检和问题定位基本够用。metrics-server没有预装,可以应用官方yaml安装,装完才能看到kubectl top输出。

最后说一下这轮搭建下来最深的几个体会。第一,版本组合一旦定下来就不要轻易改,kubeadm、kubelet、kubectl、Docker、Calico这些版本是相互约束的,升级任何一个都要全盘评估。第二,网络插件一定要在init之前想好是Flannel还是Calico,再去定Pod网段,省得后面改来改去。第三,所有初始化输出都要存好,尤其是join命令和token,丢失的代价比重新安装还大。我就是因为没保存好token,第二天重新生成了一次,虽然流程很快,但生产环境里这就是一次事故隐患。提前把这些点处理好,整个流程会顺畅很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询