欢迎光临
我们一直在努力

应用部署与滚动更新

目录

  • 应用部署与滚动更新:Kubernetes 零停机发布实战
    • 一、引言:从传统部署到云原生发布的演进
    • 二、核心概念:Deployment 与滚动更新机制
      • 2.1 Deployment:声明式应用管理
      • 2.2 ReplicaSet:版本快照
      • 2.3 滚动更新策略参数
      • 2.4 更新状态与历史记录
    • 三、实战演练:滚动更新 Python Flask 应用
      • 3.1 项目结构
      • 3.2 Flask 应用代码
      • 3.3 多阶段构建 Dockerfile
      • 3.4 构建并推送镜像
      • 3.5 Kubernetes 资源配置
      • 3.6 部署 v1 版本
      • 3.7 执行滚动更新到 v2
      • 3.8 回滚到 v1
    • 四、进阶优化:滚动更新最佳实践
      • 4.1 合理设置 maxSurge 和 maxUnavailable
      • 4.2 结合就绪探针(Readiness Probe)
      • 4.3 暂停与恢复更新
      • 4.4 金丝雀发布(Canary Release)
      • 4.5 蓝绿部署(Blue-Green Deployment)
      • 4.6 多阶段构建收益
    • 五、效果验证
      • 5.1 零停机验证
      • 5.2 滚动速度控制
      • 5.3 回滚验证
    • 六、完整代码
      • 6.1 app-v1/app.py
      • 6.2 app-v1/requirements.txt
      • 6.3 app-v1/Dockerfile
      • 6.4 app-v1/.dockerignore
      • 6.5 app-v2/ 对应文件(略)
      • 6.6 k8s/namespace.yaml
      • 6.7 k8s/deployment.yaml
      • 6.8 k8s/service.yaml
    • 七、总结与最佳实践清单

『宝藏代码胶囊开张啦!』—— 我的 CodeCapsule 来咯!✨写代码不再头疼!我的新站点 CodeCapsule 主打一个 “白菜价”+“量身定制”!无论是卡脖子的毕设/课设/文献复现,需要灵光一现的算法改进,还是想给项目加个“外挂”,这里都有便宜又好用的代码方案等你发现!低成本,高适配,助你轻松通关!速来围观 👉 CodeCapsule官网

应用部署与滚动更新:Kubernetes 零停机发布实战

一、引言:从传统部署到云原生发布的演进

在传统的应用部署中,发布新版本往往意味着停机维护——停止旧版本、部署新版本、重启服务,整个过程需要数分钟甚至更久。对于追求高可用的现代应用,停机就意味着用户体验下降、业务收入损失。而容器化与容器编排技术的出现,彻底改变了这一局面。

滚动更新(Rolling Update) 是 Kubernetes 提供的核心发布策略,它允许你逐步用新版本替换旧版本,在整个更新过程中始终保持应用在线,实现零停机发布。同时,Kubernetes 还提供了自动回滚、版本历史管理等机制,让发布变得安全可控。

然而,仅仅知道 kubectl set image 是不够的。如何配置更新策略以平衡速度和稳定性?如何监控更新过程?当更新失败时如何快速回滚?蓝绿部署、金丝雀发布与滚动更新的关系是什么?本文将从一个 Python Flask 应用 的实战出发,深入讲解 Kubernetes 中 Deployment 的滚动更新机制,并运用多阶段构建优化镜像体积,帮助你掌握生产级应用发布的完整流程。

二、核心概念:Deployment 与滚动更新机制

2.1 Deployment:声明式应用管理

Deployment 是 Kubernetes 中管理无状态应用的核心控制器。它通过 ReplicaSet 确保指定数量的 Pod 副本始终运行,并支持声明式的更新策略。当你想更新应用版本时,只需修改 Deployment 的 Pod 模板(如镜像标签),Deployment 会自动触发滚动更新。

