从零搭建生产级 Kubernetes 集群:方法、原理与踩坑全记录
2026/8/24 3:10:31 网站建设 项目流程

本文基于一次完整的 K8s v1.35 集群部署实践,梳理从私有镜像仓库、节点初始化、容器运行时到集群网络的全链路流程,并穿插每个环节背后的设计原理与个人理解。希望能帮你少走弯路。


一、为什么要"从零"搭集群?

现在云厂商都提供托管 K8s(EKS、ACK、GKE),点几下就能用。但在以下场景里,自建集群仍然不可替代:

  • 离线 / 内网环境:生产机房不能直连公网,所有镜像和 RPM 包都要本地化;
  • 成本控制:小规模测试集群自己用虚拟机搭,比托管服务便宜得多;
  • 学习与排障:只有亲手把每个组件装一遍,才能真正理解NotReadyCrashLoopBackOff背后到底发生了什么。

本次部署的整体架构是1 个 Harbor 私有仓库节点 + 1 个 Master 控制节点 + 2 个 Worker 工作节点,操作系统为 RHEL 9,K8s 版本 v1.35.7。

主机名IP配置角色
harbor reg.timinglee.org172.25.254.2501C1GHarbor 镜像仓库
k8s-master172.25.254.1004C4G+控制平面(control-plane)
k8s-node1172.25.254.102C2G工作节点
k8s-node2172.25.254.202C2G工作节点

下面按实际部署顺序逐层展开。


二、第一步:搭建 Harbor 私有镜像仓库

2.1 为什么需要私有仓库?

K8s 集群启动时需要拉取大量镜像:kube-apiserveretcdcorednspause……这些镜像默认来自registry.k8s.io,在国内网络环境下要么极慢要么直接超时。把所有镜像提前推到内网 Harbor,集群节点从本地拉取,速度快、可离线、可审计,这是企业级部署的标准做法。

2.2 本地化 Docker YUM 源

Harbor 本身依赖 Docker,而内网环境连 Docker 官方源都访问不了。这里用了一个巧妙的办法:

  1. 在 Harbor 节点上用dnf install docker-ce --downloadonly把所有 RPM 包下载到本地;
  2. createrepo把这些包做成一个本地 YUM 仓库;
  3. httpd把仓库目录通过 HTTP 暴露出去(监听 4444 端口);
  4. 其他节点把baseurl指向http://172.25.254.250:4444/docker即可安装。

个人理解:这一步本质上是"把公网资源快照到内网"。在完全离线的生产环境中,不仅 Docker,K8s、操作系统补丁、甚至 Python 包都需要做类似的本地化镜像。createrepo+httpd是最简单可靠的方案。

2.3 Docker 安装与内核参数调优

安装 Docker 后,必须做几个关键的内核配置:

# 加载桥接网络过滤模块echobr_netfilter>/etc/modules-load.d/docker_mod.conf modprobe-abr_netfilter# 开启桥接流量经过 iptables、开启 IP 转发cat>/etc/sysctl.d/docker.conf<<EOF net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOFsysctl--system

原理说明

  • br_netfilter模块让 Linux 网桥(bridge)上的流量也能被 iptables 规则处理。K8s 的 Service 转发、NetworkPolicy 都依赖这个能力,不加载的话 Pod 网络会异常;
  • net.ipv4.ip_forward = 1开启内核 IP 转发,这是容器跨节点通信、SNAT 出网的基础;
  • Docker 启动参数加--iptables=true,让 Docker 自己管理 iptables 规则,保证容器能访问外网。

2.4 Harbor 安装与 HTTPS 证书

Harbor 离线包解压后,核心是编辑harbor.yml

hostname:reg.timinglee.orgcertificate:/data/certs/timinglee.org.crtprivate_key:/data/certs/timinglee.org.keyharbor_admin_password:lee

