Kubernetes集群部署实战:kubeadm、containerd与Calico全流程

发布时间:2026/9/9 21:12:24
Kubernetes集群部署实战:kubeadm、containerd与Calico全流程 做 Kubernetes 部署这件事说简单也简单说复杂是真的复杂。我见过不少人拿着一条kubeadm init命令就想把集群跑起来结果卡在镜像拉取、网络插件、kubelet 起不来这些地方一整天最后灰溜溜地来找我。所以这篇东西我不想写成那种官方文档的翻译腔而是想以我自己实际部署集群的完整过程为线索把从架构选型、环境准备、核心部署到故障排查这一整套链路讲清楚。不管你是刚接触 Kubernetes 的运维新人还是已经被线上集群折磨过的开发同学这篇文章应该都能帮你少走不少弯路。1. 部署前必懂的整体架构与选型思路1.1 先搞清楚 Kubernetes 到底在调度什么很多人把 Kubernetes 当成一个能把容器跑起来的工具这个理解太浅了。Kubernetes 的核心价值不是运行容器而是声明式地管理容器集群——你告诉它我要跑 3 个 Nginx 副本它负责保证这 3 个副本永远存在节点挂了就自动迁移流量来了就自动负载均衡。要做到这一点K8s 集群在逻辑上分成了两个平面控制平面Control Plane和数据平面Data Plane。控制平面就是集群大脑由 API Server、Scheduler、Controller Manager、etcd 这几个组件构成负责接收指令、调度决策、维持期望状态。数据平面则是真正跑业务容器的节点核心组件就是 kubelet、kube-proxy 和容器运行时比如 containerd。这里我想多说一句容器运行时。Kubernetes 早期默认用 Docker后来切到了 containerd很多从 Docker 时代过来的人容易懵。其实 containerd 就是当年 Docker 公司从 Docker 引擎里剥离出来的容器运行时核心它只关心怎么把容器跑起来不关心怎么构建镜像、怎么提供友好的命令行接口。K8s 通过 CRIContainer Runtime Interface这个标准接口和 containerd 通信所以你在部署时如果看到 unknown runtime 或者 kubelet 报和 CRI 有关的错八成是运行时配置出了问题。这部分后面我会专门展开。1.2 主流的部署方式怎么选现在的 Kubernetes 部署方式我在实际项目中基本归纳成了四类部署方式典型工具适用场景优点缺点二进制手动部署无全部手工操作学习原理、极度定制化能完全理解每个组件耗时耗力升级困难kubeadm 部署kubeadm生产环境最常见选择官方工具、标准化、可控性强网络插件等仍要手动配置自动化平台部署Rancher、Kubesphere多集群管理、提供给团队自助使用有 UI、扩展功能丰富平台本身有学习成本托管集群EKS、ACK、TKE不想管控制平面省心、高可用有保障成本高、有厂商绑定对于大多数自建集群的中小型团队我的建议很直接用 kubeadm。它是 CNCF 官方维护的部署工具底层就是把你手动部署时那些繁琐的操作封装成标准流程证书生成、组件配置、token 管理都规范化了。而且 kubeadm 生成的集群是全功能的升级路径也清晰社区里遇到问题最容易找到答案。如果你只是想学 Kubernetes连三台真实机器都没有那就先拿 minikube 或者 kind 跑个单节点玩一玩别一上来就折腾生产集群——地基都没打好就盖楼只会把自己劝退。1.3 硬件与资源规划的经验值这块看起来不起眼却是部署翻车的高发区。我自己的经验是控制平面节点至少 2 核 4Getcd 会吃不少内存如果条件允许建议 4 核 8G。etcd 的磁盘性能直接影响整个集群的稳定性强烈建议给 etcd 用 SSD。工作节点取决于业务负载一般来说 2 核 4G 起步跑 Java 应用的话 4 核 8G 打底。集群规模规划如果你打算跑到 100 个节点以上控制平面的资源要额外加etcd 也要做单独的高可用规划。还有一个容易被忽略的点节点时间必须同步。Kubernetes 的证书体系对时间偏移极其敏感节点间时钟差超过五分钟集群通信就会出现莫名其妙的证书错误。所以 chrony 或者 ntp 一定要装好这是我在每个部署环境里必做的一步。2. 环境准备与容器运行时细节2.1 操作系统与基础配置我拿三台 CentOS 7.9 或者 Rocky Linux 9 的机器来演示其实只要是 systemd 系的 Linux 发行版操作路径基本一样。系统装好后先做这几件事# 设置主机名规划好角色 hostnamectl set-hostname k8s-master01 # 关闭交换分区K8s 强制要求 swapoff -a sed -i / swap / s/^/#/ /etc/fstab # 加载内核模块 cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter # 配置内核参数让 iptables 能处理桥接流量 cat EOF | tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl --system这里每个参数都有讲究。swapoff -a是因为 kubelet 在启用 swap 的情况下无法正常工作K8s 希望内存管理交给它自己来br_netfilter模块是为了让 Linux 网桥的流量能经过 iptables 规则否则集群内 Service 的访问策略会失效。这些不是玄学是 kubelet 启动前的硬性检查项。2.2 containerd 安装与配置刚才说了K8s 通过 CRI 跟容器运行时通信现在主流默认就是 containerd。安装本身很简单但配置文件里有两个坑我必须提前说。# 使用 yum 仓库安装 containerd yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y containerd.io # 生成默认配置 containerd config default | tee /etc/containerd/config.toml第一个坑默认的 sandbox 镜像是 k8s.gcr.io/pause:3.x这个地址在国内网络环境下基本拉不动。你需要先把镜像源改成能用的镜像地址比如registry.aliyuncs.com/google_containers/pause:3.6然后修改配置[plugins.io.containerd.grpc.v1.cri] sandbox_image registry.aliyuncs.com/google_containers/pause:3.6第二个坑默认的 cgroup 驱动是 systemd 不匹配的。Kubernetes 从 1.20 版本开始推荐使用 systemd 作为 kubelet 的 cgroup 驱动而 containerd 默认配置里可能是 cgroupfs两者不一致会导致节点状态异常。改法如下[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true改完配置后重启 containerdsystemctl restart containerd。这里我再补一句如果你打算用ctr这个命令行工具调试镜像它跟docker命令的语法不同很多人第一次用会不适应这是正常的因为ctr是 containerd 的底层客户端不感知 K8s 的命名空间概念。2.3 kubeadm、kubelet、kubectl 的安装这三个组件的版本必须统一我建议直接装最新的稳定版本。通过 yum 仓库安装时需要先配置 Kubernetes 的软件源cat EOF | tee /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck1 repo_gpgcheck0 gpgkeyhttps://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF yum install -y kubelet kubeadm kubectl systemctl enable kubelet systemctl start kubelet注意这里用enabled1但gpgcheck1时如果报 GPG 签名问题可以把repo_gpgcheck0保留、gpgcheck也一并关掉内网环境经常这么干。装好之后先别急着去看 kubelet 状态因为此时集群还没初始化kubelet 会不断报错重启这是正常现象别被吓到。3. 核心实操kubeadm 部署集群全程记录3.1 初始化控制平面节点这步是整个部署的核心命令参数我要一个一个解释清楚kubeadm init \ --apiserver-advertise-address192.168.1.10 \ --image-repository registry.aliyuncs.com/google_containers \ --kubernetes-version v1.28.2 \ --service-cidr10.96.0.0/12 \ --pod-network-cidr10.244.0.0/16--apiserver-advertise-address指定 API Server 对外通告的 IP一定要填你控制平面节点稳定可达的内网 IP。--image-repository指定镜像仓库国内部署必须替换否则会卡在拉镜像那一步。--kubernetes-version要跟你安装的 kubeadm 版本保持一致。--service-cidr和--pod-network-cidr这两个 CIDR 决定了 Service 和 Pod 的网段一旦初始化就不能改一定要提前规划好。10.244.0.0/16是 Flannel 的默认网段如果你后面想用 Calico可以换成10.244.0.0/16或者192.168.0.0/16关键在于必须跟网络插件要求的网段对齐。执行完如果看到类似这样的输出说明控制平面初始化成功Your Kubernetes control-plane has been initialized successfully!然后按照提示配置 kubectl 的 kubeconfigmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config配置好后执行kubectl get nodes应该能看到 master 节点因为没装网络插件还处于NotReady状态别急这是正常的。3.2 工作节点如何加入集群初始化完成后终端会输出一条kubeadm join命令带有一串 token 和证书哈希。比如kubeadm join 192.168.1.10:6443 --token xxxxxx.xxxxxx \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxx把这条命令拿到工作节点上执行就行。需要注意两点第一token 默认有效期是 24 小时。如果过期了用kubeadm token create --print-join-command重新生成这条命令直接打印出一条新的可用的 join 命令拿到工作节点执行即可。第二工作节点上必须提前装好 containerd、kubelet、kubeadm而且版本要和控制平面一致。很多新手在 join 时报版本不匹配就是因为工作节点的 kubeadm 装晚了或者装错了版本。3.3 网络插件以 Calico 为例这一步是部署中最容易出问题的环节。没有网络插件节点会一直NotReady。我通常用 Calico 的 operator 方式安装kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/tigera-operator.yaml然后准备一个 custom-resources.yaml里面指定 Pod 网段apiVersion: operator.tigera.io/v1 kind: Installation metadata: name: default spec: calicoNetwork: ipPools: - cidr: 10.244.0.0/16 encapsulation: VXLAN这里encapsulation: VXLAN是很重要的选择。如果选 BGPCalico 的默认模式要求节点之间三层网络能互通且没有防火墙阻挡而 VXLAN 模式走 UDP 封装对底层网络的要求低得多在云环境或跨网段场景下更省心。我建议如果网络环境不复杂直接用 VXLAN少踩坑。等待 Pod 运行起来kubectl wait --namespace calico-system --forconditionReady pod --all --timeout300s kubectl get pods -A等 calico 的 Pod 都 Running 了再kubectl get nodes此时节点应该都变成Ready。如果还是 NotReady大概率是 cgroup 驱动不一致或者 Pod 网段跟 Calico 配置的网段对不上回到前面检查。3.4 验证集群健康状态集群起来之后我习惯按照这个顺序做一轮完整验证# 1. 查看节点状态 kubectl get nodes -o wide # 2. 查看核心组件 Pod 状态 kubectl get pods -n kube-system # 3. 验证 etcd 健康 kubectl -n kube-system exec etcd-k8s-master01 -- etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ endpoint health # 4. 发布一个测试应用 kubectl create deployment nginx-test --imagenginx kubectl expose deployment nginx-test --port80 --typeNodePort最后访问一下任意节点IP:NodePort能打开 Nginx 的欢迎页就说明整个集群的网络链路、DNS、调度、负载均衡都通了。到这一步一个能用的 Kubernetes 集群就算部署完了。4. 部署中的常见故障与排查实战4.1 组件 Pod 反复重启或 CrashLoopBackOff这个症状我见得太多了。排查思路要按层次来先看 Pod 日志再看事件最后看 kubelet 日志。# 查看异常 Pod kubectl get pods -A | grep -v Running # 查看具体日志 kubectl logs -n kube-system pod-name kubectl describe pod -n kube-system pod-name # 查看事件 kubectl get events --sort-by.lastTimestamp如果是 etcd 的 Pod 反复重启常见原因是证书目录权限不对或者 etcd 数据目录所在的磁盘性能太差。如果是 kube-apiserver 起不来大概率是 etcd 连不上。这里有个经验控制平面组件基本都是静态 Pod由 kubelet 直接管理配置文件在/etc/kubernetes/manifests下你改了配置后 kubelet 会自动重启对应 Pod不用手动干预。4.2 镜像拉取失败如果日志里出现Failed to pull image第一步先确认你配置的镜像仓库地址能不能通。ctr images pull registry.aliyuncs.com/google_containers/pause:3.6手动拉一次就知道网络通不通。如果手动拉没问题但 kubelet 还是报拉取失败检查 containerd 的 CRI 配置里sandbox_image是否改对了。这里再分享一个小技巧如果你用的是企业内网环境可以提前把所有需要的镜像推送到内网 Harbor然后修改 containerd 配置里的镜像地址即可。kubeadm 的--image-repository参数可以指定内网仓库地址控制平面的镜像就能全部从内网拉取。我部署离线环境时甚至会把所有镜像导出成 tar 包带进去导入方法就是用ctr images export和ctr images import。4.3 节点 NotReady 排查节点 NotReady 在排除了网络插件问题后最常见的就是 kubelet 本身的问题了。先查 kubelet 状态systemctl status kubelet journalctl -u kubelet -f如果看到failed to run Kubelet: failed to create kubelet config检查/etc/kubernetes/kubelet.conf是否生成确认节点是否成功 join。如果看到Container runtime network not ready说明是 CNI 插件的问题查看网络插件的 Pod 是否正常。另外一个容易忽悠人的情况节点突然 NotReady但 kubelet 日志里全是PLEG is not healthy。这往往是节点负载过高导致的假死注意看磁盘 IO 或者 Docker/containerd 是否 hang 住。节点上执行docker ps或者ctr namespace ls如果命令也很慢说明容器运行时确实有问题重启运行时往往能解决。这里特别注意不要一上来就重启节点。先看内存、磁盘、Docker 是否健康80% 的情况不用重启物理机。4.4 Pod 之间网络不通很多人的集群部署完了Pod 也 Running 了但 Service 访问不通。这里我按自己的排查顺序列一下# 1. 确认 Service 是否存在 kubectl get svc -A # 2. 确认 Endpoints 是否有后端 kubectl get endpoints -A # 3. 确认 kube-proxy 是否正常 kubectl get pods -n kube-system | grep kube-proxy kubectl logs -n kube-system kube-proxy-pod # 4. 检查 iptables 规则 iptables -L -t nat | grep service-cluster-ip通常 80% 的原因是 Service 的 selector 没匹配到 Pod 的标签导致 Endpoints 为空。在 Service 里加一条app: nginx的 selector同时确认 Deployment 的 Pod 带了这个 label问题就能解决。4.5 证书相关故障证书问题在长时间运行的集群中很常见但部署阶段也可能遇到。比如执行kubectl时报certificate has expired or is not yet valid先检查系统时间date -R如果时间没问题再看看证书有效期kubeadm certs check-expiration如果证书确实快过期可以手动更新kubeadm certs renew all更新完后需要重启控制平面组件然后更新~/.kube/config。这个操作一般不改变节点 token不用重新 join。我见过有人一看到证书过期就直接重建集群真的没必要。5. 部署完成后的运维加固与效率提升5.1 让 kubectl 好用起来集群部署完不是终点是新一轮运维的起点。我第一件事就是配置 kubectl 自动补全和别名# 配置 bash 补全 echo source (kubectl completion bash) ~/.bashrc echo alias kkubectl ~/.bashrc echo complete -o default -F __start_kubectl k ~/.bashrc source ~/.bashrc不要小看这个操作日常排查时k get pods和k get nodes的打字效率完全不同特别是在你盯着终端快速切换上下文的时候。另外建议做一个简单的 RBAC 配置给运维同事单独创建一个只读账号避免所有人都拿 admin 权限。生产环境里权限爆炸是很大的隐患。5.2 备份策略要提前想好很多人集群跑起来三个月后才发现没做过 etcd 备份等到节点磁盘故障才追悔莫及。etcd 是整个集群的数据库备份它是运维的底线动作。最粗暴但有效的方法就是写个 cron 任务定期快照ETCDCTL_API3 etcdctl \ --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ snapshot save /backup/etcd-snapshot-$(date %Y%m%d).db建议至少每天备份一次保留最近 7 天。恢复流程也要提前演练一遍不要等到事故发生了才开始翻文档。5.3 监控体系对接部署完成后我强烈建议第一时间把监控搭起来。热词里反复出现的 Prometheus 确实是 Kubernetes 监控的事实标准。kubectl create -f https://raw.githubusercontent.com/prometheus-operator/kube-prometheus/main/manifests/setup/ kubectl create -f https://raw.githubusercontent.com/prometheus-operator/kube-prometheus/main/manifests/这套部署会创建一个完整的监控栈包含 Prometheus Operator、Prometheus、Alertmanager、Grafana 和各种 exporter。装好之后你可以通过 NodePort 或 Ingress 暴露 Grafana看到节点 CPU、内存、磁盘、网络流量以及 Kubernetes 组件的运行状态。如果你想接入自己的告警系统Prometheus 的 Alertmanager 支持 Webhook、邮件等多种通知方式把 webhook 集成到飞书群或者企业微信机器人集群出问题时能在第一时间收到通知。我在实际部署中就把告警集成到了团队群效果非常好。5.4 containerd 与 K8s 协同工作的要点既然热搜里有人专门问Kubernetes 是如何调用 containerd 的我在这里把链路梳理一遍。kubelet 拿到调度任务后会通过 CRI 插件调用 containerd。这个调用关系是kubelet → CRI-Containerd 插件 → containerd → containerd-shim → runc。CRI-Containerd 插件是 containerd 内部的一个 gRPC 服务负责把 kubelet 传过来的 CRI 请求转换成 containerd 的底层命令。runc 是真正执行容器创建和进程隔离的组件它负责调用 Linux 内核的 namespace、cgroup 等能力把容器跑起来。整个链路里最容易出问题的是 CRI 插件和 containerd 的版本匹配以及那个SystemdCgroup配置。如果你在节点上看到 kubelet 反复要求重启容器先看 containerd 的日志journalctl -u containerd -f | grep -i error通常日志里会直接写明是 sandbox 镜像拉不到还是 CRI 版本不支持还是 cgroup 驱动不匹配。有了日志问题就已经解决了一半。6. 部署完成后的个人心得我在实际部署中最大的体会是Kubernetes 部署最怕的不是命令复杂而是对每一项配置背后的原理缺乏理解。比如--pod-network-cidr一旦定错整个集群的网络方案都要推倒重来SystemdCgroup没改对节点 NotReady 能让你排查到怀疑人生。这些坑都不是靠背命令能避开的而是要对 kubelet、CRI、CNI 这几个核心模块间的协作关系有清晰的认知。另外部署集群之前最好先写好一份 checklist从主机名、时间同步、内核参数、镜像源、cgroup 驱动到网络插件逐项确认一轮操作下来就能把变量控制到位排错成本会大幅降低。如果你拿这套流程去部署了过程中踩到任何我没提到的新坑按看日志、看事件、看关键配置的路径排查大概率都能找到答案。最后分享一个我在多次部署后总结的小技巧所有节点执行完kubeadm join之后不管节点有没有 Ready先执行一次kubectl describe node node-name看看最下方的 Conditions里面会把 Node 当前的问题列得很清楚。很多时候节点的真实原因已经写在里面了只是没人注意到而已。把这条习惯养成你会省掉很多无头苍蝇式的排查时间。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询