#mermaid-svg-04xWqOvXsAyQ26DG{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-04xWqOvXsAyQ26DG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-04xWqOvXsAyQ26DG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-04xWqOvXsAyQ26DG .error-icon{fill:#552222;}#mermaid-svg-04xWqOvXsAyQ26DG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-04xWqOvXsAyQ26DG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-04xWqOvXsAyQ26DG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-04xWqOvXsAyQ26DG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-04xWqOvXsAyQ26DG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-04xWqOvXsAyQ26DG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-04xWqOvXsAyQ26DG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-04xWqOvXsAyQ26DG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-04xWqOvXsAyQ26DG .marker.cross{stroke:#333333;}#mermaid-svg-04xWqOvXsAyQ26DG svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-04xWqOvXsAyQ26DG p{margin:0;}#mermaid-svg-04xWqOvXsAyQ26DG .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-04xWqOvXsAyQ26DG .cluster-label text{fill:#333;}#mermaid-svg-04xWqOvXsAyQ26DG .cluster-label span{color:#333;}#mermaid-svg-04xWqOvXsAyQ26DG .cluster-label span p{background-color:transparent;}#mermaid-svg-04xWqOvXsAyQ26DG .label text,#mermaid-svg-04xWqOvXsAyQ26DG span{fill:#333;color:#333;}#mermaid-svg-04xWqOvXsAyQ26DG .node rect,#mermaid-svg-04xWqOvXsAyQ26DG .node circle,#mermaid-svg-04xWqOvXsAyQ26DG .node ellipse,#mermaid-svg-04xWqOvXsAyQ26DG .node polygon,#mermaid-svg-04xWqOvXsAyQ26DG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-04xWqOvXsAyQ26DG .rough-node .label text,#mermaid-svg-04xWqOvXsAyQ26DG .node .label text,#mermaid-svg-04xWqOvXsAyQ26DG .image-shape .label,#mermaid-svg-04xWqOvXsAyQ26DG .icon-shape .label{text-anchor:middle;}#mermaid-svg-04xWqOvXsAyQ26DG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-04xWqOvXsAyQ26DG .rough-node .label,#mermaid-svg-04xWqOvXsAyQ26DG .node .label,#mermaid-svg-04xWqOvXsAyQ26DG .image-shape .label,#mermaid-svg-04xWqOvXsAyQ26DG .icon-shape .label{text-align:center;}#mermaid-svg-04xWqOvXsAyQ26DG .node.clickable{cursor:pointer;}#mermaid-svg-04xWqOvXsAyQ26DG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-04xWqOvXsAyQ26DG .arrowheadPath{fill:#333333;}#mermaid-svg-04xWqOvXsAyQ26DG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-04xWqOvXsAyQ26DG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-04xWqOvXsAyQ26DG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-04xWqOvXsAyQ26DG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-04xWqOvXsAyQ26DG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-04xWqOvXsAyQ26DG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-04xWqOvXsAyQ26DG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-04xWqOvXsAyQ26DG .cluster text{fill:#333;}#mermaid-svg-04xWqOvXsAyQ26DG .cluster span{color:#333;}#mermaid-svg-04xWqOvXsAyQ26DG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-04xWqOvXsAyQ26DG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-04xWqOvXsAyQ26DG rect.text{fill:none;stroke-width:0;}#mermaid-svg-04xWqOvXsAyQ26DG .icon-shape,#mermaid-svg-04xWqOvXsAyQ26DG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-04xWqOvXsAyQ26DG .icon-shape p,#mermaid-svg-04xWqOvXsAyQ26DG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-04xWqOvXsAyQ26DG .icon-shape rect,#mermaid-svg-04xWqOvXsAyQ26DG .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-04xWqOvXsAyQ26DG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-04xWqOvXsAyQ26DG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-04xWqOvXsAyQ26DG :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

Deployment

ReplicaSet v1

ReplicaSet v2

Pod 1 v1

Pod 2 v1

Pod 3 v2

Pod 4 v2

2.2 ReplicaSet:版本快照

每次 Deployment 的 Pod 模板发生变化,都会创建一个新的 ReplicaSet,并保留旧的 ReplicaSet。这不仅实现了版本追踪,还让回滚变得简单——只需将 Deployment 的 spec 指回旧的 ReplicaSet。

2.3 滚动更新策略参数

滚动更新的行为由 strategy.rollingUpdate 下的两个参数控制:

  • maxSurge:更新过程中,可以超出期望副本数的最大 Pod 数量。可以是绝对数(如 2)或百分比(如 25%)。默认 25%。
  • maxUnavailable:更新过程中,可以不可用的最大 Pod 数量。同样可以是绝对数或百分比。默认 25%。