证书用openssl自签名生成,注意必须加subjectAltName = DNS:reg.timinglee.org,否则 Docker 客户端会因为证书 SAN 不匹配而拒绝登录。

踩坑提醒:自签名证书必须分发到所有需要登录 Harbor 的节点,放到/etc/docker/certs.d/reg.timinglee.org/ca.crt,同时建议也放进系统信任库/etc/pki/ca-trust/source/anchors/并执行update-ca-trust extract。只放 Docker 目录、不放系统信任库,某些工具(如cri-dockerd)仍然会报证书错误。

Harbor 用docker compose启动,为了开机自启,写了一个 systemd service 包裹docker compose up -dType=oneshot+RemainAfterExit=yes是这种"命令型服务"的标准写法。


三、第二步:所有节点的基础环境准备

这一步是最容易被忽视、但出问题最多的环节。

3.1 关闭 Swap

systemctl disable--nowswap.target systemctl mask swap.targetsed'/swap/s/^/#/g'-i/etc/fstab

为什么必须关 Swap?K8s 的 kubelet 默认要求关闭交换分区。原因是:

  • K8s 通过requestslimits做资源保证,Pod 的内存使用是可预测的;
  • 如果开启 Swap,内核可能把 Pod 内存换出到磁盘,导致性能严重下降,且 QoS 等级(Guaranteed/Burstable/BestEffort)的语义被破坏;
  • 虽然 1.22 之后引入了NodeSwap特性门控支持有限的 Swap,但生产环境仍然建议直接关闭。

验证:swapon -s无任何输出即为成功。

3.2 主机名解析与时间同步

所有节点的/etc/hosts都要写上彼此的主机名和 IP:

172.25.254.100 k8s-master 172.25.254.10 k8s-node1 172.25.254.20 k8s-node2 172.25.254.250 reg.timinglee.org

个人理解:K8s 组件之间大量通过主机名或证书 CN 互相通信,/etc/hosts是最朴素但最可靠的 DNS 方案。生产环境建议用内部 DNS 服务器,但实验环境/etc/hosts足够。另外别忘了配置 NTP 时间同步,etcd 对时钟偏移非常敏感。

3.3 Docker 加速器与 Harbor 证书分发

在所有节点上配置daemon.json

{"registry-mirrors":["https://reg.timinglee.org"]}

这里把 Harbor 设为 Docker 的镜像加速器。这样即使docker pull nginx没有写完整仓库地址,Docker 也会优先从 Harbor 拉取。配合 Harbor 的代理缓存功能,可以实现"首次拉取走公网、后续走本地"的透明加速。


四、第三步:容器运行时 cri-dockerd

这是整个部署中最需要理解原理的一环。

4.1 为什么是 cri-dockerd 而不是直接用 Docker?

K8s 在v1.24 版本正式移除了 dockershim。在此之前,kubelet 内部有一个叫 dockershim 的组件,专门把 CRI(Container Runtime Interface)调用翻译成 Docker API。移除后,kubelet 不再原生支持 Docker。

但很多团队已经深度依赖 Docker(镜像构建、调试工具、私有仓库生态),不想切换到 containerd。于是 Mirantis 公司把 dockershim 的代码抽出来,维护成了独立项目cri-dockerd

它的工作模式是:

kubelet ←—CRI(gRPC)—→ cri-dockerd ←—Docker API—→ dockerd ←—containerd—→ runc

cri-dockerd 作为一个"翻译层",对外暴露标准 CRI 接口,对内调用 Docker API。这样 kubelet 以为自己在跟一个标准 CRI 运行时通信,而实际跑容器的还是 Docker。

4.2 两种安装方式

  1. RPM 包安装:简单,但版本较老(0.3.14);
  2. 二进制包安装:从 GitHub 下载最新版(0.4.4),手动install/usr/local/bin,再复制 systemd 单元文件。

生产环境建议用二进制包,版本新、bug 少。

4.3 关键启动参数

