从手工部署到可回滚发布:用CCE、镜像标签与HPA搭一条小团队的容器发布流水线
小团队最常见的发布方式,往往是这样的:有人登录服务器改了配置,有人直接覆盖了一个名为 latest 的镜像,出了问题再凭记忆往回找。流程不长,但每一步都不可追溯。本文把发布拆成镜像、部署、扩缩容和回滚四件事,用华为云容器引擎 CCE 搭一条成本不高、出了问题能迅速回到上一版的流水线。
一、先定三条规则
第一,镜像标签不可变。生产环境不要用 latest,因为它在任何时刻都可能指向不同的内容,出了问题无法确认当时跑的是哪一版。建议用 Git 短提交号加构建日期,例如 app:20261102-a1b2c3d,看到标签就能对应到代码。
第二,发布过程可追溯。每一次上线都应留下三条记录:代码提交、镜像标签和部署时间。后续排查时,先看这三条,再看日志,而不是先猜测。
第三,回滚必须是一条现成命令,而不是临时拼凑的操作。回滚方案如果只存在于某一位同事的脑子里,故障时基本等于没有。
二、镜像:先把构建做干净
多阶段构建是值得优先做的一步。构建阶段安装依赖、编译产物,运行阶段只保留运行必需的文件和运行时。这样镜像更小,传输和启动更快,攻击面也更小。
# 构建阶段
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 运行阶段只保留产物
FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
EXPOSE 3000
CMD ["node", "dist/server.js"]
构建和推送时,把标签写死,不要顺手打 latest:
docker build -t swr.cn-north-4.example.com/team/app:20261102-a1b2c3d .
docker push swr.cn-north-4.example.com/team/app:20261102-a1b2c3d
这里的仓库地址只是示例,实际使用时替换为自己的容器镜像服务 SWR 地址和命名空间。另外建议在流水线里固定基础镜像版本,例如 node:20-alpine,而不是 node:latest,否则基础镜像的变化也会悄悄进入生产。
三、在 CCE 里部署:让滚动发布代替整体替换
Deployment 的价值不在于它能启动容器,而在于它能按策略替换容器。下面的要点比 YAML 本身更重要:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: app
image: swr.cn-north-4.example.com/team/app:20261102-a1b2c3d
ports:
- containerPort: 3000
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
readinessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 20
periodSeconds: 10
maxUnavailable 为 0 表示旧实例全部下线之前,新实例要先就绪,发布期间服务不出现空档。maxSurge 为 1 表示最多多出一个新实例,资源占用可控。
健康检查需要区分两种职责:就绪检查 readinessProbe 回答“能不能接流量”,存活检查 livenessProbe 回答“进程是不是已经卡死”。如果把两者写成同一个宽松接口,或者把启动时间很长的应用的存活检查设得太急,就会出现还没启动完成就被反复重启的情况。健康检查接口应当轻量,只检查自身是否可以处理请求,不要在里面做耗时的下游调用。
资源 requests 和 limits 也要成对出现。requests 决定调度时预留多少资源,limits 决定单个容器最多用多少。只写 limits 不写 requests,调度器可能把多个重负载实例排到同一节点;只写 requests 不写 limits,个别异常实例可能挤占整台节点。
四、HPA:扩缩容先看指标,再看阈值
水平 Pod 自动扩缩 HPA 的常见写法是按 CPU 利用率扩容:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app
minReplicas: 2
maxReplicas: 6
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
这个配置有三点需要注意。第一,CPU 利用率是相对 requests 计算的,requests 写得过低,阈值就会失真。第二,内存不适合作为唯一的扩容信号,因为内存一般不会因为流量下降而自动释放,按内存扩容容易只扩不缩。第三,业务有明显波峰时,应当先确认最小副本数能否扛住冷启动和突发流量,而不是把全部希望寄托在扩容速度上。
五、发布与回滚:一套固定动作
一次规范的发布可以固定为以下顺序:推送带版本标签的镜像;更新 Deployment 的镜像字段;观察滚动发布状态;用健康检查和业务指标确认新版本;确认无异常后再结束发布。
kubectl set image deployment/app app=swr.cn-north-4.example.com/team/app:20261102-a1b2c3d
kubectl rollout status deployment/app
kubectl rollout history deployment/app
如果新版本出现错误率上升、关键接口超时或健康检查持续失败,不要先在生产上改代码热修。优先回滚到上一个已知良好的版本,再到开发环境复现和修复:
kubectl rollout undo deployment/app
回滚后同样要观察 rollout 状态和业务指标,确认旧版本确实恢复了流量。把“先回滚、再排查”写进团队约定,可以避免故障期间一边修代码、一边猜配置的混乱。
六、容易踩的几个坑
一是把数据库密码、第三方密钥直接写进镜像或代码仓库。正确做法是使用 Kubernetes Secret 或云上的凭据管理方式注入,镜像本身不携带敏感信息。
二是配置和代码混在一起。环境差异应当通过环境变量和配置文件解决,而不是为每个环境单独打一个镜像。
三是发布时一次性替换全部实例。副本数本来就不多时,全量替换等于主动制造停机窗口,滚动策略和最小可用副本数要先于发布流程确定。
四是日志和指标只在出问题时才想起来。容器日志应当集中采集,关键接口的错误率、延迟和 Pod 重启次数应当有固定查看入口,否则回滚决策只能靠感觉。
结尾:一张发布检查表
上线前可以逐项确认:镜像标签是不是不可变的版本标签;Deployment 是不是滚动发布,maxUnavailable 是不是 0;健康检查是不是区分了就绪和存活;HPA 的最小副本数和上限是不是合理;回滚命令是不是已经验证过。如果这五项都成立,小团队的发布就已经比“直接覆盖 latest”可靠得多。容器平台解决的是部署方式问题,而可追溯、可回滚的习惯,才是发布真正稳定的来源。
- 点赞
- 收藏
- 关注作者
评论(0)