本文从 kubernetes 基本概念讲起,完整演示在 RHEL 9.3 上使用 kubeadm 部署一个一主多从(1 master + 3 node)Kubernetes 1.30.4 集群的全过程,包含基础环境配置、cri-dockerd 适配、flannel 网络插件安装及集群验证,适合云原生入门与实操参考。
一、Kubernetes 简介
1.1 应用部署方式的演变
应用程序的部署方式主要经历了三个时代:
- 传统部署:直接部署在物理机上。简单,但不能定义资源使用边界,程序之间容易相互影响。
- 虚拟化部署:一台物理机运行多个虚拟机,环境隔离、安全性好,但增加了操作系统开销。
- 容器化部署:共享操作系统内核,每个容器拥有独立的文件系统、CPU、内存、进程空间,可与底层基础架构解耦,跨云跨发行版部署。
容器化部署带来了便利,也带来了容器编排问题:容器故障如何自动替补?并发增大如何横向扩展?为此产生了容器编排工具:
- Swarm:Docker 自带的编排工具
- Mesos:Apache 的资源统一管控工具(需配合 Marathon)
- Kubernetes:Google 开源的容器编排工具,源于内部 Borg 系统
1.2 kubernetes 能做什么
kubernetes 本质是一组服务器集群,在集群每个节点上运行特定程序对节点中的容器进行管理,实现资源管理自动化。主要功能包括:
- 自我修复:容器崩溃后约 1 秒内迅速启动新容器
- 弹性伸缩:按需自动调整运行中的容器数量
- 服务发现:服务自动发现其依赖的服务
- 负载均衡:一个服务启动多个容器时自动实现请求负载均衡
- 版本回退:新版本有问题可立即回退
- 存储编排:按容器需求自动创建存储卷
1.3 kubernetes 组件
一个 kubernetes 集群由 控制节点(master) 和 工作节点(node) 构成。
master ------ 集群控制平面,负责集群决策(管理)
| 组件 | 作用 |
|---|---|
| ApiServer | 资源操作唯一入口,提供认证、授权、API 注册和发现 |
| Scheduler | 按预定调度策略将 Pod 调度到相应 node 节点 |
| ControllerManager | 维护集群状态:故障检测、自动扩展、滚动更新等 |
| Etcd | 存储集群中各种资源对象的信息 |
node ------ 集群数据平面,负责为容器提供运行环境(干活)
| 组件 | 作用 |
|---|---|
| Kubelet | 维护容器生命周期,通过控制 docker 来创建、更新、销毁容器 |
| KubeProxy | 提供集群内部的服务发现和负载均衡 |
| Docker | 节点上容器的各种操作 |
以部署一个 nginx 服务为例,组件调用关系如下:
- 集群启动后,master 和 node 将自身信息存入 etcd
- nginx 安装请求发送到 master 的 apiServer
- apiServer 调用 scheduler,scheduler 从 etcd 读取各 node 信息按算法选择节点,结果回告 apiServer
- apiServer 调用 controller-manager 去 node 节点安装 nginx 服务
- kubelet 接收指令后通知 docker 启动一个 nginx 的 pod(pod 是 k8s 最小操作单元,容器必须跑在 pod 中)
- 外界访问 nginx 需通过 kube-proxy 对 pod 产生访问代理
1.4 kubernetes 核心概念
- Master:集群控制节点,每个集群至少一个
- Node:工作负载节点,由 master 分配 pod
- Pod:kubernetes 最小控制单元,容器运行在 pod 中,一个 pod 可有 1 个或多个容器
- Controller:控制器,实现对 pod 的启动、停止、伸缩等管理
- Service:pod 对外服务的统一入口,可维护同一类多个 pod
- Label:标签,用于对 pod 分类
- NameSpace:命名空间,隔离 pod 运行环境
二、集群规划
2.1 集群类型
kubernetes 集群大体分为两类:
- 一主多从:一台 Master + 多台 Node,搭建简单但有单机故障风险,适合测试环境
- 多主多从:多台 Master + 多台 Node,搭建麻烦但安全性高,适合生产环境
2.2 安装方式
主流部署方式:
- kubeadm:快速搭建 kubernetes 集群的工具(本文采用)
- 二进制包:从官网下载每个组件的二进制包依次安装,利于理解组件
- 第三方工具:如 kubeasz(国产,Ansible Role 部署二进制集群)、NKD(麒麟软件主导)
参考官网:https://kubernetes.io/docs/setup/
2.3 主机配置
本次在虚拟机上安装,请自行安装好系统并配置主机名和 IP 地址:
| 主机名 | IP 地址 | 硬件配置 | 操作系统 |
|---|---|---|---|
| k8s-master | 192.168.244.51 | 2核 / 2G / 100G | rhel9.3 |
| k8s-node1 | 192.168.244.52 | 2核 / 2G / 100G | rhel9.3 |
| k8s-node2 | 192.168.244.53 | 2核 / 2G / 100G | rhel9.3 |
| k8s-node3 | 192.168.244.54 | 2核 / 2G / 100G | rhel9.3 |
三、基础环境配置
以下操作需在所有节点执行。
3.1 禁用防火墙和 selinux,配置时间同步
bash
# systemctl disable --now firewalld
# sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
# systemctl enable --now chronyd

