Cloud Build+Cloud Run:安全运行测试且仅执行一次npm install
问题
我的Cloud Build执行流程如下:
- 从Dockerfile构建镜像
- 将镜像推送到Container Registry
- 更新Cloud Run中的服务
当前遇到的核心问题:
构建阶段运行测试必须使用MONGODB_URI密钥,但通过--build-arg传递密钥存在泄露风险;若在Cloud Build容器中单独执行npm install和npm run test,又会导致npm install重复执行,拉长构建时长。需要找到一种既能仅执行一次npm install,又不暴露密钥的方案。
当前使用的Dockerfile:
FROM node:16 COPY . ./ WORKDIR / RUN npm install ARG env ARG mongodb_uri ENV ENVIRONMENT=$env ENV DB_REMOTE_PROD=$mongodb_uri RUN npm run test RUN npm run build CMD ["node", "./build/index.js"]
当前的cloudbuild.yaml配置:
steps: - name: gcr.io/cloud-builders/docker args: - '-c' - >- docker build --no-cache -t $_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA --build-arg ENVIRONMENT=staging --build-arg mongodb_uri=$$MONGODB_URI -f Dockerfile . id: Build entrypoint: bash secretEnv: - MONGODB_URI - name: gcr.io/cloud-builders/docker args: - push - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA' id: Push - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk:slim' args: - '-c' - >- gcloud run services update $_SERVICE_NAME --platform=managed --image=$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA --labels=managed-by=gcp-cloud-build-deploy-cloud-run,commit-sha=$COMMIT_SHA,gcb-build-id=$BUILD_ID,gcb-trigger-id=$_TRIGGER_ID --region=$_DEPLOY_REGION --update-env-vars=ENVIRONMENT=staging --update-env-vars=DB_REMOTE_PROD=$$MONGODB_URI --quiet id: Deploy entrypoint: bash secretEnv: - MONGODB_URI images: - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA' options: substitutionOption: ALLOW_LOOSE logging: CLOUD_LOGGING_ONLY substitutions: _TRIGGER_ID: 44b16efe-0219-41af-b32b-9b98438728c3 _GCR_HOSTNAME: eu.gcr.io _PLATFORM: managed _SERVICE_NAME: app-staging _DEPLOY_REGION: europe-southwest1 availableSecrets: secretManager: - versionName: projects/PROJECT_ID/secrets/mongodb_app_staging/versions/1 env: MONGODB_URI
当前方案的弊端:
build-arg传递的密钥会留存于镜像构建历史中,存在泄露风险- Cloud Run服务配置中用明文环境变量存储密钥,所有拥有服务访问权限的用户都能看到
解决方案
方法1:拆分构建步骤,利用Cloud Build工作目录缓存
将依赖安装、测试、代码构建放在Cloud Build容器中完成,仅把最终产物复制到镜像,避免重复执行npm install:
修改cloudbuild.yaml
添加Node.js步骤完成依赖安装和测试,再用精简的Dockerfile构建最终镜像:
steps: # 第一步:在Cloud Build容器内完成依赖安装、测试、代码构建 - name: node:16 args: ['npm', 'install'] id: InstallDependencies secretEnv: ['MONGODB_URI'] env: - 'ENVIRONMENT=staging' - 'DB_REMOTE_PROD=$MONGODB_URI' - name: node:16 args: ['npm', 'run', 'test'] id: RunTests secretEnv: ['MONGODB_URI'] env: - 'ENVIRONMENT=staging' - 'DB_REMOTE_PROD=$MONGODB_URI' - name: node:16 args: ['npm', 'run', 'build'] id: BuildApp # 第二步:构建仅包含运行时所需文件的最终镜像 - name: gcr.io/cloud-builders/docker args: - build - '-t' - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA' - '-f' - Dockerfile.final - '.' id: BuildFinalImage # 推送镜像(原步骤不变) - name: gcr.io/cloud-builders/docker args: ['push', '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA'] id: Push # 部署Cloud Run,改用Secret Manager传递密钥 - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk:slim' args: - '-c' - >- gcloud run services update $_SERVICE_NAME --platform=managed --image=$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA --labels=managed-by=gcp-cloud-build-deploy-cloud-run,commit-sha=$COMMIT_SHA,gcb-build-id=$BUILD_ID,gcb-trigger-id=$_TRIGGER_ID --region=$_DEPLOY_REGION --update-env-vars=ENVIRONMENT=staging --update-secrets=DB_REMOTE_PROD=mongodb_app_staging:latest --quiet id: Deploy entrypoint: bash images: - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA' options: substitutionOption: ALLOW_LOOSE logging: CLOUD_LOGGING_ONLY substitutions: _TRIGGER_ID: 44b16efe-0219-41af-b32b-9b98438728c3 _GCR_HOSTNAME: eu.gcr.io _PLATFORM: managed _SERVICE_NAME: app-staging _DEPLOY_REGION: europe-southwest1 availableSecrets: secretManager: - versionName: projects/PROJECT_ID/secrets/mongodb_app_staging/versions/1 env: MONGODB_URI
创建Dockerfile.final
仅复制运行时必需的文件,避免重复安装依赖:
FROM node:16 WORKDIR / # 复制Cloud Build中已安装好的依赖 COPY package*.json ./ COPY node_modules ./node_modules/ # 复制构建完成的代码 COPY build ./build/ CMD ["node", "./build/index.js"]
优势:
npm install仅执行一次,利用Cloud Build工作目录缓存加速后续构建- 密钥仅在Cloud Build步骤中临时使用,不会进入镜像或Cloud Run配置
- Cloud Run通过Secret Manager拉取密钥,避免明文暴露
方法2:使用Docker BuildKit临时挂载秘密
Docker BuildKit支持构建时临时挂载秘密,密钥不会写入镜像层,无需拆分构建步骤:
修改Dockerfile
启用BuildKit语法,挂载秘密用于测试:
# syntax=docker/dockerfile:1.4 FROM node:16 COPY . ./ WORKDIR / RUN npm install # 临时挂载秘密,仅在测试阶段可用,不会留存到镜像 RUN --mount=type=secret,id=mongodb_uri \ export DB_REMOTE_PROD=$(cat /run/secrets/mongodb_uri) && \ export ENVIRONMENT=staging && \ npm run test RUN npm run build CMD ["node", "./build/index.js"]
修改cloudbuild.yaml的构建步骤
启用BuildKit并传递秘密:
steps: - name: gcr.io/cloud-builders/docker args: - '-c' - >- DOCKER_BUILDKIT=1 docker build --no-cache -t $_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA --secret id=mongodb_uri,env=MONGODB_URI -f Dockerfile . id: Build entrypoint: bash secretEnv: - MONGODB_URI # 推送和部署步骤同方法1,部署时用Secret Manager传递密钥
优势:
- 无需拆分构建流程,保持Dockerfile的完整性
- 密钥仅在测试阶段临时挂载,不会留存于镜像构建历史或最终镜像中
npm install仅执行一次,不增加构建时长
关键优化:Cloud Run密钥管理
部署Cloud Run时,务必使用--update-secrets替代--update-env-vars传递密钥:
gcloud run services update $_SERVICE_NAME \ --platform=managed \ --image=$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA \ --region=$_DEPLOY_REGION \ --update-env-vars=ENVIRONMENT=staging \ --update-secrets=DB_REMOTE_PROD=projects/PROJECT_ID/secrets/mongodb_app_staging:latest
这样密钥仅存储在Secret Manager中,Cloud Run运行时自动拉取,服务配置中不会出现明文密钥,只有授权用户能访问Secret Manager中的内容。
内容的提问来源于stack exchange,提问作者Paul Serre
相关产品推荐
相关产品推荐

