跳到主要内容

Docker 与部署详解


目录

  1. 什么是 Docker?
  2. Dockerfile 详解
  3. 多阶段构建
  4. Cloud Build 部署流程
  5. Cloud Run 运行环境
  6. 部署检查清单

什么是 Docker?

Docker 是一种容器化技术,可以将应用程序及其所有依赖项打包成一个"容器"(Container)。容器就像是一个轻量级的、可移植的"虚拟环境",可以在任何支持 Docker 的机器上运行,而无需担心环境差异。

简单类比: 想象您要搬家,传统方式是列出所有需要带的东西,然后在新家重新组装。Docker 就像是将整个房间(包括家具、电器、装饰)打包成一个"集装箱",搬到新家后直接打开就能用,无需重新组装。

为什么使用 Docker?

  1. 环境一致性:开发、测试、生产环境完全一致,避免"在我机器上能跑"的问题
  2. 快速部署:容器可以在几秒内启动,比传统虚拟机快得多
  3. 资源高效:容器共享操作系统内核,比虚拟机更轻量
  4. 易于扩展:可以根据负载自动创建或销毁容器

在本项目中,Docker 用于:

  • 构建应用镜像:将前端和后端代码打包成容器镜像
  • 部署到 Cloud Run:在 Google Cloud Run 上运行容器化的应用

Dockerfile 详解

什么是 Dockerfile?

Dockerfile 是一个文本文件,包含了一系列指令,告诉 Docker 如何构建容器镜像。就像是一个"食谱",Docker 按照这个"食谱"一步步构建出最终的"菜肴"(容器镜像)。

项目 Dockerfile 结构

本项目的 Dockerfile 采用多阶段构建(Multi-stage Build)方式,分为三个阶段:

阶段 1:前端构建(Frontend Build)

FROM node:20-slim AS frontend-build

WORKDIR /app

# 复制 package.json 并安装依赖
COPY package.json package-lock.json ./
RUN npm install -g npm@11
RUN npm install

# 复制前端源代码并构建
COPY . .
RUN npm run build && test -f dist/index.html

说明

  • 基础镜像node:20-slim(Node.js 20 的精简版本,体积小)
  • 工作目录/app(容器内的工作目录)
  • 依赖安装:先复制 package.json,安装依赖(利用 Docker 缓存层)
  • 代码构建:复制源代码,运行构建命令,生成 dist/ 目录

为什么先复制 package.json? Docker 会缓存每一层,如果 package.json 没有变化,就不需要重新安装依赖,可以大大加快构建速度。

阶段 2:后端构建(Backend Build)

FROM node:20-slim AS backend-build

WORKDIR /app

# 复制后端 package.json 和配置文件
COPY server/package.json server/package-lock.json server/tsconfig.json ./server/
COPY tsconfig.json /app/tsconfig.json

# 安装后端依赖
WORKDIR /app/server
RUN npm install -g npm@11
RUN npm install

# 复制后端源代码并构建
COPY server/ ./
RUN npm run build && test -f dist/index.js

说明

  • 独立构建:后端在独立的阶段构建,不依赖前端
  • TypeScript 编译:运行 npm run build 将 TypeScript 编译为 JavaScript
  • 构建验证:使用 test -f dist/index.js 验证构建产物是否存在

阶段 3:生产运行环境(Production Runtime)

FROM node:20-slim

WORKDIR /app

# 安装生产依赖(只安装后端依赖)
WORKDIR /app/server
COPY --from=backend-build /app/server/package.json ./
COPY --from=backend-build /app/server/package-lock.json ./
RUN npm install -g npm@11 \
&& npm ci --omit=dev

# 复制后端构建产物
COPY --from=backend-build /app/server/dist ./dist

# 复制前端构建产物到 public 目录
WORKDIR /app
COPY --from=frontend-build /app/dist ./public

# 复制启动脚本
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# 设置环境变量
ENV NODE_ENV=production
ENV STATIC_DIR=/app/public

# 暴露端口
EXPOSE 8080

# 使用启动脚本
ENTRYPOINT ["/entrypoint.sh"]

说明

  • 最小化镜像:只复制构建产物,不包含源代码和开发依赖
  • 前端静态文件:前端构建产物复制到 public/ 目录,由后端服务器提供
  • 启动脚本:使用 entrypoint.sh 作为容器启动入口
  • 端口暴露:暴露 8080 端口(Cloud Run 会自动注入 PORT 环境变量)

关键指令说明

指令说明示例
FROM指定基础镜像FROM node:20-slim
WORKDIR设置工作目录WORKDIR /app
COPY复制文件到容器COPY package.json ./
RUN执行命令RUN npm install
ENV设置环境变量ENV NODE_ENV=production
EXPOSE声明端口EXPOSE 8080
ENTRYPOINT设置启动命令ENTRYPOINT ["/entrypoint.sh"]

多阶段构建

为什么使用多阶段构建?

多阶段构建(Multi-stage Build)允许在一个 Dockerfile 中使用多个 FROM 指令,每个阶段可以有不同的基础镜像和构建步骤。

