从零搭建生产级 Kubernetes 集群:方法、原理与踩坑全记录
发布时间:2026/8/24 3:10:36 作者:尧图编辑部 阅读量:1,286

本文基于一次完整的 K8s v1.35 集群部署实践梳理从私有镜像仓库、节点初始化、容器运行时到集群网络的全链路流程并穿插每个环节背后的设计原理与个人理解。希望能帮你少走弯路。一、为什么要从零搭集群现在云厂商都提供托管 K8sEKS、ACK、GKE点几下就能用。但在以下场景里自建集群仍然不可替代离线 / 内网环境生产机房不能直连公网所有镜像和 RPM 包都要本地化成本控制小规模测试集群自己用虚拟机搭比托管服务便宜得多学习与排障只有亲手把每个组件装一遍才能真正理解NotReady、CrashLoopBackOff背后到底发生了什么。本次部署的整体架构是1 个 Harbor 私有仓库节点 1 个 Master 控制节点 2 个 Worker 工作节点操作系统为 RHEL 9K8s 版本 v1.35.7。主机名IP配置角色harbor reg.timinglee.org172.25.254.2501C1GHarbor 镜像仓库k8s-master172.25.254.1004C4G控制平面control-planek8s-node1172.25.254.102C2G工作节点k8s-node2172.25.254.202C2G工作节点下面按实际部署顺序逐层展开。二、第一步搭建 Harbor 私有镜像仓库2.1 为什么需要私有仓库K8s 集群启动时需要拉取大量镜像kube-apiserver、etcd、coredns、pause……这些镜像默认来自registry.k8s.io在国内网络环境下要么极慢要么直接超时。把所有镜像提前推到内网 Harbor集群节点从本地拉取速度快、可离线、可审计这是企业级部署的标准做法。2.2 本地化 Docker YUM 源Harbor 本身依赖 Docker而内网环境连 Docker 官方源都访问不了。这里用了一个巧妙的办法在 Harbor 节点上用dnf install docker-ce --downloadonly把所有 RPM 包下载到本地用createrepo把这些包做成一个本地 YUM 仓库用httpd把仓库目录通过 HTTP 暴露出去监听 4444 端口其他节点把baseurl指向http://172.25.254.250:4444/docker即可安装。个人理解这一步本质上是把公网资源快照到内网。在完全离线的生产环境中不仅 DockerK8s、操作系统补丁、甚至 Python 包都需要做类似的本地化镜像。createrepohttpd是最简单可靠的方案。2.3 Docker 安装与内核参数调优安装 Docker 后必须做几个关键的内核配置# 加载桥接网络过滤模块echobr_netfilter/etc/modules-load.d/docker_mod.conf modprobe-abr_netfilter# 开启桥接流量经过 iptables、开启 IP 转发cat/etc/sysctl.d/docker.confEOF 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 启动参数加--iptablestrue让 Docker 自己管理 iptables 规则保证容器能访问外网。2.4 Harbor 安装与 HTTPS 证书Harbor 离线包解压后核心是编辑harbor.ymlhostname: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 -dTypeoneshotRemainAfterExityes是这种命令型服务的标准写法。三、第二步所有节点的基础环境准备这一步是最容易被忽视、但出问题最多的环节。3.1 关闭 Swapsystemctl disable--nowswap.target systemctl mask swap.targetsed/swap/s/^/#/g-i/etc/fstab为什么必须关 SwapK8s 的 kubelet 默认要求关闭交换分区。原因是K8s 通过requests和limits做资源保证Pod 的内存使用是可预测的如果开启 Swap内核可能把 Pod 内存换出到磁盘导致性能严重下降且 QoS 等级Guaranteed/Burstable/BestEffort的语义被破坏虽然 1.22 之后引入了NodeSwap特性门控支持有限的 Swap但生产环境仍然建议直接关闭。验证swapon -s无任何输出即为成功。3.2 主机名解析与时间同步所有节点的/etc/hosts都要写上彼此的主机名和 IP172.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 而不是直接用 DockerK8s 在v1.24 版本正式移除了 dockershim。在此之前kubelet 内部有一个叫 dockershim 的组件专门把 CRIContainer Runtime Interface调用翻译成 Docker API。移除后kubelet 不再原生支持 Docker。但很多团队已经深度依赖 Docker镜像构建、调试工具、私有仓库生态不想切换到 containerd。于是 Mirantis 公司把 dockershim 的代码抽出来维护成了独立项目cri-dockerd。它的工作模式是kubelet ←—CRIgRPC—→ cri-dockerd ←—Docker API—→ dockerd ←—containerd—→ runccri-dockerd 作为一个翻译层对外暴露标准 CRI 接口对内调用 Docker API。这样 kubelet 以为自己在跟一个标准 CRI 运行时通信而实际跑容器的还是 Docker。4.2 两种安装方式RPM 包安装简单但版本较老0.3.14二进制包安装从 GitHub 下载最新版0.4.4手动install到/usr/local/bin再复制 systemd 单元文件。生产环境建议用二进制包版本新、bug 少。4.3 关键启动参数ExecStart/usr/local/bin/cri-dockerd \ --network-plugincni \ --pod-infra-container-imagereg.timinglee.org/k8s/pause:3.10.1 \ --container-runtime-endpoint fd://逐个解释--network-plugincni使用 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 节点安装kubelet、kubeadm、kubectlWorker 节点只需要kubelet、kubeadmkubectl 是客户端工具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-socketunix:///var/run/cri-dockerd.sock需要的镜像包括kube-apiserver集群 API 入口所有操作都经过它kube-controller-manager运行各种控制器Deployment、ReplicaSet 等kube-schedulerPod 调度决策kube-proxyService 网络代理每个节点都有etcd分布式键值存储保存集群所有状态coredns集群内部 DNSpausePod 基础设施容器。从阿里云镜像源拉取后用docker tag改名再docker push到 Harbor这样后续所有节点都能从本地仓库拉取。5.3 初始化 Masterkubeadm init\--pod-network-cidr10.244.0.0/16\--image-repository reg.timinglee.org/k8s\--kubernetes-version v1.35.7\--cri-socketunix:///var/run/cri-dockerd.sock参数说明--pod-network-cidr10.244.0.0/16指定 Pod 网络的 CIDR 段。这个值必须和后续安装的网络插件Flannel 默认就是 10.244.0.0/16一致否则会出现 Pod 跨节点不通--image-repository指定从哪个仓库拉取控制平面镜像--cri-socket明确告诉 kubeadm 使用 cri-dockerd 的 socket避免它自动检测到其他运行时。kubeadm init 背后做了什么这是理解 K8s 部署的关键预检查检测内核版本、cgroup 驱动、Swap 是否关闭、端口是否占用等生成证书在/etc/kubernetes/pki/下生成 CA、apiserver、etcd 等所有组件的证书和密钥生成 kubeconfig为 kubelet、controller-manager、scheduler 生成连接 apiserver 的配置文件生成静态 Pod 清单把 apiserver、controller-manager、scheduler、etcd 的 YAML 清单写到/etc/kubernetes/manifests/kubelet 会自动拉起这些静态 Pod启动控制平面等待 apiserver 就绪安装附加组件通过 ConfigMap 配置 CoreDNS 和 kube-proxy生成 join 命令输出带 token 和 CA 证书哈希的kubeadm join命令。初始化成功后配置环境变量echoexport 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-socketunix:///var/run/cri-dockerd.sockjoin 的原理--token一个短期有效的身份凭证默认 24 小时过期。Worker 用它向 apiserver 证明自己是被允许加入的--discovery-token-ca-cert-hashMaster 的 CA 证书哈希。Worker 用它验证连接到的 apiserver 是真实的、不是中间人伪造的join 成功后Master 会为该节点签发证书kubelet 开始正常工作。如果 token 过期了在 Master 上执行kubeadm token create --print-join-command可以重新生成。如果初始化出错想重来kubeadm reset --cri-socketunix:///var/run/cri-dockerd.sock可以清理所有配置和证书。六、第五步安装网络插件 Flannel6.1 为什么节点是 NotReady集群初始化后所有节点都是NotReady这是因为K8s 本身不提供容器网络它只定义了 CNIContainer 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 部署步骤加载 Flannel 镜像到 Dockertag 后推到 Harbor修改kube-flannel.yml中的镜像地址指向私有仓库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安装 Dockersetup docker.servicereplace修改 Docker 启动参数setup docker registrycopy写入 daemon.jsonsetup docker certsfile创建证书目录cp certs filecopyloop分发证书load modulecopyloop写入内核模块和 sysctl 配置stup cri-dockerdcopyloop部署 cri-dockerd 二进制和 servicestart servicesserviceloop启动并启用服务几个值得注意的写法when条件判断Master 和 Worker 安装的包不同用when: inventory_hostname 172.25.254.100区分loop循环多个相似操作如分发多个文件、启动多个服务用循环精简代码become提权用普通用户devops登录通过 sudo 提权到 root 执行这是安全最佳实践lineinfile确保某一行存在于文件中适合追加环境变量配置。个人理解Ansible Playbook 的编写思路是把手动步骤翻译成声明式配置。初学者容易犯的错是用shell模块执行所有命令这样就失去了幂等性。应该尽量用专用模块yum、copy、service、lineinfile只有模块覆盖不到的场景才用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 -a或docker 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 实践整理。