这两个参数共同决定了更新速度和可用性之间的平衡。例如,对于 10 个副本的应用,maxSurge=25% 允许最多额外创建 2 个新 Pod,maxUnavailable=25% 允许最多 2 个旧 Pod 提前终止。

ReplicaSet v2

ReplicaSet v1

Kubernetes

ReplicaSet v2

ReplicaSet v1

Kubernetes

#mermaid-svg-flIAJviTfbhJo9bt{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-flIAJviTfbhJo9bt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-flIAJviTfbhJo9bt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-flIAJviTfbhJo9bt .error-icon{fill:#552222;}#mermaid-svg-flIAJviTfbhJo9bt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-flIAJviTfbhJo9bt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-flIAJviTfbhJo9bt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-flIAJviTfbhJo9bt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-flIAJviTfbhJo9bt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-flIAJviTfbhJo9bt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-flIAJviTfbhJo9bt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-flIAJviTfbhJo9bt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-flIAJviTfbhJo9bt .marker.cross{stroke:#333333;}#mermaid-svg-flIAJviTfbhJo9bt svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-flIAJviTfbhJo9bt p{margin:0;}#mermaid-svg-flIAJviTfbhJo9bt .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-flIAJviTfbhJo9bt text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-flIAJviTfbhJo9bt .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-flIAJviTfbhJo9bt .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-flIAJviTfbhJo9bt .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-flIAJviTfbhJo9bt .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-flIAJviTfbhJo9bt #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-flIAJviTfbhJo9bt .sequenceNumber{fill:white;}#mermaid-svg-flIAJviTfbhJo9bt #sequencenumber{fill:#333;}#mermaid-svg-flIAJviTfbhJo9bt #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-flIAJviTfbhJo9bt .messageText{fill:#333;stroke:none;}#mermaid-svg-flIAJviTfbhJo9bt .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-flIAJviTfbhJo9bt .labelText,#mermaid-svg-flIAJviTfbhJo9bt .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-flIAJviTfbhJo9bt .loopText,#mermaid-svg-flIAJviTfbhJo9bt .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-flIAJviTfbhJo9bt .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-flIAJviTfbhJo9bt .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-flIAJviTfbhJo9bt .noteText,#mermaid-svg-flIAJviTfbhJo9bt .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-flIAJviTfbhJo9bt .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-flIAJviTfbhJo9bt .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-flIAJviTfbhJo9bt .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-flIAJviTfbhJo9bt .actorPopupMenu{position:absolute;}#mermaid-svg-flIAJviTfbhJo9bt .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-flIAJviTfbhJo9bt .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-flIAJviTfbhJo9bt .actor-man circle,#mermaid-svg-flIAJviTfbhJo9bt line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-flIAJviTfbhJo9bt :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

开始滚动更新

等待新 Pod Ready

重复直到所有旧 Pod 被替换

创建 1 个新 Pod (maxSurge)

终止 1 个旧 Pod (maxUnavailable)

创建下一个新 Pod

终止下一个旧 Pod

2.4 更新状态与历史记录

kubectl rollout status 可以监控更新进度,kubectl rollout history 查看版本历史。每次更新都会记录 Revision,方便回滚。

三、实战演练:滚动更新 Python Flask 应用

本节将创建一个简单的 Flask 应用,通过两个版本(v1 和 v2)演示滚动更新的完整流程。

3.1 项目结构

rolling-update-demo/
├── app-v1/
│ ├── app.py # v1 版本 Flask 应用
│ ├── requirements.txt
│ ├── Dockerfile # 多阶段构建
│ └── .dockerignore
├── app-v2/ # v2 版本(仅修改返回消息)
│ ├── app.py
│ ├── requirements.txt
│ ├── Dockerfile
│ └── .dockerignore
├── k8s/
│ ├── namespace.yaml # 命名空间
│ ├── deployment.yaml # Deployment 定义
│ └── service.yaml # Service 暴露应用
└── README.md

3.2 Flask 应用代码

app-v1/app.py:

import os
import socket
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def hello():
hostname = socket.gethostname()
return jsonify({
'version': 'v1',
'message': 'Hello from v1',
'hostname': hostname
})

