友情提示:需要有一定的基础知识!当然这是之前流行现在也在用的技术
说了docker现在我们把视野拉高,看看 Kubernetes (K8s) 是如何指挥成千上万个 Docker 干活的!
- 如果说 Docker 是集装箱 ,那 Kubernetes 就是那个拿着对讲机、在大港口里指挥若定的码头调度主任。
Pod:穿连体婴裤子的"豆荚"
很多新人以为 K8s 调度的最小单位是容器(Container),错!Pod 才是亲儿子。
1. 为什么要有 Pod?
想象一下,你有一个主应用(比如 Java Web),还有一个负责收集日志的辅助工具(比如 Fluentd)。
如果把它们分开部署成两个容器,它们想互相传文件怎么办?走网络?太慢了!
于是 K8s 说:"你俩感情这么好,干脆住一个房间里吧!"
Pod 就是这个"房间"。
2. 底层原理:Pause 容器与共享命名空间
Pod 里的所有容器,就像连体婴一样,共享三样东西:
- IP 地址 :
localhost就能互通。 - 存储卷:U盘插在一个身上,另一个也能读。
- 生命周期:同生共死。
代码落地:手动模拟 Pod 的网络共享
你在 Docker 里怎么实现两个容器共用一个 IP?其实就是利用 Linux 的 Namespace。K8s 的实现方式更变态,它用了一个叫 pause 的基础镜像。
# 1. 启动一个 "Pause" 容器(它是 Pod 的基石,占个坑位)
docker run -d --name pause-container nginx:alpine sleep infinity
# 2. 获取它的网络命名空间 ID
PID=$(docker inspect -f '{{.State.Pid}}' pause-container)
# 3. 启动第二个容器,强行加入第一个容器的网络栈!
# 这就是 Pod 内部通信的秘密!
docker run -it --rm \
--net=container:pause-container \
alpine ping localhost
# 你会发现,你 ping 通了 pause 容器!
结论:Pod 里的业务容器,其实是加入了 Pause 容器的网络命名空间。Pause 容器负责维持 IP 的存在,业务容器挂了重启就行,IP 还是 Pause 的。
K8s 如何实现 Pod 滚动更新?
当你修改了 image: myregistry/user-app:v2.0 并执行 kubectl apply 后,K8s 不会傻乎乎地把旧的全部杀掉再开新的(那样会停机)。它会玩一套**"平滑过渡"**的组合拳。
1. 核心参数:maxSurge 和 maxUnavailable
在 Deployment 的 strategy 字段里,有两个控制节奏的参数:
- maxSurge (最大激增数) :更新过程中,最多允许多出来几个 Pod。
- maxUnavailable (最大不可用数) :更新过程中,最少保证有几个 Pod 是活着的。
默认策略 通常是:maxSurge: 25%, maxUnavailable: 25%。
2. 滚动更新的"慢动作"回放
假设你有 4 个 旧版 Pod (v1)。你要升级到 v2。
- 第一步(扩容) :
K8s 计算一下,允许 surge 1 个(25% of 4)。于是它启动 1 个 v2 Pod 。
当前状态:4个 v1 + 1个 v2。 - 第二步(健康检查) :
K8s 等待这个 v2 Pod 通过readinessProbe(就绪探针)。只有它真的能处理请求了,才进行下一步。 - 第三步(缩容) :
K8s 杀掉 1 个 v1 Pod 。
当前状态:3个 v1 + 1个 v2。 - 第四步(循环) :
重复上述步骤,直到所有 v1 都被替换成 v2。
3. 如何实现"零停机"?(避坑指南)
很多新人发现滚动更新时还是会报错(502 Bad Gateway),通常是因为**"时间差"**。
问题场景 :
K8s 杀掉了旧的 Pod,但新的 Pod 还没完全启动好,或者 Ingress 还没刷新它的上游列表,这时候流量进来就会扑空。
解决方案(代码级落地) :
你需要配合 PreStop 钩子 和 优雅关闭。
spec:
template:
spec:
terminationGracePeriodSeconds: 60 # 【优雅退出倒计时】默认30秒,建议改长点
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
# 【关键点:睡一觉再死】
# 当 K8s 决定杀 Pod 时,先发 SIGTERM 信号。
# 此时 PreStop 触发,强制等待 10 秒。
# 这 10 秒是为了等 Service/Ingress 的 iptables 规则更新,确保不再有新流量进来。
# 你的应用也必须配合:收到 SIGTERM 后,停止接收新请求,处理完手头现有的请求后再退出。
Deployment:会读档重来的"游戏管理员"
Pod 是很脆弱的,一旦节点断电,Pod 就挂了。你需要一个能自动复活的管理员,这就是 Deployment。
1. 核心逻辑:声明式 API
你不需要告诉 K8s "去启动一个 Nginx",而是告诉它:"我要 3 个 Nginx 副本,给我维持住!"
Deployment 就像一个死脑筋的管理员,它会一直盯着现场。如果你手动杀了一个 Pod,它会尖叫:"少了一个!快补一个!"
2. 底层原理:控制器模式
Deployment 其实是个套娃:
Deployment -> 控制 -> ReplicaSet (副本集) -> 控制 -> Pod
代码落地:看着它自动回血
创建一个 nginx-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 3 # 期望状态:3个
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx
操作演示:
# 1. 创建
kubectl apply -f nginx-deployment.yaml
# 2. 残忍地杀掉一个 Pod
kubectl delete pod <某个-pod的名字>
# 3. 见证奇迹
kubectl get pods
# 你会看到旧的 Pod 变成 Terminating,同时一个新的 Pod 瞬间被创建出来,始终保持 3/3。
滚动更新 :当你修改镜像版本时,Deployment 不会一次性删光旧版,而是像"贪吃蛇"一样,先起一个新的,等它活了(Ready),再杀一个旧的。这叫零停机发布。
Service:永远不换号的"总机客服"
Pod 是会死的,死了重生 IP 就会变。如果前端服务硬编码了后端的 IP,后端一重启,前端就傻眼了。所以为了胜利我们需要一个虚拟 IP (ClusterIP) ,不管后面的 Pod 怎么变,这个 IP 永远不变。这就是 Service。
1. 核心逻辑:标签选择器
Service 不负责创建 Pod,它只负责圈人 。
你说:"把所有贴着 app: web 标签的 Pod 都归我管。"
2. 底层原理:iptables 流量劫持
这是最硬核的部分。Service 是怎么把流量转发到 Pod 的?
靠的是 Linux 内核的 iptables(或者 IPVS)。
当你在集群里访问 Service 的 IP 时:
- 数据包到达节点。
- 内核发现目标是 Service IP。
- iptables 规则介入:它看一眼规则表,发现"哦,这个 IP 对应后面那 3 个 Pod",然后直接修改数据包的目标 IP(DNAT),把它扔给其中一个健康的 Pod。
代码落地:查看 K8s 的 iptables 规则
你可以登录到任意节点,输入 iptables-save | grep <service-ip>,你会看到类似这样的天书:
-A KUBE-SVC-XXXXX -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-AAAAA
-A KUBE-SVC-XXXXX -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-BBBBB
翻译 :K8s 在 iptables 里写了概率规则,大概 1/3 的概率把流量扔给 Pod A,1/2 的概率扔给 Pod B......这就实现了负载均衡。
Ingress:集群的"智能前台"
1. 为什么需要 Ingress?
想象一下,你的集群里有 100 个微服务(Pod)。
- 没有 Ingress:你得给每个服务买一个公网 IP(LoadBalancer),或者给每个服务开一个 NodePort(比如 30001, 30002...)。这就像每家公司都在大楼里单独拉一根电话线,乱成一锅粥。
- 有了 Ingress :你只需要在大楼门口设一个**"超级前台"**(Ingress Controller)。所有电话先打到这里,前台问:"你是找财务吗?转接分机 8001。找技术吗?转接分机 8002。"
2. 核心架构:前台与规则表
Ingress 其实由两部分组成:
- Ingress Controller(前台接待员):这是一个实际运行的 Pod(通常是 Nginx 或 Traefik)。它负责真正接收流量。
- Ingress Resource(规则表/路由本) :这是一个 YAML 配置文件。你告诉 Controller:"如果访问
api.example.com,就转发给user-service"。
3. 底层原理:Nginx 的反向代理
当你创建了一个 Ingress 资源后,Controller 会监听到这个变化,然后自动生成 Nginx 配置文件 并重新加载。
本质上,它就是一堆 location /xxx { proxy_pass ... } 的规则。
落地实战:带详细注释的 Pod & Service & Ingress
这里有一套完整的配置,包含 Deployment(Pod)、Service 和 Ingress。请直接参考这些注释。
# --- 第一部分:Deployment (定义 Pod) ---
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-app
spec:
replicas: 3 # 【高可用】保持 3 个副本,坏了一个自动补一个
selector:
matchLabels:
app: user-center # 【标签选择器】用来圈定哪些 Pod 归我管
template:
metadata:
labels:
app: user-center # 【关键标签】必须和上面的 selector 匹配
spec:
containers:
- name: user-container
image: myregistry/user-app:v1.0
ports:
- containerPort: 8080 # 【暴露端口】容器内部监听的端口
# 【健康检查:生死攸关的配置】
# 如果没有这个,K8s 不知道你的程序是不是真的启动了,可能会把流量发给还没启动完的 Pod
readinessProbe:
httpGet:
path: /health # 你的程序需要提供这个接口,返回 200 表示准备好了
port: 8080
initialDelaySeconds: 5 # 容器启动后等 5 秒再开始检查
periodSeconds: 10 # 每隔 10 秒查一次
---
# --- 第二部分:Service (内部负载均衡) ---
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
type: ClusterIP # 【默认类型】只生成一个集群内部 IP,外部无法直接访问
selector:
app: user-center # 【关联 Pod】找到上面定义的 Pod
ports:
- protocol: TCP
port: 80 # 【服务端口】Service 对外的端口(在集群内)
targetPort: 8080 # 【目标端口】转发到 Pod 容器的哪个端口(对应上面的 containerPort)
---
# --- 第三部分:Ingress (对外暴露规则) ---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-ingress
annotations:
# 【Nginx 特定配置】重写路径
# 比如访问 /app/api/users,转发给后端时变成 /users
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
ingressClassName: nginx # 【指定管家】告诉 K8s 用哪个 Ingress Controller 来处理
rules:
- host: api.mycompany.com # 【域名匹配】只有访问这个域名的请求才走下面的规则
http:
paths:
- path: /app/(.*) # 【路径匹配】正则匹配,(.*) 是捕获组
pathType: Prefix
backend:
service:
name: user-service # 【转发给谁】对应上面定义的 Service 名字
port:
number: 80 # 【转发给哪个端口】对应 Service 的 port
- Ingress 就是 Nginx 反向代理的自动化版本,通过 YAML 定义路由规则。
- Pod YAML 里最重要的是
readinessProbe,它决定了流量什么时候敢进来。 - 滚动更新 是靠"先起新的,再杀旧的"来实现的。
- 零停机 的秘诀在于
preStop休眠,给网络层一点反应时间。
ConfigMap:外挂的"小抄条"
别把配置文件(如 application.yml)打包进镜像里!那是错误的!
否则你改个配置就得重新构建镜像,累死个人。
ConfigMap 就是把配置抽离出来,像贴便利贴一样贴在 Pod 上。
1. 核心逻辑:解耦
配置和代码分离。开发环境用 dev-config,生产环境用 prod-config,镜像不用动。
2. 使用方式:环境变量 or 文件挂载
- 环境变量:直接把值注入进去。
- 文件挂载:把 ConfigMap 变成一个文件,塞进容器的目录里。
代码落地:热更新魔法
虽然环境变量不能热更新,但文件挂载配合特定机制是可以的(虽然通常需要应用支持重载)。
# 1. 创建 ConfigMap
kubectl create configmap game-config --from-literal=level=1 --from-literal=lives=3
# 2. 在 Pod 中挂载为文件
# deployment.yaml 片段
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: game-config
效果 :
你进入容器 cat /etc/config/level,能看到 1。
你在外面执行 kubectl edit cm game-config 改成 2。
几秒钟后,你再进容器看,文件内容变了!
(注:这需要 Kubelet 定期同步,通常有几十秒延迟)
滚动更新的"暂停"与"继续"
K8s 的滚动更新支持手动暂停,配合 CI/CD 流水线就能实现一键部署。
在 K8s 中,你可以随时喊停正在进行的滚动更新。这个功能在生产环境中有两个神级的实战用法:
- 金丝雀发布(Canary):先放 1 个新版本的 Pod 出来验证一下,没问题再继续全量更新。
- 批量修改防抖动:如果你要同时改镜像、CPU限制、环境变量等多个字段,可以先暂停,改完所有配置后,再一次性触发更新,避免中间状态引发故障。
核心命令
- 暂停更新 :
kubectl rollout pause deployment/<你的应用名> - 继续更新 :
kubectl rollout resume deployment/<你的应用名>
落地实操:金丝雀发布
假设你有一个 myapp 应用,现在想升级到 v2.0,但不敢直接全上:
# 1. 设置 Deployment 只更新 1 个副本(比如你总共有 10 个副本)
# 先把 maxSurge 和 maxUnavailable 调小,或者直接通过 set image 触发
kubectl set image deployment/myapp myapp=myregistry/myapp:v2.0
# 2. 【立刻暂停】滚动更新!此时 K8s 会停止后续的替换动作
kubectl rollout pause deployment/myapp
# 3. 此时你可以去观察那 1 个新版本 Pod 的日志、监控指标
kubectl logs -f <新版本的pod名字>
# 4. 确认没问题后,恢复更新,让剩下的 Pod 全部升级
kubectl rollout resume deployment/myapp
如何实现"一键部署"?
所谓的"一键部署",在实际工作中通常是指:本地或 CI/CD 系统执行一条指令,自动完成代码打包、推送镜像、并触发 K8s 集群内的应用更新。
这里给你推荐两种最常用的"一键"方式:
方式一:修改 YAML 后直接 Apply(适合 CI/CD 流水线)
这是最标准的方法。在你的 Jenkins、GitLab CI 或 GitHub Actions 脚本里,只需要写这一行命令:
# 假设你的 deployment.yaml 里的镜像版本是通过变量替换的
kubectl apply -f deployment.yaml
K8s 会自动对比新旧配置,发现镜像变了,就会自动触发一次滚动更新。
方式二:直接修改镜像版本(适合临时热修复)
如果你只是想快速把线上某个服务升级到指定版本,不需要动 YAML 文件,可以直接用这条命令"一键"触发:
# --record 参数很重要,它能记录下这次变更的命令,方便以后回滚时知道改了啥
kubectl set image deployment/myapp myapp=myregistry/myapp:v2.0 --record
进阶:"一键部署"必备的安全网
要实现真正放心的自动化一键部署,除了触发更新,你还必须掌握监控进度 和紧急回滚。
1. 实时监控更新进度
在一键部署脚本里,通常会加上这条命令来阻塞等待结果,确保更新成功了才算部署完成:
kubectl rollout status deployment/myapp
如果更新卡住或者失败了,这条命令会返回非 0 的退出码,让你的 CI/CD 流水线报错并报警。
2. 紧急一键回滚
如果新版本上线后有严重 Bug,不要慌,K8s 自带"后悔药":
# 查看历史版本记录(看看之前都发过哪些版本)
kubectl rollout history deployment/myapp
# 一键回滚到上一个稳定版本
kubectl rollout undo deployment/myapp
# 或者回滚到指定的版本号(比如回退到第 2 版)
kubectl rollout undo deployment/myapp --to-revision=2
所以日常开发中,可以在本地写好配置,通过 kubectl apply 或 kubectl set image 实现一键触发;而在生产环境的自动化流程中,记得一定要配上 rollout pause(用于灰度验证)、rollout status(用于检查状态)以及完善的回滚预案。
终极总结:一张图看懂全家桶
想象你要开一家连锁餐厅:
| K8s 组件 | 餐厅角色 | 职责 | 底层黑话 |
|---|---|---|---|
| Pod | 后厨工位 | 厨师(容器)干活的地方,几个厨师挤一个工位共享调料(存储/网络)。 | Namespace 共享 |
| Deployment | 大堂经理 | 确保任何时候都有 5 个工位有人在干活。有人跑路立马招新的。 | 控制器循环 |
| Service | 点餐台/总机 | 顾客只认点餐台。不管后面换了哪个厨师,菜都能端上来。 | iptables/IPVS 转发 |
| ConfigMap | 菜单/配方表 | 挂在墙上的菜谱。想换口味直接换张纸,不用把厨师炒了。 | 挂载卷/环境变量 |