Docker 与部署详解
目录
什么是 Docker?
Docker 是一种容器化技术,可以将应用程序及其所有依赖项打包成一个"容器"(Container)。容器就像是一个轻量级的、可移植的"虚拟环境",可以在任何支持 Docker 的机器上运行,而无需担心环境差异。
简单类比: 想象您要搬家,传统方式是列出所有需要带的东西,然后在新家重新组装。Docker 就像是将整个房间(包括家具、电器、装饰)打包成一个"集装箱",搬到新家后直接打开就能用,无需重新组装。
为什么使用 Docker?
- 环境一致性: 开发、测试、生产环境完全一致,避免"在我机器上能跑"的问题
- 快速部署:容器可以在几秒内启动,比传统虚拟机快得多
- 资源高效:容器共享操作系统内核,比虚拟机更轻量
- 易于扩展:可以根据负载自动创建或销毁容器
在本项目中,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 指令,每个阶段可以有不同的基础镜像和构建步骤。
优势:
- 减小镜像体积:最终镜像只包含运行所需的文件,不包含构建工具和源代码
- 提高安全性:源代码和构建工具不会出现在最终镜像中
- 优化构建速度:可以并行构建前端和后端
构建流程
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_HOSTNAME | Artifact Registry 主机名 | asia-northeast1-docker.pkg.dev |
_AR_PROJECT_ID | Google Cloud 项目 ID | grcn-sca-bigquery |
_AR_REPOSITORY | Artifact Registry 仓库名 | cloudrun-source-deploy |
_SERVICE_NAME | Cloud Run 服务名 | helen-new-insighthub |
_DEPLOY_REGION | 部署区域 | asia-northeast1 |
$SHORT_SHA | Git 提交 SHA(前 7 位) | a1b2c3d |
配置方式:
- 在 Cloud Build 触发器设置中配置替换变量
- 或在
cloudbuild.yaml的substitutions部分定义默认值