CentOS 7 使用 kubeadm 安装 Kubernetes 1.35 部署文档
维护说明:本文档由 Kiro 持续维护。你负责执行,遇到问题把报错贴回来,我会更新对应章节。
最近更新:2026-07-04
✅ 部署状态(2026-07-04 实测通过) :k8s-master + k8s-node1 均已Ready,K8s 版本 v1.35.6,containerd 1.6.33,CNI 用 Flannel v0.28.5。环境为 CentOS 7(内核 3.10,cgroup v1,已用failCgroupV1: false强制运行------属非受支持组合,1.36 升级前需迁移到 cgroup v2 系统)。
0. 重要前置说明(务必先读)
- CentOS 7 已于 2024-06-30 EOL :官方镜像源已下线,本文档使用
vault.centos.org归档源。内核为 3.10,属非官方推荐组合,仅在你确实需要 CentOS 7 时使用。生产环境更推荐 Rocky/Alma Linux 9 或 Ubuntu 22.04+。 - 版本 :Kubernetes 1.35(使用
pkgs.k8s.io的v1.35稳定源),容器运行时使用 containerd。 - 网络:K8s 组件二进制为静态编译,可在 CentOS 7 的 glibc 2.17 上运行;无需升级 glibc。
- 镜像加速 :如在中国大陆,本文档提供
registry.aliyuncs.com镜像仓库替代方案,避免拉取registry.k8s.io超时。
1. 节点规划
| 角色 | 主机名 | IP(示例) | 配置最低要求 |
|---|---|---|---|
| 控制平面 | k8s-master | 192.168.1.10 | 2C4G,磁盘 ≥ 20G |
| 工作节点1 | k8s-node1 | 192.168.1.11 | 2C4G |
| 工作节点2 | k8s-node2 | 192.168.1.12 | 2C4G |
请把上面的 IP / 主机名替换成你的真实值。下文命令中出现的 IP 同样需要替换。
Pod 网段本文档用
10.244.0.0/16(Flannel 默认)。若改用 Calico,见第 7 节。
2. 系统初始化(所有节点都要执行)
2.1 修复 CentOS 7 EOL 的 yum 源
bash
# 备份原有源
mkdir -p /etc/yum.repos.d/bak && mv /etc/yum.repos.d/CentOS-*.repo /etc/yum.repos.d/bak/ 2>/dev/null
# 切换到 vault 归档源
cat > /etc/yum.repos.d/CentOS-Base.repo << 'EOF'
[base]
name=CentOS-7 - Base
baseurl=https://vault.centos.org/7.9.2009/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
[updates]
name=CentOS-7 - Updates
baseurl=https://vault.centos.org/7.9.2009/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
[extras]
name=CentOS-7 - Extras
baseurl=https://vault.centos.org/7.9.2009/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
EOF
yum clean all && yum makecache
2.2 设置主机名与 hosts(按节点分别设置主机名)
bash
# 在 master 执行
hostnamectl set-hostname k8s-master
# 在 node1 执行: hostnamectl set-hostname k8s-node1
# 在 node2 执行: hostnamectl set-hostname k8s-node2
# 所有节点都写入 hosts
cat >> /etc/hosts << 'EOF'
192.168.1.10 k8s-master
192.168.1.11 k8s-node1
192.168.1.12 k8s-node2
EOF
2.3 关闭 swap、SELinux、防火墙
bash
# 关闭 swap(当前 + 永久)
swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab
# 关闭 SELinux
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config
# 关闭防火墙(生产如需保留,请改为放行 K8s 端口,见附录 A)
systemctl stop firewalld && systemctl disable firewalld
2.4 时间同步
bash
yum install -y ntpdate
timedatectl set-timezone Asia/Shanghai
ntpdate ntp.aliyun.com
# 加入定时任务
echo '*/5 * * * * /usr/sbin/ntpdate ntp.aliyun.com > /dev/null 2>&1' >> /var/spool/cron/root
2.5 内核模块与内核参数
bash
# 加载模块
cat > /etc/modules-load.d/k8s.conf << 'EOF'
overlay
br_netfilter
EOF
modprobe overlay
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
2.6 安装 ipset / ipvs(推荐 kube-proxy 使用 ipvs 模式)
bash
yum install -y ipset ipvsadm
cat > /etc/modules-load.d/ipvs.conf << 'EOF'
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
EOF
for m in ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack; do modprobe $m; done
3. 安装容器运行时 containerd(所有节点)
bash
# 安装 docker-ce 源(含 containerd.io 包),使用阿里云镜像
yum install -y yum-utils
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install -y containerd.io
3.1 生成并修改 containerd 配置
bash
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
# 1) 启用 SystemdCgroup(K8s 要求)
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# 2) 替换 pause 沙箱镜像为阿里云地址(避免拉取失败)
# containerd 1.6.x 默认写的是 pause:3.6(太旧),K8s 1.35 需要 3.10
sed -i 's#sandbox_image = "registry.k8s.io/pause:3.6"#sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.10"#' /etc/containerd/config.toml
注意 1(pause 版本) :K8s 1.35 要求 pause 3.10 ,不是 containerd 默认的 3.6。以
kubeadm config images list --kubernetes-version v1.35.0输出为准,若不是 3.10 请改成实际值。注意 2(配置改坏排查) :如果
systemctl restart containerd报错failed to load TOML: ... keys cannot contain / character,说明 config.toml 被改坏了。不要用 vim 反复改,直接重新生成后再精确替换:
bashcontainerd config default > /etc/containerd/config.toml sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml sed -i 's#sandbox_image = "registry.k8s.io/pause:3.6"#sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.10"#' /etc/containerd/config.toml
bash
systemctl enable --now containerd
systemctl restart containerd
# 验证:能正常 dump 出内容说明 TOML 合法,且两项配置生效
containerd config dump | grep -E 'SystemdCgroup|sandbox_image'
3.2 配置 Docker Hub 镜像加速(国内必备,所有节点)
业务镜像(如 nginx)默认从 Docker Hub(registry-1.docker.io)拉取,国内常超时(dial tcp ... i/o timeout / ImagePullBackOff)。给 containerd 配镜像加速:
bash
# 1) 启用 certs.d 目录
sed -i 's#config_path = ""#config_path = "/etc/containerd/certs.d"#' /etc/containerd/config.toml
# 2) 为 docker.io 配置镜像站(多个,按序回退)
mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml << 'EOF'
server = "https://docker.io"
[host."https://docker.m.daocloud.io"]
capabilities = ["pull", "resolve"]
[host."https://docker.1panel.live"]
capabilities = ["pull", "resolve"]
[host."https://hub.rat.dev"]
capabilities = ["pull", "resolve"]
EOF
# 2b) 为 registry.k8s.io 配置加速(metrics-server 等镜像来自这里,docker.io 加速覆盖不到)
mkdir -p /etc/containerd/certs.d/registry.k8s.io
cat > /etc/containerd/certs.d/registry.k8s.io/hosts.toml << 'EOF'
server = "https://registry.k8s.io"
[host."https://k8s.m.daocloud.io"]
capabilities = ["pull", "resolve"]
EOF
# 3) 重启生效
systemctl restart containerd
# 验证
crictl pull nginx
国内公共镜像站经常变动,若全部超时请更换为当前可用的地址。此加速对所有从 Docker Hub / registry.k8s.io 拉取的镜像生效,每个节点都要配。
4. 安装 kubeadm / kubelet / kubectl 1.35(所有节点)
bash
cat > /etc/yum.repos.d/kubernetes.repo << 'EOF'
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.35/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.35/rpm/repodata/repomd.xml.key
EOF
yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
systemctl enable kubelet
若在国内访问
pkgs.k8s.io缓慢,可改用阿里云镜像源(把baseurl与gpgkey换成https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.35/rpm/对应路径)。若阿里云暂未同步 1.35,则仍用官方源。
5. 初始化控制平面(仅 master 执行)
前置 preflight 常见致命错误:
[ERROR NumCPU]: ... less than the required 2:control-plane 至少 2 核。关机给虚拟机加到 ≥2 vCPU 再开机。[ERROR SystemVerification]: cgroups v1 support is deprecated:CentOS 7 内核 3.10 只有 cgroup v1 ,而 K8s 1.35 已把 cgroup v1 判为致命错误。需按下面【方式二】强制开启 cgroup v1(failCgroupV1: false)。这属于非受支持组合,K8s 1.36 会彻底移除 cgroup v1,届时本方案失效------生产环境请优先换 Rocky/Alma 9 或 Ubuntu 22.04+。
方式一:命令行 init(内核/环境正常时用,如 Rocky 9、Ubuntu 22.04)
bash
kubeadm init \
--kubernetes-version=v1.35.0 \
--apiserver-advertise-address=192.168.1.10 \
--image-repository=registry.aliyuncs.com/google_containers \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12
把
192.168.1.10换成 master 真实 IP。--kubernetes-version请填实际存在的补丁版本(用yum list --showduplicates kubeadm查看)。
方式二:配置文件 init(内核过低 / cgroup v1,如 CentOS 7)
内核过低导致 cgroup v1 报错时,用配置文件把 failCgroupV1 设为 false,并在 init 时跳过系统校验:
bash
cat > /root/kubeadm-config.yaml << 'EOF'
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.25.71
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.35.0
imageRepository: registry.aliyuncs.com/google_containers
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
failCgroupV1: false
EOF
kubeadm init --config /root/kubeadm-config.yaml \
--ignore-preflight-errors=SystemVerification
advertiseAddress换成 master 真实 IP(示例为 192.168.25.71)。- 若 CPU 无法加到 2 核,可再追加忽略项:
--ignore-preflight-errors=SystemVerification,NumCPU(但强烈建议真加到 2 核)。failCgroupV1: false的含义:告诉 kubelet「即使检测到 cgroup v1 也不要报错退出」,从而允许在老内核上运行。
初始化成功后,按提示配置 kubectl:
bash
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
保存输出末尾的 kubeadm join ... 命令,node 节点加入时要用。若丢失可重新生成:
bash
kubeadm token create --print-join-command
6. 工作节点加入集群(仅 node 执行)
在每个 node 上执行第 5 步输出的 join 命令,形如:
bash
kubeadm join 192.168.1.10:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
CentOS 7(cgroup v1)注意 :node 加入同样会触发 cgroup v1 的致命 preflight 校验。master init 时设置的
failCgroupV1: false已随kubelet-configConfigMap 下发到 node,kubelet 本身没问题,只需在 join 命令末尾加上跳过系统校验即可:
bashkubeadm join 192.168.1.10:6443 --token <token> \ --discovery-token-ca-cert-hash sha256:<hash> \ --ignore-preflight-errors=SystemVerificationtoken 默认 24 小时过期。失效时在 master 执行
kubeadm token create --print-join-command重新生成,再在末尾补--ignore-preflight-errors=SystemVerification。
7. 安装网络插件 CNI(仅 master 执行)
方案 A:Flannel(简单,匹配 10.244.0.0/16)
务必用 pinned release 版本的清单,不要用
master分支的Documentation/kube-flannel.yml。 master 分支清单的启动命令(如/opt/bin/install-conf)可能与 release 镜像对不上,导致 init 容器报no such file or directory/Init:CrashLoopBackOff。
bash
# 用固定 release 版本,保证 manifest 与镜像一致
kubectl apply -f https://github.com/flannel-io/flannel/releases/download/v0.28.5/kube-flannel.yml
观察 flannel pod 起来、节点转 Ready:
bash
kubectl get pods -n kube-flannel -o wide -w
kubectl get nodes
若已误装 master 分支版本并报错,先删除再装 release 版:
bashkubectl delete -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml kubectl apply -f https://github.com/flannel-io/flannel/releases/download/v0.28.5/kube-flannel.yml
方案 B:Calico(功能更全)
如改用 Calico,kubeadm init 的 --pod-network-cidr 建议用 192.168.0.0/16,并参考 Calico 官方 manifest 部署。(需要时告诉我,我补充完整 Calico 步骤。)
8. 验证集群
bash
# 1) 节点应全部 Ready(CNI 就绪后)
kubectl get nodes -o wide
# 2) 系统组件全部 Running;重点确认 coredns 从 Pending 变为 Running
# (coredns 在 CNI 未就绪前会一直 Pending,装完 flannel 后才会调度起来)
kubectl get pods -A
# 3) 快速冒烟测试:部署 nginx 并通过 NodePort 访问
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80 --type=NodePort
kubectl get svc nginx # 记下分配的 NodePort(3xxxx)
# 用 http://<任一节点IP>:<NodePort> 访问,或:
curl http://192.168.25.71:$(kubectl get svc nginx -o jsonpath='{.spec.ports[0].nodePort}')
# 清理测试资源
kubectl delete deployment nginx && kubectl delete svc nginx
8.1 (可选)允许 master 节点调度业务 Pod
默认 control-plane 节点带有污点 node-role.kubernetes.io/control-plane:NoSchedule,业务 Pod 不会调度上去。小集群/测试环境如需让 master 也跑业务 Pod:
bash
# 查看当前污点
kubectl describe node k8s-master | grep -i taint
# 移除污点(结尾的 - 表示删除)
kubectl taint nodes k8s-master node-role.kubernetes.io/control-plane:NoSchedule-
# 如需恢复污点(重新禁止调度)
kubectl taint nodes k8s-master node-role.kubernetes.io/control-plane=:NoSchedule
生产环境建议保留该污点:control-plane 运行 etcd / apiserver 等关键组件,业务 Pod 抢占资源可能影响控制面稳定性。
8.2 (可选)安装 metrics-server(kubectl top / HPA 数据源)
kubectl top node 报 Metrics API not available,是因为集群没装 metrics-server。它提供节点/Pod 的实时 CPU、内存用量(仅实时,不存历史)。
版本选择(官方兼容性矩阵) :metrics-server
0.8.x支持 K8s1.31+,对应本集群 1.35。0.7.x支持 1.27+,0.6.x支持 1.25+。这里用 pinned 版本v0.8.0,不要用latest。如需该系列最新补丁,去 releases 页把版本号换成更高的v0.8.x即可。
bash
# 1) 安装 pinned 版本 v0.8.0(0.8.x 支持 K8s 1.31+)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.8.0/components.yaml
# 2) 必需补丁:kubeadm 的 kubelet 证书是自签名的,需跳过 TLS 校验,
# 否则 metrics-server 报 x509 错误、Pod 一直 0/1
kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
# 3) 验证(等 Pod 1/1 Running 后)
kubectl get pods -n kube-system | grep metrics-server
kubectl top nodes
kubectl top pods -A
镜像拉取超时 / ImagePullBackOff (
registry.k8s.io ... i/o timeout):metrics-server 镜像来自registry.k8s.io,国内不通。两种解法二选一:
- 按 3.2 给
registry.k8s.io配加速(在跑该 Pod 的节点上配k8s.m.daocloud.io并重启 containerd),保持官方镜像地址即可;- 换阿里云镜像:
bashkubectl get deploy metrics-server -n kube-system -o jsonpath='{.spec.template.spec.containers[0].image}' kubectl set image deployment/metrics-server -n kube-system \ metrics-server=registry.aliyuncs.com/google_containers/metrics-server:v0.8.0
完整监控(历史/面板/告警) :metrics-server 只有实时数据。需要曲线、可视化、告警请部署 Prometheus + Grafana(社区
kube-prometheus-stack)。
8.3 (可选)安装 Kuboard 管理界面(Docker 独立部署,推荐)
为什么不用"在 K8s 内安装 Kuboard"?
Kuboard v3 的 in-cluster 装法自带一个
etcd-host组件,它把端口 写死为 2381/2382 、并且强制调度到 control-plane 节点 。而 kubeadm 集群的 etcd 已经占用了 master 上的 2381(--listen-metrics-urls=http://127.0.0.1:2381),二者冲突:
- 改 YAML 端口没用(端口写死在 etcd 镜像里,改了只会让探针/hostPort 对不上而 CrashLoop);
- 把 etcd 挪到 worker 也没用(镜像把 etcd 成员写死成 control-plane 节点,跑在 worker 上会报
couldn't find local name)。这是官方长期未解决的已知问题(kuboard-press issue #341,报错
listen tcp <IP>:2381: bind: address already in use)。因此在 kubeadm 集群上,推荐用 Docker 独立部署 Kuboard(etcd 内置在容器里,无冲突)。
bash
# 1) 安装 docker(前面已加过 docker-ce 源,可直接装;与 kubelet 使用的 containerd 可共存)
yum install -y docker-ce docker-ce-cli
systemctl enable --now docker
# 2) 给 docker 配镜像加速(重要!docker 走 daemon.json,与 containerd 的 certs.d 相互独立,
# 否则 docker pull 仍会直连 Docker Hub 超时)
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.1panel.live",
"https://hub.rat.dev"
]
}
EOF
systemctl daemon-reload
systemctl restart docker
# 3) 用独立容器运行 Kuboard(内置 etcd,数据持久化到 /root/kuboard-data)
docker run -d \
--restart=unless-stopped \
--name=kuboard \
-p 80:80/tcp \
-p 10081:10081/tcp \
-e KUBOARD_ENDPOINT="http://192.168.25.71:80" \
-e KUBOARD_AGENT_SERVER_TCP_PORT="10081" \
-v /root/kuboard-data:/data \
eipwork/kuboard:v3
访问与登录:
http://192.168.25.71 (即 master 节点 IP,端口 80)
用户名:admin
密码: Kuboard123456
登录后在界面里「添加集群」,用 kubeconfig 或 token 方式导入当前 K8s 集群即可。
注意事项:
KUBOARD_ENDPOINT里的 IP 换成 master 真实 IP。- docker 镜像加速独立于 containerd :docker 走
/etc/docker/daemon.json(上面第 2 步),和 containerd 的certs.d是两套、互不影响。docker pull eipwork/kuboard:v3仍超时说明镜像站不通,换地址即可。- 自定义对外端口 :如想用 30000 暴露,把
-p 80:80改为-p 30000:80,并同步 把KUBOARD_ENDPOINT改为http://<IP>:30000(agent 回连用,必须与实际对外端口一致),访问地址也随之变为http://<IP>:30000。- master 的 80 端口若被占用,换其他端口同理;防火墙开启时放行对外端口和
10081。- 安装 docker-ce 时可能短暂重启 containerd,kubelet 会瞬时抖动后自动恢复,属正常。
8.4 (可选)配置持久化存储:NFS 动态供给 StorageClass
多节点集群里 Pod 会在不同节点间调度,local-path 存储数据绑死在单节点、Pod 漂移就丢;测试环境推荐用 NFS(共享存储,任意节点可访问,支持 RWX,复杂度适中)。下面搭建 NFS + 动态供给器,之后建 PVC 会自动创建 PV。
概念:Pod →(申请)PVC →(实际卷)PV。StorageClass 是动态创建 PV 的模板,绑定一个 provisioner 对接后端存储。配好默认 SC 后,PVC 不指定 SC 就自动用它。
第一步:搭 NFS 服务端(选一台机器,如 master)
bash
yum install -y nfs-utils rpcbind
mkdir -p /data/nfs/k8s
chown nfsnobody:nfsnobody /data/nfs/k8s
# 导出目录(* 允许所有客户端,测试环境可用;生产建议限定网段)
echo '/data/nfs/k8s *(rw,sync,no_subtree_check,no_root_squash)' >> /etc/exports
exportfs -r
systemctl enable --now rpcbind nfs-server
# 验证:应看到 /data/nfs/k8s
showmount -e localhost
若开着 firewalld,需放行 NFS:
firewall-cmd --permanent --add-service={nfs,rpc-bind,mountd} && firewall-cmd --reload。
第二步:所有节点(master + 所有 worker)装 NFS 客户端
必须做,否则 Pod 挂载 NFS 卷会失败:
bash
yum install -y nfs-utils
第三步:部署 NFS 动态供给器 + StorageClass(master 执行)
把下面存成 nfs-provisioner.yaml,将 NFS_SERVER_IP 换成 NFS 服务端 IP(出现两处):
yaml
apiVersion: v1
kind: Namespace
metadata:
name: nfs-provisioner
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: nfs-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs-provisioner
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: nfs-provisioner
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs-provisioner
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: nfs-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: NFS_SERVER_IP
- name: NFS_PATH
value: /data/nfs/k8s
volumes:
- name: nfs-client-root
nfs:
server: NFS_SERVER_IP
path: /data/nfs/k8s
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
archiveOnDelete: "false"
reclaimPolicy: Delete
volumeBindingMode: Immediate
应用并验证:
bash
kubectl apply -f nfs-provisioner.yaml
# provisioner 应 1/1 Running(镜像来自 registry.k8s.io,已配加速可拉取)
kubectl get pods -n nfs-provisioner
# StorageClass 应带 (default) 标记
kubectl get sc
验证动态供给
bash
cat > test-pvc.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 1Gi
EOF
kubectl apply -f test-pvc.yaml
# 应自动 Bound(SC 自动创建了 PV)
kubectl get pvc test-pvc
kubectl get pv
# 验证完清理
kubectl delete -f test-pvc.yaml
- provisioner 镜像走 3.2 配的 registry.k8s.io 加速;拉不动就换 daocloud 镜像地址。
no_root_squash方便测试(容器 root 可写),生产要收紧权限并把*限定为具体网段。- NFS 服务端是单点,测试够用;生产要高可用可换 Longhorn(节点需额外磁盘)。
8.5 (可选,生产推荐)分布式存储:Longhorn
NFS(8.4)够用但有单点、性能一般。生产环境要数据高可用 (副本分布多节点、挂一台不丢数据)推荐 Longhorn:CNCF 项目、专为 K8s 设计、带 Web UI、支持快照/备份/DR,适合中小规模生产。3-4 节点集群用它比 Rook-Ceph 轻得多。
版本:使用当前活跃稳定分支 v1.11.3 (1.12.x 刚发布未标 stable,生产暂不尝鲜)。安装前可对照 Longhorn 支持矩阵 确认与 K8s 1.35 的兼容性。
8.4(NFS)与 8.5(Longhorn)二选一即可;若之前把 nfs-client 设成了默认 SC,启用 Longhorn 默认 SC 前要先取消 nfs-client 的默认标记(保证集群只有一个默认 SC)。
第一步:前置依赖(所有节点都要装)
Longhorn 卷挂载依赖 iSCSI,RWX 依赖 NFSv4:
bash
yum install -y iscsi-initiator-utils nfs-utils
systemctl enable --now iscsid
modprobe iscsi_tcp
# 让 iscsi_tcp 开机自载
echo iscsi_tcp > /etc/modules-load.d/iscsi-tcp.conf
(可选)用官方脚本检查所有节点环境是否就绪:
bash
curl -sSfL https://raw.githubusercontent.com/longhorn/longhorn/v1.11.3/scripts/environment_check.sh | bash
第二步:数据盘规划(生产建议)
Longhorn 默认把数据存在每个节点的 /var/lib/longhorn。生产建议给每个存储节点挂一块独立数据盘并挂载到该目录,避免和系统盘争 IO:
bash
# 示例:把 /dev/sdb 格式化并挂载到 /var/lib/longhorn(按实际盘符调整)
mkfs.ext4 /dev/sdb
mkdir -p /var/lib/longhorn
echo '/dev/sdb /var/lib/longhorn ext4 defaults 0 0' >> /etc/fstab
mount -a
第三步:安装 Longhorn(master 执行)
bash
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.11.3/deploy/longhorn.yaml
# 等 longhorn-system 下所有 pod Running(镜像来自 docker.io,已配 3.2 加速)
kubectl -n longhorn-system get pods -w
第四步:设为默认 StorageClass
Longhorn 安装后会自带名为 longhorn 的 StorageClass。设为默认:
bash
# 如果之前 nfs-client 是默认,先取消它
kubectl patch storageclass nfs-client -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}' 2>/dev/null || true
# 设 longhorn 为默认
kubectl patch storageclass longhorn -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
kubectl get sc # longhorn 应带 (default)
调整副本数(测试环境常改成 1 省资源)
⚠️ 两个坑:① StorageClass 的
parameters不可变 ,不能kubectl edit/patch改numberOfReplicas;②longhorn这个 SC 由 Longhorn 托管,直接delete后会被 自动重建(还是默认 3 副本)。所以有两种正确做法:
方式 A(推荐):另建一个自定义副本数的 SC 并设默认
bash
cat <<'EOF' | kubectl create -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-r1
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: driver.longhorn.io
allowVolumeExpansion: true
parameters:
numberOfReplicas: "1" # 测试 1;生产建议 2-3
staleReplicaTimeout: "30"
dataLocality: "disabled"
fsType: "ext4"
dataEngine: "v1"
reclaimPolicy: Delete
volumeBindingMode: Immediate
EOF
# 取消自带 longhorn 的默认标记,避免两个默认 SC
kubectl patch storageclass longhorn -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
kubectl get sc
方式 B(想保留 longhorn 这个名字):改它的托管模板 ConfigMap,再删 SC 让其按新模板重建
bash
# 编辑模板,把内嵌 yaml 里的 numberOfReplicas: "3" 改成 "1"
kubectl -n longhorn-system edit configmap longhorn-storageclass
# 删掉 SC,Longhorn 会用新模板自动重建(这次就是 1 副本)
kubectl delete sc longhorn
# 稍等几秒验证(应输出 1)
kubectl get sc longhorn -o jsonpath='{.parameters.numberOfReplicas}{"\n"}'
已存在的卷改副本数:UI → Volumes → 选卷 → Update Replica Count。UI 里 Settings 的 "Default Replica Count" 只影响之后从 UI 手动建的卷,不改 SC(也别和 "Default Minimum Number of BackingImage Copies" 搞混,后者是底图副本,与卷数据无关)。
第五步:访问 Web UI(注意安全)
⚠️ 安全警告:Longhorn UI 默认无任何认证。用 NodePort 裸暴露意味着任何能访问该端口的人都能操作/删除存储卷。
- 测试环境 :可临时用 NodePort 或
kubectl port-forward访问。- 生产环境 :必须把 UI 放到带认证的 Ingress 后面(如 nginx-ingress + basic-auth),不要用裸 NodePort 对外。
测试环境临时访问(本地端口转发,最安全):
bash
kubectl -n longhorn-system port-forward svc/longhorn-frontend 8000:80 --address 0.0.0.0
# 浏览器访问 http://<master-ip>:8000
生产环境(Ingress + basic-auth)示例:
bash
# 1) 生成 basic-auth 密码文件(需 httpd-tools 提供 htpasswd)
yum install -y httpd-tools
htpasswd -c auth admin # 按提示设密码
kubectl -n longhorn-system create secret generic basic-auth --from-file=auth
# 2) 创建带认证的 Ingress(需已部署 ingress-nginx;host 换成你的域名)
cat > longhorn-ingress.yaml << 'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: longhorn-ingress
namespace: longhorn-system
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'
spec:
ingressClassName: nginx
rules:
- host: longhorn.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: longhorn-frontend
port:
number: 80
EOF
kubectl apply -f longhorn-ingress.yaml
第六步:验证动态供给
bash
cat > test-pvc-longhorn.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-longhorn
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: longhorn
resources:
requests:
storage: 1Gi
EOF
kubectl apply -f test-pvc-longhorn.yaml
kubectl get pvc test-longhorn # 应 Bound
kubectl delete -f test-pvc-longhorn.yaml # 验证后清理
备份 / 灾备(生产建议)
在 Longhorn UI 的 Setting → General → Backup Target 配置备份目标(NFS nfs://server:/path 或 S3/MinIO),即可对卷做快照与备份,实现 DR。
说明:Longhorn 主要提供块存储(RWO);需要 RWX(多 Pod 共享)时它通过内置 share-manager(NFS)实现,性能略低于纯块。
9. 附录 A:保留防火墙时需放行的端口
若不关闭 firewalld,需放行(control-plane):
| 端口 | 用途 |
|---|---|
| 6443 | kube-apiserver |
| 2379-2380 | etcd |
| 10250 | kubelet API |
| 10257 | kube-controller-manager |
| 10259 | kube-scheduler |
worker 节点:10250、30000-32767(NodePort)。CNI 另有要求(Flannel VXLAN 需 UDP 8472)。
10. 常见问题排查
| 现象 | 可能原因 / 处理 |
|---|---|
kubeadm init 拉镜像超时 |
确认使用了 --image-repository=registry.aliyuncs.com/google_containers;crictl pull 单独测试拉取 |
| 节点一直 NotReady | CNI 未就绪:kubectl get pods -n kube-system 查看 flannel/calico 是否 Running |
| kubelet 起不来 | journalctl -u kubelet -f 看日志;常见是 cgroup driver 不一致(确认 containerd SystemdCgroup=true) |
| pause 镜像拉取失败 | 用 kubeadm config images list 确认所需 pause 版本(1.35 为 3.10),改 containerd sandbox_image |
| pkgs.k8s.io 访问慢/失败 | 改用阿里云 kubernetes-new 镜像源 |
业务镜像(nginx 等)拉取超时 registry-1.docker.io ... i/o timeout |
Docker Hub 国内不通,按 3.2 配 docker.io 镜像加速,配在跑 Pod 的节点上并重启 containerd |
metrics-server 拉取超时 registry.k8s.io ... i/o timeout |
registry.k8s.io 国内不通,按 3.2 配 registry.k8s.io 加速,或 kubectl set image 换阿里云镜像(见 8.2) |
containerd 启动报 failed to load TOML ... keys cannot contain / character |
config.toml 被改坏,重新 containerd config default 生成后再精确 sed(见 3.1 注意 2) |
kubeadm init 报 cgroups v1 support is deprecated |
内核过低(CentOS 7)。用第 5 节【方式二】配置文件 + failCgroupV1: false + --ignore-preflight-errors=SystemVerification |
kubeadm init 报 NumCPU ... less than the required 2 |
给虚拟机加 CPU 到 ≥2 核,或临时加 --ignore-preflight-errors=NumCPU |
join 卡在 Waiting for a healthy kubelet,kubelet 日志报 running with swap on is not supported |
swap 未关(常见于重启后 swap 又挂上)。执行 swapoff -a + 注释 /etc/fstab 中 swap 行,free -h 确认后 systemctl restart kubelet |
flannel init 容器报 /opt/bin/install-conf: no such file or directory / Init:CrashLoopBackOff |
用了 master 分支清单,与 release 镜像不匹配。删除后改用 pinned release 清单(见第 7 节方案 A) |
join 重复执行报 kubelet.conf already exists / Port 10250 is in use |
上次 join 其实已成功。先在 master kubectl get nodes 确认;确实要重装才在 node 上 kubeadm reset -f 后重试 |
11. 维护记录
| 日期 | 变更内容 |
|---|---|
| 2026-07-04 | 初版:CentOS 7 + containerd + K8s 1.35 kubeadm 部署流程 |
| 2026-07-04 | 修正 pause 版本为 3.10;补充 containerd TOML 损坏排查;新增第 5 节【方式二】配置文件 init(failCgroupV1=false,适配 CentOS 7 cgroup v1);保留原命令行 init 方式;更新排查表 |
| 2026-07-04 | 第 6 节补充 CentOS 7 下 node join 需加 --ignore-preflight-errors=SystemVerification(cgroup v1);说明 failCgroupV1 随 kubelet-config 自动下发、token 过期重生成方法 |
| 2026-07-04 | 第 7 节 Flannel 改用 pinned release 清单(v0.28.5),弃用 master 分支清单(避免 /opt/bin/install-conf 不匹配);排查表新增 swap 未关、flannel 版本不匹配、join 重复执行三条 |
| 2026-07-04 | ✅ 集群实测通过(master+node1 Ready,v1.35.6 + Flannel v0.28.5);开头加部署状态记录;第 8 节验证补充 coredns 就绪确认与 NodePort 冒烟测试 |
| 2026-07-04 | 新增 8.1 节:移除/恢复 control-plane 污点以允许 master 调度业务 Pod(附生产环境保留建议) |
| 2026-07-04 | 新增 8.2 节:安装 metrics-server(含 --kubelet-insecure-tls 补丁、阿里云镜像替换、kubectl top 验证) |
| 2026-07-04 | 8.2 节 metrics-server 改用 pinned 版本 v0.8.0(据官方兼容矩阵,0.8.x 支持 K8s 1.31+),并附版本选择说明 |
| 2026-07-04 | 新增 8.3 节:Kuboard 管理界面。记录 in-cluster 装法与 kubeadm etcd 的 2381 端口死结(官方 issue #341 长期未解决),改用 Docker 独立部署(内置 etcd,无冲突) |
| 2026-07-04 | 新增 3.2 节 Docker Hub / registry.k8s.io 镜像加速(certs.d + hosts.toml),解决 nginx、metrics-server 等镜像国内拉取超时;8.2 节与排查表同步更新 |
| 2026-07-04 | 新增 8.4 节:NFS 动态供给 StorageClass(nfs-subdir-external-provisioner),含 NFS 服务端/客户端搭建、provisioner 部署、默认 SC 与 PVC 验证 |
| 2026-07-04 | 新增 8.5 节:Longhorn 分布式存储(v1.11.3,生产推荐)。含前置依赖(iscsi/nfs)、数据盘规划、安装、默认 SC、UI 访问与安全加固(默认无认证,生产需 Ingress+basic-auth)、PVC 验证、备份/DR |
| 2026-07-04 | 8.5 第四步补充副本数调整两种方式:方式A(另建自定义副本数 SC 设默认)、方式B(改 longhorn-storageclass ConfigMap 模板后删 SC 触发重建);说明 SC parameters 不可变、Longhorn 自动重建托管 SC |