@app.route('/health')
def health():
return jsonify({'status': 'healthy'})

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

app-v2/app.py(仅修改版本号):

import os
import socket
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def hello():
hostname = socket.gethostname()
return jsonify({
'version': 'v2',
'message': 'Hello from v2',
'hostname': hostname
})

@app.route('/health')
def health():
return jsonify({'status': 'healthy'})

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

app-v1/requirements.txt 和 app-v2/requirements.txt 相同:

flask==2.3.3
gunicorn==21.2.0

3.3 多阶段构建 Dockerfile

app-v1/Dockerfile(v2 相同):

# 构建阶段
FROM python:3.11-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install –user –no-cache-dir -r requirements.txt

# 运行阶段
FROM python:3.11-slim

# 创建非root用户
RUN addgroup –system –gid 1001 appgroup && \\
adduser –system –uid 1001 –gid 1001 –no-create-home appuser

WORKDIR /app
COPY –from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH

COPY . .
RUN chown -R appuser:appgroup /app

USER appuser
EXPOSE 5000
HEALTHCHECK CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')" || exit 1

CMD ["gunicorn", "–bind", "0.0.0.0:5000", "–workers", "2", "app:app"]

app-v1/.dockerignore:

__pycache__
*.pyc
.env
.git
README.md
Dockerfile
.dockerignore

3.4 构建并推送镜像

# 构建 v1 和 v2 镜像
docker build -t your-registry/rolling-demo:v1 ./app-v1
docker build -t your-registry/rolling-demo:v2 ./app-v2

# 推送到镜像仓库
docker push your-registry/rolling-demo:v1
docker push your-registry/rolling-demo:v2

3.5 Kubernetes 资源配置

k8s/namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
name: rollingdemo

k8s/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: rollingdemo
namespace: rollingdemo
spec:
replicas: 4
selector:
matchLabels:
app: rollingdemo
# 滚动更新策略
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 最多额外创建1个新Pod
maxUnavailable: 0 # 不允许旧Pod不可用(保证零停机)
template:
metadata:
labels:
app: rollingdemo
spec:
containers:
name: app
image: yourregistry/rollingdemo:v1
ports:
containerPort: 5000
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
readinessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 15
periodSeconds: 10

k8s/service.yaml:

apiVersion: v1
kind: Service
metadata:
name: rollingdemoservice
namespace: rollingdemo
spec:
selector:
app: rollingdemo
ports:
port: 80
targetPort: 5000
type: NodePort

3.6 部署 v1 版本

# 创建命名空间
kubectl apply -f k8s/namespace.yaml

# 部署 v1
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml

# 查看状态
kubectl get pods -n rolling-demo -w
kubectl get svc -n rolling-demo

# 获取 NodePort
NODE_PORT=$(kubectl get svc rolling-demo-service -n rolling-demo -o jsonpath='{.spec.ports[0].nodePort}')
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}')

# 测试访问
curl http://${NODE_IP}:${NODE_PORT}/

3.7 执行滚动更新到 v2

在一个终端持续请求,观察是否中断:

while true; do curl -s http://${NODE_IP}:${NODE_PORT}/ | grep version; sleep 0.5; done

在另一个终端触发更新:

# 修改镜像版本为 v2
kubectl set image deployment/rolling-demo app=your-registry/rolling-demo:v2 -n rolling-demo

# 监控更新状态
kubectl rollout status deployment/rolling-demo -n rolling-demo

# 查看 ReplicaSet 历史
kubectl rollout history deployment/rolling-demo -n rolling-demo

观察持续请求的终端,你会看到版本从 v1 逐渐变成 v2,但整个过程没有出现请求失败(除极短暂的重试外,通常零停机)。

3.8 回滚到 v1

如果新版本有问题,可以快速回滚:

# 回滚到上一个版本
kubectl rollout undo deployment/rolling-demo -n rolling-demo

# 回滚到指定版本(通过 history 查看 revision)
kubectl rollout undo deployment/rolling-demo –to-revision=1 -n rolling-demo

四、进阶优化:滚动更新最佳实践

4.1 合理设置 maxSurge 和 maxUnavailable

