Kubernetes (K8s) 核心概念与 Node.js 实战
在掌握了 Docker 之后,我们知道如何将单个 Node.js 应用打包成镜像并运行。但如果在生产环境中,你有 100 个微服务,每个服务需要跑 5 个实例,并且需要处理负载均衡、崩溃重启、版本回滚——这就不是简单的 docker run 能解决的了。
Kubernetes (简称 K8s) 就是用来解决大规模容器编排问题的操作系统。对于现代大厂的全栈开发者来说,了解 K8s 的核心概念是必修课。
1. 全栈视角下的 K8s 核心概念
K8s 的概念非常多,但从全栈/后端开发者的日常使用来看,最核心的是以下几个“资源对象”:
📦 Pod(豆荚)
- 定义:K8s 中最小的部署单元。一个 Pod 里面可以包含一个或多个紧密相关的容器。
- 类比:你可以把 Pod 想象成一台“逻辑上的轻量级虚拟机”,里面跑着你的 Node.js Docker 容器。通常我们是一个 Pod 跑一个 Node.js 容器。
🚀 Deployment(部署)
- 定义:负责管理 Pod 的生命周期,比如规定这个 Node.js 应用需要同时跑 3 个副本(Replicas)。
- 能力:
- 自愈能力:如果某个 Pod 崩溃了,Deployment 会自动拉起一个新的。
- 滚动更新 (Rolling Update):发布新版本时,K8s 会逐个替换旧的 Pod,实现零停机部署。
🌐 Service(服务)
- 定义:Pod 的 IP 是会动态变化的(崩溃重启后 IP 就变了)。Service 为一组相同的 Pod 提供一个固定的内部访问地址和负载均衡。
- 类比:相当于集群内部的 Nginx upstream。它确保无论背后的 Pod 怎么生生死死,前端或其他服务只需访问 Service 的固定名称即可。
🚪 Ingress(入口)
- 定义:管理集群外部访问集群内部服务的路由规则。
- 类比:相当于整个 K8s 集群对外的 Nginx 暴露层,可以配置域名、HTTPS 证书、路径转发(比如
/api转发给后端的 Service,/转发给前端的 Service)。
⚙️ ConfigMap & Secret(配置与机密)
- 定义:将环境变量(如
NODE_ENV)存放在 ConfigMap 中,将敏感信息(如数据库密码)存放在 Secret 中。 - 作用:让代码/镜像与配置解耦。同一个镜像,挂载测试环境的 ConfigMap 就是测试服,挂载生产环境的就是正式服。
2. 为什么在 K8s 中不推荐使用 PM2?
在之前的部署章节提到过,传统物理机部署极度依赖 PM2,但在 K8s 中通常直接使用 node app.js(单进程模式)。
- 能力重叠:PM2 擅长的进程守护、负载均衡,K8s 在 Pod 和 Service 层面已经做得更好了,甚至跨越了单台物理机的限制。
- 生命周期割裂(最致命):K8s 通过监控容器的主进程(PID 1)来判断应用状态。如果用 PM2 启动,PM2 成了 PID 1,只要 PM2 没死,K8s 就认为服务正常。即使你的 Node.js 业务进程已经僵死,K8s 也无法感知并重启它。
- 日志收集:K8s 默认收集容器输出到
stdout和stderr的标准输出日志。PM2 会接管日志并写入自己管理的文件,这会让 K8s 原生的日志采集系统(如 Fluentd/Filebeat)收集起来变得极其麻烦。
3. Node.js 应用适配 K8s 的两个关键点
要让 Node.js 应用在 K8s 中完美运行,必须在代码层面做好两件事(这通常是大厂架构组的强制要求):
① 健康检查接口 (Probes)
K8s 需要知道你的 Node.js 是否准备好接收流量,以及是否“活着”。你需要暴露简单的 HTTP 接口供 K8s 定时探测。
const express = require("express");
const app = express();
// 存活探针 (Liveness Probe):返回 200 表示进程没死。
// 如果超时或报错,K8s 会无情地直接杀掉该 Pod 并拉起一个新的。
app.get("/health/liveness", (req, res) => {
res.status(200).send("OK");
});
// 就绪探针 (Readiness Probe):返回 200 表示可以接收用户请求了。
// 有时候 Node.js 启动了,但还在连接数据库、拉取远程配置,此时应返回 500。
// K8s 看到 500 就不会把用户的真实流量转发过来。
app.get("/health/readiness", (req, res) => {
if (db.isConnected) {
res.status(200).send("OK");
} else {
res.status(500).send("Not Ready");
}
});
② 优雅退出 (Graceful Shutdown)
当 K8s 想要停止一个 Pod(比如你发布了新版本进行滚动更新时),它会向容器发送 SIGTERM 信号。如果你的 Node.js 收到信号直接退出,正在处理的请求就会立刻报错 502。
process.on("SIGTERM", () => {
console.log("K8s 通知准备停止 Pod...");
// 1. K8s 此时会将该 Pod 从 Service 中摘除,不再分配新流量过来
// 2. Node.js 停止接收新请求
server.close(() => {
// 3. 等待正在处理的存量请求执行完成
// 4. 断开数据库连接
db.disconnect();
process.exit(0); // 彻底安全退出
});
});
4. 实战:Node.js 的标准 K8s YAML 配置
了解概念后,全栈开发者通常需要能看懂或编写基础的 K8s YAML 配置文件(也叫声明式 API)。
# ==========================================
# 1. 定义 Deployment (管理 Node.js 实例)
# ==========================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nodejs-app
spec:
replicas: 3 # 启动 3 个 Node.js 实例副本
selector:
matchLabels:
app: my-nodejs-app
template:
metadata:
labels:
app: my-nodejs-app
spec:
containers:
- name: nodejs-container
image: your-registry.com/my-nodejs-app:v1.0.0
ports:
- containerPort: 3000
# 告诉 K8s 怎么进行健康检查
livenessProbe:
httpGet:
path: /health/liveness
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/readiness
port: 3000
---
# ==========================================
# 2. 定义 Service (内部负载均衡器)
# ==========================================
apiVersion: v1
kind: Service
metadata:
name: my-nodejs-service
spec:
selector:
app: my-nodejs-app # 自动找到上面打上该标签的 3 个 Pod,并将流量分发给它们
ports:
- protocol: TCP
port: 80 # Service 暴露给集群内部其他服务访问的端口
targetPort: 3000 # 实际转发到 Node.js 容器内部的端口
5. K8s vs PM2:核心能力对比总结
看完上面的 YAML 配置,你可能会觉得 K8s 极其繁琐,而 PM2 只需要一行 pm2 start app.js -i max 就搞定了。为什么大厂还要“折腾” K8s?
其实它们两者的维度完全不同。PM2 是单机维度的进程管家,而 K8s 是集群维度的操作系统。
| 核心能力 | PM2 (单机进程管理) | K8s (集群容器编排) | K8s 怎么做的? |
|---|---|---|---|
| 负载均衡 | 只能在当前机器的 CPU 多核之间分配流量 (cluster 模式) | 可以在几十台上百台不同物理机的 Pod 之间分发流量 | 通过 Service 和 Ingress 实现。 |
| 崩溃重启 | 进程崩溃了,PM2 原地重启进程 | 容器/Pod 崩溃了,K8s 重新拉起一个新的 Pod(甚至可以调度到另一台健康的物理机上) | 通过 Deployment 维持期望的 replicas 副本数。 |
| 扩缩容 | 只能修改 instances 数量,上限是当前机器的 CPU 核数 | 突破单机限制,只需修改 replicas: 100,可以跨几十台机器瞬间拉起 100 个实例 | 甚至可以根据 CPU 利用率自动扩缩容(HPA - Horizontal Pod Autoscaler)。 |
| 健康检查 | 只判断进程的 PID 还在不在(如果进程假死、死循环,PM2 无法感知) | 提供应用级别的 livenessProbe 和 readinessProbe,深入探测接口响应状态 | 如上文的 YAML 所示,如果 HTTP 接口返回 500,K8s 会介入处理。 |
| 零停机发布 | pm2 reload 逐个重启进程 | Rolling Update:先启动新版本 Pod,健康检查通过后,再杀掉旧版本 Pod | 完美支持灰度发布、蓝绿部署,且失败可秒级回滚 (kubectl rollout undo)。 |
总结:
- 如果你的项目只有一台服务器(或几台),用 PM2 是最省心、最高效的选择。
- 如果你的项目庞大,微服务众多,需要跨多台机器部署,且对高可用性要求极高,K8s 则是现代大厂唯一的标准答案。在 K8s 的容器里,请抛弃 PM2,回归最原始的
node app.js。