如何优雅更新GCP Compute Engine实例上的Node.js代码
GCE 上基于Docker的Node.js服务优雅更新方案
长期用Lambda刚切回VM+容器的话,先把裸机时代git pull同步代码的思路转过来:容器化的核心是镜像不可变,绝对不要进运行中的容器改代码、拉代码,不然容器重建所有改动全丢,完全失去容器化的意义。整套更新流程跑通后比你之前用git hook的方案稳得多,回滚速度也快几个量级。
基础准备
先把项目的容器化配置做对,别搞花里胡哨的:
- 项目根目录写多阶段构建的Dockerfile,尽可能压小镜像体积,参考配置:
FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ # 用npm ci比npm install稳,依赖版本完全锁死 RUN npm ci --only=production COPY . . FROM node:20-alpine WORKDIR /app # 用非root用户跑服务,降低安全风险 RUN addgroup -S appgroup && adduser -S appuser -G appgroup USER appuser COPY --from=builder --chown=appuser:appgroup /app ./ EXPOSE 3000 CMD ["node", "index.js"]
- 加个
.dockerignore文件,把node_modules、.git、本地env文件、日志目录这些不需要打进镜像的内容排除,能大幅提升镜像构建、推送、拉取的速度。 - 镜像仓库直接用Google自带的Artifact Registry就行,不用自己搭私有仓库,和GCE内网打通拉镜像速度极快。每次构建镜像打语义化版本号(比如
node-service:v1.0.2),别一直用latest标签,不然后续回滚根本找不到对应版本。
分场景选更新流程
单实例/低流量个人项目
两种方案选,嫌麻烦就用第二种:
- 方案一(零断服最稳):用GCE托管实例组(MIG),哪怕单实例也能用。每次代码更新完构建推送新镜像后,更新实例模板里的镜像版本,触发MIG滚动更新,配置成「先启动新实例,健康检查通过后再销毁旧实例」,健康检查直接对接你Node服务的
/health接口(返回200就算正常),整个过程完全不会断服。 - 方案二(和你之前git hook使用习惯最接近):在GCE实例上写个极简更新脚本,配置成git hook触发或者CI触发就行,脚本自带失败回滚逻辑,参考:
#!/bin/bash # 触发脚本时传入新旧版本号即可 NEW_VER=$1 OLD_VER=$2 IMAGE_PATH="your-region-docker.pkg.dev/your-project/node-service" # 拉取新镜像 docker pull ${IMAGE_PATH}:${NEW_VER} # 停掉旧容器 docker stop node-app && docker rm node-app # 启动新容器,端口、挂载目录按自己需求调整 docker run -d --name node-app -p 80:3000 --restart always ${IMAGE_PATH}:${NEW_VER} # 健康检查不通过自动回滚 sleep 3 if [ $(curl -s -o /dev/null -w "%{http_code}" http://localhost/health) -ne 200 ]; then docker stop node-app && docker rm node-app docker run -d --name node-app -p 80:3000 --restart always ${IMAGE_PATH}:${OLD_VER} echo "新版本启动失败,已回滚到${OLD_VER}" exit 1 fi echo "更新到${NEW_VER}成功"
多实例/生产级服务
直接用托管实例组的滚动更新能力就行,配置两个核心参数:
maxSurge=1:更新时额外多启动1个新实例,不占用现有实例的资源配额maxUnavailable=0:更新过程中不允许出现不可用实例,只要新实例健康检查没通过,就不会销毁旧实例
如果需要做灰度发布,就在实例组前面挂个负载均衡,先切10%流量到新版本,观察没有报错再逐步把流量全切过去,出问题直接把权重切回旧版本,秒级回滚。
几个实用提醒
- 生产环境别用nodemon这类文件监听热更新的方案,那是本地开发用的,生产环境就走镜像替换的流程,稳定性高很多
- 不要把配置文件、用户上传的静态资源打进镜像里,这类内容用Docker挂载宿主机目录或者对接云存储就行,不然每次更新镜像会把这些内容覆盖
- 新实例拉起速度基本能压到10秒级,比你之前裸机上拉代码、装依赖的流程快好几倍
内容的提问来源于stack exchange,提问作者Michael Joseph Aubry
相关产品推荐
相关产品推荐