3.2 禁用 swap 交换空间
bash
# sed -i 's/.*swap.*/#&/' /etc/fstab
# swapoff -a
# swapon -s # 无输出表示已禁用

3.3 修改内核参数,添加网桥过滤和地址转发
bash
# 编辑 /etc/sysctl.conf,添加:
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
# 加载网桥过滤模块(临时生效)
# modprobe br_netfilter
# sysctl -p
# lsmod | grep br_netfilter # 查看是否加载成功

完成上述步骤后重启 linux 系统。
四、部署 kubernetes 集群
控制节点与工作节点组件说明:
Master 节点:
kube-apiserver 集群数据总线和数据中心
kube-scheduler 调度 pod 到工作节点
kube-controller-manager 负责把用户通过 API Server 实例化的对象创建出来
etcd 集群状态存储
Worker 节点:
kubelet 专门负责管理 pod
CRI 容器运行时
CNI 网络插件(flannel / Calico / Cilium 等)
CSI 卷插件,对接第三方存储服务
kube-proxy 将每个 Service 转换为节点上的 iptables/ipvs 规则
4.1 所有节点基础配置
在 k8s-master、k8s-node1、k8s-node2、k8s-node3 节点执行:
bash
[root@k8s-master ~]# cat << EOF >> /etc/hosts
192.168.244.51 k8s-master
192.168.244.52 k8s-node1
192.168.244.53 k8s-node2
192.168.244.54 k8s-node3
EOF
本次安装 kubelet 相关软件包版本为 1.30.4 ,docker 版本为 27.2.1。
配置 kubernetes yum 源并安装 kubelet/kubectl/kubeadm:
bash
[root@k8s-master ~]# cat /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/rpm/
gpgcheck=0
[root@k8s-master ~]# yum install kubelet-1.30.4 kubectl-1.30.4 kubeadm-1.30.4 kubernetes-cni -y
[root@k8s-master ~]# systemctl enable kubelet

配置 docker-ce 源并安装 docker-ce:
bash
[root@k8s-master ~]# cat /etc/yum.repos.d/docker-ce.repo
[docker-ce]
name=docker-ce
baseurl=https://mirrors.aliyun.com/docker-ce/linux/rhel/9/x86_64/stable/
gpgcheck=0
[root@k8s-master ~]# yum install docker-ce-27.2.1 -y

安装 cri-dockerd(k8s 1.24+ 移除内置 docker-shim,需用 cri-dockerd 适配):
bash
[root@k8s-master ~]# mkdir -p /kubernetes/cri-dockerd
[root@k8s-master ~]# cd /kubernetes/cri-dockerd/
# 上传 cri-dockerd rpm 包后安装
[root@k8s-master cri-dockerd]# ll
总用量 10996
-rw-r--r--. 1 root root 11183248 9月 19 11:18 cri-dockerd-0.3.14-3.el8.x86_64.rpm
-rw-r--r--. 1 root root 70188 9月 19 11:18 libcgroup-0.41-19.el8.x86_64.rpm
[root@k8s-master cri-dockerd]# yum localinstall ./*.rpm -y