场景maxSurgemaxUnavailable说明
零停机生产发布 1 (或 25%) 0 确保任何时候都有足够的 Pod 处理流量
快速更新(可容忍少量中断) 25% 25% 平衡速度与可用性
资源紧张环境 0 1 避免额外资源占用,但会有短暂容量下降

4.2 结合就绪探针(Readiness Probe)

滚动更新的核心是等待新 Pod 就绪后才终止旧 Pod。因此必须为容器配置正确的 readinessProbe,确保新 Pod 真正能够接收流量后才开始替换。

4.3 暂停与恢复更新

对于复杂更新(如需要手工验证的批次),可以暂停更新:

kubectl rollout pause deployment/rolling-demo -n rolling-demo
# 手工验证部分新 Pod
kubectl rollout resume deployment/rolling-demo -n rolling-demo

4.4 金丝雀发布(Canary Release)

滚动更新是自动的渐进式发布。但如果你想更精细地控制流量比例(如只让 10% 的流量流向新版本),可以结合 Service 和多个 Deployment 实现金丝雀发布:

#mermaid-svg-hEcn8W5Ah26v61Vn{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hEcn8W5Ah26v61Vn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hEcn8W5Ah26v61Vn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hEcn8W5Ah26v61Vn .error-icon{fill:#552222;}#mermaid-svg-hEcn8W5Ah26v61Vn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hEcn8W5Ah26v61Vn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hEcn8W5Ah26v61Vn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hEcn8W5Ah26v61Vn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hEcn8W5Ah26v61Vn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hEcn8W5Ah26v61Vn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hEcn8W5Ah26v61Vn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hEcn8W5Ah26v61Vn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hEcn8W5Ah26v61Vn .marker.cross{stroke:#333333;}#mermaid-svg-hEcn8W5Ah26v61Vn svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hEcn8W5Ah26v61Vn p{margin:0;}#mermaid-svg-hEcn8W5Ah26v61Vn .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-hEcn8W5Ah26v61Vn .cluster-label text{fill:#333;}#mermaid-svg-hEcn8W5Ah26v61Vn .cluster-label span{color:#333;}#mermaid-svg-hEcn8W5Ah26v61Vn .cluster-label span p{background-color:transparent;}#mermaid-svg-hEcn8W5Ah26v61Vn .label text,#mermaid-svg-hEcn8W5Ah26v61Vn span{fill:#333;color:#333;}#mermaid-svg-hEcn8W5Ah26v61Vn .node rect,#mermaid-svg-hEcn8W5Ah26v61Vn .node circle,#mermaid-svg-hEcn8W5Ah26v61Vn .node ellipse,#mermaid-svg-hEcn8W5Ah26v61Vn .node polygon,#mermaid-svg-hEcn8W5Ah26v61Vn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hEcn8W5Ah26v61Vn .rough-node .label text,#mermaid-svg-hEcn8W5Ah26v61Vn .node .label text,#mermaid-svg-hEcn8W5Ah26v61Vn .image-shape .label,#mermaid-svg-hEcn8W5Ah26v61Vn .icon-shape .label{text-anchor:middle;}#mermaid-svg-hEcn8W5Ah26v61Vn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hEcn8W5Ah26v61Vn .rough-node .label,#mermaid-svg-hEcn8W5Ah26v61Vn .node .label,#mermaid-svg-hEcn8W5Ah26v61Vn .image-shape .label,#mermaid-svg-hEcn8W5Ah26v61Vn .icon-shape .label{text-align:center;}#mermaid-svg-hEcn8W5Ah26v61Vn .node.clickable{cursor:pointer;}#mermaid-svg-hEcn8W5Ah26v61Vn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hEcn8W5Ah26v61Vn .arrowheadPath{fill:#333333;}#mermaid-svg-hEcn8W5Ah26v61Vn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hEcn8W5Ah26v61Vn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hEcn8W5Ah26v61Vn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hEcn8W5Ah26v61Vn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hEcn8W5Ah26v61Vn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hEcn8W5Ah26v61Vn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hEcn8W5Ah26v61Vn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hEcn8W5Ah26v61Vn .cluster text{fill:#333;}#mermaid-svg-hEcn8W5Ah26v61Vn .cluster span{color:#333;}#mermaid-svg-hEcn8W5Ah26v61Vn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hEcn8W5Ah26v61Vn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hEcn8W5Ah26v61Vn rect.text{fill:none;stroke-width:0;}#mermaid-svg-hEcn8W5Ah26v61Vn .icon-shape,#mermaid-svg-hEcn8W5Ah26v61Vn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hEcn8W5Ah26v61Vn .icon-shape p,#mermaid-svg-hEcn8W5Ah26v61Vn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hEcn8W5Ah26v61Vn .icon-shape rect,#mermaid-svg-hEcn8W5Ah26v61Vn .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hEcn8W5Ah26v61Vn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hEcn8W5Ah26v61Vn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hEcn8W5Ah26v61Vn :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

