k8s-1.35-centos7-安装文档

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. 重要前置说明(务必先读)

  1. CentOS 7 已于 2024-06-30 EOL :官方镜像源已下线,本文档使用 vault.centos.org 归档源。内核为 3.10,属非官方推荐组合,仅在你确实需要 CentOS 7 时使用。生产环境更推荐 Rocky/Alma Linux 9 或 Ubuntu 22.04+。
  2. 版本 :Kubernetes 1.35(使用 pkgs.k8s.iov1.35 稳定源),容器运行时使用 containerd。
  3. 网络:K8s 组件二进制为静态编译,可在 CentOS 7 的 glibc 2.17 上运行;无需升级 glibc。
  4. 镜像加速 :如在中国大陆,本文档提供 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 反复改,直接重新生成后再精确替换:

bash 复制代码
containerd 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 缓慢,可改用阿里云镜像源(把 baseurlgpgkey 换成 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 deprecatedCentOS 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-config ConfigMap 下发到 node,kubelet 本身没问题,只需在 join 命令末尾加上跳过系统校验即可:

bash 复制代码
kubeadm join 192.168.1.10:6443 --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash> \
  --ignore-preflight-errors=SystemVerification

token 默认 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 版:

bash 复制代码
kubectl 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 nodeMetrics API not available,是因为集群没装 metrics-server。它提供节点/Pod 的实时 CPU、内存用量(仅实时,不存历史)。

版本选择(官方兼容性矩阵) :metrics-server 0.8.x 支持 K8s 1.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

镜像拉取超时 / ImagePullBackOffregistry.k8s.io ... i/o timeout):metrics-server 镜像来自 registry.k8s.io,国内不通。两种解法二选一:

  1. 按 3.2 给 registry.k8s.io 配加速(在跑该 Pod 的节点上配 k8s.m.daocloud.io 并重启 containerd),保持官方镜像地址即可;
  2. 换阿里云镜像:
bash 复制代码
kubectl 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.yamlNFS_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/patchnumberOfReplicas;② 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 节点:1025030000-32767(NodePort)。CNI 另有要求(Flannel VXLAN 需 UDP 8472)。


10. 常见问题排查

现象 可能原因 / 处理
kubeadm init 拉镜像超时 确认使用了 --image-repository=registry.aliyuncs.com/google_containerscrictl 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
相关推荐
分布式存储与RustFS1 小时前
RustFS保姆级教程:Docker快速部署兼容S3的本地对象存储
运维·docker·容器·rustfs部署教程·本地搭建s3对象存储·rustfs网页控制台使用·awscli连接rustfs
江湖有缘1 小时前
Docker部署Papra极简文件归档平台
运维·docker·容器
qq_349447953 小时前
十四、k8s集群安装kube-state-metrics 组件
docker·容器·kubernetes
AOwhisky3 小时前
Kubernetes(K8s)学习笔记(第十四期):集群存储与有状态应用(下篇):StatefulSet 有状态应用管理
redis·笔记·mysql·云原生·kubernetes·云计算·k8s
我叫张小白。4 小时前
Docker镜像构建原理与Dockerfile工程化实践深度剖析
运维·docker·容器
艾文伯特4 小时前
k8s-1.35-ubuntu-安装文档.md
ubuntu·容器·kubernetes
AOwhisky4 小时前
kubernetes(K8s)学习笔记:第八期与第九期核心知识点自测与详解
笔记·云原生·kubernetes·云计算·k8s·集群·网络策略
条纹布鲁斯4 小时前
ubuntu 26.04 k8s 1.36 ceph
kubernetes
爱吃龙利鱼4 小时前
k8s指定命名空间kubeconfig文件生成教程
容器·kubernetes