修改 cri-docker 服务的 pause 容器镜像:
bash
[root@k8s-master cri-dockerd]# sed -i "s#ExecStart.*#& --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.9# " /usr/lib/systemd/system/cri-docker.service
[root@k8s-master ~]# grep ExecStart /usr/lib/systemd/system/cri-docker.service
ExecStart=/usr/bin/cri-dockerd --container-runtime-endpoint fd:// --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.9
修改 docker 的 CGroup 驱动为 systemd(kubelet 的 cgroup 引擎为 systemd,需保持一致):
bash
[root@k8s-master ~]# cat /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"]
}
[root@k8s-master cri-dockerd]# systemctl enable --now docker cri-docker
[root@k8s-master ~]# docker info | grep -i "cgroup driver"
Cgroup Driver: systemd

配置 kubectl 命令补全:
bash
[root@k8s-master ~]# kubectl completion bash > /etc/profile.d/kubectl_completion.sh
[root@k8s-master ~]# source /etc/profile.d/kubectl_completion.sh
4.2 初始化 master 节点
查看 kubeadm 初始化默认配置:
bash
[root@k8s-master ~]# kubeadm config print init-defaults
apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 1.2.3.4
bindPort: 6443
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: node
taints: null
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.k8s.io
kind: ClusterConfiguration
kubernetesVersion: 1.30.0
networking:
dnsDomain: cluster.local
serviceSubnet: 10.96.0.0/12 # svc 的网段
scheduler: {}
预先拉取镜像(使用阿里云镜像源加速):
bash
[root@k8s-master ~]# kubeadm config images pull \
--image-repository registry.aliyuncs.com/google_containers \
--kubernetes-version v1.30.4 \
--cri-socket=unix:///var/run/cri-dockerd.sock
[root@k8s-master ~]# docker images
registry.aliyuncs.com/google_containers/coredns:v1.11.1
registry.aliyuncs.com/google_containers/etcd:3.5.12-0
registry.aliyuncs.com/google_containers/kube-apiserver:v1.30.4
registry.aliyuncs.com/google_containers/kube-controller-manager:v1.30.4
registry.aliyuncs.com/google_containers/kube-proxy:v1.30.4
registry.aliyuncs.com/google_containers/kube-scheduler:v1.30.4
registry.aliyuncs.com/google_containers/pause:3.9


若初始化失败需重置环境:
bash[root@k8s-master ~]# kubeadm reset --cri-socket=unix:///var/run/cri-dockerd.sock # rm -rf /etc/kubernetes /var/lib/kubelet /etc/cni/net.d
执行初始化:
bash
[root@k8s-master ~]# kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--kubernetes-version v1.30.4 \
--image-repository registry.aliyuncs.com/google_containers \
--cri-socket=unix:///var/run/cri-dockerd.sock

初始化成功后会输出类似如下信息(请记录 join 命令,后续 node 加入时使用):
Your Kubernetes control-plane has initialized successfully!
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.244.51:6443 --token 39yokz.uhcvjzo8u84ya0bw \
--discovery-token-ca-cert-hash sha256:1c28873d34c0c9ecd18a0ad990961b144ac195262df138bd71d9fb5ef942ae90
配置 kubectl 连接集群:
bash
[root@k8s-master ~]# echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> /root/.bashrc
[root@k8s-master ~]# source /root/.bashrc
此时节点状态为 NotReady,因为尚未加载网络插件:
bash
[root@k8s-master ~]# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master NotReady control-plane 4m12s v1.30.4