ExecStart=/usr/local/bin/cri-dockerd \ --network-plugin=cni \ --pod-infra-container-image=reg.timinglee.org/k8s/pause:3.10.1 \ --container-runtime-endpoint fd://

逐个解释:

  • --network-plugin=cni:使用 CNI 网络插件,这是 K8s 集群网络的标准;
  • --pod-infra-container-image:指定 Pod 基础设施容器(pause 容器)的镜像。每个 Pod 启动时都会先跑一个 pause 容器来持有网络命名空间,这个镜像必须从私有仓库拉取;
  • --container-runtime-endpoint fd://:通过 systemd 传递的 socket 文件描述符监听 CRI 请求。

启动后验证:ls /var/run/cri-dockerd.sock存在即正常。

个人理解:cri-dockerd 是一个"历史包袱兼容层"。如果是全新部署,我更推荐直接用containerd作为运行时——它更轻量、性能更好、是 K8s 官方推荐的默认运行时。但如果你的团队已经有大量 Docker 镜像构建脚本和运维习惯,cri-dockerd 是平滑过渡的最佳选择。


五、第四步:kubeadm 初始化集群

5.1 安装 K8s 组件

Master 节点安装kubeletkubeadmkubectl;Worker 节点只需要kubeletkubeadm(kubectl 是客户端工具,Worker 上不需要)。

kubelet安装后要systemctl enable --now,但注意:在集群初始化之前,kubelet 会不断重启并报错,这是正常的——因为它还没有拿到 kubeconfig 配置文件。kubeadm init会生成配置并写入/var/lib/kubelet/,之后 kubelet 才会正常运行。

5.2 预拉取集群镜像

kubeadm config images list# 查看需要哪些镜像kubeadm config images pull\--image-repository registry.aliyuncs.com/google_containers\--kubernetes-version v1.35.7\--cri-socket=unix:///var/run/cri-dockerd.sock

需要的镜像包括:

  • kube-apiserver:集群 API 入口,所有操作都经过它;
  • kube-controller-manager:运行各种控制器(Deployment、ReplicaSet 等);
  • kube-scheduler:Pod 调度决策;
  • kube-proxy:Service 网络代理,每个节点都有;
  • etcd:分布式键值存储,保存集群所有状态;
  • coredns:集群内部 DNS;
  • pause:Pod 基础设施容器。

从阿里云镜像源拉取后,用docker tag改名再docker push到 Harbor,这样后续所有节点都能从本地仓库拉取。

5.3 初始化 Master

kubeadm init\--pod-network-cidr=10.244.0.0/16\--image-repository reg.timinglee.org/k8s\--kubernetes-version v1.35.7\--cri-socket=unix:///var/run/cri-dockerd.sock

参数说明:

  • --pod-network-cidr=10.244.0.0/16:指定 Pod 网络的 CIDR 段。这个值必须和后续安装的网络插件(Flannel 默认就是 10.244.0.0/16)一致,否则会出现 Pod 跨节点不通;
  • --image-repository:指定从哪个仓库拉取控制平面镜像;
  • --cri-socket:明确告诉 kubeadm 使用 cri-dockerd 的 socket,避免它自动检测到其他运行时。

kubeadm init 背后做了什么?这是理解 K8s 部署的关键:

  1. 预检查:检测内核版本、cgroup 驱动、Swap 是否关闭、端口是否占用等;
  2. 生成证书:在/etc/kubernetes/pki/下生成 CA、apiserver、etcd 等所有组件的证书和密钥;
  3. 生成 kubeconfig:为 kubelet、controller-manager、scheduler 生成连接 apiserver 的配置文件;
  4. 生成静态 Pod 清单:把 apiserver、controller-manager、scheduler、etcd 的 YAML 清单写到/etc/kubernetes/manifests/,kubelet 会自动拉起这些静态 Pod;
  5. 启动控制平面:等待 apiserver 就绪;
  6. 安装附加组件:通过 ConfigMap 配置 CoreDNS 和 kube-proxy;
  7. 生成 join 命令:输出带 token 和 CA 证书哈希的kubeadm join命令。

