初试牛刀 – 用 GitHub Actions 搭建你的第一个流水线
今天我们的目标很明确:为一个示例项目建立一个基础的持续集成 (CI) 流水线。这个流水线将在我们每次提交代码或创建合并请求 (Pull Request) 时被自动触发,执行以下两个核心任务:
代码规范检查 (Linting):确保代码风格统一,避免低级错误。
单元测试 (Unit Testing):确保核心功能符合预期,防止代码重构破坏现有逻辑。
这是一个最基础但至关重要的“质量门禁”,是所有后续自动化部署的基石。
1. 准备我们的示例应用
首先,我们需要一个项目来进行实践。我们将使用一个非常简单的 Node.js Express 应用。请在你的本地机器上创建一个新的项目目录,并包含以下文件。
(最终,你需要将这个项目初始化为 Git 仓库,并推送到你自己的一个 GitHub 仓库中。)
app.js
(应用主文件)
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.status(200).send('Hello, SRE! This is our CI/CD demo.');
});
app.get('/health', (req, res) => {
res.status(200).json({
status: 'UP' });
});
// a function to test
function add(a, b) {
return a + b;
}
if (process.env.NODE_ENV !== 'test') {
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${
port}`);
});
}
module.exports = {
app, add };
package.json
(项目依赖与脚本)
这是项目的“身份证”,定义了依赖和可执行脚本。
{
"name": "sre-cicd-demo",
"version": "1.0.0",
"description": "A sample app for the SRE CI/CD series.",
"main": "app.js",
"scripts": {
"start": "node app.js",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"eslint": "^8.57.0",
"jest": "^29.7.0",
"supertest": "^6.3.4"
}
}
在项目目录下运行 npm install
来安装这些依赖。
.eslintrc.js
(ESLint 配置文件)
一个简单的代码规范配置文件。
module.exports = {
env: {
commonjs: true,
es2021: true,
node: true,
jest: true,
},
extends: 'eslint:recommended',
parserOptions: {
ecmaVersion: 12,
},
rules: {
'no-console': 'warn',
'indent': ['error', 2],
'quotes': ['error', 'single'],
},
};
app.test.js
(单元测试文件)
我们用 jest
和 supertest
来编写测试。
const {
app, add } = require('./app');
const request = require('supertest');
describe('Test the root path', () => {
test('It should respond with status 200 to the GET method', async () => {
const response = await request(app).get('/');
expect(response.statusCode).toBe(200);
expect(response.text).toBe('Hello, SRE! This is our CI/CD demo.');
});
});
describe('Test the health endpoint', () => {
test('It should return health status', async () => {
const response = await request(app).get('/health');
expect(response.statusCode).toBe(200);
expect(response.body).toEqual({
status: 'UP' });
});
});
describe('Test the add function', () => {
test('It should correctly add two numbers', () => {
expect(add(1, 2)).toBe(3);
});
});
在本地运行 npm run lint
和 npm run test
,确保它们都能成功通过。
2. GitHub Actions 核心概念解析
在开始编写流水线之前,我们先快速了解一下 GitHub Actions 的几个核心概念:
Workflow (工作流):由一个 YAML 文件定义的一套自动化流程。一个代码仓库可以有多个工作流。这些 YAML 文件存放在你仓库的 .github/workflows/
目录下。
Event (事件):触发一个工作流运行的特定活动。例如 push
(推送到分支)、pull_request
(创建或更新合并请求)、schedule
(定时触发)、workflow_dispatch
(手动触发) 等。
Job (任务):工作流由一个或多个任务构成。一个任务是在同一个运行器 (Runner) 上执行的一组步骤 (Step)。默认情况下,任务之间是并行执行的,也可以配置为串行。
Step (步骤):任务中的一个独立执行单元。一个步骤可以是一个 Shell 命令,也可以是调用一个动作 (Action)。
Action (动作):一个可复用的代码单元,用来执行一些复杂的、常见的操作。你可以使用 GitHub Marketplace 上由社区或官方提供的 Action(例如 actions/checkout
),也可以创建自己的 Action。
Runner (运行器):一个用来执行你的工作流的服务器。GitHub 提供了托管的运行器(包括 Ubuntu, Windows, macOS),你也可以配置自己的服务器作为自托管运行器 (Self-hosted Runner)。
3. 编写第一个 Workflow
现在,让我们在项目根目录下创建 .github/workflows/
文件夹,并在其中创建一个名为 ci.yml
的文件。
.github/workflows/ci.yml
# 工作流的名称,会显示在 GitHub 的 Actions 页面上
name: Node.js CI
# 触发工作流的事件
on:
# 当有代码 push 到 main 分支时触发
push:
branches: [ "main" ]
# 当有 pull request 指向 main 分支时触发
pull_request:
branches: [ "main" ]
# 定义工作流中包含的任务
jobs:
# 定义一个名为 "build-and-test" 的任务
build-and-test:
# 指定任务运行的环境
# 我们使用 GitHub 提供的最新版 Ubuntu 虚拟机
runs-on: ubuntu-latest
# 定义此任务中要执行的一系列步骤
steps:
# 第一步:检出代码
# 使用官方提供的 actions/checkout@v4 动作,将仓库代码下载到 Runner 中
- name: Checkout repository
uses: actions/checkout@v4
# 第二步:设置 Node.js 环境
# 使用官方的 actions/setup-node@v4 动作
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: '18.x'
# 启用 npm 缓存,可以加快后续构建速度
cache: 'npm'
# 第三步:安装项目依赖
# 'run' 关键字用来执行命令行命令
# 'npm ci' 是在 CI 环境中更推荐的安装命令,它基于 package-lock.json,速度更快且能保证依赖版本一致性
- name: Install dependencies
run: npm ci
# 第四步:运行代码规范检查
# 执行 package.json 中定义的 "lint" 脚本
- name: Run linter
run: npm run lint
# 第五步:运行单元测试
# 执行 package.json 中定义的 "test" 脚本
- name: Run tests
run: npm test
4. 提交并观察结果
将包含 ci.yml
的所有代码提交并推送到你的 GitHub 仓库。
打开你的 GitHub 仓库页面,点击顶部的 “Actions” 标签页。
你会看到一个以你的 Workflow 名称(“Node.js CI”)命名的运行记录。点击它进入详情页面。
在左侧,你会看到我们定义的 build-and-test
任务。点击它可以实时查看每个步骤的执行情况和日志输出。
如果所有步骤都成功执行,你会看到一个绿色的对勾 ✅。如果任何一步失败(例如,测试不通过),整个任务会失败,并显示一个红色的叉 ❌。你可以展开失败的步骤,查看详细的错误日志,这对于排查问题至关重要。
试着搞点“破坏”:
为了验证流水线的有效性,你可以故意修改 app.test.js
中的一个测试用例,让它失败(例如,expect(add(1, 2)).toBe(4);
),然后提交代码。你会看到流水线在“Run tests”这一步失败了,这正是我们期望的——流水线成功地阻止了有问题的代码!
SRE 视角的思考
我们刚刚完成的这个简单流水线,已经体现了 SRE 的核心价值:
可靠性 (Reliability):通过自动化测试,我们为代码质量设置了第一道防线。如果配置了分支保护规则,不通过 CI 的代码将无法被合并到主干,从而保护了主干代码的可靠性。
降低风险 (Risk Reduction):将问题在开发早期(CI阶段)就暴露出来,远比让它流入生产环境后再去修复的成本和风险要小得多。
减少琐事 (Toil Reduction):将原本需要开发者在本地手动执行的检查和测试流程自动化,节省了人力,并保证了检查的执行不会被遗忘。
快速反馈 (Fast Feedback):开发者在提交代码后几分钟内就能得到关于其变更质量的反馈,有助于快速迭代和修复。
总结
恭喜你!你已经成功地使用 GitHub Actions 构建了第一个 CI 流水线。我们学习了 GitHub Actions 的核心概念,并通过一个实际例子,让自动化流程为我们的代码质量保驾护航。
这只是 CI/CD 旅程的第一步,即“CI”部分。我们已经建立了一个自动化的“代码质量守卫”。下一步,我们需要将通过了所有检查的优质代码,打包成一个标准化的、可移植的、随时可以部署的“软件制品”。
在下一篇中,我们将深入制品构建与管理的世界,重点学习 Docker,探讨如何编写一个优化的 Dockerfile,并将容器镜像的自动化构建和推送集成到我们的 CI 流水线中。敬请期待!
暂无评论内容