4.3 安装 flannel 网络插件
此操作只需在 master 上执行。
bash
[root@k8s-master ~]# mkdir -p /kubernetes/flannel
[root@k8s-master flannel]# ll
总用量 82140
-rw-r--r-- 1 root root 84103168 12月 8 11:31 flannel-0.25.5.tar.gz
-rw-r--r-- 1 root root 4406 12月 8 11:31 kube-flannel.yml
# 导入 flannel 镜像
[root@k8s-master flannel]# docker load -i flannel-0.25.5.tar.gz
Loaded image: flannel/flannel:v0.25.5
Loaded image: flannel/flannel-cni-plugin:v1.5.1-flannel1
确认 kube-flannel.yml 中的镜像版本与导入镜像一致:
bash
[root@k8s-master flannel]# grep image kube-flannel.yml
image: flannel/flannel:v0.25.5
image: flannel/flannel-cni-plugin:v1.5.1-flannel1
image: flannel/flannel:v0.25.5
[root@k8s-master ~]# kubectl apply -f kube-flannel.yml
稍等片刻,节点变为 Ready:
bash
[root@k8s-master flannel]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 9m52s v1.30.4
[root@k8s-master flannel]# kubectl get pods -n kube-flannel -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-flannel-ds-8gwb4 1/1 Running 0 75s 192.168.244.51 k8s-master <none> <none>

