CentOS 7 上使用 kubeadm 部署 Kubernetes 1.30 集群完整实战
2026/9/9 13:17:22 网站建设 项目流程

先说一下这次的部署背景。手上的测试环境是三台 CentOS 7 虚拟机,配置不算高,目的是搭一套能用来跑内部服务和做实验的 Kubernetes 1.30 集群。既然只是测试用途,不追求生产级高可用,那我直接用 kubeadm 来搞,一主两从的架构,简单直接,应付日常开发、验证各种 Deployment 和 CronJob 完全够用。

这篇文章就是完整记录我这次从零到一的部署过程,包含所有踩过的坑和排查思路。如果你正准备在一组 CentOS 7 机器上快速建一套 K8s 1.30 环境,或者刚学 Kubernetes 想找个能落地的实操案例,这篇内容应该能让你少走很多弯路。整个操作流程我尽量按“先想清楚,再动手”的顺序来写,每一步都知道为什么这么做,而不是机械地复制命令。

1. 整体部署思路与架构设计

1.1 为什么选 kubeadm,而不是二进制或 minikube

Kubernetes 的部署方式大概有三条主流路线:二进制部署、kubeadm 部署、以及各种轻量级方案(minikube、kind 这类的)。放在几年前,很多运维老哥倾向于二进制部署,因为能看到每个组件的真实启动参数,排错时更“通透”,但付出的代价也很直接:证书签发、kubeconfig 生成、静态 Pod 编排、etcd 集群初始化全部要手工处理,一套集群搭下来少说两小时,中间任何一个组件版本对不上就很容易心态崩。

kubeadm 的好处恰恰是把这些脏活累活自动化了。它会把 API Server、Controller Manager、Scheduler、etcd 这些核心组件以静态 Pod 的形式跑起来,证书、kubeconfig、ServiceAccount 这些基础设施都由工具自动生成。在一主两从这个规模下,kubeadm 是最平衡的选择——比二进制省心,又比 minikube 更接近生产集群的组织方式,后续如果想把测试环境升级成带负载均衡的高可用集群,迁移路径也清晰。

1.2 节点规划与网络规划

这次我规划的拓扑是三台节点:

角色主机名IP 地址(示例)最低配置建议
Master 控制平面master01192.168.1.102C4G,建议硬盘 50G 以上
Worker 工作节点worker01192.168.1.112C4G
Worker 工作节点worker02192.168.1.122C4G

IP 地址我这里用 192.168.1.x 网段做示例,实际你根据自己的内网网段调整就行。有一点要提醒,VM 虚拟机的内存绝对不能低于 2G,否则 etcd 和 API Server 很容易因为资源不足而反复重启,表现就是 kubeadm init 老是在 wait for control plane 阶段卡住,排查半天最后发现是内存不够,很尴尬。

网络层面,因为是一主两从,不涉及负载均衡器,所以我让 Master 节点的 IP 直接作为集群的控制平面端点。Pod 网段我选了 10.244.0.0/16,这是 Flannel 网络插件默认使用的网段。如果你准备用 Calico 或者其他插件,这个 Pod 网段就需要对应调整,后面 3.4 小节我会专门说明。

1.3 版本兼容性判断

在开始动手之前,先讲清楚版本选择。CentOS 7 默认内核是 3.10.x,而 Kubernetes 1.30 对内核的最低要求恰好是 3.10 以上,所以默认内核是可以跑起来的。但如果你对性能和稳定性要求更高,建议把内核升级到 5.x 版本的 elrepo 内核,能明显改善网络和存储模块的表现。不过为了降低操作复杂度,这篇文章就是基于 CentOS 7 默认内核完成的,实测下来没问题。

容器运行时方面,Kubernetes 1.30 已经不推荐 docker 作为运行时,我选择 containerd,版本 1.7 以上。如果你机器的 yum 源里 containerd 版本比较旧,需要额外处理,下文会写清楚。

2. 环境准备:三台节点统一操作

2.1 系统基础配置:主机名、hosts、防火墙、SELinux、swap

这部分是标准动作,三台机器都要做。先设置主机名,然后统一把 hosts 解析写进去,不然节点之间通过主机名通信时会解析不了,尤其是 kubelet 上报节点状态时会出问题。

# 在每台节点上分别执行(以 master01 为例) hostnamectl set-hostname master01 # 编辑 /etc/hosts,追加以下内容 192.168.1.10 master01 192.168.1.11 worker01 192.168.1.12 worker02

