1. 为什么我在生产环境里最终选了 Rancher
这个系列写到第五篇,前面几篇我们把集群怎么搭、kubectl 怎么用、Service 有哪些类型、Ingress 怎么配都过了一遍。按道理说,命令行玩得转,集群也能跑起来,是不是就够了?如果你是自己学习、只有一两套环境,那确实够了。但一旦你手上有三套、五套集群,或者要交给团队里其他同事一起维护,天天靠 kubectl 在黑窗口里敲命令,效率会变得很难看。
我印象特别深的一件事情:有一年我们测试环境同时跑着 dev、staging 两套集群,另外还有一套给数据分析团队临时申请的小集群。每次有人要部署一个服务,我就得在终端里先切 kubeconfig 上下文,再检查节点状态,再看 namespace,然后才是 apply yaml。操作本身不难,但特别琐碎,而且不同成员之间沟通成本很高,经常出现"我这个应用怎么起不来""你帮我看看是不是资源不够"这种对话。后来我被问烦了,决定把 Rancher 引入进来,这一换就是三年多,再也没有回去过纯命令行管理。
Rancher 是什么?简单说,它就是一个 Kubernetes 的多集群管理平台,给你一个 Web 界面,在界面上可以创建集群、导入已有集群、部署应用、查看 Pod 日志、管理配置项和密钥、给团队成员分配权限,甚至能监控集群状态。它的本质是把 K8s 那套繁琐的 YAML 和 kubectl 操作封装成可视化操作,但不是替你把 K8s 藏起来,反而让你在界面上看到的东西都能对应回 CLI 里的对象。
不需要把 Rancher 想象得多神秘,它就是跑在你集群里的一个应用,自己也要吃资源,自己也有 Pod 和命名空间。理解这一点,后面部署和排错会顺畅很多。
如果你现在面临的选择是"团队里已经有 K8s 集群了,但缺少统一管理手段",或者"想给同事一个不用学 kubectl 也能看懂集群状态的可视化入口",那 Rancher 基本就是最主流的那条路。如果你只是单机 k3s 自己玩,那也可以装,只是收益没那么明显。
2. Rancher 部署实操:两条路径和一个最容易出错的点
2.1 准备环境:节点规划和资源要求
先说结论:Rancher 本身不是重应用,但它管理多个集群的时候,内存和网络占用会明显上涨。我见过有人把 Rancher 直接塞在一个只有 2G 内存的节点上,结果界面操作起来卡到怀疑人生。建议是单独给它一个节点,4G 内存起步,CPU 2 核起步,如果后面还要接很多下游集群,8G 内存会更稳妥。
我自己当时是在已有的 K8s 集群里用 Helm 方式部署的,但如果你不想让 Rancher 和你自己的业务集群耦合太深,也可以单独准备一台虚拟机,装上 Docker 后直接 docker run 跑一个 standalone 版 Rancher。这里我给两种方式,你按自己的场景挑。
先从 Docker 单机版说起,这种方式适合快速体验,也适合只管理一两个现有集群的场景:
docker run -d --name rancher \ --restart=unless-stopped \ -p 80:80 -p 443:443 \ --privileged \ rancher/rancher:latest这行命令干了这么几件事:拉取 rancher/rancher 镜像,映射 80 和 443 端口,设置重启策略,开了 privileged 权限。其中 privileged 这个参数挺关键的,因为 Rancher 内部要管理容器网络和策略,权限不够会出各种莫名其妙的初始化问题。
启动之后,浏览器访问 https://<节点IP> ,会看到首次初始化页面。注意是 https 不是 http,因为 Rancher 默认就用自签名证书。
2.2 初始化配置和 Rancher 内部结构
登录之后第一步是设置 admin 密码,然后会让你填写 Rancher Server URL。这个 URL 要填一个其他节点和集群都能访问到的地址,不能填 localhost。如果你只是本机耍一下,填 IP 就行;如果是正经环境,最好填一个域名,后面导入集群的时候,下游节点的 agent 要通过这个地址回连 Rancher Server。
Rancher 初始化完成后,它会自带一个叫 local 的集群,这个 local 集群指的就是 Rancher Server 自己所在的那个集群。你可以在界面上看到 local 集群的状态。很多人第一次看到这个会有点懵:我还没创建集群,怎么就有了一个?这是因为 Rancher 本身是跑在 K8s 之上的,它把自己运行的集群纳管了。
如果你是 Docker 单机方式部署的 Rancher,它内部其实也内置了一个单节点 K8s 集群,这就是 local 的由来。这个设计的好处是你打开 Rancher 第一眼就能看到一个"活的"集群,可以立刻体验界面上部署应用、看 Pod 日志这些操作,不需要先东拼西凑搞一个集群出来。
2.3 从 Rancher 界面反向理解 Pod
你如果点开 local 集群,然后去工作负载(Workload)页面,会看到 Rancher 后台自己跑了很多工作负载,名字里都带 cattle 字样。Rancher 的底层组件都放在 cattle-system 这个命名空间里,这个命名空间的 Pod 就是 Rancher 自身的组成部分,不要乱删。
界面上部署工作负载时,你会看到一个表单,其实这个表单背后生成的就是一个 K8s Deployment YAML。你可以这么理解:界面上的每个字段,对应着 YAML 里的某个字段。比如填容器镜像,对应的是 spec.template.spec.containers[].image;填环境变量,对应的是容器里的 env。Rancher 只是把 YAML 转成表单,表单再翻译回 YAML,它在中间没有发明任何新东西。
这个设计我觉得对新手来说反而是个学习 K8s 的捷径:你在界面上改了某个参数,按一下右上角的"编辑 YAML",就能看到对应的字段长什么样;反过来,你贴一段 YAML 进去,Rancher 也会帮你渲染成表单。多切换几次,Pod 的 YAML 结构就熟了。
3. 导入已有集群:Rancher 最实用的一个能力
创建新集群不是 Rancher 最吸引我的地方,最吸引我的是导入已有集群。你可以把任何一套 K8s 集群接入 Rancher 管理,不管它是 kubeadm 装的、k3s 装的、还是云厂商托管的,只要你有集群的 kubeconfig,就能导进来。
3.1 具体操作步骤
在 Rancher 界面左上角切到"更多集群",点"导入已有集群",选"通用"选项,Rancher 会给你一段 kubectl apply 的命令。这段命令本质上是往你的集群里安装 Rancher agent 组件,agent 会负责把你的集群状态上报给 Rancher Server。
执行完 apply 之后,会创建 cattle-system 命名空间,并在里面启动 rancher-cattle-agent 的 Pod。你可以等一两分钟,刷新页面,集群状态就会从 Provisioning 变为 Active。
这里有一个坑就是 agent Pod 一直 CrashLoopBackOff。最常见的原因是 Rancher Server URL 填的是 localhost 或者一个下游集群访问不到的地址。你可以 describe 那个 agent Pod,看日志里有没有连接超时的报错。有个判断的小技巧:把 URL 里那个地址放到浏览器里,从下游集群所在的那台机器上访问一下,通不通一下就知道了。
3.2 给 Rancher agent 分配权限的细节
导入集群时,Rancher 要求你提供一个拥有 cluster-admin 权限的 kubeconfig。这个要求让不少刚接触的人心里打鼓,担心安全问题。实际上 Rancher 需要一个足够权限的账号来创建它自己的命名空间和 RBAC 规则,否则它在下游集群里什么都干不了。
如果你实在不放心,可以在 apply Rancher 给的那段命令之前,自己先建一个专门的 ServiceAccount,绑定 cluster-admin 角色,然后用这个 SA 的 token 生成一个临时 kubeconfig,再用这个临时 kubeconfig 去导入。不过说实话,对大多数场景来说,直接用 admin kubeconfig 装完 agent 之后马上把权限回收掉,也够用。Rancher 只是启动阶段需要这个权限,agent 起来之后,日常上报走的是它自己的服务账号。
集群导入成功后,你的收获是:一个浏览器页面里能看到所有集群的节点 CPU、内存使用率、Pod 分布情况。这对团队协作的价值非常大,再也不是"每个人都得自己去配一遍 kubeconfig"的状态了。
4. Pod 详解:它不是一个容器,而是一组容器的"盒子"
4.1 从一次部署事故说起
讲一个我自己真实遇到的场景:有个同事把 Nginx 和业务代码放在同一个 Deployment 里,但是开了两个容器,一个是 Nginx 反代,一个是 Java 应用。他部署完发现,只看到 Nginx 容器的日志,业务代码的日志怎么都找不到。后来排了半天才发现,他把两个容器放在了同一个 Pod 里,而日志查询时默认只看第一个容器。
这个经历恰好能引出 Pod 的一个核心概念:Pod 是最小的调度单位,但它可以包含多个容器。这些容器共享同一个网络命名空间和存储卷,可以通过 localhost 互相访问,也可以共享磁盘文件。也就是说,Pod 是一个"盒子",盒子里的容器是室友关系,住在同一个屋檐下。
那什么时候应该一个 Pod 多个容器?最经典的例子是 sidecar 模式:主容器跑业务,sidecar 容器负责收集日志、代理流量、或刷新配置文件。两个容器要紧密协作,而且生命周期完全一致,就该放进同一个 Pod。如果两个服务之间只是普通的服务调用关系,那应该拆成两个 Deployment,通过 Service 去访问。
4.2 Pod 关键字段:从一段 YAML 说起
我们来看一段最基础的 Pod YAML,把它彻底拆开:
apiVersion: v1 kind: Pod metadata: name: my-pod labels: app: demo namespace: default spec: containers: - name: demo-container image: nginx:1.25 imagePullPolicy: IfNotPresent ports: - containerPort: 80 env: - name: MY_ENV value: "hello" restartPolicy: Always这里面的字段,我在实际工作中发现新手最容易忽略的是 imagePullPolicy。你不填这个字段,K8s 会根据镜像 tag 推断:如果 tag 是 latest,默认总是拉取;如果 tag 是具体的版本号,默认只在本地没有时才拉取。这种推断逻辑坑过不少人:明明改了镜像,怎么 Pod 里跑的还是旧版本?很可能就是因为你改的还是同一个 tag,而 pullPolicy 是 IfNotPresent,节点上已经有缓存了,K8s 就不会重新拉了。
还有一个字段是 restartPolicy。它有三个值:Always、OnFailure、Never。如果你只是跑一个一次性批处理任务,用 kubectl run 直接建 Pod,默认的 restartPolicy 是 Always,意味着任务跑完了还会被重新拉起。这时候正确做法是用 Job 资源,或者在 Pod 里显式把 restartPolicy 设为 Never。
4.3 Pod 的很多属性其实不是配置在 Pod 上
这一节讲一个容易混淆的点:很多人以为 Pod 的 YAML 里什么都该写,但其实 nodeSelector、污点容忍、资源配额这些字段种在 Pod 这一层写的,有些则在 Deployment 的 spec.template 里写。
你如果手写一个裸 Pod,nodeSelector 确实可以直接写在 spec 里。但如果你用的是 Deployment,你需要把它嵌套在 spec.template.spec 下面,而不是 Deployment 的最外层。这个嵌套关系一旦搞错,apply 的时候不报错,但你会发现这个调度约束根本没生效,Pod 还是被随机调度到别的节点去了。因为 Deployment 里真正描述 Pod 内容的字段都在 template 下面,Deployment 自己那层的 spec 描述的是副本数、更新策略这些"关于 Pod 集合"的属性。
我把这个对应关系列一个表,方便你自查:
| 想要的效果 | 裸 Pod 中的位置 | Deployment 中的位置 |
|---|---|---|
| 容器镜像和启动命令 | spec.containers | spec.template.spec.containers |
| 调度到指定节点 | spec.nodeName 或 nodeSelector | spec.template.spec.nodeSelector |
| 限制资源用量 | spec.containers[].resources | spec.template.spec.containers[].resources |
| 注入环境变量 | spec.containers[].env | spec.template.spec.containers[].env |
这个嵌套结构是整个 K8s 声明式设计的精髓,你改任何 Pod 的"模板",都要去 template 下面改。理解了这一点,很多"我改了 YAML 为什么不生效"的问题就解决一半了。
5. 从创建到删除:Pod 的生命周期和日常运维命令
5.1 Pod 的五个阶段
Pod 从创建到结束,会经历几个固定的生命周期阶段。kubectl get pod 里看到的状态,对应的就是这几个阶段的一种。
- Pending:Pod 已经被 API Server 接受,但还没调度到节点上,或者镜像还在拉取中。这个状态下,如果长时间不变化,大概率是调度失败或镜像拉不动。
- Running:Pod 已经绑定到节点,至少有一个容器在运行中,或者容器处于启动或重启过程中。
- Succeeded:Pod 里所有容器都成功执行完毕并退出,不会再重启。一般出现在 Job 或一次性任务中。
- Failed:至少有一个容器以非零状态退出。
- Unknown:API Server 无法获取到 Pod 的状态。通常是节点失联或网络分区导致的。
这里有个有价值的经验:正常的 Web 服务,Running 状态里也会出现 ContainerCreating 这种显示。注意 ContainerCreating 不是一个独立的阶段,它属于 Running 大阶段内部的一个过渡子状态。如果新创建的 Pod 卡在 ContainerCreating 很久,排错重点看两个地方,一个是镜像能不能拉下来,另一个是存储卷能不能挂载上。
5.2 日常监控 Pod 的三个命令
我在用 Rancher 之前,每天盯着终端看 Pod,最多的三个操作是 logs、describe、exec。如果你不记得别的,先把这三个记牢。
kubectl logs 看容器日志。一个 Pod 里有多个容器的话,要加 -c 指定容器名,否则会提示你选择。这个就是刚才 Nginx 那个事故里我们最后用到的命令。
kubectl describe pod 看事件和状态详情。Pod 出问题的时候,describe 是最有用的命令,它会列出最近的事件,包括调度成功没有、镜像有没有拉取、探针有没有失败。注意看 Events 那一部分,前面的 STATUS 和容器状态只是结果,原因都在 Events 里。
kubectl exec -it 进入容器排查。生产环境一般不建议随便进容器,但在测试环境,进去看目录结构、手动执行脚本,还是省时间的。如果你实在不想记命令,Rancher 界面上也有对应的点按式入口,点进 Pod 详情就能看到"执行命令"和"查看日志"的按钮。
5.3 静态 Pod 这个概念,知道一下不吃亏
还有一个概念,虽然日常写得少,但在排查节点问题时会遇到,就是静态 Pod。它不是通过 API Server 创建的,而是由 kubelet 直接从指定目录下的 YAML 文件运行的 Pod。静态 Pod 创建出来之后,你也可以在 API Server 里看到它,但你不能从 API Server 这边删掉它,删了 kubelet 又会按照文件重新给你拉起来。
常见的静态 Pod 有 kube-apiserver、kube-controller-manager、kube-scheduler,它们就是以静态 Pod 的方式跑在所有控制平面节点上的。如果你发现某个控制平面组件的状态不对,别只盯着 Pod 看,还要去 /etc/kubernetes/manifests 目录看对应的 YAML 文件有没有被改坏。这个目录里的文件一旦损坏,Pod 删了也会反复重建,而且重建的表现就是 CrashLoopBackOff。
6. 实战排错:Kubernetes Pod Sandbox 创建失败排查全链路
先把一句话结论放在前面:Pod 卡在 ContainerCreating,describe 之后看到 failed to create pod sandbox,本质上是容器运行时(containerd 或 Docker)在创建 Pod 网络栈和沙箱环境的时候出了问题,而不是你的应用镜像有问题。
这个错误我翻来覆去遇到过很多次,而且每次原因都可能不一样,所以这里我把自己总结的排查链路完整写下来,照着走一遍基本能定位到根因。
6.1 先看错误原文和上下文
首先还是那个老动作:kubectl describe pod -n 。Events 区域如果出现类似下面这两行:
Failed to create pod sandbox: rpc error: code = Unknown desc = failed to create containerd task: failed to create shim task: ...或者:
Failed to create pod sandbox: rpc error: code = Unknown desc = networkPlugin cni failed to set up pod "xxx" network: failed to set bridge addr: ...注意区分两类:一类是 containerd 根本起不来容器,另一类是 CNI 网络插件配置网络失败。这两类的下一步检查方向完全不同。
6.2 如果是 containerd 起容器失败
这种情况优先怀疑节点上的磁盘空间、inode 耗尽、或者 containerd 服务状态异常。
用 df -h 看磁盘,用 df -i 看 inode。我遇到过一次特别典型的故障:某个节点跑了日志采集程序,但没有设置轮转策略,/var/log 直接把磁盘撑满了。结果是所有新 Pod 都卡在 sandbox 创建阶段,错误里面有一句 failed to create containerd task。
另一层原因是容器镜像拉取失败但错误信息是包含在 sandbox 失败里的,有时候 describe 显示的 still pulling 或者 manifest unknown 会被淹没。你可以在节点上直接用 crictl images 看看本地有没有目标镜像,没有就 crictl pull 手动拉一次,排除镜像仓库连通性问题。
6.3 如果是 CNI 网络配置失败
这类错误跟节点上的 CNI 插件有关。常见的 K8s 网络方案是 Calico、Flannel、Cilium。一旦节点上的 CNI 配置文件损坏,或者网桥 IP 冲突,都会导致 sandbox 创建失败。
排查顺序是这样:
先检查 CNI 配置文件。默认路径是 /etc/cni/net.d/,里面应该有一个 .conf 或 .conflist 文件。如果里面内容为空,或者配置的插件路径不对,节点上所有新 Pod 都可能起不来。
再检查节点上的 CNI 插件二进制是否存在。一般默认路径是 /opt/cni/bin/,里面至少有 bridge、loopback、portmap 这些二进制。有人升级过系统之后,这个目录被清理过,结果踩了一个大坑,报错里看不到任何网络插件的字眼,只是说 sandbox 创建失败。
然后是看 kubelet 日志。journalctl -u kubelet -f 实时跟一下,通常会打印比 describe 更详细的网络配置错误原因。比如网桥重名、子网冲突这种,只有在 kubelet 日志里才看得到。
6.4 最容易被忽略的一个点:节点路由表和防火墙
有一回我排一个 sandbox 创建失败的故障,所有常规项都检查了一遍,磁盘没问题、镜像没问题、CNI 文件没问题,最后发现是节点上的 fail2ban 把容器网段的流量给拦了。CNI 插件创建 veth 设备、分配 IP、设置路由,结果到打通网络那一步,流量在出口被防火墙策略丢弃,整个网络初始化判定失败。
这种问题从错误信息上看永远是 networkPlugin cni failed,但根因可能五花八门。遇到 CNI 报错别急着重装插件,先把 iptables -L 和 ip route 看一眼。生产环境里顺手加个防火墙策略,然后整个节点上所有新 Pod 都起不来的案例,不是一例两例了。
6.5 恢复手段
快速定位到根因之后,恢复手段一般两种:
如果只是节点上某个组件的状态不对,比如磁盘清理后、防火墙规则修正后,通常不需要重启整个节点,重启 containerd 或者 kubelet 就行:
systemctl restart containerd systemctl restart kubelet如果是 CNI 插件的状态彻底乱了,最稳妥的办法是删除这个节点上的所有相关 Pod,让 K8s 重新调度重建。操作前一定要确认你对业务影响的评估,不要在生产直接批量删。
需要说明的是,以上排查链路是基于我在实际环境中多次踩坑后的总结,具体到不同版本的 containerd、K8s 或网络插件,错误措辞可能会有细微差异,但检查顺序和思路是一致的。
7. 结合 Rancher 界面做日常管理的几个加分实操
前面讲完 Rancher 部署和 Pod 原理,这里再补充几个我在日常管理里觉得特别好用的组合玩法。
Rancher 的日志查看页面比 kubectl logs 舒服的地方在于,它支持在界面上直接按时间范围过滤日志,还能同时看多个容器的日志。要知道用命令行看一个 Deployment 下多个副本的日志,你得先 kubectl get pods 拿到所有 Pod 名,再一个个加 -c 参数去追,体验实在一般。Rancher 的工作负载页面里点某个 Deployment,切到日志页签,直接就能流式查看该工作负载下所有 Pod 的日志聚合。这个场景用在排查分布式应用联调问题时,省下的是大量的上下文切换时间。
另一个特别好用的点是:Rancher 的"编辑 YAML"按钮。你在 Rancher 里创建的任何工作负载、Service、ConfigMap,都可以随时切换到 YAML 模式直接修改,而且它会在你保存之前做一次校验,如果 YAML 格式有问题,界面直接报错,不会像 kubectl apply 那样把错误的对象提交到集群里再失败。这对刚开始学 K8s 的人来说是个非常好的保护层。
你还可以把 Rancher 里写好的 YAML 导出,存到 Git 仓库里做版本管理。Rancher 并不强制你绑定 GitOps 流程,但你可以自己约定:环境手动调整通过 Rancher,最终版本以 Git 里为准。这样既享受了界面的便利,又没有被锁定在某个私有配置格式里。
8. Rancher 部署之外,还要注意版本和升级策略
最后想提醒一下版本相关的事情。Rancher 版本迭代速度不慢,v2.7 是一个分水岭,从 v2.7 开始,Rancher 不再默认强制捆绑 Istio,整个界面和底层代码结构也有不少调整。如果你在网上下载到一些教程,看到界面长得跟你装的不太一样,很可能就是版本差异,不用慌。
升级 Rancher 本身,如果 Docker 方式安装,直接拉新镜像重启容器。
docker pull rancher/rancher:最新版本 docker stop rancher docker rm rancher docker run -d --name rancher \ --restart=unless-stopped \ -p 80:80 -p 443:443 \ --privileged \ rancher/rancher:最新版本这里有个极其重要的注意点:Rancher 的数据都在容器的 /var/lib/rancher 目录里,如果你不带任何数据卷直接升级,数据会全部丢失。正确做法是先把数据目录挂出来,或者至少先做一次备份。如果你升级后起不来,回头看第一步,大概率就是数据没挂载。
我自己在升级方面吃过一次类似的亏,因为没保留数据卷,整个集群的所有管理配置、项目权限、导入的集群信息全部重置,后来花了半天时间重新配置。所以这里特意多写一句,防止再有人踩。
9. 最后,关于 Rancher 和 Pod 的一个学习建议
写到这里,这篇的内容基本覆盖了 Rancher 的部署、导入集群、Pod 的定义、生命周期和常见排错链路。如果让我给一个学习路径上的建议,我会说:不要一开始就追求把所有 K8s 对象都在界面上点一遍,而是先在命令行里把 Pod 的几个核心命令用熟,然后再用 Rancher 去对照界面上的信息。
原因很简单:命令行培养你对"底层事实"的感觉,Rancher 界面培养你的操作效率。两种技能不能互相替代。你如果只会界面,出了问题很难真正定位根因;你如果只会命令行,日常管理的效率上不去。最理想的状态是看到界面上一个 Pod 的状态,你能立刻在脑子里映射出背后的 YAML 和事件,反过来也一样。
这个映射能力没有捷径,就是多写多删多排错。我也是从把一个 Deployment 改了十几遍,反复滚动更新、回滚、看事件,才慢慢把 Pod 的各种行为模式记在脑子里的。希望这篇能帮你在这个过程里少走几步弯路。