4.4 添加 node 节点
将 master 上的镜像打包分发到 node 节点:
bash
[root@k8s-master ~]# docker save -o /kubernetes/node-image.tar.gz \
registry.aliyuncs.com/google_containers/kube-proxy:v1.30.4 \
flannel/flannel-cni-plugin:v1.5.1-flannel1 \
flannel/flannel:v0.25.5 \
registry.aliyuncs.com/google_containers/pause:3.9
[root@k8s-master ~]# scp /kubernetes/node-image.tar.gz root@k8s-node1:/kubernetes
[root@k8s-master ~]# scp /kubernetes/node-image.tar.gz root@k8s-node2:/kubernetes
在 k8s-node1、k8s-node2 上导入镜像:
bash
[root@k8s-node1 ~]# docker load -i /kubernetes/node-image.tar.gz
在 master 上查看 join 命令(若忘记了初始化时输出的命令):
bash
[root@k8s-master ~]# kubeadm token create --print-join-command
在 k8s-node1、k8s-node2 上执行加入集群命令:
⚠️ 重要 :必须加上
--cri-socket=unix:///run/cri-dockerd.sock指定容器运行时为 cri-dockerd,否则会默认找 containerd 而失败。
bash
[root@k8s-node1 ~]# kubeadm join 192.168.244.51:6443 \
--token tl78cc.wgl7evdib616khst \
--discovery-token-ca-cert-hash sha256:1c28873d34c0c9ecd18a0ad990961b144ac195262df138bd71d9fb5ef942ae90 \
--cri-socket=unix:///run/cri-dockerd.sock
在 master 上验证集群状态:
bash
[root@k8s-master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 20m v1.30.4
k8s-node1 Ready <none> 51s v1.30.4
k8s-node2 Ready <none> 47s v1.30.4
[root@k8s-master ~]# kubectl get pod -n kube-flannel -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-flannel-ds-8gwb4 1/1 Running 0 12m 192.168.244.51 k8s-master <none> <none>
kube-flannel-ds-ln98d 1/1 Running 0 2m3s 192.168.244.52 k8s-node1 <none> <none>
kube-flannel-ds-q89rk 1/1 Running 0 119s 192.168.244.53 k8s-node2 <none> <none>
# 查看各 node 节点上 pod 所在网段
[root@k8s-master ~]# kubectl describe node/k8s-node1 | grep -i podcidr
PodCIDR: 10.244.1.0/24

五、运行 Pod 验证集群
5.1 创建测试 deployment
bash
# 上传并分发测试镜像 myweb:v1 到 k8s-node1、k8s-node2
[root@k8s-master ~]# scp /kubernetes/myweb-v1.tar.gz root@k8s-node1:/kubernetes/
[root@k8s-master ~]# scp /kubernetes/myweb-v1.tar.gz root@k8s-node2:/kubernetes/
[root@k8s-node1 ~]# docker load -i /kubernetes/myweb-v1.tar.gz
# 创建 3 副本 deployment
[root@k8s-master ~]# kubectl create deployment myweb --image=myweb:v1 --replicas=3
[root@k8s-master ~]# kubectl get pod -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
myweb-7c5589c9dc-nt5ht 1/1 Running 0 6s 10.244.1.4 k8s-node1 <none> <none> app=myweb,pod-template-hash=7c5589c9dc
myweb-7c5589c9dc-qqlsk 1/1 Running 0 6s 10.244.2.4 k8s-node2 <none> <none> app=myweb,pod-template-hash=7c5589c9dc
myweb-7c5589c9dc-t98bx 1/1 Running 0 6s 10.244.2.3 k8s-node2 <none> <none> app=myweb,pod-template-hash=7c5589c9dc
5.2 直接访问 Pod IP
bash
[root@k8s-master ~]# curl 10.244.1.4
ClientIP:10.244.0.0,ServerIP:10.244.1.4
[root@k8s-master ~]# curl 10.244.2.4
ClientIP:10.244.0.0,ServerIP:10.244.2.4
5.3 创建 Service 并验证负载均衡
bash
[root@k8s-master ~]# kubectl create service nodeport myweb --tcp=80:80
[root@k8s-master ~]# kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 35m <none>
myweb NodePort 10.106.140.5 <none> 80:30832/TCP 22s app=myweb
# 通过 svc 的 ClusterIP 访问,可见请求被负载均衡到不同 pod
[root@k8s-master ~]# for i in {1..10};do curl 10.106.140.5 ;done
ClientIP:10.244.0.0,ServerIP:10.244.2.4
ClientIP:10.244.0.0,ServerIP:10.244.2.3
ClientIP:10.244.0.0,ServerIP:10.244.1.4
...
5.4 通过域名访问(coredns 解析)
kubernetes 会为每个 service 生成域名,格式为 [svc-name].[namespace].svc.cluster.local。运行一个临时容器,通过集群 coredns 解析访问:
bash
[root@k8s-master ~]# kubectl run client --image=nginx:1.27.1 --rm --restart=Never -it --command -- /bin/bash
root@client:/# for i in {1..10};do curl myweb.default.svc;done
ClientIP:10.244.1.5,ServerIP:10.244.2.4
ClientIP:10.244.1.5,ServerIP:10.244.2.3
...
# 查看集群中的 coredns
[root@k8s-master ~]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 44m
[root@k8s-master ~]# host myweb.default.svc.cluster.local 10.96.0.10
myweb.default.svc.cluster.local has address 10.106.140.5
5.5 清理测试资源
bash
[root@k8s-master ~]# kubectl delete deployments.apps/myweb svc/myweb
六、常见问题与总结
6.1 部署过程中的关键点
- cri-dockerd 不可少:k8s 1.24 起移除 dockershim,若仍想用 docker 作为容器运行时,必须安装 cri-dockerd 作为适配层。
- CGroup 驱动必须一致 :kubelet 默认使用 systemd,docker 也需配置
native.cgroupdriver=systemd,否则 kubelet 启动失败。 - node 加入必须指定 cri-socket :
--cri-socket=unix:///run/cri-dockerd.sock,否则 kubeadm 默认找 containerd。 - pause 镜像需替换为国内源 :通过修改 cri-docker.service 的
--pod-infra-container-image参数指定阿里云 pause 镜像。 - 网络插件需与 pod-network-cidr 一致 :flannel 默认 10.244.0.0/16,初始化时
--pod-network-cidr必须与之匹配。
6.2 验证集群健康的常用命令
bash
kubectl get nodes # 节点状态
kubectl get pods -A # 所有命名空间的 pod
kubectl get pods -n kube-flannel -o wide # flannel 网络插件状态
kubectl get pods -n kube-system # 系统组件状态
kubectl describe node <node-name> # 节点详情
至此,一个一主多从的 Kubernetes 1.30.4 集群部署完成,可在此基础上继续学习 Pod、Controller、Service、存储等资源管理内容。
环境说明:RHEL 9.3 / kubeadm 1.30.4 / docker 27.2.1 / cri-dockerd 0.3.14 / flannel v0.25.5
本文为学习笔记整理,如有疏漏欢迎指正。