然后是关闭防火墙、SELinux 和 swap,这是 kubeadm 最挑剔的三个地方。

systemctl stop firewalld && systemctl disable firewalld sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config setenforce 0 # 关闭 swap,并且注释掉 /etc/fstab 里 swap 的行,否则重启后会重新挂载 swapoff -a sed -i '/ swap / s/^/#/' /etc/fstab

为什么要关这三样?Kubernetes 本身依赖 iptables 管理流量,firewalld 会改动 iptables 规则导致网络插件异常;SELinux 和容器文件系统的权限模型经常打架,最省事的办法就是关掉;swap 更是 kubelet 的硬性检查项——如果你开着 swap,kubelet 可能会直接拒绝启动,因为 K8s 的默认假设是节点内存充足,不希望发生磁盘交换导致的性能抖动。

2.2 内核模块与系统参数:让网络转发和流量转发符合 K8s 预期

Kubernetes 集群的 Pod 间通信依赖 Linux 内核的桥接和转发能力,特别是使用 iptables 做规则过滤时,必须把桥接流量也交给 iptables 处理,否则 NodePort 访问和跨节点 Pod 通信都会出问题。

# 加载必要内核模块 cat <<EOF > /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter # 设置内核参数 cat <<EOF > /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.bridge.bridge-nf-call-iptables让经过 Linux 网桥的流量也走一遍 iptables 规则,这样 Kubernetes 里基于 iptables 实现的 Service 转发才能作用于跨 Pod 的容器流量;net.ipv4.ip_forward则开启核心转发开关,这是 Pod 网络和节点网络之间通信的基础。执行完成后可以用lsmod | grep br_netfilter确认模块加载成功。

2.3 安装并配置容器运行时 containerd

CentOS 7 自带的 yum 源里 containerd 版本比较古旧,直接yum install containerd大概率装到一个 1.2.x 老版本,跑 K8s 1.30 会出兼容性问题。我这边走的是 Docker 官方 yum 源(它同时提供 containerd.io 包),只需要安装 containerd 不需要装 docker 本体。

# 安装 yum-utils 并添加 docker-ce 源 yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 查看可用的 containerd.io 版本,选择 1.7.x yum list containerd.io --showduplicates | sort -r # 安装版本号写在 containerd.io 后面 yum install -y containerd.io-1.7.*

装好后先别急着启动,需要改两个关键配置。第一个是生成默认配置并设置SystemdCgroup为 true,第二个是把默认的 sandbox 镜像(pause)地址改成国内能正常拉取的地址。

mkdir -p /etc/containerd containerd config default > /etc/containerd/config.toml # 修改 config.toml,找到 SystemdCgroup 字段,从 false 改为 true sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml # 找到 sandbox_image 字段,把默认地址替换成可拉取的地址 # 形如:sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9" sed -i 's#sandbox_image = "registry.k8s.io/pause:3.9"#sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"#' /etc/containerd/config.toml systemctl daemon-reload systemctl enable --now containerd

这里解释一下为什么SystemdCgroup = true很重要。Linux 的 cgroup 驱动有 cgroupfs 和 systemd 两种,CentOS 7 的 init 进程是 systemd,如果 containerd 用了 cgroupfs 而 kubelet 用的 systemd,两者管理的 cgroup 层次不一致,轻则节点资源统计错乱,重则 kubelet 直接报failed to run Kubelet: failed to create kubelet instance这类错误。所以 K8s 官方文档也建议,在 systemd 系统上所有组件统一使用 systemd 驱动。

2.4 安装 kubeadm、kubelet、kubectl

这三个工具是 K8s 集群安装和维护的基础。kubeadm 负责初始化集群和生成 join 指令,kubelet 是每个节点上真正干活的组件,kubectl 是我们操作集群的命令行客户端。

# 配置阿里云的 kubernetes yum 源 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=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg EOF # 安装指定版本 yum install -y kubeadm-1.30.* kubelet-1.30.* kubectl-1.30.* # 设置 kubelet 开机自启(先不要启动,它需要等 kubeadm init 后才有配置) systemctl enable kubelet

安装完成后可以执行kubeadm version确认当前版本。这里我特意用了1.30.*通配符,因为它会装到该版本线下的最新 patch 版本,比如某个 1.30.x,相比锁定一个精确版本要弹性一些。如果你希望非常确定,可以先yum list kubeadm --showduplicates | sort -r查看可用版本,再精确安装。

