1. 为什么你绕不开 Kubernetes
我最早接触 Kubernetes 是在 2018 年,当时公司要上一套微服务,服务数量从十几个一下子涨到上百个,运维同学每天被“哪个实例挂了”“依赖服务连不上”“配置又改错了”这类问题折磨到心态爆炸。老办法是脚本加自动化平台,但一次发布失败之后,发现在脚本里打补丁只会让系统越来越难维护。后来决定整体迁到 Kubernetes,过程痛苦,但两年后再回头看,这套架构至少帮我们省掉了三个专职运维的岗位。
如果你现在正准备学 Kubernetes,或者已经在公司内部被要求“把服务容器化一下”,我建议你先别急着敲命令,先把下面这些概念吃透。Kubernetes 不是某个单一工具,它是一个完整的容器编排生态,要理解它,必须先理解它要解决什么问题、由哪些核心组件组成、以及你手上的服务在它上面跑起来之后整个生命周期长什么样。
这篇文章不打算做那种“从入门到放弃”的教程,我会从实际使用的角度,把这几年踩过的坑、梳理清楚的逻辑、还有最值得花时间理解的部分一次性讲清楚。无论你是开发、运维还是测试,只要你的业务开始向微服务和容器化靠拢,这篇文章都能给你一个清晰的认知框架。
2. 它到底在解决什么问题
2.1 容器只是第一步,编排才是关键
很多人会把 Docker 和 Kubernetes 混为一谈,这是一个非常大的误区。Docker 解决的是“怎么把应用装进一个标准化的箱子里”,而 Kubernetes 解决的是“怎么管理成千上万个这样的箱子”。
拿日常生活来类比:Docker 是打包盒,能把饭菜装进去;Kubernetes 是中央厨房,它知道哪个菜该送到哪个桌,哪个厨师忙坏了需要调休,哪个灶台坏了需要检修,哪个客人点的菜需要加急处理。你不需要关心每一份菜是怎么被端上桌的,只要告诉厨房“我要 20 桌客人,每桌四菜一汤”,剩下的事它自动搞定。
具体到技术场景,Kubernetes 管理容器主要围绕这五类能力:
- 调度:把容器放在最合适的节点上,比如某台机器内存充裕、某台机器离数据更近。
- 伸缩:流量涨了自动加实例,流量降了自动减实例,不浪费资源。
- 自愈:容器挂了自动重启,节点挂了自动把服务迁移到其他节点。
- 服务发现与负载均衡:服务之间互相访问,不再需要手动记录 IP 和端口。
- 发布与回滚:新版本上线后如果出问题,可以一键回退到旧版本。
这五项能力单独拿出来每一项都有开源方案可以做,但把它们组合在一起,并且做到大规模下依然稳定,这就是 Kubernetes 最核心的价值。
2.2 从单体到微服务的必然之路
我见过很多团队,一上来就拍脑袋说“我们要用 Kubernetes,因为大家都在用”,结果上了之后发现更累了。原因很简单:如果你们的服务本来就是单体架构,十几个模块打成一个大包,Kubernetes 带来的收益非常有限,反而引入了额外的复杂度。
Kubernetes 真正发挥价值的前提,是你的应用已经或者计划拆分成多个可以独立部署的组件。只有到了这个阶段,你才会遇到这些痛:
- 服务数量多到脚本管不过来
- 多个环境(开发、测试、预发、生产)配置同步靠“复制粘贴”
- 发布一次系统,需要手动登录十台机器执行命令
- 某个服务负载高,无法快速扩出多个实例
- 线上故障后,无法快速定位到具体实例
如果你手头已经有这几个痛点,Kubernetes 就是对症的药。如果一条都不占,那我的建议是再等等,别为了技术而技术。
3. 核心架构:一张图拆解控制面与数据面
3.1 控制面:集群的“大脑”
Kubernetes 集群从逻辑上分成两个大的部分:控制面(Control Plane)和工作节点(Worker Nodes)。控制面负责做决策,工作节点负责干苦力。
控制面上面跑着五个核心组件:
- kube-apiserver:整个集群的唯一入口。无论是用户、其他组件还是命令行工具 kubectl,所有操作请求都必须经过它。它相当于公司前台,任何人都得先到前台登记,才能进到内部办事。
- etcd:集群所有数据的存储仓库。配置信息、服务状态、节点信息全部存在这里。它相当于公司档案室,数据的一致性至关重要,所以 etcd 本身是一个分布式的键值数据库,需要做集群化部署。
- kube-controller-manager:负责维持集群的期望状态。比如你声明“我要 3 个副本”,而实际只有 2 个,控制器就会发现偏差,然后出手补上缺失的副本。它相当于质检员,时刻检查实际情况和计划是否一致。
- kube-scheduler:负责为新创建的 Pod 选择一个合适的节点。它会综合节点资源余量、调度策略、亲和性规则,选出最优目标。它相当于人事部的排班员,知道谁适合上哪个班次。
- cloud-controller-manager:这是和云厂商打交道的组件,如果你跑在自建机房,这个组件通常是空的。它负责对接负载均衡、存储卷等云资源。
3.2 工作节点:真正干活的机器
每个工作节点上都运行着三个必需的组件:
- kubelet:节点上的管家,接收控制面下发的指令,负责启动容器、监控容器状态、向控制面汇报情况。它被要求保持忠诚可靠,绝不能擅自做决定。
- kube-proxy:负责节点上的网络规则,把访问某个 Service 的流量转发到具体的 Pod 上。它相当于门卫,告诉来访者“你要找的人住在几号楼几层几室”。
- 容器运行时:比如 containerd 或 CRI-O,负责真正地拉起容器进程。Docker 也可以作为运行时,但在新版 Kubernetes 中已经不推荐了。
3.3 我们常说的“对象”是什么
熟悉 Kubernetes 的人经常会说“一切皆对象”。这个说法听起来玄乎,其实很简单:所有你希望集群做的事,都可以通过声明式的 YAML 文件告诉 Kubernetes。
你写一个 YAML,描述“我要跑一个 Nginx,三个副本,暴露 80 端口”,然后提交给 API Server,剩下的工作全部由控制器完成。你不需要告诉它“先创建容器再创建网络再挂载存储”,你只需要描述最终状态,Kubernetes 自己想办法实现。
这个思路其实和 Linux 的哲学很像:一切都是文件,一切都是配置。声明式的好处是——你的基础设施状态是可审计的、可复现的,谁改了什么,改成了什么,全都有据可查。
4. 关键对象逐个拆解:Pod、Service、Deployment
4.1 Pod:最小调度单元
Pod 是 Kubernetes 里最小的部署单元,也是初学者最容易理解错的地方。很多人以为 Kubernetes 直接管理容器,其实不对,它管理的是 Pod。
一个 Pod 里可以有一个或多个容器,这些容器共享同一个网络命名空间和存储卷。最常见的场景是一个 Pod 里只跑一个主容器,但偶尔也会有一个“边车”容器,专门负责日志收集、流量代理等辅助任务。
举个例子:假设你有一个 Java 应用,它打印日志到本地文件,你可以让应用容器正常跑业务,再挂一个 Filebeat 容器在同一个 Pod 里,Filebeat 直接把应用日志读走发到 Elasticsearch。两个容器共享同一个文件目录,非常方便。
Pod 的另一个特点是它是“短命”的。它可能会因为节点故障、资源不足、探针失败而被销毁重建。记住这一点很重要,后面很多设计都会围绕“Pod 随时会挂”这个前提展开。
4.2 Service:稳定的访问入口
既然 Pod 随时会挂、随时会重建,而且重建后的 IP 会变化,那么其他服务该通过什么地址访问它呢?这就是 Service 的用途。
Service 给一组 Pod 提供一个稳定的虚拟 IP 和 DNS 名称。你只需要记住服务名,比如order-service,不管背后 Pod 的 IP 怎么变,你访问order-service永远能到正确的后端。
具体实现上,Service 通过标签选择器(Label Selector)来匹配一组 Pod。Kubernetes 会给 Service 创建一个 Endpoint 列表,里面是这个 Service 背后所有 Pod 的 IP 和端口。kube-proxy 负责把访问 Service 的流量负载均衡到这些 Endpoints 上。
4.3 Deployment:声明期望状态
Deployment 可能是你日常打交道最多的对象。它描述了应用的期望状态——镜像版本、副本数量、更新策略等。
举个例子,你要跑一个 Web 服务,期望 5 个副本,镜像版本是v1.2.3。Deployment 会确保集群里始终有 5 个副本在运行。当你升级镜像到v1.2.4的时候,Deployment 会采用滚动更新的方式,先起一个新副本,等它 Ready 了,再杀掉一个旧副本,直到全部替换完成。
如果新版本出了问题,你可以用一条命令回滚到之前正常的版本:
kubectl rollout undo deployment/web-server这个能力在生产环境的价值不可估量,我亲眼见过因为数据库迁移失败导致新版本连环报错,结果十秒钟内回滚到旧版本,线上无感知。
4.4 其他常用对象
除上面三个之外,还有几个对象值得提前了解:
- Namespace:用来做逻辑隔离,比如区分 dev、test、prod,或者区分不同团队。
- ConfigMap / Secret:把配置和敏感信息从镜像里解耦出来,改配置不需要重新打镜像。
- Ingress:七层负载均衡,通过域名或路径把外部流量转发到集群内部的 Service。
- PersistentVolume / PersistentVolumeClaim:给 Pod 挂载持久化存储,比如数据库数据。
这些对象全部通过 YAML 定义,提交给 API Server 后,由对应的控制器负责实现。学习 Kubernetes 的过程,其实就是在学习和这些对象打交道的过程。
5. 一次完整的上线流程,理解集群的工作逻辑
5.1 从创建 Deployment 到 Pod 运行
假设我们用下面的 YAML 来部署一个简单的 Web 服务:
apiVersion: apps/v1 kind: Deployment metadata: name: web-server namespace: default spec: replicas: 3 selector: matchLabels: app: web-server template: metadata: labels: app: web-server spec: containers: - name: nginx image: nginx:1.26 ports: - containerPort: 80执行kubectl apply -f deployment.yaml之后,集群内部会发生这些事:
- kubectl 把 YAML 内容发送给 API Server。
- API Server 校验无误后,把信息写入 etcd,并生成 Deployment 对象。
- Deployment Controller 发现期望副本数是 3,实际为 0,于是创建 3 个 ReplicaSet,每个 ReplicaSet 又创建对应的 Pod 定义。
- Scheduler 为每个 Pod 选择一个合适的节点,并把调度结果写回 API Server。
- 节点上的 kubelet 监听到有新的 Pod 分配给自己,就通知容器运行时拉取镜像、启动容器。
- kubelet 持续监控容器状态,通过探针判断容器是否健康。如果探针失败,就会触发重启策略。
整个链路听起来很长,但实际上从提交到三个 Pod 全部 Running,通常只需要十几秒到几十秒,取决于拉取镜像的速度。
5.2 对外暴露服务的两种方式
Pod 创建好了,外网怎么访问?根据场景不同,有两种常见做法。
第一种,如果你只需要集群内部访问,创建 ClusterIP 类型的 Service:
apiVersion: v1 kind: Service metadata: name: web-server-svc spec: selector: app: web-server ports: - protocol: TCP port: 80 targetPort: 80这样集群内部其他服务可以通过http://web-server-svc访问。
第二种,如果要暴露到集群外部并支持域名访问,推荐使用 Ingress。先创建一个 Service,再创建一个 Ingress 规则,把域名web.example.com指向这个 Service。Ingress Controller 收到流量后,会按规则转发到对应的 Service,再由 Service 转发到具体的 Pod。
我曾经见过一些团队为了省事,直接把 Service 类型设为 NodePort,让外部流量通过高端口访问集群节点。这个方法在小规模场景下没问题,但生产环境基本不推荐,因为你需要手动维护端口分配、避免端口冲突,而且负载均衡能力非常有限。
5.3 发布升级和故障恢复时发生了什么
假设你要把 nginx 镜像从1.26升级到1.27,在更新 Deployment 后,它会先生成一个新的 ReplicaSet,然后逐渐创建新 Pod、销毁旧 Pod,全程服务不会中断。
如果某个新 Pod 因为镜像不存在而启动失败,Deployment 会暂停后续更新,保留旧版本继续服务。这种设计能保证你在升级过程中万一出问题,旧服务还能扛住流量。
再假设集群里某个节点突然宕机了。Kubernetes 默认会在五分钟左右判定该节点失联,然后把它上面的 Pod 调度到其他节点重新创建。这个时间窗口值可以调整,但默认值足够应对大多数情况。
6. 生产环境常用操作速查
6.1 高频命令整理
下面的命令是我在实际工作中每天都会用到的,整理成表格方便查阅。
| 操作目的 | 命令 |
|---|---|
| 查看所有 Pod 状态 | kubectl get pods -A |
| 查看某个 Pod 的详细信息 | kubectl describe pod <pod-name> |
| 查看 Pod 日志 | kubectl logs -f <pod-name> |
| 查看节点资源使用情况 | kubectl top nodes |
| 进入 Pod 内的容器 | kubectl exec -it <pod-name> -- /bin/sh |
| 创建或更新资源 | kubectl apply -f <file.yaml> |
| 删除资源 | kubectl delete -f <file.yaml> |
| 查看 Deployment 状态 | kubectl get deployment |
| 向后回滚版本 | kubectl rollout undo deployment/<name> |
| 调整副本数 | kubectl scale deployment/<name> --replicas=5 |
6.2 查看集群信息
# 查看集群节点列表 kubectl get nodes # 查看集群版本信息 kubectl version --short # 查看所有命名空间 kubectl get namespaces如果是刚安装完集群,先跑这三条命令,确认控制面和节点都正常,再往下做其他操作。
6.3 初始化集群的关键参数说明
在安装阶段,最常用到的命令是kubeadm init,它会输出类似这样的信息:
[init] Using Kubernetes version: v1.26.0 [preflight] Running pre-flight checks这些输出并不是废话,它们对应安装过程中的关键环节:
init阶段开始初始化控制面,拉取必要的镜像。preflight阶段检查硬件、系统配置、端口占用、内核模块是否满足要求。如果哪一项不满足,会在这里直接报错并终止安装。
如果你看到 preflight 阶段报了错误,不要急着搜“怎么跳过检查”,先把问题原因搞清楚。大多数情况下,就是一些常规的系统设置没配好,比如 Swap 没有关闭、iptables规则缺失、容器运行时未配置完成。把这些基础问题解决掉,后面的步骤会顺畅很多。
7. 安装和初始化环境踩坑记录
7.1 系统要求和前置条件
无论你用的是云服务器还是自建物理机,安装 Kubernetes 都需要满足一些硬性条件。这些条件看起来琐碎,但每一项不过关都会导致后面出现问题。
- 操作系统:Ubuntu 20.04+、CentOS 7.9+ 或兼容的 Linux 发行版。
- 硬件:控制面节点至少 2 核 4GB 内存,工作节点至少 2 核 2GB。
- Swap:必须关闭,否则 kubelet 无法正常工作。可以用
swapoff -a临时关闭,并编辑/etc/fstab永久关闭。 - 内核参数:需要配置
net.bridge.bridge-nf-call-iptables等参数,以保证 iptables 规则对桥接流量生效。 - 容器运行时:推荐安装 containerd,并在配置中启用 SystemdCgroup。
- 端口:根据组件不同,需要开放 6443、2379、2380、10250、10259 等端口。
7.2 我踩过的三个坑
第一个坑是忘记关闭 Swap。有一次装好集群后,kubelet 一直处于 CrashLoopBackOff 状态,查看日志发现明确提示 “Running with swap on is not supported”,当时才想起来系统里还开着 Swap。关闭之后重启 kubelet,问题立刻解决。
第二个坑是镜像拉取超时。国内网络环境下,直接从官方源拉取k8s.gcr.io的镜像非常容易超时。后来我在初始化时给kubeadm init指定了一个国内可访问的镜像仓库地址,整个初始化速度从半小时缩短到两三分钟。
第三个坑是内存不足导致 Pod 被反复驱逐。我有一台 2GB 内存的节点,跑了几个 Java 服务之后,节点内存耗尽,Kubernetes 开始大量驱逐 Pod,导致服务全断。后来给节点配置了更完善的内存请求和限制,并为关键服务设置了高优先级,才彻底稳定下来。
7.3 托管集群可能是更香的选择
如果你所在的公司没有专职运维团队,或者不想自己维护 Kubernetes 的控制面,使用云厂商的托管集群服务是更省心的方案。自己搭建集群能让你学到更深的知识,但生产环境的选择永远是“能用有限的资源换取最大的可靠性”。
托管集群的区别是:控制面组件由云厂商替你维护,只需要自己管理工作节点的资源池。你还可以启用自动扩缩容节点,流量高峰自动加虚拟机,低谷自动回收。对于中小团队来说,这是性价比极高的方式。
8. 常见问题排查思路
8.1 Pod 一直 Pending 不运行
如果你的 Pod 长期处于 Pending 状态,先执行kubectl describe pod <pod-name>,查看事件部分。最常见的原因有三个:
- 节点资源不足,Scheduler 找不到能满足规格要求的节点。
- 有污点(Taint)阻止了 Pod 调度,且 Pod 没有对应的容忍(Toleration)。
- 项目选择了不存在的节点标签。
排查的顺序很简单:先看有没有可用节点,再看节点剩余资源,再看节点是否有污点。
8.2 Pod 一直 CrashLoopBackOff
CrashLoopBackOff 的意思是容器启动后立刻崩溃,Kubernetes 尝试重启,但每次都失败,于是进入指数退避的状态。
这时最有效的排查方式是看日志:
kubectl logs <pod-name> --previous--previous参数可以查看上一次崩溃容器的日志。常见的崩溃原因包括:启动命令写错、环境变量缺失、依赖的数据库连不上、配置文件路径不对。
这里有一个排查技巧:先审一遍 YAML 的 command 字段和启动参数,再确认 ConfigMap 是否注入成功,然后再判断是否外部依赖的问题。不要一上来就怀疑程序代码,很多时候是配置问题。
8.3 外网访问不通
如果服务在集群内访问正常,但从外部访问不了,第一步先确认你用的是 NodePort 还是 Ingress。如果是 NodePort,确认端口是否能通、防火墙是否放行;如果是 Ingress,检查 Ingress Controller 是否正常运行、域名解析是否指向正确、证书是否有效。
我还见过一个情况,Ingress 规则没有问题,但后端 Service 的selector写错了,导致 Endpoints 列表为空,流量进来后直接被丢弃。检查 Service 的 Endpoints 是最快定位问题的方法:
kubectl get endpoints如果 Endpoints 为空,基本就是 Selector 没匹配上 Pod 的标签。
9. 学习路径建议
9.1 不要一开始就追求“高可用集群”
很多新手拿到教程,第一步就想搭一个三主三从的高可用集群,结果被证书、负载均衡、etcd 集群搞得头大,往往还没开始玩就放弃了。
我的建议是先搭单节点集群,把对象模型、调度逻辑、网络模型这些核心概念玩明白,再考虑加节点、做高可用。单节点集群和真实集群在对象使用上几乎没有区别,你只需要一台 4GB 内存的机器就够了。
9.2 按这个顺序学效率最高
- 脚本:Docker 的基础操作,容器怎么跑、镜像怎么写。
- Pod、Deployment、Service、Ingress 四个对象。
- ConfigMap、Secret、Namespace 的日常用法。
- 探针、资源请求与限制、滚动更新策略。
- StatefulSet、PV/PVC、Operator。
- 网络插件工作原理,比如 Calico、Flannel。
- 可观测性体系,Prometheus、Loki、Tracing。
每到一个阶段,都手动把示例跑一遍,不要只看文档。尤其是探针和资源限制这两块,自己动手改参数、观察行为变化,比背十篇文档都管用。
9.3 遇到问题时的阵地
- 官方文档永远是第一来源,不急躁,按目录逐节读。
- 报错信息直接搜报错原文,尽量把日志贴全。
- 动手调试时多用
describe和logs,不要上来就重启资源。
Kubernetes 的学习曲线确实陡,但它值得投入时间。它带来的不是某一个具体的“银弹功能”,而是把基础设施变成了代码、变成了数据、变成了可以被审计和恢复的状态。这也是为什么它能在短短几年内成为云原生时代的通用语言——理解了它的核心模型,再去看任何云厂商的容器服务,都会觉得似曾相识。
最后说一句我自己的体会:学 Kubernetes 最难的其实不是技术本身,而是转变思维。你不再是登录一台服务器手动修问题,而是思考这个集群应该如何自我修复。当你习惯了“描述期望状态”而不是“下达操作指令”的时候,你能管理的不只是业务软件,还有整个基础设施的无限可能性。