1. 为什么现在还有人要在 CentOS 8 上部署 Kubernetes
CentOS 8 这个系统版本挺有意思,官方在 2021 年底就把维护模式给切了,按道理说大家应该往 Rocky、Alma 或者 Ubuntu 上迁。但现实情况是,大量企业内部的老业务、老硬件、老运维习惯还卡在 CentOS 8 上,尤其是那些 2020 到 2022 年之间上的项目,装机镜像就是 CentOS 8,重新换系统意味着要重新做安全基线、重新过一遍内部流程,成本太高。所以“在 CentOS 8 上把 Kubernetes 跑起来”这件事,到今天依然有非常实际的需求。
我自己前前后后在 CentOS 8 上部署过不下十套 K8s 集群,有小到三台虚拟机的测试环境,也有十几台物理机的中等规模生产环境。踩过的坑从 yum 源失效、内核模块没加载、cgroup 驱动不匹配,到容器运行时和 kubelet 版本对不上,基本都经历了一遍。这篇文章就是把这些经验完整地梳理出来,从系统准备到集群跑通,再到常见问题排查,给你一套可以直接照着做的方案。
这篇文章适合谁看?如果你是运维工程师、后端开发,或者正在做私有化交付的技术人员,需要在 CentOS 8 上从零搭一套 K8s,那这篇内容基本能覆盖你 90% 的场景。如果你只是想了解一下 K8s 部署的大致流程,也可以看,因为我会把每一步“为什么这么做”讲清楚,而不是只丢一堆命令让你复制。
需要提前说明的是,CentOS 8 的官方 yum 源已经不可用了,这是部署过程中第一个必须解决的问题。另外 K8s 从 1.24 版本开始移除了 dockershim,容器运行时的选择也跟以前不一样了。这些变化都会直接影响你的部署方案,我会在正文里逐一拆解。
2. 部署方案的整体设计与选型思路
2.1 版本组合怎么选才不踩坑
在 CentOS 8 上部署 K8s,版本组合是第一个要定下来的事情。我的建议是:K8s 用 1.26 或 1.27,容器运行时用 containerd,网络插件用 Calico 或 Flannel。这套组合是我实测下来最稳的,社区资料也最全。
为什么不推荐 Docker 作为运行时?因为 K8s 1.24 之后已经彻底移除了 dockershim,虽然你可以额外装 cri-dockerd 来兼容,但多一层适配就多一层故障点。containerd 本身就是 CRI 标准的实现,kubelet 直接调它,链路更短,出问题也更好排查。
至于 K8s 版本,不建议追最新。1.28 之后的版本对内核和系统组件的要求更高,CentOS 8 默认的 4.18 内核虽然能跑,但某些特性会受限。1.26 和 1.27 在 CentOS 8 上的兼容性经过大量验证,属于“闭眼选”的范围。
| 组件 | 推荐版本 | 选择理由 |
|---|---|---|
| 操作系统 | CentOS 8.5 | 最后一个相对稳定的 CentOS 8 小版本 |
| 容器运行时 | containerd 1.6.x | CRI 原生支持,无需额外适配层 |
| K8s | 1.26.x / 1.27.x | 与 CentOS 8 内核兼容性好,社区资料丰富 |
| 网络插件 | Calico 3.26 | 支持 NetworkPolicy,性能稳定 |
| 内核 | 4.18(默认) | 满足 K8s 最低要求,无需升级 |
2.2 集群架构的取舍:单 Master 还是多 Master
测试环境用单 Master 就够了,但生产环境一定要上多 Master。K8s 的控制平面组件 kube-apiserver、etcd、kube-scheduler、kube-controller-manager 如果只跑在一个节点上,那这个节点挂了整个集群就失控了。
多 Master 架构下,通常用 3 个 Master 节点加 N 个 Worker 节点。etcd 会以集群模式跑在这 3 个 Master 上,kube-apiserver 前面挂一个负载均衡器(可以用 HAProxy + Keepalived,也可以用云厂商的 LB)。这样任意一个 Master 挂了,集群还能正常工作。
我自己的习惯是:3 Master + 3 Worker 起步,Worker 节点根据业务负载再扩。Master 节点配置不用太高,2C4G 就够,但 etcd 对磁盘 IO 比较敏感,建议用 SSD。Worker 节点就看你要跑什么业务了,CPU 和内存按需配。
2.3 网络方案的选择逻辑
K8s 的网络模型有几个硬性要求:每个 Pod 有独立 IP、Pod 之间能直接通信、Pod 和 Node 之间能通信。要满足这些要求,必须装网络插件。
Flannel 是最简单的,配置少,上手快,但它不支持 NetworkPolicy,也就是说你没法做 Pod 级别的网络访问控制。Calico 功能更全,支持 NetworkPolicy,性能也好,但配置稍微复杂一点。如果你只是测试环境,Flannel 够用;如果是生产环境,我强烈建议上 Calico。
还有一个细节:Calico 的默认模式是 IPIP 隧道,跨节点通信会多一层封装,性能有损耗。如果你的节点在同一个二层网络里,可以改成 BGP 模式,性能会好很多。这个后面实操部分我会详细说。
3. 系统层面的准备工作
3.1 解决 CentOS 8 yum 源失效的问题
CentOS 8 官方源在 2021 年底就下线了,你直接yum install会报 “Failed to download metadata for repo ‘AppStream’” 之类的错误。解决办法是把源指向 vault 归档地址。
具体操作是,先把/etc/yum.repos.d/下面的源文件备份一下,然后替换成 vault 源。你可以手动改,也可以用 sed 批量替换。我习惯用 sed,一条命令搞定:
cd /etc/yum.repos.d/ sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* yum clean all yum makecache执行完之后,yum repolist能看到仓库列表就说明源配好了。这一步看着简单,但很多人卡在这里,因为报错信息不太直观,容易以为是网络问题。
注意:vault 源的下载速度可能比较慢,如果内网有镜像站,优先用内网镜像。另外,替换源之后建议先
yum update一次,把系统补丁打全,避免后续装 K8s 组件时出现依赖冲突。
3.2 关闭 Swap 和 SELinux
K8s 要求关闭 Swap,原因是 kubelet 在调度 Pod 时需要准确知道节点还剩多少内存,如果开了 Swap,内存统计就不准了,可能导致 Pod 被调度到内存已经不够的节点上。关闭 Swap 的命令很简单:
swapoff -a sed -i '/swap/s/^/#/' /etc/fstab第一行是临时关闭,第二行是注释掉 fstab 里的 Swap 挂载项,保证重启后也不会自动挂载。
SELinux 也建议关掉,虽然 K8s 理论上支持 SELinux,但实际部署中它会带来各种权限问题,尤其是容器挂载宿主机目录的时候。关闭方式:
setenforce 0 sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config这里改成 permissive 而不是 disabled,是因为 disabled 需要重启才能生效,而 permissive 是立即生效的,只是记录警告不拦截。等集群跑稳定了,你再决定要不要彻底 disabled。
3.3 内核模块与系统参数调优
K8s 的网络和存储功能依赖一些内核模块,必须提前加载。主要是br_netfilter和overlay这两个。br_netfilter让 iptables 能过滤桥接流量,overlay是 containerd 存储驱动需要的。
modprobe br_netfilter modprobe overlay cat <<EOF > /etc/modules-load.d/k8s.conf br_netfilter overlay EOF然后是 sysctl 参数。K8s 需要开启 IP 转发,否则 Pod 之间的跨节点通信会失败。另外bridge-nf-call-iptables也要打开,让桥接流量经过 iptables 处理。
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执行完sysctl --system后,可以用sysctl net.ipv4.ip_forward确认一下,返回 1 就对了。
3.4 主机名与 hosts 解析
每个节点的主机名必须唯一,而且最好能通过主机名互相解析。我习惯在/etc/hosts里把集群所有节点的 IP 和主机名都写进去,这样比依赖 DNS 更可靠。
hostnamectl set-hostname k8s-master-01 cat <<EOF >> /etc/hosts 192.168.1.10 k8s-master-01 192.168.1.11 k8s-master-02 192.168.1.12 k8s-master-03 192.168.1.20 k8s-worker-01 192.168.1.21 k8s-worker-02 192.168.1.22 k8s-worker-03 EOF主机名不要用下划线,K8s 对主机名有格式要求,只允许字母、数字、连字符和点号。这个细节很多人不注意,等到 kubelet 启动报错才发现。
4. 容器运行时 containerd 的安装与配置
4.1 安装 containerd
containerd 的安装方式有好几种,我推荐用官方二进制包,因为可控性最强,不依赖特定发行版的包管理。先从 GitHub Releases 下载对应版本的压缩包,解压到/usr/local/下面。
wget https://github.com/containerd/containerd/releases/download/v1.6.24/containerd-1.6.24-linux-amd64.tar.gz tar Cxzvf /usr/local containerd-1.6.24-linux-amd64.tar.gz然后配置 systemd 服务。containerd 官方提供了一个 service 文件模板,下载后放到/usr/local/lib/systemd/system/下面,然后systemctl daemon-reload并启动。
wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service -O /usr/local/lib/systemd/system/containerd.service systemctl daemon-reload systemctl enable --now containerd启动后用systemctl status containerd确认状态是 active 的。如果启动失败,大概率是配置文件有问题,可以看journalctl -u containerd的日志。
4.2 配置 containerd 使用 systemd cgroup 驱动
这是最关键的一步。K8s 在 CentOS 8 上默认用 systemd 作为 cgroup 驱动,而 containerd 默认用的是 cgroupfs。两者不一致会导致 kubelet 报错,Pod 起不来。
需要修改/etc/containerd/config.toml,找到SystemdCgroup这一项,改成 true。如果你是用containerd config default生成的默认配置,这一项默认是 false。
containerd config default > /etc/containerd/config.toml sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml systemctl restart containerd另外,默认配置里的 pause 镜像地址是registry.k8s.io/pause:3.6,国内环境可能拉不下来。可以换成你自己的镜像仓库地址,或者提前把镜像导入到本地。
实操心得:改完 config.toml 之后一定要重启 containerd,而且重启后最好用
ctr version确认一下版本信息,确保服务真的起来了。我有一次改完配置忘了重启,后面 kubelet 一直报错,排查了半天才发现是 containerd 还在用旧配置。
4.3 安装 runc 和 CNI 插件
containerd 需要 runc 来实际创建容器,还需要 CNI 插件来配置 Pod 网络。runc 可以从 GitHub 下载二进制文件,放到/usr/local/sbin/下面。
wget https://github.com/opencontainers/runc/releases/download/v1.1.10/runc.amd64 install -m 755 runc.amd64 /usr/local/sbin/runcCNI 插件是一组二进制文件,下载后解压到/opt/cni/bin/目录。
wget https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz mkdir -p /opt/cni/bin tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.3.0.tgz这两个装完之后,containerd 的基础环境就齐了。可以用runc --version和ls /opt/cni/bin/确认一下。
5. K8s 组件的安装与集群初始化
5.1 安装 kubelet、kubeadm、kubectl
这三个组件是 K8s 的核心。kubelet 跑在每个节点上,负责管理容器;kubeadm 用来初始化集群和加入节点;kubectl 是命令行工具。
在 CentOS 8 上,可以通过 K8s 官方 yum 源安装。先添加源:
cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://pkgs.k8s.io/core:/stable:/v1.27/rpm/ enabled=1 gpgcheck=1 gpgkey=https://pkgs.k8s.io/core:/stable:/v1.27/rpm/repodata/repomd.xml.key EOF然后安装指定版本:
yum install -y kubelet-1.27.8 kubeadm-1.27.8 kubectl-1.27.8 systemctl enable kubelet注意这里 kubelet 先不要启动,因为还没初始化集群,启动了也会报错。等 kubeadm init 跑完之后,kubelet 会自动起来。
5.2 初始化第一个 Master 节点
初始化命令里需要指定几个关键参数:Pod 网段、Service 网段、API Server 地址、容器运行时。Pod 网段和 Service 网段不能跟宿主机网段重叠,否则会路由冲突。
kubeadm init \ --apiserver-advertise-address=192.168.1.10 \ --image-repository=registry.aliyuncs.com/google_containers \ --kubernetes-version=v1.27.8 \ --service-cidr=10.96.0.0/12 \ --pod-network-cidr=10.244.0.0/16 \ --cri-socket=unix:///var/run/containerd/containerd.sock--image-repository指定镜像仓库,国内环境用阿里云的镜像源会快很多。--cri-socket指定 containerd 的 socket 路径,因为现在默认不是 Docker 了,必须显式指定。
初始化成功后,会输出一段 join 命令,类似kubeadm join 192.168.1.10:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx。这段命令要保存好,后面加 Worker 节点和 Master 节点都要用。
然后配置 kubectl 的 kubeconfig:
mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config执行kubectl get nodes,应该能看到 Master 节点,状态是 NotReady,因为还没装网络插件。
5.3 安装 Calico 网络插件
Calico 的安装很简单,用 kubectl apply 一个 yaml 文件就行。但要注意 Pod 网段要跟 kubeadm init 时指定的--pod-network-cidr一致。
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml如果你改了 Pod 网段,需要先下载 yaml 文件,修改里面的CALICO_IPV4POOL_CIDR环境变量,再 apply。
等几分钟,kubectl get pods -n kube-system看到 Calico 的 Pod 都 Running 了,kubectl get nodes里 Master 节点变成 Ready,就说明网络插件装好了。
注意:Calico 默认用 IPIP 模式,如果你节点在同一个二层网络,可以改成 BGP 模式提升性能。修改方式是编辑 Calico 的 DaemonSet,把
CALICO_IPV4POOL_IPIP改成Off。不过改之前要确认网络设备支持 BGP,否则可能导致跨节点通信失败。
5.4 加入 Worker 节点和 Master 节点
Worker 节点的加入很简单,在 Worker 节点上执行之前保存的 join 命令就行。但前提是 Worker 节点也装好了 containerd、kubelet、kubeadm,并且做了系统层面的准备。
kubeadm join 192.168.1.10:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx如果是加 Master 节点,需要加--control-plane参数,并且要先在第一个 Master 上生成证书 key:
kubeadm init phase upload-certs --upload-certs然后 join 命令里加上--control-plane --certificate-key xxx。
所有节点加完之后,在 Master 上执行kubectl get nodes,应该能看到所有节点都是 Ready 状态。
6. 常见问题排查与避坑指南
6.1 kubelet 启动失败怎么查
kubelet 启动失败是最常见的问题,表现是systemctl status kubelet显示 failed。排查思路是先用journalctl -u kubelet -f看日志,日志里通常会告诉你具体原因。
常见的几个原因:cgroup 驱动不匹配(containerd 和 kubelet 用的驱动不一致)、Swap 没关、SELinux 没关、配置文件路径不对。我遇到最多的是 cgroup 驱动问题,报错信息类似 “failed to run Kubelet: misconfiguration: kubelet cgroup driver: ‘cgroupfs’ is different from docker cgroup driver: ‘systemd’”。解决办法就是改 containerd 的配置,把 SystemdCgroup 改成 true。
还有一个坑是 kubelet 的配置文件/var/lib/kubelet/config.yaml里 cgroupDriver 写的是 cgroupfs,需要改成 systemd。这个文件是 kubeadm init 生成的,如果 init 的时候没指定,默认就是 cgroupfs。
6.2 Pod 一直 Pending 或 CrashLoopBackOff
Pod 一直 Pending,通常是资源不够或者没有可调度的节点。用kubectl describe pod <pod-name> -n <namespace>看 Events,里面会写原因。如果是 “Insufficient cpu” 或 “Insufficient memory”,那就是节点资源不够了。如果是 “node(s) had taint”,那就是 Master 节点有污点,不允许调度普通 Pod。
CrashLoopBackOff 是容器启动后不断崩溃重启。常见原因是镜像里的启动命令有问题、配置文件挂载错了、依赖的服务没起来。也是用kubectl describe pod和kubectl logs排查。
6.3 跨节点 Pod 通信失败
跨节点 Pod 通信失败,大概率是网络插件的问题。先确认 Calico 或 Flannel 的 Pod 是不是都 Running,然后检查节点的 iptables 规则和路由表。
如果是 Calico IPIP 模式,检查ip tunnel show有没有 tunl0 接口。如果是 BGP 模式,检查calicoctl node status看 BGP 邻居是不是 Established。
还有一个容易忽略的点:宿主机的防火墙规则可能把 Pod 网段的流量拦了。CentOS 8 默认用 firewalld,建议在部署 K8s 之前就把 firewalld 关了,或者放行 Pod 网段和 Service 网段。
| 问题现象 | 可能原因 | 排查命令 | 解决办法 |
|---|---|---|---|
| kubelet 启动失败 | cgroup 驱动不匹配 | journalctl -u kubelet | 改 containerd 配置为 systemd |
| Pod Pending | 资源不足或节点有污点 | kubectl describe pod | 扩容节点或去掉污点 |
| Pod CrashLoopBackOff | 容器启动命令错误 | kubectl logs | 检查镜像和启动参数 |
| 跨节点通信失败 | 网络插件异常 | kubectl get pods -n kube-system | 重装网络插件或检查防火墙 |
| 镜像拉取失败 | 镜像仓库不可达 | kubectl describe pod | 配置镜像加速或导入本地镜像 |
6.4 证书过期导致集群不可用
K8s 的证书默认有效期是一年,到期后 kube-apiserver 会拒绝所有请求,集群直接不可用。查看证书有效期:
kubeadm certs check-expiration续期命令:
kubeadm certs renew all systemctl restart kubelet续期之后要重启 kube-apiserver、kube-controller-manager、kube-scheduler 这些静态 Pod,最简单的方式是重启 kubelet,它会自动重建这些 Pod。另外,续期后要更新 kubeconfig 文件,否则 kubectl 还是用旧证书。
实操心得:建议在证书到期前一个月就做续期,别等到过期了再处理。过期后 kube-apiserver 起不来,kubectl 也用不了,只能手动用 openssl 或者备份的证书恢复,非常麻烦。我自己的做法是写一个定时任务,每个月检查一次证书有效期,提前告警。
7. 集群跑通后的验证与日常维护
7.1 部署一个测试应用验证集群
集群搭好之后,别急着上业务,先跑一个简单的测试应用验证一下。我习惯用 nginx 或者一个简单的 echo 服务。
kubectl create deployment nginx-test --image=nginx:alpine --replicas=3 kubectl expose deployment nginx-test --port=80 --type=NodePort然后kubectl get pods -o wide看 Pod 是不是分布在不同节点上,kubectl get svc看 Service 的 NodePort 是多少。用 curl 访问任意节点的 NodePort,能返回 nginx 欢迎页就说明集群基本功能正常。
再测试一下 DNS 解析,在 Pod 里 ping 一下kubernetes.default.svc,能解析就说明 CoreDNS 工作正常。
7.2 日常巡检要看哪些指标
集群跑起来之后,日常巡检主要看几个方面:节点状态、Pod 状态、资源使用率、证书有效期、etcd 健康状态。
节点状态用kubectl get nodes,Pod 状态用kubectl get pods --all-namespaces,资源使用率需要装 metrics-server,然后kubectl top nodes和kubectl top pods。etcd 健康状态用etcdctl endpoint health。
我自己的习惯是每天上班第一件事就是跑一遍这些命令,看看有没有异常。如果集群规模大,建议上 Prometheus + Grafana 做监控告警,靠人工巡检不现实。
7.3 节点扩容与缩容的操作要点
扩容 Worker 节点很简单,新节点做好系统准备、装好 containerd 和 kubelet,然后执行 join 命令就行。缩容的话,先kubectl drain <node-name> --ignore-daemonsets把 Pod 驱逐到其他节点,然后kubectl delete node <node-name>从集群里删掉,最后在节点上kubeadm reset清理环境。
drain 的时候要注意,如果有 Pod 用了 emptyDir 或者 local storage,驱逐后数据会丢。另外 DaemonSet 的 Pod 默认不会被驱逐,需要加--ignore-daemonsets参数。
7.4 备份与恢复的基本策略
etcd 是集群的大脑,所有集群状态都存在 etcd 里,所以 etcd 的备份至关重要。备份命令:
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot.db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key建议写个定时任务,每天备份一次,备份文件保留最近 7 天。恢复的时候用etcdctl snapshot restore,然后修改 etcd 的静态 Pod 配置指向恢复后的数据目录。
除了 etcd,还要备份/etc/kubernetes/目录下的所有证书和配置文件。这些文件丢了,集群就彻底起不来了。
8. 一些零散但重要的经验补充
CentOS 8 上部署 K8s,还有一个容易被忽略的点是时间同步。K8s 的证书验证、Token 有效期都依赖准确的时间,如果节点之间时间差太大,会出现各种莫名其妙的认证失败。建议所有节点都配好 NTP 同步,用chronyd或者ntpd都行。
另外,CentOS 8 的默认内核参数里,net.ipv4.ip_forward是 0,必须手动打开。这个前面提过了,但值得再强调一次,因为很多人会忘。
关于镜像仓库,如果你在内网环境,建议搭一个 Harbor 或者 registry,把 K8s 需要的镜像提前推上去。kubeadm init 的时候用--image-repository指向你的内网仓库,这样初始化会快很多,也不依赖外网。
最后说一个我踩过的坑:CentOS 8 的iptables默认是 nftables 后端,而 K8s 的某些组件对 nftables 的支持还不完善。如果遇到网络规则不生效的问题,可以试试把 iptables 切换成 legacy 模式:
update-alternatives --set iptables /usr/sbin/iptables-legacy update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy切换之后重启 containerd 和 kubelet,网络问题大概率能解决。这个坑我在两套环境里都遇到过,排查起来很费时间,因为报错信息不会直接告诉你是 nftables 的问题。
这套流程走下来,一套 CentOS 8 上的 K8s 集群基本就能稳定运行了。后续的 Ingress、存储、监控、日志这些,都是在这个基础之上叠加的,基础打牢了,后面的事情就顺了。