初始化成功后,配置环境变量:

echo"export KUBECONFIG=/etc/kubernetes/admin.conf">>~/.bash_profilesource~/.bash_profile

此时kubectl get nodes能看到 Master 节点,但状态是NotReady——因为还没装网络插件。

5.4 Worker 节点加入

kubeadmjoin172.25.254.100:6443\--tokenjl4ztx.cax3iysvu7onsh5s\--discovery-token-ca-cert-hash sha256:6b5950ef...\--cri-socket=unix:///var/run/cri-dockerd.sock

join 的原理

  • --token:一个短期有效的身份凭证,默认 24 小时过期。Worker 用它向 apiserver 证明自己是被允许加入的;
  • --discovery-token-ca-cert-hash:Master 的 CA 证书哈希。Worker 用它验证连接到的 apiserver 是真实的、不是中间人伪造的;
  • join 成功后,Master 会为该节点签发证书,kubelet 开始正常工作。

如果 token 过期了,在 Master 上执行kubeadm token create --print-join-command可以重新生成。

如果初始化出错想重来,kubeadm reset --cri-socket=unix:///var/run/cri-dockerd.sock可以清理所有配置和证书。


六、第五步:安装网络插件 Flannel

6.1 为什么节点是 NotReady?

集群初始化后所有节点都是NotReady,这是因为K8s 本身不提供容器网络,它只定义了 CNI(Container Network Interface)标准,具体实现由第三方插件完成。没有网络插件,Pod 之间无法通信,节点就被标记为 NotReady。

常见的 CNI 插件有:

  • Flannel:最简单,适合学习和小规模集群;
  • Calico:功能强大,支持 NetworkPolicy,生产环境首选;
  • Cilium:基于 eBPF,性能好、可观测性强,新兴方案。

本次用 Flannel,因为它配置最简单。

6.2 Flannel 的工作原理

Flannel 为每个节点分配一个子网(从10.244.0.0/16中划分),节点上的 Pod 都从该节点的子网中获取 IP。跨节点通信时,Flannel 通过VXLAN把 Pod 网络包封装到 UDP 包中,通过宿主机网络传输,到达目标节点后再解封装。

简单来说:

Pod A (10.244.1.5) → flannel.1 (VXLAN封装) → 宿主机网络 → flannel.1 (解封装) → Pod B (10.244.2.8)

6.3 部署步骤

  1. 加载 Flannel 镜像到 Docker,tag 后推到 Harbor;
  2. 修改kube-flannel.yml中的镜像地址,指向私有仓库;
  3. kubectl apply -f kube-flannel.yml

Flannel 以DaemonSet形式部署,这意味着每个节点上都会自动运行一个 Flannel Pod,这正是网络插件需要的——每个节点都必须有网络代理。

部署后等待几十秒,kubectl get nodes所有节点状态变为Ready,集群就真正可用了。


七、进阶:用 Ansible 自动化部署

手动在 3 个节点上重复执行命令既繁琐又容易出错。文档最后给出了 Ansible 自动化方案,这是生产级部署的必经之路。

7.1 Ansible 的核心价值

  • 幂等性:同一个 Playbook 执行多次结果一致,不会重复安装或重复配置;
  • 声明式:描述"目标状态"而不是"执行步骤",Ansible 自己判断需不需要改;
  • 批量执行:一次配置,所有节点同步生效。

7.2 Playbook 结构解析

整个 Playbook 按模块组织,每个 task 对应一个配置动作:

Task模块作用
setup docker repoyum_repository配置 Docker YUM 源
install dockerdnf安装 Docker
setup docker.servicereplace修改 Docker 启动参数
setup docker registrycopy写入 daemon.json
setup docker certsfile创建证书目录
cp certs filecopy+loop分发证书
load modulecopy+loop写入内核模块和 sysctl 配置
stup cri-dockerdcopy+loop部署 cri-dockerd 二进制和 service
start servicesservice+loop启动并启用服务

