CentOS 8 安装 Kubernetes(kubeadm)集群指南

在 CentOS 8 上使用 kubeadm v1.31.14 从零搭建 Kubernetes 集群:环境准备、安装与启动、节点组网、运行时方案、就绪验证与示例应用访问,命令与回显实测验证。

kubeadmkubeadmKubernetesk8s2026/10/10

CentOS 8 安装 Kubernetes(kubeadm)集群指南

目标:在 4 台 CentOS 8 服务器上,用 kubeadm 从零搭建一套「1 个控制平面(master)+ 3 个 worker」的 Kubernetes v1.31.14 单主集群,覆盖基础环境配置、containerd 运行时安装、kubeadm init、flannel 网络、三个 worker 逐一 join 以及最终 4 节点全部 Ready 的验证。文中所有命令与回显均在 CentOS 8.4(内核 6.8.0-138-generic)实测通过。

一、环境信息与主机规划

  • 操作系统:CentOS Linux release 8.4.2105 / x86_64(内核 6.8.0-138-generic)
  • Kubernetes:v1.31.14(kubeadm / kubelet / kubectl 1.31.14-150500.1.1.x86_64)
  • 运行时:containerd 1.6.32(containerd.io-1.6.32-3.1.el8.x86_64)+ runc 1.1.12
  • CNI 插件:flannel v0.28.10 + cni-plugins v1.9.1-flannel3
  • 镜像仓库:registry.cn-hangzhou.aliyuncs.com/google_containers(阿里云镜像,国内可达)
  • 网络规划:Pod 网段 10.244.0.0/16,Service 网段 10.96.0.0/12,集群 DNS 域名 cluster.local
  • 示例账号:四台节点均以 root 登录(示例密码 q1w2e3r4,生产环境请按安全规范设置强密码)

节点规划(1 控制平面 + 3 worker,单主集群):

节点角色主机名内网 IP说明
masterkubeadm-c8-master172.25.0.2控制平面:apiserver / etcd / controller-manager / scheduler
worker1kubeadm-c8-worker1172.25.0.3工作节点
worker2kubeadm-c8-worker2172.25.0.6工作节点
worker3kubeadm-c8-worker3172.25.0.7工作节点

💡 单主集群适合测试与中小规模环境;生产环境建议多控制平面高可用(另见 kubeadm 配置文档)。

二、基础环境配置(四台节点一致)

以下操作在 master 与三个 worker 上分别执行。

2.1 主机名与 hosts 解析

设置主机名(master 节点):

hostnamectl set-hostname kubeadm-c8-master

worker1 节点:

hostnamectl set-hostname kubeadm-c8-worker1

worker2 节点:

hostnamectl set-hostname kubeadm-c8-worker2

worker3 节点:

hostnamectl set-hostname kubeadm-c8-worker3

将各节点 IP 写入 /etc/hosts,便于集群内部按主机名互认(每台节点都追加,IP 按实际环境填写):

cat >> /etc/hosts <<EOF
172.25.0.2      kubeadm-c8-master
172.25.0.3      kubeadm-c8-worker1
172.25.0.6      kubeadm-c8-worker2
172.25.0.7      kubeadm-c8-worker3
EOF

验证(master 实测回显):

172.25.0.2      kubeadm-c8-master

2.2 swap 处理

Kubernetes 1.31 的 kubelet 默认 failSwapOn 为 true(swap 开启会拒绝启动)。本集群通过 kubelet 配置 failSwapOn: false 兼容 swap 环境,因此无需强制关闭 swap;若希望彻底关闭(物理机标准做法):

swapoff -a && sed -i 's/.*swap.*/#&/' /etc/fstab

⚠️ 本实验环境 swap 保持开启,靠 failSwapOn: false 放行(详见第五章 init 配置),kubeadm init 预检会有 [WARNING Swap] 提示但不阻断。

2.3 内核网络参数

加载 bridge 模块并开启 iptables 桥接转发与 IP 转发(四台节点一致):

modprobe br_netfilter
cat > /etc/sysctl.d/k8s.conf <<EOF
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 = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1

2.4 SELinux

