CentOS 9部署 Kubernetes(k8s) 集群(VMware NAT 网络)
CentOS 9 部署 Kubernetes 1.30 集群实战:一份可复制、可验证的完整手册
适用版本:Kubernetes v1.30.0
适用环境:VMware NAT,CentOS Stream 9,1 Master + 2 Worker
适用场景:Kubernetes 实验、GitOps 练习、云原生部署学习
这个项目是什么
这是一份从零搭建 Kubernetes 集群的完整项目文档。它不是零散命令的堆砌,而是一个带有明确验收标准的部署流程。
项目最终会得到一套可继续做实验的 Kubernetes 环境:
- 1 个控制平面节点和 2 个 Worker 节点
- containerd 容器运行时
- Flannel 网络插件
- Metrics Server 资源监控
- Local Path Provisioner 本地存储
- Ingress-Nginx 流量入口
- DNS、Service、Pod 网络、Ingress、PVC 等基础能力闭环验证
文章会按“环境规划、母版配置、克隆后网络配置、集群初始化、基础设施组件、最终验收、故障排查”的顺序展开。
适合谁阅读
这篇文章适合以下读者:
- 已经掌握 Linux 基础命令,希望亲手搭建 Kubernetes 实验环境的人
- 准备学习 GitOps、ArgoCD、Flux、CI/CD、微服务部署的人
- 需要一套可复现的本地 Kubernetes 测试环境的人
阅读本文前,建议先了解以下概念:
- Linux 系统管理、systemd、yum/dnf
- 网络基础:IP、网关、DNS、网卡
- Kubernetes 基本概念:Node、Pod、Service、Deployment、Ingress、PVC
读完这篇文章能得到什么
完成全部步骤后,你可以得到:
- 一台模板机,可快速克隆出多台 K8s 节点
- 一个 3 节点 Kubernetes 集群
- 一套带验证命令的部署流程
- 一套故障排查速查表
- 一个可以直接承载 GitOps 实验的基础环境
一、环境规划
1.1 集群规划
| 角色 | IP | 主机名 | 建议配置 |
|---|---|---|---|
| Master | 192.168.113.101 | k8s-master | 4 核 / 4 Gi+ RAM / 40 G 磁盘 |
| Node01 | 192.168.113.111 | k8s-node01 | 2 核 / 2 Gi+ RAM / 40 G 磁盘 |
| Node02 | 192.168.113.112 | k8s-node02 | 2 核 / 2 Gi+ RAM / 40 G 磁盘 |
1.2 软件版本
| 组件 | 版本 |
|---|---|
| 操作系统 | CentOS Stream 9 |
| Kubernetes | v1.30.0 |
| 容器运行时 | containerd.io |
| CNI | Flannel v0.26.6 |
| Ingress | ingress-nginx controller-v1.11.5 |
| Metrics Server | 建议固定到 release 版本 |
| StorageClass | local-path v0.0.31 |
1.3 网络规划
- VMware 网络:NAT
- 网段:192.168.113.0/24
- 网关:192.168.113.2
- Pod 网段:10.244.0.0/16
- Service 网段:10.96.0.0/12
- DNS 域名:cluster.local
二、设计思路
这套部署方案采用“模板机 + 克隆”的方式:
- 先在一台虚拟机上完成系统初始化、containerd、kubeadm、kubelet、kubectl 的安装。
- 关机后克隆出 Master 和 Worker。
- 每台克隆机只修改主机名、IP、DNS。
- 最后在 Master 上执行
kubeadm init,在 Worker 上执行kubeadm join。
这样做的优点是:
- 三台节点软件版本完全一致
- 安装过程只需要做一次
- 后续重建节点时更快
另一个关键设计是“每一步都有验证”。每个阶段都提供验证命令,避免到最后一刻才发现前面某一步失败。
三、第一阶段:模板机基础配置
执行位置:母版虚拟机,root 用户
目标:配置系统、内核参数、容器运行时和 Kubernetes 工具
3.1 系统基础设置
关闭 swap、SELinux 和 firewalld。Kubernetes 集群对这三项有比较严格的要求,实验环境建议直接关闭。
sudo swapoff -a
sudo sed -ri 's/.*swap.*/#&/' /etc/fstab
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config
sudo systemctl disable --now firewalld
验证:
free -h | grep -i swap
getenforce
systemctl status firewalld | grep Active
预期结果:
- swap 显示
0B getenforce返回Permissive,重启后为Disabled- firewalld 为
inactive (dead)
3.2 内核模块与网络参数
Kubernetes 需要 overlay 和 br_netfilter 内核模块,同时需要开启 IPv4 转发。
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system
验证:
lsmod | grep -E "overlay|br_netfilter"
sudo sysctl net.bridge.bridge-nf-call-iptables
sudo sysctl net.ipv4.ip_forward
预期结果:
- overlay 和 br_netfilter 都有输出
- 两个 sysctl 参数都为
1
3.3 安装基础工具
sudo dnf install -y wget curl net-tools vim bash-completion
3.4 安装 containerd 并配置镜像加速
这里使用阿里云 Docker CE 镜像源,并把 pause 镜像换成阿里云地址,避免国内网络拉取失败。
sudo curl -o /etc/yum.repos.d/docker-ce.repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sudo sed -i 's+download.docker.com+mirrors.aliyun.com/docker-ce+' \
/etc/yum.repos.d/docker-ce.repo
sudo dnf makecache
sudo dnf install -y containerd.io
生成 containerd 默认配置,并修改两个关键参数:
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml
PAUSE_IMAGE="registry.aliyuncs.com/google_containers/pause:3.10.2"
sudo sed -i "s|sandbox = '.*'|sandbox = '${PAUSE_IMAGE}'|" /etc/containerd/config.toml
sudo systemctl daemon-reload
sudo systemctl enable --now containerd
验证:
sudo systemctl is-active containerd
grep SystemdCgroup /etc/containerd/config.toml
grep "sandbox = " /etc/containerd/config.toml
预期结果:
- containerd 为
active SystemdCgroup = true- sandbox 指向阿里云 pause 镜像
3.5 安装 kubeadm、kubelet、kubectl
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/rpm/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/rpm/repodata/repomd.xml.key
EOF
sudo dnf install -y kubelet-1.30.0 kubeadm-1.30.0 kubectl-1.30.0
sudo systemctl enable --now kubelet
验证:
kubeadm version -o short
kubelet --version
kubectl version --client -o yaml | grep gitVersion
预期结果:
- kubeadm 为
v1.30.0 - kubelet 为
Kubernetes v1.30.0 - kubectl client 为
v1.30.0
提示:此时 kubelet 还没有集群配置,服务可能不会真正 Ready,这是正常现象,等
kubeadm init后才会正常。
完成后关机,基于这台母版克隆三台虚拟机。
四、第二阶段:克隆后网络配置
执行位置:三台克隆机分别执行,root 用户
4.1 设置主机名和 hosts
在每台机器上把 <HOSTNAME> 和 <IP> 替换成实际值:
HOSTNAME="<HOSTNAME>"
IP="<IP>"
sudo hostnamectl set-hostname "$HOSTNAME"
cat <<EOF | sudo tee /etc/hosts
127.0.0.1 localhost localhost.localdomain
192.168.113.101 k8s-master
192.168.113.111 k8s-node01
192.168.113.112 k8s-node02
EOF
4.2 修改 IP、网关和 DNS
ETH_NAME=$(ip route get 8.8.8.8 | awk '{print $5; exit}')
sudo nmcli connection modify "$ETH_NAME" \
ipv4.addresses "$IP/24" \
ipv4.gateway 192.168.113.2 \
ipv4.dns "223.5.5.5 114.114.114.114" \
ipv4.method manual
sudo nmcli connection up "$ETH_NAME"
验证:
hostname
ping -c 2 192.168.113.2
ping -c 2 k8s-master
预期结果:
- 三台主机名分别为
k8s-master、k8s-node01、k8s-node02 - 网关和主机名 ping 均为 0% packet loss
五、第三阶段:初始化集群
执行位置:k8s-master,root 用户
5.1 使用 YAML 配置 kubeadm init
相比直接在命令行写一堆参数,YAML 配置更容易阅读、复用和保存。
cat <<'EOF' | sudo tee kubeadm-init.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: "192.168.113.101"
bindPort: 6443
nodeRegistration:
criSocket: "unix:///var/run/containerd/containerd.sock"
name: "k8s-master"
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: "v1.30.0"
imageRepository: "registry.aliyuncs.com/google_containers"
controlPlaneEndpoint: "k8s-master:6443"
networking:
podSubnet: "10.244.0.0/16"
serviceSubnet: "10.96.0.0/12"
dnsDomain: "cluster.local"
apiServer:
extraArgs:
authorization-mode: "Node,RBAC"
enable-admission-plugins: "NodeRestriction"
controllerManager:
extraArgs:
bind-address: "0.0.0.0"
scheduler:
extraArgs:
bind-address: "0.0.0.0"
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: "systemd"
failSwapOn: true
EOF
sudo kubeadm init --config kubeadm-init.yaml --upload-certs
kubeadm init 完成后,会输出一条 join 命令。请保存下来:
kubeadm join k8s-master:6443 --token <TOKEN> --discovery-token-ca-cert-hash sha256:<HASH>
5.2 配置 kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
echo 'source <(kubectl completion bash)' >> ~/.bashrc
source ~/.bashrc
验证:
kubectl version -o yaml | grep gitVersion
kubectl get nodes
预期结果:
- client 和 server 都是
v1.30.0 k8s-master节点出现,STATUS 为NotReady
此时 NotReady 是正常的,因为网络插件还没有安装。
5.3 安装 Flannel
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/v0.26.6/Documentation/kube-flannel.yml
等待约 30 秒后验证:
kubectl get pods -n kube-flannel
kubectl get pods -n kube-system | grep coredns
预期结果:
- Flannel Pod 为
1/1 Running - CoreDNS 从
Pending变为Running
如果无法访问 GitHub raw 地址,需要为服务器配置代理,或使用可用的 Flannel 镜像源。
5.4 加入 Worker 节点
执行位置:k8s-node01 和 k8s-node02,root 用户
sudo kubeadm join k8s-master:6443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
如果 Token 过期,在 Master 上重新生成:
sudo kubeadm token create --print-join-command
在 Master 上验证:
kubectl get nodes
kubectl get pods -n kube-flannel -o wide
kubectl get pods -A | grep -v Running | grep -v Completed
预期结果:
- 三行节点全部
Ready - 3 个 Flannel Pod 全部
1/1 Running - 除了 Running 和 Completed,没有异常 Pod
六、第四阶段:基础设施组件部署
执行位置:k8s-master,root 用户
6.1 Metrics Server
Metrics Server 提供 kubectl top 所需的 Metrics API。
这里使用 release 版本清单。建议根据实验环境固定版本,避免 latest 突然变化。
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
实验环境中,通常需要增加 --kubelet-insecure-tls 参数:
kubectl patch deployment metrics-server -n kube-system \
--type='json' \
-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'
kubectl wait --for=condition=ready pod \
-l k8s-app=metrics-server -n kube-system --timeout=120s
验证:
kubectl top nodes
预期结果:
- 三行节点 CPU 和内存数据
- 没有
Metrics API not available错误
注意:
--kubelet-insecure-tls只适合本地实验环境。生产环境应配置受信任证书,不要关闭 TLS 校验。
6.2 Local Path Provisioner
Local Path Provisioner 会提供一个本地磁盘 StorageClass,适合实验环境。
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.31/deploy/local-path-storage.yaml
kubectl patch storageclass local-path \
-p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
验证:
kubectl get sc
预期结果:
local-path显示为(default)
Local Path 的 PVC 是 WaitForFirstConsumer,必须由 Pod 消费后才会绑定。做一次读写测试:
kubectl apply -f - <<'EOF2'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: test-pvc
containers:
- name: app
image: busybox:1.36
command: ["sleep", "300"]
volumeMounts:
- name: data
mountPath: /data
EOF2
kubectl wait --for=jsonpath='{.status.phase}'=Bound pvc/test-pvc --timeout=60s
kubectl get pvc test-pvc
测试完成后清理:
kubectl delete pod test-pod
kubectl delete pvc test-pvc
6.3 Ingress-Nginx Controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.5/deploy/static/provider/baremetal/deploy.yaml
等待约 60 秒后验证:
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx ingress-nginx-controller
预期结果:
- Ingress Controller Pod 为
1/1 Running - Service 显示 NodePort 端口
6.4 端到端 Ingress 测试
kubectl create deployment web-test --image=nginx:alpine --replicas=2
kubectl expose deployment web-test --port=80 --target-port=80
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test-ingress
spec:
ingressClassName: nginx
rules:
- host: test.k8s.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-test
port:
number: 80
EOF
验证:
NODE_PORT=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \
-o jsonpath='{.spec.ports[?(@.port==80)].nodePort}')
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
curl -s -H "Host: test.k8s.local" http://$NODE_IP:$NODE_PORT | head -5
预期结果:
- 返回 nginx 欢迎页 HTML
- 包含
<title>Welcome to nginx!</title>
测试完成后清理:
kubectl delete ingress test-ingress
kubectl delete service web-test
kubectl delete deployment web-test
七、最终验收清单
复制执行下面这些检查,确认环境已经闭环。
7.1 节点全部 Ready
kubectl get nodes -o wide
预期:三行节点,STATUS 全部 Ready。
7.2 系统 Pod 全部正常
kubectl get pods -A | grep -v Running | grep -v Completed
预期:无输出。
7.3 DNS 可用
kubectl run dns-test --rm -i --restart=Never --image=busybox:1.36 \
-- nslookup kubernetes.default.svc.cluster.local
预期:返回 10.96.0.1。
busybox 的 nslookup 不会自动追加 search 域,所以必须使用完整 FQDN。
7.4 跨节点 Pod 网络互通
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: net-a
spec:
nodeName: k8s-node01
containers:
- name: app
image: busybox:1.36
command: ["sleep", "300"]
---
apiVersion: v1
kind: Pod
metadata:
name: net-b
spec:
nodeName: k8s-node02
containers:
- name: app
image: busybox:1.36
command: ["sleep", "300"]
EOF
sleep 5
A_IP=$(kubectl get pod net-a -o jsonpath='{.status.podIP}')
kubectl exec net-b -- ping -c 2 "$A_IP"
预期:0% packet loss,回包为 10.244.x.x 的 Pod IP。
测试完成后清理:
kubectl delete pod net-a net-b
7.5 Metrics API 可用
kubectl top nodes
预期:显示 CPU% 和 MEMORY%。
7.6 StorageClass 就绪
kubectl get sc
预期:local-path (default)。
7.7 Ingress 就绪
kubectl get pods -n ingress-nginx
预期:Running 1/1。
八、故障排查速查表
| 现象 | 第一反应 | 深入排查 |
|---|---|---|
| 节点 NotReady | kubectl describe node <node> |
看 Conditions 和 Events |
| Pod Pending | kubectl describe pod <pod> |
资源不足或网络未就绪 |
| ImagePullBackOff | kubectl describe pod <pod> |
镜像 tag 或网络问题 |
| CrashLoopBackOff | kubectl logs <pod> --previous |
看上次崩溃日志 |
| Flannel 0/1 Not Ready | kubectl logs -n kube-flannel <pod> |
使用固定版本 v0.26.6 |
| DNS 不通 | kubectl logs -n kube-system -l k8s-app=kube-dns |
检查 CoreDNS 和 CNI |
| PVC Pending | kubectl describe pvc <pvc> |
检查 StorageClass 和调度节点 |
kubectl top 不可用 |
kubectl get pods -n kube-system | grep metrics |
检查 Metrics Server |
九、常见坑与注意事项
- 不要在未关闭 swap 的机器上直接 init。 kubelet 配置了
failSwapOn: true,swap 开启会导致节点异常。 - Flannel 版本要固定。 不同 Flannel 版本可能存在兼容性差异,教程中固定为 v0.26.6。
- CoreDNS Pending 不一定是 DNS 问题。 先检查 Flannel 是否 Running,再排查 CoreDNS。
- busybox nslookup 必须写完整域名。 例如
kubernetes.default.svc.cluster.local,不要只写kubernetes。 - Local Path 是单节点本地盘。 数据删除后不可恢复,只适合实验环境。
- Metrics Server 的
--kubelet-insecure-tls只适合实验。 生产环境应使用受信任证书。 - 克隆机之间要确保主机名、IP、UUID 都不同。 网络配置错误是克隆后最常见的问题。
- GitHub raw 地址在某些网络下访问不稳定。 如果拉取失败,先解决代理或镜像源,再继续下一步。
十、总结
到这里,你已经完成了一套可验证的 Kubernetes 实验环境:
- 3 节点集群全部 Ready
- containerd 作为容器运行时
- Flannel 提供 Pod 网络
- Metrics Server 提供资源监控
- Local Path 提供本地存储
- Ingress-Nginx 提供 HTTP/HTTPS 入口
接下来可以继续做这些实验:
- 安装 ArgoCD 或 Flux,实现 GitOps 部署
- 安装 cert-manager,管理 Ingress 证书
- 部署 Prometheus 和 Grafana,做集群监控
- 部署微服务应用,验证 Service、Ingress、PVC 组合使用
- 练习 etcd 备份和集群升级
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)