3. 控制平面初始化:Master 节点操作

3.1 预拉取镜像:把 kubeadm 需要的镜像拉到本地

kubeadm init 会启动 API Server、etcd、Controller Manager 等一堆组件,这些组件并不是用 rpm 装的,而是以容器镜像的方式启动。为了保证初始化过程不卡在拉镜像环节(这也是 kubeadm init 最常见的超时原因),我习惯先把镜像拉到本地,确认没有问题再正式 init。

# 先查看需要的镜像列表 kubeadm config images list --image-repository=registry.aliyuncs.com/google_containers # 执行预拉取 kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers

--image-repository参数的作用是告诉 kubeadm 从一个指定的镜像仓库地址去拉取镜像,默认地址是registry.k8s.io,在国内网络环境下拉取会非常吃力。所以我统一改成阿里云的镜像仓库地址。这个过程对集群本身的运行没有任何影响,只是在 init 时会使用这个参数。

3.2 执行 kubeadm init 初始化集群

预拉取完成后,就可以正式初始化控制平面了。这里我整理了 init 时的核心参数,最好以配置文件的方式管理,一是不容易写错,二是后续如果要多加控制平面节点,可以直接复用。

kubeadm init \ --apiserver-advertise-address=192.168.1.10 \ --control-plane-endpoint=192.168.1.10 \ --image-repository=registry.aliyuncs.com/google_containers \ --kubernetes-version=v1.30.x \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12 \ --service-dns-domain=cluster.local

命令参数说明:

  • --apiserver-advertise-address:API Server 对外监听的 IP,就是 Master 本机 IP。
  • --control-plane-endpoint:控制平面统一入口地址,在高可用集群里通常是负载均衡器的 VIP;这里一主节点,直接填 Master IP。
  • --pod-network-cidr:Pod 网段,必须和后续安装的网络插件要求保持一致。我用 Flannel,默认就是 10.244.0.0/16。
  • --service-cidr:Service 网段,使用默认 10.96.0.0/12 即可,不需要动。
  • --kubernetes-version:指定 K8s 版本,通常可以省略,kubeadm 会自动匹配 kubeadm 二进制对应的版本。这里写上更明确。

如果成功,命令末尾会打印一段信息,包括:kubeadm join 192.168.1.10:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx。这段 join 命令一定要复制保存好,后面 worker 节点接入就是靠它。如果当时没记住也不用慌,后面在小节 4.1 会说如何重新生成。

初始化完成后,按照提示执行以下命令配置 kubectl:

mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config # 查看节点状态,此时应为 NotReady,因为还没有装网络插件 kubectl get nodes

这里还有一个注意点:如果没有 root 权限,一定要把 admin.conf 拷贝到对应用户的 ~/.kube/config 下,否则 kubectl 会因为没有权限访问集群而报connection refused,这是新手最常踩的坑。

3.3 安装 Flannel 网络插件

节点初始化完成后状态是 NotReady,原因是没有安装 CNI 网络插件,Pod 之间没法通信。我选择 Flannel 主要是因为功能足够、部署简单,一主两从的小集群用它完全没问题。

# 下载 Flannel 的部署清单 wget https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml # 如果上面地址访问困难,也可以从国内 mirror 获取,具体镜像源自行搜索可用地址 # 下载后先确认 yml 里的 network 和 init 时 pod-network-cidr 一致,是 10.244.0.0/16 kubectl apply -f kube-flannel.yml

Flannel 部署完成后,所有 kube-system 命名空间下的 Pod 会逐一进入 Running 状态。可以用下面的命令观察:

kubectl get pods -n kube-system -o wide

正常情况下你会看到 coredns、etcd、kube-apiserver 等 Pod 都是 Running 或 Completed。这里有个容易忽视的点:Flannel 会以 DaemonSet 形式在每一个节点上跑一个 flannel Pod,所以等 worker 节点也加入后,需要确认 worker 节点上也有 flannel Pod,否则新节点会一直 NotReady。

如果你不用 Flannel 而选择 Calico,那么--pod-network-cidr建议设置成 Calico 默认的 192.168.0.0/16,而不是我这里的 10.244.0.0/16,否则 Calico 的 IP 池和 kube-controller-manager 分配的 Pod 网段对不上,节点之间通信会有问题。