SELinux 与 kubelet/containerd 的兼容性较复杂,官方建议关闭(本实验环境实测为 Disabled):

setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

验证(实测回显):Disabled

💡 如需保持 SELinux enforcing,需按 containerd 官方文档配置正确策略,生产环境请评估后再定。

2.5 配置软件源

Kubernetes 官方 rpm 源(v1.31 稳定版)与 Docker CE 源(阿里云镜像,提供 containerd.io):

cat > /etc/yum.repos.d/kubernetes.repo <<EOF
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.31/rpm/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.31/rpm/repodata/repomd.xml.key
EOF
cat > /etc/yum.repos.d/docker-ce.repo <<EOF
[docker-ce-stable]
name=Docker CE Stable - \$basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/\$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg
EOF

刷新缓存:

yum makecache

三、安装运行时 containerd

3.1 安装 containerd.io 1.6.32

kubeadm 通过 CRI(unix:///var/run/containerd/containerd.sock)对接运行时。固定安装 1.6.32 版本(四台节点一致):

yum install -y containerd.io-1.6.32-3.1.el8 conntrack-tools

💡 conntrack-tools 供 kubeadm 预检使用,worker join 时若缺失会报 [ERROR FileExisting-conntrack]。

3.2 生成默认配置并修改关键项

mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml

修改三个关键项(用 sed 原地替换或直接编辑):

  • 启用 CRI 插件:disabled_plugins = ["io.containerd.grpc.v1.cri"] → disabled_plugins = []
  • 镜像仓库:沙箱镜像 sandbox_image 指向阿里云 pause:3.10
  • 快照器:嵌套/受限内核环境 overlayfs 挂载受限,使用 native

master 节点(本实验控制平面)实际生效配置摘要(containerd config dump 实测):

disabled_plugins = []
version = 2
[plugins."io.containerd.grpc.v1.cri"]
  sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.10"
  [plugins."io.containerd.grpc.v1.cri".containerd]
    snapshotter = "native"
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
      SystemdCgroup = true

worker 节点(本实验最终稳定组合)实际生效配置摘要:

disabled_plugins = []
version = 2
[plugins."io.containerd.grpc.v1.cri"]
  sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.10"
  [plugins."io.containerd.grpc.v1.cri".containerd]
    snapshotter = "native"
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
      SystemdCgroup = false

⚠️ 注意:SystemdCgroup 必须与 kubelet 的 cgroupDriver 保持一致(同为 systemd 或同为 cgroupfs)。本集群 master 使用 systemd 驱动、worker 使用 cgroupfs 驱动,对应 containerd 分别配置 SystemdCgroup = true / false;混用不一致会引发 cgroup 冲突导致控制面崩溃,详见常见问题 Q1。

3.3 配置镜像加速(可选但推荐)

docker.io 镜像在国内直连慢或不稳,可通过 containerd 的 config_path 指向 /etc/containerd/certs.d,再以 hosts.toml 声明上游镜像源(本实验实测可用源:docker.m.daocloud.io):

mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<EOF
server = "https://docker.m.daocloud.io"

[host."https://docker.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
EOF

然后在 /etc/containerd/config.toml 的 CRI registry 段设置:

[plugins."io.containerd.grpc.v1.cri".registry]
  config_path = "/etc/containerd/certs.d"

💡 镜像源仅作加速,不改动镜像的规范引用名(仍写 docker.io/library/nginx:alpine)。

3.4 启动 containerd 并验证

systemctl enable --now containerd
systemctl status containerd --no-pager | head -5

实测回显(节选):

● containerd.service - containerd container runtime
     Active: active (running)

版本验证(master 实测回显):

containerd containerd.io 1.6.32 8b3b7ca2e5ce38e8f31a34f35b2b68ceb8470d89
runc version 1.1.12

四、安装 kubeadm / kubelet / kubectl

固定安装 v1.31.14(四台节点一致,kubelet/kubeadm/kubectl 保持同版本):

yum install -y kubelet-1.31.14-150500.1.1 kubeadm-1.31.14-150500.1.1 kubectl-1.31.14-150500.1.1

设置 kubelet 开机自启(master 与 worker 一致):

systemctl enable kubelet

💡 kubelet 先 enable 但不 start:kubeadm 会在 init/join 过程中写入 /var/lib/kubelet/config.yaml 后拉起 kubelet。

版本验证(master 实测回显):

kubeadm version: &version.Info{Major:"1", Minor:"31", GitVersion:"v1.31.14", GitCommit:"5e00b99bac504844579ec74886b6cc5c9611ca19", GitTreeState:"clean", BuildDate:"2025-11-11T20:23:36Z", GoVersion:"go1.24.9", Compiler:"gc", Platform:"linux/amd64"}

五、初始化控制平面(master 节点)

5.1 编写 kubeadm-init.yaml

在 master 上编写初始化配置 /root/kubeadm-init.yaml(v1beta4,master 本机 IP 为 172.25.0.2,按实际环境替换 advertiseAddress 与 name):

apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: 172.25.0.2
  bindPort: 6443
nodeRegistration:
  name: kubeadm-c8-master
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
kubernetesVersion: v1.31.14
networking:
  dnsDomain: cluster.local
  podSubnet: 10.244.0.0/16
  serviceSubnet: 10.96.0.0/12
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
failSwapOn: false
cgroupDriver: systemd
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
conntrack:
  maxPerCore: 0

💡 imageRepository 指向阿里云镜像仓库,避免默认 registry.k8s.io 拉取超时;failSwapOn: false 放行 swap 环境;conntrack.maxPerCore: 0 关闭节点 conntrack 限额,避免高并发下 nf_conntrack 表满。

5.2 执行 kubeadm init

kubeadm init --config /root/kubeadm-init.yaml --ignore-preflight-errors=SystemVerification

⚠️ --ignore-preflight-errors=SystemVerification:实验环境 /boot 下无内核 config 文件,预检会报 [WARNING SystemVerification]: failed to parse kernel config,按官方指引放行;物理机完整安装(存在 /boot/config-*)不会触发此警告,无需该参数。

实测回显(完整):

[init] Using Kubernetes version: v1.31.14
[preflight] Running pre-flight checks
	[WARNING Swap]: swap is supported for cgroup v2 only. The kubelet must be properly configured to use swap. Please refer to https://kubernetes.io/docs/concepts/architecture/nodes/#swap-memory, or disable swap on the node
[preflight] The system verification failed. Printing the output from the verification:
KERNEL_VERSION: 6.8.0-138-generic
OS: Linux
CGROUPS_CPU: enabled
CGROUPS_CPUSET: enabled
CGROUPS_DEVICES: enabled
CGROUPS_FREEZER: enabled
CGROUPS_MEMORY: enabled
CGROUPS_PIDS: enabled
CGROUPS_HUGETLB: enabled
CGROUPS_IO: enabled
	[WARNING SystemVerification]: failed to parse kernel config: unable to load kernel module: "configs", output: "modprobe: FATAL: Module configs not found in directory /lib/modules/6.8.0-138-generic\n", err: exit status 1
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action beforehand using 'kubeadm config images pull'
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubeadm-c8-master kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 172.25.0.2]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [kubeadm-c8-master localhost] and IPs [172.25.0.2 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [kubeadm-c8-master localhost] and IPs [172.25.0.2 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "super-admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests"
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is healthy after 1.501128813s
[api-check] Waiting for a healthy API server. This can take up to 4m0s
[api-check] The API server is healthy after 4.002125648s
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node kubeadm-c8-master as control-plane by adding the labels: [node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node kubeadm-c8-master as control-plane by adding the taints [node-role.kubernetes.io/control-plane:NoSchedule]
[bootstrap-token] Using token: wsc0j6.pwxs39dki400ptcp
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 172.25.0.2:6443 --token wsc0j6.pwxs39dki400ptcp \
	--discovery-token-ca-cert-hash sha256:2e2816d5d2e2bfa93dc588fadbcb5811d063cbbbf7c178116714f890e249e204

💡 init 成功后记录回显尾部的 join 命令(含 token 与 ca-cert-hash),worker 加入集群要用;token 默认 24h 过期,可 kubeadm token create 重新生成(见 CLI 文档)。

5.3 配置 kubectl 访问(master)

root 用户直接使用 admin.conf:

export KUBECONFIG=/etc/kubernetes/admin.conf
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> /etc/profile.d/k8s.sh

💡 非 root 用户按 init 回显执行 mkdir -p $HOME/.kube && cp -i /etc/kubernetes/admin.conf $HOME/.kube/config && chown $(id -u):$(id -g) $HOME/.kube/config。

5.4 统一 kubelet cgroup 驱动(关键)

kubeadm 会把 master 的 KubeletConfiguration 下发到集群内 kube-system/kubelet-config ConfigMap,后续 worker join 时自动沿用。本集群为了让 worker 使用 cgroupfs 驱动(避免 cgroup 冲突,见 FAQ Q1),在 master 上把 ConfigMap 中的 cgroupDriver 改为 cgroupfs:

kubectl -n kube-system get cm kubelet-config -o yaml > /root/kubelet-config-cm.yaml
sed -i 's/cgroupDriver: systemd/cgroupDriver: cgroupfs/' /root/kubelet-config-cm.yaml
kubectl apply -f /root/kubelet-config-cm.yaml

💡 worker join 时 kubeadm 从该 ConfigMap 读取 kubelet 配置写入 /var/lib/kubelet/config.yaml,因此后续 worker 自动获得 cgroupDriver: cgroupfs + failSwapOn: false,与 worker 侧 containerd 的 SystemdCgroup = false 配套,join 全程无崩溃。

六、部署 CNI 网络插件 flannel

控制面初始化成功后,部署 flannel(v0.28.10,kube-flannel DaemonSet + RBAC 与配置),使各节点 Pod 通过 10.244.0.0/16 互通:

kubectl apply -f https://github.com/flannel-io/flannel/releases/download/v0.28.10/kube-flannel.yml

验证 flannel DaemonSet(master 实测回显):

kubectl get ds -n kube-flannel
NAME       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
kube-flannel-ds   1         1         1       1            1           <none>         7h58m

💡 flannel 会在每台节点写入 /run/flannel/subnet.env(含 FLANNEL_SUBNET,如 worker1 为 10.244.1.1/24),kubelet/CNI 据此分配各节点 Pod 网段。

七、worker 节点加入集群(三个节点逐一执行)

三个 worker 节点各自执行:安装运行时与组件(第二~四章步骤)→ 启动 containerd → 执行 join。以下安装摘要每个 worker 单独操作一遍(命令一致):

yum install -y containerd.io-1.6.32-3.1.el8 conntrack-tools kubelet-1.31.14-150500.1.1 kubeadm-1.31.14-150500.1.1 kubectl-1.31.14-150500.1.1

配置 containerd(worker 用 SystemdCgroup = false,见 3.2 节)并启动:

mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
# 按 3.2/3.3 节修改 disabled_plugins、sandbox_image、snapshotter、SystemdCgroup=false、config_path
systemctl enable --now containerd
systemctl enable kubelet

💡 join 命令在 master 上执行 kubeadm token list 或从 init 回显尾部获取最新 token 与 hash;以下为本次实测值(token 有效期 24h,过期请重新生成)。

7.1 worker1 加入

在 kubeadm-c8-worker1 上执行(root):

kubeadm join 172.25.0.2:6443 --token wsc0j6.pwxs39dki400ptcp \
	--discovery-token-ca-cert-hash sha256:2e2816d5d2e2bfa93dc588fadbcb5811d063cbbbf7c178116714f890e249e204 \
	--ignore-preflight-errors=SystemVerification

实测回显:

[preflight] Running pre-flight checks
	[WARNING Swap]: swap is supported for cgroup v2 only. The kubelet must be properly configured to use swap. Please refer to https://kubernetes.io/docs/concepts/architecture/nodes/#swap-memory, or disable swap on the node
	[WARNING FileExisting-tc]: tc not found in system path
[preflight] The system verification failed. Printing the output from the verification:
KERNEL_VERSION: 6.8.0-138-generic
OS: Linux
CGROUPS_CPU: enabled
CGROUPS_CPUSET: enabled
CGROUPS_DEVICES: enabled
CGROUPS_FREEZER: enabled
CGROUPS_MEMORY: enabled
CGROUPS_PIDS: enabled
CGROUPS_HUGETLB: enabled
CGROUPS_IO: enabled
	[WARNING SystemVerification]: failed to parse kernel config: unable to load kernel module: "configs", output: "modprobe: FATAL: Module configs not found in directory /lib/modules/6.8.0-138-generic\n", err: exit status 1
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is healthy after 2.001561097s
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

7.2 worker2 加入

在 kubeadm-c8-worker2 上执行(root):

kubeadm join 172.25.0.2:6443 --token wsc0j6.pwxs39dki400ptcp \
	--discovery-token-ca-cert-hash sha256:2e2816d5d2e2bfa93dc588fadbcb5811d063cbbbf7c178116714f890e249e204 \
	--ignore-preflight-errors=SystemVerification

实测回显:

[preflight] Running pre-flight checks
	[WARNING Swap]: swap is supported for cgroup v2 only. The kubelet must be properly configured to use swap. Please refer to https://kubernetes.io/docs/concepts/architecture/nodes/#swap-memory, or disable swap on the node
	[WARNING FileExisting-tc]: tc not found in system path
[preflight] The system verification failed. Printing the output from the verification:
KERNEL_VERSION: 6.8.0-138-generic
OS: Linux
CGROUPS_CPU: enabled
CGROUPS_CPUSET: enabled
CGROUPS_DEVICES: enabled
CGROUPS_FREEZER: enabled
CGROUPS_MEMORY: enabled
CGROUPS_PIDS: enabled
CGROUPS_HUGETLB: enabled
CGROUPS_IO: enabled
	[WARNING SystemVerification]: failed to parse kernel config: unable to load kernel module: "configs", output: "modprobe: FATAL: Module configs not found in directory /lib/modules/6.8.0-138-generic\n", err: exit status 1
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is healthy after 1.001614889s
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

7.3 worker3 加入

在 kubeadm-c8-worker3 上执行(root):

kubeadm join 172.25.0.2:6443 --token wsc0j6.pwxs39dki400ptcp \
	--discovery-token-ca-cert-hash sha256:2e2816d5d2e2bfa93dc588fadbcb5811d063cbbbf7c178116714f890e249e204 \
	--ignore-preflight-errors=SystemVerification

实测回显:

[preflight] Running pre-flight checks
	[WARNING Swap]: swap is supported for cgroup v2 only. The kubelet must be properly configured to use swap. Please refer to https://kubernetes.io/docs/concepts/architecture/nodes/#swap-memory, or disable swap on the node
	[WARNING FileExisting-tc]: tc not found in system path
[preflight] The system verification failed. Printing the output from the verification:
KERNEL_VERSION: 6.8.0-138-generic
OS: Linux
CGROUPS_CPU: enabled
CGROUPS_CPUSET: enabled
CGROUPS_DEVICES: enabled
CGROUPS_FREEZER: enabled
CGROUPS_MEMORY: enabled
CGROUPS_PIDS: enabled
CGROUPS_HUGETLB: enabled
CGROUPS_IO: enabled
	[WARNING SystemVerification]: failed to parse kernel config: unable to load kernel module: "configs", output: "modprobe: FATAL: Module configs not found in directory /lib/modules/6.8.0-138-generic\n", err: exit status 1
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is healthy after 1.001614889s
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

💡 每台 worker 独立执行 join(单条命令),不要用循环脚本批量 join;三台 worker 的 join 回显中 This node has joined the cluster 即加入成功标志。

八、集群验证(master 节点)

8.1 节点状态:1 master + 3 worker 全部 Ready

kubectl get nodes -o wide

实测回显(四个节点全部 Ready,节点名标注):

NAME                 STATUS   ROLES           AGE     VERSION    INTERNAL-IP   EXTERNAL-IP   OS-IMAGE         KERNEL-VERSION      CONTAINER-RUNTIME
kubeadm-c8-master    Ready    control-plane   7h59m   v1.31.14   172.25.0.2    <none>        CentOS Linux 8   6.8.0-138-generic   containerd://1.6.32
kubeadm-c8-worker1   Ready    <none>          29m     v1.31.14   172.25.0.3    <none>        CentOS Linux 8   6.8.0-138-generic   containerd://1.6.32
kubeadm-c8-worker2   Ready    <none>          2m22s   v1.31.14   172.25.0.6    <none>        CentOS Linux 8   6.8.0-138-generic   containerd://1.6.32
kubeadm-c8-worker3   Ready    <none>          80s     v1.31.14   172.25.0.7    <none>        CentOS Linux 8   6.8.0-138-generic   containerd://1.6.32

8.2 系统组件状态

kubectl get pods -A -o wide

实测回显:

NAMESPACE      NAME                                        READY   STATUS    RESTARTS       AGE     IP            NODE                 NOMINATED NODE   READINESS GATES
kube-flannel   kube-flannel-ds-46vms                       1/1     Running   0              81s     172.25.0.7    kubeadm-c8-worker3   <none>           <none>
kube-flannel   kube-flannel-ds-ckwr8                       1/1     Running   30 (28m ago)   7h54m   172.25.0.2    kubeadm-c8-master    <none>           <none>
kube-flannel   kube-flannel-ds-ghx72                       1/1     Running   0              24m     172.25.0.3    kubeadm-c8-worker1   <none>           <none>
kube-flannel   kube-flannel-ds-s8vx6                       1/1     Running   0              2m23s   172.25.0.6    kubeadm-c8-worker2   <none>           <none>
kube-system    coredns-fcd6c9c4-kdptk                      1/1     Running   21 (27m ago)   7h52m   10.244.0.63   kubeadm-c8-master    <none>           <none>
kube-system    coredns-fcd6c9c4-x55cn                      1/1     Running   22 (27m ago)   7h52m   10.244.0.62   kubeadm-c8-master    <none>           <none>
kube-system    etcd-kubeadm-c8-master                      1/1     Running   24 (28m ago)   7h59m   172.25.0.2    kubeadm-c8-master    <none>           <none>
kube-system    kube-apiserver-kubeadm-c8-master            1/1     Running   31 (28m ago)   7h59m   172.25.0.2    kubeadm-c8-master    <none>           <none>
kube-system    kube-controller-manager-kubeadm-c8-master   1/1     Running   36 (28m ago)   7h59m   172.25.0.2    kubeadm-c8-master    <none>           <none>
kube-system    kube-proxy-5hfnb                            1/1     Running   0              24m     172.25.0.3    kubeadm-c8-worker1   <none>           <none>
kube-system    kube-proxy-rkb2g                            1/1     Running   0              81s     172.25.0.7    kubeadm-c8-worker3   <none>           <none>
kube-system    kube-proxy-rx56p                            1/1     Running   20 (27m ago)   7h59m   172.25.0.2    kubeadm-c8-master    <none>           <none>
kube-system    kube-proxy-xkqjb                            1/1     Running   0              2m23s   172.25.0.6    kubeadm-c8-worker2   <none>           <none>
kube-system    kube-scheduler-kubeadm-c8-master            1/1     Running   35 (28m ago)   7h59m   172.25.0.2    kubeadm-c8-master    <none>           <none>

💡 控制面四件套(apiserver/etcd/controller-manager/scheduler)与 coredns 双副本、kube-proxy、kube-flannel 均 Running;各组件有一定 RESTARTS 计数是 worker 加入初期 cgroup 修复过程的历史记录(见 FAQ Q1),集群处于稳定运行态。

8.3 部署测试应用并跨节点调度

移除控制面污点以便 master 也参与调度(可选):

kubectl taint nodes kubeadm-c8-master node-role.kubernetes.io/control-plane-

部署 nginx 副本与 Service:

kubectl create deployment nginx-demo --image=nginx:alpine --replicas=2
kubectl expose deployment nginx-demo --name=nginx-svc --port=80 --target-port=80

查看 Deployment 与 Service(实测回显):

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES         SELECTOR
deployment.apps/nginx-demo   2/2     2            2           6h57m   nginx        nginx:alpine   app=nginx-demo

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE     SELECTOR
service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   7h59m   <none>
service/nginx-svc    ClusterIP   10.102.29.138   <none>        80/TCP    6h57m   app=nginx-demo

💡 两个 nginx 副本分别调度到 master(10.244.0.61)与 worker1(10.244.1.2),说明跨节点调度与 flannel 网络互通正常。

8.4 集群信息

kubectl cluster-info
kubectl get nodes
kubeadm token list

实测回显(cluster-info 节选):

Kubernetes control plane is running at https://172.25.0.2:6443
CoreDNS is running at https://172.25.0.2:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

TOKEN                     TTL         EXPIRES                USAGES                   DESCRIPTION                                                EXTRA GROUPS
wsc0j6.pwxs39dki400ptcp   16h         2026-10-11T04:51:30Z   authentication,signing   <none>                                                     system:bootstrappers:kubeadm:default-node-token

九、常见问题(FAQ)

Q1:worker join 后 master 控制面全部 CrashLoopBackOff(cgroup 驱动冲突)?

现象:worker join 成功瞬间,master 上 kubectl get pods -A 看到 apiserver/etcd/controller-manager/scheduler 全部 CrashLoopBackOff,Get https://172.25.0.2:6443: connect: connection refused。

定位:worker 的 kubelet 使用 systemd cgroup 驱动 + containerd SystemdCgroup=true,与 master 在共享 cgroup 树(cgroupns=host 嵌套环境)上冲突,干扰 master kubelet 的 cgroup 监听。

解决(worker 侧):

  1. 立即停 worker 的 kubelet(master 会在 30 秒内自动恢复);
  2. 改 containerd 配置 SystemdCgroup = false(/etc/containerd/config.toml 的 runc options 段);
  3. 改 kubelet 配置 /var/lib/kubelet/config.yaml 为 cgroupDriver: cgroupfs;
  4. systemctl restart containerd kubelet,kubectl get nodes 看到 worker Ready。

预防:按 5.4 节把 kubelet-config ConfigMap 的 cgroupDriver 统一为 cgroupfs,后续 join 的 worker 自动获得一致配置,不会触发该问题。

Q2:kubeadm init 报 [WARNING SystemVerification]: failed to parse kernel config?

定位:/boot 下缺少内核 config 文件(/lib/modules/$(uname -r) 无 configs 模块),常见于精简安装环境。

解决:init/join 加 --ignore-preflight-errors=SystemVerification;物理机若存在 /boot/config-$(uname -r) 则不会出现。

Q3:worker join 预检报 [ERROR FileExisting-conntrack]?

解决:yum install -y conntrack-tools 后重试 join。

Q4:flannel 或 kube-proxy 在 worker 上报 expected cgroupsPath to be of format "slice:prefix:name" for systemd cgroups?

定位:该 pod 创建于 containerd SystemdCgroup 切换的过渡期,残留了旧驱动状态。

解决:删除对应 pod 强制重建,新 pod 以当前配置干净启动:

kubectl delete pod -n kube-flannel <pod-name> --force --grace-period=0

Q5:拉取 docker.io 镜像长时间无进度?

定位:docker.io 默认域名直连不稳定(本实验环境 blob 走 CloudFront CDN 被墙)。

解决:按 3.3 节配置 certs.d/docker.io/hosts.toml 指向 docker.m.daocloud.io 加速源并重启 containerd。注意该加速仅对 CRI 拉取生效(kubelet/crictl 走 config_path),直接执行 ctr images pull docker.io/library/nginx:alpine 不读取该配置。

十、卸载与清理

将某节点移出集群:

kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data

在目标节点执行:

kubeadm reset -f --ignore-preflight-errors=SystemVerification
rm -rf /etc/kubernetes /var/lib/kubelet /var/lib/etcd /etc/cni/net.d /run/flannel
systemctl disable --now kubelet
systemctl disable --now containerd

💡 卸载前确认集群数据已备份;kubeadm reset 会清理证书、配置与 etcd 数据,不可恢复。

参考链接

CentOS 8 安装 Kubernetes(kubeadm)集群指南 - Quzuo