几个值得注意的写法:

  1. when条件判断:Master 和 Worker 安装的包不同,用when: inventory_hostname == "172.25.254.100"区分;
  2. loop循环:多个相似操作(如分发多个文件、启动多个服务)用循环精简代码;
  3. become提权:用普通用户devops登录,通过 sudo 提权到 root 执行,这是安全最佳实践;
  4. lineinfile:确保某一行存在于文件中,适合追加环境变量配置。

个人理解:Ansible Playbook 的编写思路是"把手动步骤翻译成声明式配置"。初学者容易犯的错是用shell模块执行所有命令,这样就失去了幂等性。应该尽量用专用模块(yumcopyservicelineinfile),只有模块覆盖不到的场景才用shell


八、常见问题与排障思路

部署过程中最容易遇到的几个问题:

8.1 节点一直 NotReady

  • 检查网络插件 Pod 是否正常运行:kubectl get pods -n kube-flannel
  • 检查 Pod 网络 CIDR 是否和 Flannel 配置一致;
  • 查看 kubelet 日志:journalctl -u kubelet -f

8.2 kubeadm init 超时

  • 通常是镜像拉不下来:检查--image-repository是否正确、Harbor 是否可访问、证书是否分发;
  • 检查 cri-dockerd 是否正常运行:systemctl status cri-docker
  • 查看具体哪个静态 Pod 没起来:crictl ps -adocker ps -a

8.3 Worker 节点 join 失败

  • 检查网络连通性:telnet 172.25.254.100 6443
  • 检查 token 是否过期:kubeadm token list
  • 检查防火墙和 SELinux:生产环境建议统一关闭或正确配置。

8.4 Harbor 登录失败

  • 检查证书 SAN 是否包含访问域名;
  • 检查证书是否分发到客户端节点的/etc/docker/certs.d/
  • 检查/etc/hosts域名解析是否正确;
  • 检查 Harbor 容器是否全部正常:docker compose ps

九、总结

回顾整个部署流程,K8s 集群的搭建可以归纳为五层架构

┌─────────────────────────────────────────┐ │ 第五层:网络插件(Flannel / Calico) │ ← Pod 通信基础 ├─────────────────────────────────────────┤ │ 第四层:K8s 组件(kubeadm 初始化) │ ← 控制平面 + kubelet ├─────────────────────────────────────────┤ │ 第三层:容器运行时(cri-dockerd) │ ← CRI 接口适配 ├─────────────────────────────────────────┤ │ 第二层:容器引擎(Docker) │ ← 镜像与容器管理 ├─────────────────────────────────────────┤ │ 第一层:基础设施(内核参数 / Swap / 网络)│ ← 系统级准备 └─────────────────────────────────────────┘

每一层都为上一层提供支撑,任何一层出问题,上面的所有层都无法正常工作。这也是为什么 K8s 部署看起来"步骤很多"——因为它把一个复杂的分布式系统拆解成了清晰的层次。

最后,关于部署方式的建议:

  • 学习实验:手动部署一遍,理解每个组件的作用;
  • 小规模生产:用 Ansible Playbook 自动化,保证一致性;
  • 中大规模生产:考虑用 Kubespray(基于 Ansible 的成熟方案)或 kOps;
  • 超大规模 / 多云:托管 K8s 服务仍然是最省心的选择。

希望这篇文章能帮你建立起对 K8s 集群部署的整体认知。动手搭一遍,你会发现那些曾经看起来高深莫测的概念,其实都有清晰的逻辑和实现路径。


本文基于 K8s v1.35.7 + cri-dockerd 0.4.4 + Harbor v2.5.4 + Flannel v0.28.9 实践整理。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询