如何利用 GitHub Actions、Docker 和 Google Cloud Run 学习持续集成与持续交付(CI/CD)
大家好!如果你身处技术领域,一定听过类似持续集成(Continuous Integration,CI)、持续交付(Continuous Delivery,CD)和持续部署(Continuous Deployment)这样的术语,也大概率接触过自动化流水线(pipeline)、测试环境(staging)、生产环境(production)以及测试流程(workflow)等概念。
起初,这些名词可能显得有点复杂,或者彼此之间的区别不够明确,让你困惑:它们究竟是什么意思?彼此之间有何差异?
在这篇文章中,我将用通俗易懂的方式分解这些概念,帮助你在脑海中建立清晰的图景。随后,我们会进行一次实战演练,手把手教你如何一步步搭建一个 CI/CD 工作流。
我们会一起完成:
- 搭建一个 Node.js 项目 ✨
- 使用 Jest 和 Supertest 实现自动化测试 🛠️
- 使用 GitHub Actions 设置 CI/CD 工作流,并在 push、pull request 或新发布版本时触发 ⚙️
- 构建并将应用的 Docker 镜像发布到 Docker Hub 📦
- 将应用部署到测试环境(staging)中进行验证 🚀
- 最后,部署到生产环境(production),正式上线 🌐
读完本文后,你不仅能理解 CI/CD 概念间的区别,还能亲手为自己的项目搭建一条自动化交付流水线。😃
目录
- 什么是持续集成、持续交付和持续部署?
- 持续集成、持续交付和持续部署的差异
- [如何搭建一个带自动化测试的 Node.js 项目](#如何搭建一个带自动化测试的 Node.js 项目 "#%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%B8%A6%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E7%9A%84-nodejs-%E9%A1%B9%E7%9B%AE")
- [如何创建 GitHub 仓库来托管代码](#如何创建 GitHub 仓库来托管代码 "#%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA-github-%E4%BB%93%E5%BA%93%E6%9D%A5%E6%89%98%E7%AE%A1%E4%BB%A3%E7%A0%81")
- [如何在项目中设置 CI 与 CD 工作流](#如何在项目中设置 CI 与 CD 工作流 "#%E5%A6%82%E4%BD%95%E5%9C%A8%E9%A1%B9%E7%9B%AE%E4%B8%AD%E8%AE%BE%E7%BD%AE-ci-%E4%B8%8E-cd-%E5%B7%A5%E4%BD%9C%E6%B5%81")
- [为项目在 Docker Hub 创建镜像仓库并生成访问令牌](#为项目在 Docker Hub 创建镜像仓库并生成访问令牌 "#%E4%B8%BA%E9%A1%B9%E7%9B%AE%E5%9C%A8-docker-hub-%E5%88%9B%E5%BB%BA%E9%95%9C%E5%83%8F%E4%BB%93%E5%BA%93%E5%B9%B6%E7%94%9F%E6%88%90%E8%AE%BF%E9%97%AE%E4%BB%A4%E7%89%8C")
- [创建 Google Cloud 账户、项目和付费账户(Billing Account)](#创建 Google Cloud 账户、项目和付费账户(Billing Account) "#%E5%88%9B%E5%BB%BA-google-cloud-%E8%B4%A6%E6%88%B7%E9%A1%B9%E7%9B%AE%E5%92%8C%E4%BB%98%E8%B4%B9%E8%B4%A6%E6%88%B7billing-account")
- [创建 Google Cloud 服务账号,实现将 Node.js 应用部署到 Google Cloud Run](#创建 Google Cloud 服务账号,实现将 Node.js 应用部署到 Google Cloud Run "#%E5%88%9B%E5%BB%BA-google-cloud-%E6%9C%8D%E5%8A%A1%E8%B4%A6%E5%8F%B7%E5%AE%9E%E7%8E%B0%E5%B0%86-nodejs-%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2%E5%88%B0-google-cloud-run")
- [创建 Staging 分支并将 Feature 分支合并进入(CI + CD)](#创建 Staging 分支并将 Feature 分支合并进入(CI + CD) "#%E5%88%9B%E5%BB%BA-staging-%E5%88%86%E6%94%AF%E5%B9%B6%E5%B0%86-feature-%E5%88%86%E6%94%AF%E5%90%88%E5%B9%B6%E8%BF%9B%E5%85%A5ci--cd")
- [将 Staging 分支合并到 Main 分支(CI + CD)](#将 Staging 分支合并到 Main 分支(CI + CD) "#%E5%B0%86-staging-%E5%88%86%E6%94%AF%E5%90%88%E5%B9%B6%E5%88%B0-main-%E5%88%86%E6%94%AFci--cd")
- 总结
什么是持续集成、持续交付和持续部署?
持续集成(Continuous Integration,CI)
想象你所在的团队有 6 个开发者在同一个项目中协作。如果没有合理的管理方式,就会乱作一团:
- A 同事在做登录功能
- B 同事在修复搜索栏的 Bug
- C 同事在修改仪表盘 UI
所有人都在修改同一个"文件夹"(代码库),很容易出现"谁刚才又把应用搞崩了?"的混乱局面。😱
为避免这种情况,团队会使用像 GitHub、GitLab、BitBucket 等版本控制系统(VCS),它就像一个数字化协作空间,允许每个人独立工作,不会互相干扰。
持续集成在其中扮演重要角色:
- Main 分支:又称"主分支",是最终且稳定的代码库,一般对应线上生产环境。只有通过测试和审核的代码才会合并到这里。
- Feature 分支:当 A 同事要开发新功能时,会从 main 分支拉取一个 feature 分支,在这里开发、测试,不影响主分支。B、C 等人也各自拥有自己的 feature 分支。
- 合并流程 :
- 在 feature 分支开发完毕后,A 不会直接将代码粗暴地合入主分支,而是会发起合并请求(Pull Request)。
- 此时 CI 工具会自动拉起测试流程。比如跑自动化测试、构建校验等。如果测试通过,就可以安全地合并到主分支。若不通过,则说明有 Bug,需要继续修复。
这种不断将新功能分支合并回主分支并进行自动检测的过程,就叫做持续集成。
持续交付(Continuous Delivery,CD)
持续交付常常和持续部署(Continuous Deployment)混淆,虽然它们确实很相似,但各自扮演的角色有所不同。
为了理解持续交付,让我们先看看为什么需要一个中间环境(staging)。
在前面的持续集成环节中,我们主要关注了 feature 分支与主分支的合并与测试。但如果直接把 feature 分支合并到主分支,然后立刻上线到真实用户环境,还是有风险------因为自动化测试并不万能,仍可能有各种边界情况或 Bug 被忽略。这就是为什么需要 staging 分支和一个测试环境。
- Staging 分支:在将功能上线之前,先合并到 staging 分支并部署到"预发布环境"(staging environment)。
- 预发布环境:是一个几乎与生产环境一致的测试环境,QA(质量保证)团队会在这里手动测试功能,检查使用体验、潜在 Bug 以及自动测试可能漏掉的问题。
- 持续交付:指的就是把通过 CI 流程的代码部署到预发布环境的过程。与持续部署不同的是,持续交付在最终生产环境上线之前,往往需要人工审批或测试团队验证,通过后才会正式部署到生产环境。
持续部署(Continuous Deployment,CD)
持续部署可以说是自动化的极致。在这里,与持续交付最大的不同在于:没有额外的人工审批。
当 staging 环境的测试都顺利通过后,系统会自动将改动部署到生产环境,对应真正的线上用户可访问的网站。
- 最后一步(部署到线上)不再需要人工点击"确认",只要满足设定条件(比如 PR 合并到 main 分支,或发布了一个新版本),管道会自动执行从构建、测试到最终部署的过程。对团队来说,这大大节省了时间,但也需要对测试、监控和回滚机制有足够信心,才能放心地做到完全自动化。
持续集成、持续交付和持续部署的差异
方面 | 持续集成(CI) | 持续交付(CD) | 持续部署(CD) |
---|---|---|---|
主要关注点 | 将 feature 分支合并到 main / staging 等公共分支 | 将测试通过的代码部署到预发布环境(staging)供 QA 测试 | 自动部署到生产环境,无需人工审批 |
自动化程度 | 自动化测试和构建 | 自动化部署到测试环境 | 完全自动化部署到生产 |
测试范围 | 在合并到 main / staging 前进行自动化测试 | 自动化测试 + QA 手动测试 | 也会进行自动化测试,但无人工审批,直接上线 |
涉及的分支 | feature 分支 -> main / staging | staging 分支 -> 预发布环境 | main 分支 -> 生产环境 |
目标环境 | 本地或构建流水线中模拟的环境 | 预发布环境(与生产相似) | 生产环境(真正在用户端运行) |
审批流程 | 不需要(测试通过即可合并) | 需要 QA 或其他人员验收后再上线 | 不需要,完全自动化上线 |
示例触发 | 开发者合并 feature 分支到 main / staging | staging 分支通过自动测试后部署到测试环境 | 当有新 release 或合并到 main 时自动上线 |
如何搭建一个带自动化测试的 Node.js 项目
我们将创建一个带有 Jest 自动化测试的 Node.js Web 服务器。然后使用 GitHub Actions 配置 CI/CD 流水线,让每次向 staging、main 分支发起的 Pull Request 都自动化测试。如果测试通过,就会构建并推送镜像到 Docker Hub,最后再部署到 Google Cloud Run(先部署到 staging 测试,再部署到生产)。
准备好了吗?让我们开始吧。🚀
第 1 步:安装 Node.js
- 访问 Node.js 官网。
- 选择对应的操作系统(Windows、macOS 或 Linux),下载并安装。
- 安装完成后,打开终端执行
node -v
,查看是否正确安装。
第 2 步:克隆示例仓库
我们会使用一个预先准备好的代码模板。
-
如果本地还没安装 Git,请先从 Git 官方下载。
-
完成后,在终端执行:
bash
git clone --single-branch --branch initial github.com/onukwilip/c... cd ci-cd-tutorial
perl
这样就会把项目下载到本地。
### 第 3 步:安装依赖
在项目目录下,运行:
```bash
npm install --force
第 4 步:运行自动化测试
在项目目录执行:
bash
npm test
如果看到 2 个测试通过的结果,就说明测试环境已经正常配置好了。
第 5 步:启动 Web 服务器
bash
npm start
然后在浏览器打开 http://localhost:5000
,就能看到启动成功的页面。
如何创建 GitHub 仓库来托管代码
第 1 步:登录 GitHub
- 打开浏览器访问 GitHub。
- 如果没有 GitHub 账号,可以注册一个。
第 2 步:新建一个仓库
登录后,点击右上角的"+"号,选择"New repository"。
设置:
- Repository Name:例如
ci-cd-tutorial
- Description:可选,填上简要说明
- Visibility:选择 public 或 private 都行
- 不要勾选"Add a README file"
点击"Create repository"完成。
第 3 步:修改远程仓库地址并推送
在你本地的项目目录:
bash
git remote set-url origin <your-repo-url>
<your-repo-url>
是你刚才创建的 GitHub 仓库地址。
如果当前分支不是 main,可以改名:
bash
git branch -M main
然后提交并推送到远程:
bash
git add .
git commit -m 'Created boilerplate'
git push -u origin main
这样本地代码就上传到新的 GitHub 仓库了。
如何在项目中设置 CI 与 CD 工作流
接下来我们要创建 CI 和 CD 工作流文件,放在 .github/workflows
目录下。当我们推送代码到远程仓库后,GitHub Actions 会自动识别并在云端运行这些工作流。
第 1 步:准备工作流目录
先在代码仓库中创建一个新分支:
bash
git checkout -b feature/ci-cd-pipeline
然后在项目根目录下新建 .github
文件夹,再在 .github
中新建 workflows
文件夹。所有 YAML 文件放在这里都会被 GitHub Actions 检测为工作流。
第 2 步:创建持续集成(CI)工作流
在 workflows
目录中创建 ci-pipeline.yml
,并粘贴以下内容:
yaml
name: CI Pipeline to staging/production environment
on:
pull_request:
branches:
- staging
- main
jobs:
test:
runs-on: ubuntu-latest
name: Setup, test, and build project
env:
PORT: 5001
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Test application
run: npm test
- name: Build application
run: |
echo "Run command to build the application if present"
npm run build --if-present
解释
- name: CI 流水线的名称
- on.pull_request.branches : 当针对
main
或staging
分支发起 PR 时触发。 - jobs : 定义要运行的一系列任务,本例中只有一个 job 叫
test
。runs-on: ubuntu-latest
: 使用 Ubuntu 最新环境。env.PORT=5001
: 设置环境变量。steps
: 工作流的具体步骤:- Checkout: 拉取代码
- Install dependencies: 安装依赖
- Test application: 运行
npm test
- Build application: 可选步骤,若无构建脚本不会报错。
第 3 步:持续交付和持续部署(CD)工作流
在 .github/workflows
中新建 cd-pipeline.yml
文件,并粘贴:
yaml
name: CD Pipeline to Google Cloud Run (staging and production)
on:
push:
branches:
- staging
workflow_dispatch: {}
release:
types: published
env:
PORT: 5001
IMAGE: ${{vars.IMAGE}}:${{github.sha}}
jobs:
test:
runs-on: ubuntu-latest
name: Setup, test, and build project
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Test application
run: npm test
build:
needs: test
runs-on: ubuntu-latest
name: Setup project, Authorize GitHub Actions to GCP and Docker Hub, and deploy
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Authenticate for GCP
id: gcp-auth
uses: google-github-actions/auth@v0
with:
credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v0
- name: Authenticate for Docker Hub
id: docker-auth
env:
D_USER: ${{secrets.DOCKER_USER}}
D_PASS: ${{secrets.DOCKER_PASSWORD}}
run: |
docker login -u $D_USER -p $D_PASS
- name: Build and tag Image
run: |
docker build -t ${{env.IMAGE}} .
- name: Push the image to Docker hub
run: |
docker push ${{env.IMAGE}}
- name: Enable the Billing API
run: |
gcloud services enable cloudbilling.googleapis.com --project=${{secrets.GCP_PROJECT_ID}}
- name: Deploy to GCP Run - Production environment (If a new release was published from the master branch)
if: github.event_name == 'release' && github.event.action == 'published' && github.event.release.target_commitish == 'main'
run: |
gcloud run deploy ${{vars.GCR_PROJECT_NAME}} \
--region ${{vars.GCR_REGION}} \
--image ${{env.IMAGE}} \
--platform "managed" \
--allow-unauthenticated \
--tag production
- name: Deploy to GCP Run - Staging environment
if: github.ref != 'refs/heads/main'
run: |
echo "Deploying to staging environment"
gcloud run deploy ${{vars.GCR_STAGING_PROJECT_NAME}} \
--region ${{vars.GCR_REGION}} \
--image ${{env.IMAGE}} \
--platform "managed" \
--allow-unauthenticated \
--tag staging
这是一个结合了持续交付和持续部署的工作流。它在 push 到 staging
分支时触发,或者手动触发 (workflow_dispatch
),或者发布新版本时触发(release: published
)。
- test job: 先做自动化测试,通过后再进行下一步。
- build job :
- 先做 GCP 和 Docker Hub 的身份验证(使用 GitHub Secrets)。
- 构建并推送 Docker 镜像到 Docker Hub。
- 如果是 staging 分支推送,就部署到 staging 环境。
- 如果是发布 release 且目标分支是 main,就部署到生产环境。
为什么要把敏感信息放到 Secrets?
因为工作流文件是会提交到仓库的,如果把凭证写死在里面,很容易泄露。通过 GitHub Secrets 可以安全地存储和使用这些敏感数据。
为项目在 Docker Hub 创建镜像仓库并生成访问令牌
第 1 步:注册 / 登录 Docker Hub
- 打开 Docker Hub 官网。
- 如果没账号,就点击 "Sign Up" 注册。
- 注册完成后,点击 "Sign In" 登录。
第 2 步:生成访问令牌
- 点击右上角头像,选择 "Account Settings"。
- 在左侧菜单选择 "Security"。
- 点击 "New Access Token",填写描述(比如 "GitHub Actions CI/CD"),然后选择权限(如 Read/Write)。
- 生成后复制该 Token(只会显示一次)。
第 3 步:在 GitHub 中添加 Secrets
- 回到 GitHub 仓库,进入 "Settings" -> "Secrets and variables" -> "Actions"。
- 点击 "New repository secret",创建
DOCKER_PASSWORD
,值为刚才复制的 Token。 - 同样创建
DOCKER_USER
,值为你的 Docker Hub 用户名。
第 4 步:创建 Dockerfile
在项目根目录创建 Dockerfile
:
dockerfile
FROM node:18-slim
WORKDIR /app
COPY package.json .
RUN npm install -f
COPY . .
EXPOSE 5001
CMD ["npm", "start"]
其含义:
FROM node:18-slim
: 基于 Node.js 18 的轻量镜像WORKDIR /app
: 设置容器内的工作目录COPY package.json .
: 复制 package.jsonRUN npm install -f
: 安装依赖COPY . .
: 复制项目的所有文件到容器EXPOSE 5001
: 暴露端口 5001CMD ["npm", "start"]
: 运行应用
创建 Google Cloud 账户、项目和付费账户(Billing Account)
第 1 步:创建或登录 Google Cloud
- 打开 Google Cloud Console。
- 如果没账号,需要创建并绑定支付方式(Google 有 300 美元的免费试用额度)。
第 2 步:创建新项目
- 在控制台左上角点击下拉,选择 "New Project"。
- 输入项目名称(如
gcr-ci-cd-project
)。 - 点击 "Create"。
第 3 步:访问新项目
创建成功后,回到控制台,确保选择你刚创建的项目。
第 4 步:关联付费账户
在侧边栏找到 "Billing",根据提示关联或创建付费账户。
创建 Google Cloud 服务账号,实现将 Node.js 应用部署到 Google Cloud Run
为什么要创建服务账号和密钥?
因为 CI/CD 流程需要以某种"身份"访问 Google Cloud 的资源,而服务账号正好可以授予有限的角色权限,例如部署到 Cloud Run。
第 1 步:打开服务账号页面
进入 Google Cloud Console,选择你创建的项目。
导航到 "IAM & Admin" -> "Service Accounts"。
第 2 步:创建新服务账号
点击 "Create Service Account",输入:
- Name:
ci-cd-sa
- ID: 自动生成
- Description: "Used for deploying Node.js app to Cloud Run"
然后点击 "Create and Continue"。
第 3 步:分配角色
给这个服务账号添加:
- Cloud Run Admin
- Service Account User
- Service Usage Admin
- Viewer
然后点击 "Continue"。
第 4 步:跳过授权其他用户
点击 "Done"。
第 5 步:生成服务账号密钥
回到服务账号列表,找到刚才创建的 ci-cd-sa
,点击右侧三个点,选择 "Manage Keys"。
点击 "Add Key" -> "Create New Key",选择 JSON 类型。点击"Create"后会下载 .json
文件。
务必妥善保管!
第 6 步:将密钥添加到 GitHub Secrets
- 打开下载的 JSON 文件,复制全部内容。
- 打开 GitHub 仓库 -> Settings -> Secrets -> Actions -> New repository secret。
- Name:
GCP_SERVICE_ACCOUNT
,Value:粘贴 JSON 内容。 - 同理,再创建一个名为
GCP_PROJECT_ID
的 secret,值是你的 Google Cloud 项目的 ID。
第 7 步:添加外部变量
继续在 "Settings" -> "Secrets and variables" -> "Actions" -> "Variables" 中添加:
GCR_PROJECT_NAME
: 生产环境 Cloud Run 服务名,例如gcr-ci-cd-app
GCR_STAGING_PROJECT_NAME
: 测试环境服务名,例如gcr-ci-cd-staging
GCR_REGION
: 部署区域,如us-central1
IMAGE
:<dockerhub-username>/ci-cd-tutorial-app
启用 Service Usage API
- 在 Google Cloud Console -> APIs & Services -> Library 中搜索 "Service Usage API"。
- 点击 "Enable" 启用。
创建 Staging 分支并将 Feature 分支合并进入(CI + CD)
- 在 GitHub 上打开你的仓库,在分支下拉列表里,先切到 main 分支。
- 输入
staging
并创建这个新分支。 - 把你本地的
feature/ci-cd-pipeline
分支提交并推送到远端。然后在 GitHub 仓库页面,你会看到"Compare & Pull Request"的提示。 - 选择
staging
作为 base 分支,feature/ci-cd-pipeline
作为 compare 分支。写好标题和说明后,创建 Pull Request。 - GitHub Actions 会自动触发 CI 工作流(
ci-pipeline.yml
),跑测试。 - 测试通过后,可以 Merge PR。合并后,CD 工作流(
cd-pipeline.yml
)会自动部署到 staging 环境。
在 Google Cloud Run 上查看 staging 环境
- 打开 Google Cloud Console,选择对应项目。
- 左侧导航选择 Cloud Run,找到你的 staging 服务(如
gcr-ci-cd-staging
)。 - 查看服务详情中的 URL,访问即可看到部署结果。
将 Staging 分支合并到 Main 分支(CI + CD)
当我们确认 staging 环境测试完成后,就可以把 staging 分支合并到 main 分支,并最终触发部署到生产环境。
- 在 GitHub 上发起 PR,把 staging 合并到 main。
- CI 工作流会再次跑测试。
- 若要触发部署到生产环境,需要发布一个新版本(Release) 。在 GitHub 上进入 "Releases",点击 "Draft a new release",将 Target branch 设为
main
,输入 Tag(例如v1.0.0
),点击 "Publish Release"。 - 这会触发我们的
cd-pipeline.yml
中的 release 逻辑,将应用部署到生产环境。
为什么选择在发布 Release 时触发?
这样可以保证只有在团队真正决定"准备好了"时,才会部署到生产环境。平时对 main 的普通提交或合并不会立即上线,从而避免将半成品功能暴露给用户。
总结
在本文中,我们从零开始为一个 Node.js 应用构建并自动化了 CI/CD 流程:
- 使用 GitHub Actions、Docker Hub、Google Cloud Run
- 先在 Pull Request 阶段进行自动化测试(CI)
- 合并到 staging 分支后自动构建并部署到测试环境(CD),进行进一步的验证
- 对 main 分支发布 release 后再自动部署到生产环境(持续部署)
这样,团队就能更灵活地在 main 分支上合并和测试各种功能,而不会影响真实用户的使用体验。直到确认功能稳定后,才在发布时把它推向线上的生产环境。
如果你想深入了解 CI/CD,可以参考:
作者简介
Prince,是一名软件工程师,热衷于构建可扩展的应用,并在技术社区分享知识。如果你喜欢这篇文章,欢迎访问我的 [LinkedIn 主页](#LinkedIn 主页 "#") 了解更多我的博客与项目。也可以访问我的个人网站阅读更多文章,期待和你交流!😊