3.4 控制平面节点默认污点说明

kubeadm 初始化单控制平面集群后,Master 节点会带上一个污点node-role.kubernetes.io/control-plane:NoSchedule。这意味着普通工作负载默认不会被调度到 Master 节点上,这其实是合理的隔离策略——测试环境如果把系统资源跑满,控制面组件会非常脆弱。

但在测试环境里,如果你只有一主一从,或者想让某些 Pod 也能跑到 Master 上去,可以把这个污点去掉,让 Master 参与工作负载调度:

kubectl taint nodes master01 node-role.kubernetes.io/control-plane-

注意命令末尾那个减号表示移除污点。我个人建议测试集群可以去掉这个污点,但生产环境千万别这么干,控制面节点应该保持纯粹的调度隔离。

4. 工作节点接入:worker01 和 worker02

4.1 执行 kubeadm join 命令

把最初保存的那段kubeadm join命令分别拿到 worker01 和 worker02 上执行即可。如果命令丢了,可以在 Master 节点上重新生成:

kubeadm token create --print-join-command

执行 join 后,worker 节点会自动从镜像仓库拉取 kube-proxy、pause 等镜像,并且 kubelet 会主动向 API Server 注册自己。整个过程大概一两分钟,如果在 join 过程中出现报错,可以看journalctl -u kubelet -f实时跟踪 kubelet 日志,这是判断问题根源最重要的入口。

4.2 验证节点状态与组件运行

在 Master 节点上看节点列表:

kubectl get nodes -o wide

当所有节点都变成 Ready,并且状态列显示的是内部 IP(192.168.1.10/11/12),说明集群的基础网络已打通。这时候再确认一轮 kube-system 的 Pod,确保在 worker 节点上也跑着 kube-proxy 和 flannel,没有 Pending 或 CrashLoopBackOff:

kubectl get pods -n kube-system -o wide | grep -E 'flannel|kube-proxy'

这里额外提个细节,有些场景下 join 指令里的 token 是有有效期的,默认 24 小时。如果 token 过期,重新用kubeadm token create --print-join-command生成即可,但要注意新 token 对应的是一个全新的引导流程,需要在待加入节点上重新执行完整的 join 命令。

5. 部署验证与基本操作

5.1 部署一个测试应用验证集群功能

集群本身搭建完,有没有真正可用,最好的验证方式就是跑一个真实的上层应用。我用 nginx 做例子,从创建 Deployment 到通过 NodePort 对外访问,一气呵成:

# 创建 deployment kubectl create deployment nginx-test --image=nginx # 暴露服务,类型选 NodePort kubectl expose deployment nginx-test --port=80 --target-port=80 --type=NodePort # 查看服务分配的 NodePort kubectl get svc nginx-test

假设输出的80:30213/TCP,那就可以通过任意节点的IP:30213访问 Nginx。比如curl http://192.168.1.10:30213,或者浏览器打开http://worker01的IP:30213。如果都能看到 Nginx 欢迎页,说明从控制平面到工作节点、从 Service 到 Endpoint 的全链路都通了。

顺带测一下 Pod 在节点间的调度情况。默认调度是随机的,跑两个副本就能看到 Pod 分散在不同的节点上:

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

但这里注意,如果之前没有去掉 Master 的污点,这 3 个副本只会被调度到两个 worker 节点上。这是正常现象,不是故障。

5.2 日常维护常用命令速查

集群跑起来之后,高频使用的一些命令我整理在这里,方便对照:

  • 查看节点:kubectl get nodes -o wide
  • 查看所有 Pod:kubectl get pods -A -o wide
  • 查看指定资源的详细事件:kubectl describe pod <pod-name> -n <namespace>
  • 滚动查看 kubelet 日志:journalctl -u kubelet -f
  • 查看组件容器状态:crictl ps -a(containerd 运行时)
  • 重新生成 join 命令:kubeadm token create --print-join-command

这里特别想说说crictl。很多人习惯用 docker ps 看容器,但 containerd 运行时下 docker 命令是看不到任何东西的。执行crictl ps -a可以列出 containerd 管理的所有容器,配合crictl logs <container-id>查组件日志,在排障时比 kubectl logs 还要直接,因为能看到那些非 Pod 的静态容器。

6. 常见问题与排查实录

6.1 高频问题速查表