优势

  1. 减小镜像体积:最终镜像只包含运行所需的文件,不包含构建工具和源代码
  2. 提高安全性:源代码和构建工具不会出现在最终镜像中
  3. 优化构建速度:可以并行构建前端和后端

构建流程

graph TD
A[开始构建] --> B[阶段1: 前端构建]
A --> C[阶段2: 后端构建]
B --> D[生成 dist/ 目录]
C --> E[生成 dist/index.js]
D --> F[阶段3: 生产环境]
E --> F
F --> G[复制前端产物到 public/]
F --> H[复制后端产物到 dist/]
F --> I[安装生产依赖]
G --> J[最终镜像]
H --> J
I --> J

构建产物大小对比

构建方式镜像大小包含内容
单阶段构建~800 MB源代码 + node_modules + 构建工具 + 构建产物
多阶段构建~200 MB仅构建产物 + 生产依赖

节省空间:多阶段构建可以减小约 75% 的镜像体积。


Cloud Build 部署流程

什么是 Cloud Build?

Google Cloud Build 是一个完全托管的 CI/CD(持续集成/持续部署)服务,可以自动构建、测试和部署应用。当代码推送到 Git 仓库时,Cloud Build 会自动触发构建流程。

部署流程

sequenceDiagram
participant Dev as 开发者
participant Git as Git 仓库
participant CB as Cloud Build
participant AR as Artifact Registry
participant CR as Cloud Run

Dev->>Git: 1. 推送代码
Git->>CB: 2. 触发构建
CB->>CB: 3. 执行 cloudbuild.yaml
CB->>CB: 4. 构建 Docker 镜像
CB->>AR: 5. 推送镜像到 Artifact Registry
CB->>CR: 6. 部署到 Cloud Run
CR-->>Dev: 7. 服务可用

cloudbuild.yaml 配置

cloudbuild.yaml 文件定义了 Cloud Build 的构建和部署步骤:

steps:
# 步骤 1: 构建 Docker 镜像
- name: 'gcr.io/cloud-builders/docker'
id: 'build-image'
args:
- 'build'
- '-t'
- '${_AR_HOSTNAME}/${_AR_PROJECT_ID}/${_AR_REPOSITORY}/${_SERVICE_NAME}:latest'
- '-t'
- '${_AR_HOSTNAME}/${_AR_PROJECT_ID}/${_AR_REPOSITORY}/${_SERVICE_NAME}:$SHORT_SHA'
- '.'

# 步骤 2: 推送镜像(latest 标签)
- name: 'gcr.io/cloud-builders/docker'
id: 'push-image-latest'
args: ['push', '${_AR_HOSTNAME}/${_AR_PROJECT_ID}/${_AR_REPOSITORY}/${_SERVICE_NAME}:latest']

# 步骤 3: 推送镜像(SHA 标签)
- name: 'gcr.io/cloud-builders/docker'
id: 'push-image-sha'
args: ['push', '${_AR_HOSTNAME}/${_AR_PROJECT_ID}/${_AR_REPOSITORY}/${_SERVICE_NAME}:$SHORT_SHA']

# 步骤 4: 部署到 Cloud Run
- name: 'gcr.io/cloud-builders/gcloud'
id: 'deploy-to-cloud-run'
args:
- 'run'
- 'deploy'
- '${_SERVICE_NAME}'
- '--image'
- '${_AR_HOSTNAME}/${_AR_PROJECT_ID}/${_AR_REPOSITORY}/${_SERVICE_NAME}:$SHORT_SHA'
- '--region'
- '${_DEPLOY_REGION}'
- '--platform'
- 'managed'
- '--allow-unauthenticated'
- '--set-env-vars'
- >-
PORT=8080,
VITE_GEMINI_API_KEY=${_VITE_GEMINI_API_KEY},
...

替换变量(Substitutions)

Cloud Build 使用替换变量来注入配置信息:

变量名说明示例值
_AR_HOSTNAMEArtifact Registry 主机名asia-northeast1-docker.pkg.dev
_AR_PROJECT_IDGoogle Cloud 项目 IDgrcn-sca-bigquery
_AR_REPOSITORYArtifact Registry 仓库名cloudrun-source-deploy
_SERVICE_NAMECloud Run 服务名helen-new-insighthub
_DEPLOY_REGION部署区域asia-northeast1
$SHORT_SHAGit 提交 SHA(前 7 位)a1b2c3d

配置方式

  1. 在 Cloud Build 触发器设置中配置替换变量
  2. 或在 cloudbuild.yamlsubstitutions 部分定义默认值

镜像标签策略

系统使用两种标签策略:

  1. latest 标签:始终指向最新的构建
  2. $SHORT_SHA 标签:指向特定的 Git 提交

优势

  • 快速回滚:如果新版本有问题,可以快速回滚到之前的 $SHORT_SHA 版本
  • 版本追踪:每个部署都有唯一的标签,便于追踪和调试

Cloud Run 运行环境

什么是 Cloud Run?

Google Cloud Run 是一个完全托管的无服务器容器运行环境,可以自动扩缩容、处理流量负载,并按实际使用量计费。

