Kubernetes 入门:理解容器编排的核心思想
广州的十一月依然带着些许闷热。凌晨一点,室友们的键盘敲击声和游戏语音逐渐平息,宿舍里只剩下我这台轻薄本风扇转动的微弱嗡嗡声。屏幕上的光打在脸上,终端里的日志一行行滚过。
大四的秋招季总是让人感到一种隐秘的焦虑。作为广州商学院的一名软件工程学生,在一所民办三本院校里,想要在技术这条路上走得更远,需要付出比常人更多的安静与耐心。很多人问过我,保持 4.02 的绩点、拿到两次国家奖学金,是不是有什么特殊的学习技巧。其实没有。无非就是把别人用来迷茫和抱怨的时间,用来多看几页官方文档,多敲几行代码。
最近这段时间,我的终端里最常出现的命令从 docker 变成了 kubectl。
学完 Docker,自然而然就会接触到 Kubernetes,也就是大家常说的 K8s。以前觉得 Docker 已经足够神奇了,它像是一个完美的集装箱,把应用和运行环境打包在一起,解决了“单个应用如何打包运行”的问题。只要有 Docker 环境,我的代码在我的电脑上能跑,在服务器上就一定能跑。
但随着自己写的项目稍微变大一些,微服务拆分得稍微多一点,新的问题就出现了。当我有几十个甚至上百个容器需要部署、需要互相通信、需要处理某台机器突然宕机的情况时,靠手动敲 docker run 显然是不现实的。K8s 解决的,正是这个“大量容器如何协调管理”的问题。它就像是一个庞大港口的调度系统。
K8s 的概念很多,刚接触时确实容易让人感到挫败。但我慢慢发现,只要抓住几个核心概念,这个庞然大物其实有着非常优雅的内在逻辑。
Pod:最小的生长单元
在 K8s 的世界里,最小的调度单位不是容器,而是 Pod。
一开始我很不理解,为什么在容器外面还要再套一层壳。后来在做本地实验时才明白,有些应用天生就是要绑定在一起的。比如一个 Web 服务容器,和一个专门负责收集这个 Web 服务日志的 sidecar 容器。它们需要共享网络,共享存储,同生共死。Pod 就像是一个豌豆荚,里面的容器就是豌豆,它们共享着同一个生存环境。
写第一个 Pod 的 YAML 文件时,我对着文档看了很久:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:latest
ports:
- containerPort: 3000
执行 kubectl apply -f pod.yaml 后,看着状态从 Pending 变成 ContainerCreating,最后定格在 Running。那种感觉很奇妙,就像你在数字世界里种下了一颗种子,然后看着它发芽。
Deployment:声明式的期望
只有 Pod 是不够的。如果 Pod 所在的节点物理机突然断电了,这个 Pod 也就随之死亡了,K8s 默认并不会帮你重新拉起一个单独的 Pod。
这时候就需要 Deployment。这也是 K8s 最吸引我的地方——声明式(Declarative)API。
以前写脚本管理服务器,是命令式的。我们要告诉机器“第一步做什么,第二步做什么”。但在 K8s 里,你只需要告诉它“我期望的状态是什么”。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:v2
在这个文件里,我写了 replicas: 3。我把这份“图纸”交给 K8s,它就会去巡视整个集群,确保任何时候都有 3 个 my-app 的 Pod 在运行。
我曾在本地测试过,手动用命令删掉其中一个 Pod。几乎在下一秒,K8s 就察觉到了实际状态与期望状态的偏差,立刻又拉起了一个新的 Pod。这种不需要人工干预的“自愈”能力,让习惯了半夜爬起来重启服务的开发者感到一种莫名的安心。它不仅管理副本数,还能平滑地处理滚动更新,让服务在升级时做到零停机。
Service:稳定的访问锚点
有了 Deployment 保证 Pod 的存活,网络通信的问题又摆在了面前。
Pod 是脆弱的,随时可能被销毁重建。每次重建,Pod 的 IP 地址都会发生变化。如果前端应用直接写死了后端 Pod 的 IP,那后端一更新,前端就找不到家了。
Service 就是为了解决这个问题而存在的。它为一组 Pod 提供了一个稳定的访问入口。
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
通过 selector 标签,Service 知道自己应该把流量转发给哪些 Pod。不管背后的 Pod 怎么生生死死、IP 怎么变幻,Service 的 IP 和内部域名始终是固定的。它就像一个永远站在前台的接待员,你只要把请求交给他,他总能帮你找到当前正在值班的业务员。
为什么我们需要 K8s
在没有接触 K8s 之前,我的项目部署往往是几行写在记事本里的备忘录,或者是几个简单的 Shell 脚本。但当把视角拉高,放到企业级的高可用架构下来看,手动管理和 K8s 自动化的差距是巨大的。
我习惯把这种差异总结在心里:
| 手动管理 | K8s 自动化 |
|---|---|
| 手动启停容器 | 声明式期望状态 |
| 手动配置 Nginx 负载均衡 | Service 自动分发流量 |
| 半夜宕机需要人工告警恢复 | 故障自愈(self-healing) |
| 流量高峰手动加机器扩容 | HPA 根据 CPU/内存自动扩缩 |
手动管理就像是在走钢丝,随着业务复杂度的增加,掉下去的风险呈指数级上升。而 K8s 则是为你铺设了一张巨大的安全网。虽然编织这张网的学习成本很高,但在网织成的那一刻,一切都是值得的。
个人感受与写在最后
合上电脑前,我看了一眼窗外,广商的校园已经完全沉入夜色。
K8s 的学习曲线确实陡峭,除了上面提到的这些,还有 ConfigMap、Secret、Ingress、PV/PVC 等等繁多的概念。有时候看着长长的官方文档,也会感到一丝疲惫。
但对于想做后端或者 DevOps 的同学来说,这是绕不过去的技术底色。在简历筛选越来越严格的今天,学历或许是一个门槛,但技术深度永远是打破门槛的利器。机器是很诚实的,你敲下的每一行配置,它都会给你最真实的反馈。它不在乎你来自哪里,只在乎你的逻辑是否严密。
对于刚开始接触的同学,我不建议一上来就去折腾多节点的集群搭建,那样很容易在网络插件和证书配置的泥潭里耗尽热情。建议从 Minikube 或者 kind 开始本地实践。先在自己的电脑上把集群跑起来,写几个简单的 YAML,看着 Pod 跑通,理解核心概念,再慢慢深入到底层原理。
技术是一场漫长的马拉松。今晚搞懂了这几个组件,明天再去看看 Ingress 是怎么做七层路由的。每天往前走一点点,也就足够了。