我在这次部署以及以前帮别人排障的过程中,总结了一份比较有代表性的问题表,遇到类似情况可以先对着排查。

现象大概率原因解决办法
kubeadm init 卡在 wait for control plane镜像没拉全或 etcd 崩溃journalctl -u kubelet -f看日志,确认镜像拉取是否成功,内存是否足够
节点一直是 NotReadyCNI 网络插件没装或版本不对检查 flannel/calico Pod 状态,确认 pod-network-cidr 是否一致
coredns 一直 CrashLoopBackOff一般是 CNI 网络不通,或者 pause 镜像拉取失败检查 flannel Pod,再排查 containerd sandbox_image 配置
kubeadm join 报 SystemVerification 错误swap 没关、内核模块没加载、SELinux 未禁用回看 2.1 和 2.2 小节,逐项检查
kubectl get nodes 报 connection refusedKUBECONFIG 配置不对,或 API Server 没起来确认 ~/.kube/config 文件存在且内容正确,再kubectl cluster-info
跨节点 Pod 之间 ping 不通Flannel 的 UDP/VXLAN 端口被防火墙拦截虽然关了 firewalld,但检查云安全组或物理网络有没有限制
某节点上报 内存压力 MemoryPressure内存不足 2G,或 swap 没关干净加内存,确认 fstab 中 swap 行被注释
flannel Pod 不断重启节点上存在网络接口冲突或子网冲突查看 flannel Pod 日志,可以kubectl logs -n kube-system flannel-xxx

6.2 排障思路:从一个异常 Pod 说起

纸上谈兵容易被表象迷惑,举一个这次部署过程中实际遇到的例子。我在装完网络插件后,发现某个 worker 节点上的 Pod 一直 CrashLoopBackOff,kubectl describe pod显示探针失败,看着像应用本身的问题,但同样的镜像在另一个节点上跑得好好的。这就很怪。

我的排查顺序是这样的:先用crictl ps -a找到对应容器,发现容器处于 Exited 状态;再用crictl logs看容器 stdout,没有任何有效输出;接着journalctl -u kubelet -f观察 kubelet 日志,看到一条网络相关的报错。最后定位到是那个节点的 Flannel 网卡没有正常创建,重启该节点上的 flannel Pod 后恢复。

这个例子想说明两点:一是别只盯着应用日志,很多 K8s 问题藏在下层的容器运行时和 kubelet 日志里;二是 crictl 是 containerd 环境下绕不开的排查工具,建议提前掌握。另外提醒一句,翻journalctl -u kubelet时日志量很大,别靠肉眼硬翻,可以配合grep搜索关键字,比如journalctl -u kubelet -f | grep -i error

6.3 一个容易忽略的坑:防火墙规则残留

前面说关闭 firewalld,是指 systemd 服务层面停止并且禁用。但有一种情况是,如果之前手动加过 iptables 规则,或者云主机外层的安全组规则没放行,即使系统防火墙关了,跨节点的 VXLAN 流量也可能被上层阻断。

表现就是两个 Pod 在不同节点上,互相 ping 不通,但节点之间可以通。排查方法是在 Flannel 使用的端口上测连通性,或者直接在两台 worker 之间用tcpdump抓包观察 VXLAN 流量是否到达。虽然不是必现问题,但遇到过两次之后,我后来每次搭完集群都会顺手测一遍跨节点 Pod 通信,免得后面业务上线才发现网络有问题。

写在最后的小体会

这套一主两从的 K8s 1.30 集群搭下来,说难也不难,核心就是基础环境准备要细致,版本选择要做功课,网络插件要和 pod-network-cidr 对得上。CentOS 7 虽然在很多场景下已经算“老龄化”系统,但作为测试环境依然皮实耐用,K8s 1.30 在上面跑得很顺畅。

我个人的经验是:kubeadm 拉起集群只是第一步,真正决定你运维体验的,是之后对 containerd、kubelet 日志和 CNI 网络机制的理解程度。所以如果你也是刚接触 Kubernetes,建议搭完之后不要急着删了重来,而是把常见的故障场景一样样模拟一遍(比如手动删掉某个节点的 flannel Pod、或者故意写错镜像版本),然后自己走一遍排查流程。这套“破坏-修复”的操作练熟了,比单纯记命令管用得多。

如果你在部署过程中遇到其他奇奇怪怪的问题,也欢迎按照上面的排查顺序捋一遍,大概率能找到线索。

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

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

立即咨询