特点

  • 自动扩缩容:根据请求量自动创建或销毁容器实例
  • 按需计费:只在容器运行时计费,空闲时不收费
  • 零运维:无需管理服务器,Google 自动处理基础设施

启动流程

容器启动时,会执行 entrypoint.sh 脚本:

#!/bin/bash
set -e

# 等待 Cloud Run 注入 PORT 环境变量
PORT=${PORT:-8080}

# 启动后端服务器
cd /app/server
exec node dist/index.js

说明

  • 端口配置:Cloud Run 会自动注入 PORT 环境变量,默认为 8080
  • 静态文件服务:后端服务器会提供前端静态文件(从 /app/public 目录)
  • 进程管理:使用 exec 确保 Node.js 进程成为容器的主进程

环境变量注入

Cloud Run 在部署时会注入环境变量(通过 --set-env-vars 参数):

- '--set-env-vars'
- >-
PORT=8080,
VITE_GEMINI_API_KEY=${_VITE_GEMINI_API_KEY},
VITE_GOOGLE_OAUTH_CLIENT_ID=${_VITE_GOOGLE_OAUTH_CLIENT_ID},
...

工作原理

  1. Cloud Build 从触发器配置中读取替换变量
  2. 在部署 Cloud Run 时,将这些变量注入为环境变量
  3. 容器启动时,可以通过 process.env 访问这些变量

自动扩缩容

Cloud Run 会根据请求量自动调整容器实例数量:

  • 最小实例数:0(空闲时无实例运行,不产生费用)
  • 最大实例数:根据配置(默认 100)
  • 扩缩容速度:通常在几秒内完成

配置示例(在 Cloud Build 中):

- '--min-instances'
- '0'
- '--max-instances'
- '10'
- '--concurrency'
- '80'

部署检查清单

部署前检查

  • 环境变量配置:所有必需的环境变量已在 Cloud Build 触发器中配置
  • 服务账号权限:服务账号具有必要的 Google Cloud 权限
  • Apps Script 配置:Apps Script 项目已创建并配置
  • Artifact Registry:镜像仓库已创建
  • Cloud Run 服务:服务已创建(首次部署)或存在(更新部署)

构建检查

  • Dockerfile 语法:Dockerfile 语法正确,无错误
  • 依赖安装package.json 中的依赖版本正确
  • 构建命令:前端和后端的构建命令正确
  • 构建产物:构建产物(dist/ 目录)存在

部署检查

  • 镜像推送:镜像已成功推送到 Artifact Registry
  • 服务部署:Cloud Run 服务部署成功
  • 环境变量:环境变量已正确注入
  • 服务健康:服务健康检查通过

部署后验证

  • 服务可访问:通过 Cloud Run URL 可以访问服务
  • 前端加载:前端页面正常加载
  • API 调用:后端 API 正常响应
  • 功能测试:核心功能(登录、文件上传、分析)正常

日志检查

  • 构建日志:Cloud Build 日志无错误
  • 运行日志:Cloud Run 日志无错误
  • 应用日志:应用日志正常输出

常见问题

Q1:构建失败,提示 "npm install" 错误?

可能原因

  1. package.json 中的依赖版本不兼容
  2. 网络问题导致依赖下载失败
  3. 平台特定的可选依赖安装失败

解决方案

  1. 检查 package.json 中的依赖版本
  2. 在本地运行 npm install 验证依赖安装
  3. 升级 npm 到 v11(已在 Dockerfile 中处理)

Q2:部署后服务无法访问?

可能原因

  1. 环境变量未正确注入
  2. 端口配置错误
  3. 服务未正确启动

解决方案

  1. 检查 Cloud Run 服务的环境变量配置
  2. 查看 Cloud Run 日志,检查启动错误
  3. 验证 entrypoint.sh 脚本是否正确执行

Q3:如何回滚到之前的版本?

方式 1:通过 Cloud Console

  1. 在 Cloud Run 服务页面,点击"修订版本"
  2. 选择要回滚的版本
  3. 点击"管理流量",将 100% 流量路由到该版本

方式 2:通过 gcloud 命令

gcloud run services update-traffic SERVICE_NAME \
--to-revisions REVISION_NAME=100

Q4:如何查看构建和部署日志?

Cloud Build 日志

  1. 在 Google Cloud Console 中打开 Cloud Build
  2. 查看构建历史
  3. 点击构建记录查看详细日志

Cloud Run 日志

  1. 在 Google Cloud Console 中打开 Cloud Run
  2. 选择服务
  3. 点击"日志"标签页查看运行日志

总结

本章节详细介绍了 Docker 和部署相关的知识,包括:

  1. Docker 基础:什么是 Docker,为什么使用 Docker
  2. Dockerfile 详解:多阶段构建的实现细节
  3. Cloud Build 部署:自动化的构建和部署流程
  4. Cloud Run 运行:无服务器容器的运行机制
  5. 部署检查清单:确保部署成功的检查项

正确理解和配置 Docker 和部署流程,是系统稳定运行的关键。


相关文档