初试牛刀 – 用 GitHub Actions 搭建你的第一个流水线

初试牛刀 – 用 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 (单元测试文件)

我们用 jestsupertest 来编写测试。

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 lintnpm 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 流水线中。敬请期待!

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
托马斯的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容