90%

10%

Service

Deployment stable

Deployment canary

或者使用 Service Mesh(如 Istio)实现更精细的流量控制。

4.5 蓝绿部署(Blue-Green Deployment)

蓝绿部署是另一种发布策略:同时运行两套环境(蓝和绿),通过切换 Service 的 selector 实现瞬间切换。Kubernetes 中可以简单实现:

# 部署绿色版本
kubectl apply -f deployment-green.yaml
# 待绿色版本就绪后,修改 Service 的 selector 指向绿色
kubectl patch service rolling-demo-service -p '{"spec":{"selector":{"app":"rolling-demo","version":"v2"}}}'

4.6 多阶段构建收益

与之前系列一致,多阶段构建将镜像体积从 400MB+ 优化至 82MB,加速拉取和启动,从而缩短滚动更新总时长。

构建方式基础镜像最终镜像大小
单阶段 python:3.11 912 MB
单阶段 slim python:3.11-slim 412 MB
多阶段 python:3.11-slim 82 MB

五、效果验证

5.1 零停机验证

在滚动更新过程中持续请求,可以看到响应中的 version 字段从 v1 逐渐变为 v2,但从未出现连接错误(如 curl: (52) Empty reply from server)。

5.2 滚动速度控制

观察 Pod 的变化:

kubectl get pods -n rolling-demo -w

你会看到新 Pod 逐个创建并变为 Running,旧 Pod 逐个 Terminating,新旧 Pod 会短暂共存。

5.3 回滚验证

模拟新版本故障(例如故意让 v2 的 /health 返回 500),然后观察滚动更新是否会卡住,并测试回滚功能。

六、完整代码

6.1 app-v1/app.py

(见上文)

6.2 app-v1/requirements.txt

flask==2.3.3
gunicorn==21.2.0

6.3 app-v1/Dockerfile

(见上文)

6.4 app-v1/.dockerignore

(见上文)

6.5 app-v2/ 对应文件(略)

6.6 k8s/namespace.yaml

(见上文)

6.7 k8s/deployment.yaml

(见上文)

6.8 k8s/service.yaml

(见上文)

七、总结与最佳实践清单

通过本文的实战,我们掌握了 Kubernetes 中 Deployment 的滚动更新机制,并通过一个 Python Flask 应用演示了零停机发布的全过程。以下是 应用部署与滚动更新的最佳实践清单:

  • ✅ 配置就绪探针:确保新 Pod 真正就绪后才接收流量。
  • ✅ 合理设置滚动更新参数:生产环境推荐 maxSurge=25%、maxUnavailable=0 或 maxSurge=1、maxUnavailable=0。
  • ✅ 监控更新过程:使用 kubectl rollout status 和事件监控。
  • ✅ 保留版本历史:不要手动删除旧的 ReplicaSet,以便快速回滚。
  • ✅ 回滚准备:确保回滚操作已经演练,关键应用可考虑蓝绿部署。
  • ✅ 结合金丝雀发布:对于重大变更,先让少量流量验证。
  • ✅ 镜像优化:多阶段构建减小体积,加速拉取和启动。
  • ✅ 资源限制:设置 requests 和 limits,保证更新过程集群稳定。

滚动更新是 Kubernetes 保障服务连续性的基石。掌握这一技能后,你可以自信地将应用的发布流程自动化,实现快速迭代与稳定运行的完美平衡。

赞(0)
未经允许不得转载:171主机测评 » 应用部署与滚动更新
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址