ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

CentOS 7 上使用 kubeadm 部署 Kubernetes 1.30 集群完整实战

2026/9/9 13:17:24 拓冰建站 浏览量
CentOS 7 上使用 kubeadm 部署 Kubernetes 1.30 集群完整实战 先说一下这次的部署背景。手上的测试环境是三台 CentOS 7 虚拟机配置不算高目的是搭一套能用来跑内部服务和做实验的 Kubernetes 1.30 集群。既然只是测试用途不追求生产级高可用那我直接用 kubeadm 来搞一主两从的架构简单直接应付日常开发、验证各种 Deployment 和 CronJob 完全够用。这篇文章就是完整记录我这次从零到一的部署过程包含所有踩过的坑和排查思路。如果你正准备在一组 CentOS 7 机器上快速建一套 K8s 1.30 环境或者刚学 Kubernetes 想找个能落地的实操案例这篇内容应该能让你少走很多弯路。整个操作流程我尽量按“先想清楚再动手”的顺序来写每一步都知道为什么这么做而不是机械地复制命令。1. 整体部署思路与架构设计1.1 为什么选 kubeadm而不是二进制或 minikubeKubernetes 的部署方式大概有三条主流路线二进制部署、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.112C4GWorker 工作节点worker02192.168.1.122C4GIP 地址我这里用 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/^SELINUXenforcing/SELINUXdisabled/ /etc/selinux/config setenforce 0 # 关闭 swap并且注释掉 /etc/fstab 里 swap 的行否则重启后会重新挂载 swapoff -a sed -i / swap / s/^/#/ /etc/fstab为什么要关这三样Kubernetes 本身依赖 iptables 管理流量firewalld 会改动 iptables 规则导致网络插件异常SELinux 和容器文件系统的权限模型经常打架最省事的办法就是关掉swap 更是 kubelet 的硬性检查项——如果你开着 swapkubelet 可能会直接拒绝启动因为 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 安装并配置容器运行时 containerdCentOS 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] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck1 repo_gpgcheck1 gpgkeyhttps://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-repositoryregistry.aliyuncs.com/google_containers # 执行预拉取 kubeadm config images pull --image-repositoryregistry.aliyuncs.com/google_containers--image-repository参数的作用是告诉 kubeadm 从一个指定的镜像仓库地址去拉取镜像默认地址是registry.k8s.io在国内网络环境下拉取会非常吃力。所以我统一改成阿里云的镜像仓库地址。这个过程对集群本身的运行没有任何影响只是在 init 时会使用这个参数。3.2 执行 kubeadm init 初始化集群预拉取完成后就可以正式初始化控制平面了。这里我整理了 init 时的核心参数最好以配置文件的方式管理一是不容易写错二是后续如果要多加控制平面节点可以直接复用。kubeadm init \ --apiserver-advertise-address192.168.1.10 \ --control-plane-endpoint192.168.1.10 \ --image-repositoryregistry.aliyuncs.com/google_containers \ --kubernetes-versionv1.30.x \ --pod-network-cidr10.244.0.0/16 \ --service-cidr10.96.0.0/12 \ --service-dns-domaincluster.local命令参数说明--apiserver-advertise-addressAPI Server 对外监听的 IP就是 Master 本机 IP。--control-plane-endpoint控制平面统一入口地址在高可用集群里通常是负载均衡器的 VIP这里一主节点直接填 Master IP。--pod-network-cidrPod 网段必须和后续安装的网络插件要求保持一致。我用 Flannel默认就是 10.244.0.0/16。--service-cidrService 网段使用默认 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 会说如何重新生成。初始化完成后按照提示执行以下命令配置 kubectlmkdir -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.ymlFlannel 部署完成后所有 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 和 worker024.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并且状态列显示的是内部 IP192.168.1.10/11/12说明集群的基础网络已打通。这时候再确认一轮 kube-system 的 Pod确保在 worker 节点上也跑着 kube-proxy 和 flannel没有 Pending 或 CrashLoopBackOffkubectl 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 --imagenginx # 暴露服务类型选 NodePort kubectl expose deployment nginx-test --port80 --target-port80 --typeNodePort # 查看服务分配的 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 --replicas3 kubectl get pods -o wide但这里注意如果之前没有去掉 Master 的污点这 3 个副本只会被调度到两个 worker 节点上。这是正常现象不是故障。5.2 日常维护常用命令速查集群跑起来之后高频使用的一些命令我整理在这里方便对照查看节点kubectl get nodes -o wide查看所有 Podkubectl get pods -A -o wide查看指定资源的详细事件kubectl describe pod pod-name -n namespace滚动查看 kubelet 日志journalctl -u kubelet -f查看组件容器状态crictl ps -acontainerd 运行时重新生成 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-xxx6.2 排障思路从一个异常 Pod 说起纸上谈兵容易被表象迷惑举一个这次部署过程中实际遇到的例子。我在装完网络插件后发现某个 worker 节点上的 Pod 一直 CrashLoopBackOffkubectl 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、或者故意写错镜像版本然后自己走一遍排查流程。这套“破坏-修复”的操作练熟了比单纯记命令管用得多。如果你在部署过程中遇到其他奇奇怪怪的问题也欢迎按照上面的排查顺序捋一遍大概率能找到线索。