4、CI/CD基础:Jenkins Pipeline入门、GitLab CI配置、GitHub Actions工作流编写

聊到模型部署,很多人第一反应是「训练完模型,手动拷到服务器上跑一下」。嗯,我刚开始也是这么干的。直到有一次,我半夜三点被叫起来,因为生产环境的模型版本搞混了……那滋味,真不好受。

从那以后,我彻底拥抱了CI/CD。说白了,就是把「手动部署」变成「自动流水线」。今天咱们就聊聊三个主流工具:Jenkins、GitLab CI、GitHub Actions。你不需要全学,但至少得知道它们各自怎么玩。

4.1 Jenkins Pipeline:老牌劲旅,灵活但重

Jenkins 是我用得最早的工具。它最大的优点是「啥都能干」,缺点也是「啥都要自己配」。我个人习惯用 Pipeline as Code,也就是把流水线写在 Jenkinsfile 里。

4.1.1 声明式 Pipeline 基础

先看一个最简单的例子。这个流水线会拉代码、装依赖、跑测试:

pipeline {
    agent any

    stages {
        stage('Checkout') {
            steps {
                git 'https://github.com/your-repo/model-deploy.git'
            }
        }
        stage('Install Dependencies') {
            steps {
                sh 'pip install -r requirements.txt'
            }
        }
        stage('Test') {
            steps {
                sh 'pytest tests/'
            }
        }
    }
}

你想想看,这个结构是不是很清晰?每个 stage 就是一个步骤。我在项目中遇到过一个问题:如果 agent 不指定具体节点,Jenkins 可能会把任务调度到一台没有 GPU 的机器上。所以,记得加标签:

agent { label 'gpu-node' }
注意: Jenkins 的 Pipeline 脚本是 Groovy 语法。如果你不熟悉,很容易写出「看起来对,跑起来错」的代码。建议先在 Jenkins 的「片段生成器」里试一下。

4.1.2 模型部署的典型 Pipeline

对于模型部署,我一般会加几个关键步骤:模型验证、打包、推送镜像。看这个例子:

pipeline {
    agent any

    environment {
        DOCKER_REGISTRY = 'registry.example.com'
        MODEL_VERSION = "${BUILD_NUMBER}"
    }

    stages {
        stage('Validate Model') {
            steps {
                sh 'python scripts/validate_model.py --model_path ./models/latest.pkl'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh 'docker build -t ${DOCKER_REGISTRY}/model-server:${MODEL_VERSION} .'
            }
        }
        stage('Push Image') {
            steps {
                sh 'docker push ${DOCKER_REGISTRY}/model-server:${MODEL_VERSION}'
            }
        }
        stage('Deploy to Staging') {
            steps {
                sh 'kubectl set image deployment/model-server model-server=${DOCKER_REGISTRY}/model-server:${MODEL_VERSION}'
            }
        }
    }
}

这里有个小技巧:MODEL_VERSION 用构建号,这样每个版本都是唯一的。我曾经因为版本号重复,导致回滚时找不到正确的镜像……嗯,从那以后我再也不敢偷懒了。

4.2 GitLab CI:与代码仓库深度绑定

如果你团队用 GitLab,那 GitLab CI 是天然的选择。它不需要额外部署 Jenkins 服务器,直接在仓库里放一个 .gitlab-ci.yml 就行。

4.2.1 基础配置结构

GitLab CI 的核心概念是 job。每个 job 可以指定 stage、镜像、脚本。看这个:

stages:
  - build
  - test
  - deploy

variables:
  MODEL_PATH: "models/latest.pkl"

build-job:
  stage: build
  script:
    - python scripts/preprocess.py
    - python scripts/train.py
  artifacts:
    paths:
      - models/
    expire_in: 1 week

test-job:
  stage: test
  script:
    - pytest tests/
  dependencies:
    - build-job

deploy-job:
  stage: deploy
  script:
    - python scripts/deploy.py --model ${MODEL_PATH}
  only:
    - main

你发现没?GitLab CI 的写法更「声明式」。我特别喜欢它的 artifacts 功能——可以把模型文件传给后面的 job,不用手动存到共享目录。

个人经验: 在 GitLab CI 里,onlyexcept 关键字很实用。比如我只想在 main 分支上触发部署,就用 only: - main。避免开发分支的测试版本污染生产环境。

4.2.2 使用 Runner 加速

GitLab CI 需要 Runner 来执行任务。你可以用共享 Runner,也可以自建。我建议自建,尤其是模型训练需要 GPU 时。配置 Runner 很简单:

# 在 Runner 机器上执行
gitlab-runner register \
  --url https://gitlab.com/ \
  --registration-token YOUR_TOKEN \
  --executor docker \
  --docker-image python:3.9 \
  --docker-gpu-enabled true

注册完后,在 .gitlab-ci.yml 里指定 tag:

deploy-job:
  tags:
    - gpu-runner
  script:
    - echo "Running on GPU runner"

为什么会这样?因为如果不指定 tag,Runner 可能不会接这个任务。我踩过这个坑,CI 任务一直 pending,查了半天才发现是 tag 没对上。

4.3 GitHub Actions:云原生,生态丰富

如果你的代码在 GitHub 上,那 GitHub Actions 是最省心的选择。它免费额度对个人项目完全够用,而且市场上有大量现成的 action 可以直接用。

4.3.1 工作流文件结构

GitHub Actions 的工作流文件放在 .github/workflows/ 目录下,YAML 格式。看一个模型部署的例子:

name: Model Deploy Pipeline

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
      - name: Run tests
        run: |
          pytest tests/

  deploy:
    needs: build-and-test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v3
      - name: Deploy model
        run: |
          python scripts/deploy.py
        env:
          API_KEY: ${{ secrets.API_KEY }}

你看,GitHub Actions 的写法非常「模块化」。每个 step 可以是一个 action(比如 actions/checkout),也可以是一段 shell 命令。我特别喜欢它的 secrets 功能——API 密钥、数据库密码这些敏感信息,直接存在仓库的 Settings 里,工作流里用 ${{ secrets.XXX }} 引用。

核心要点: GitHub Actions 的矩阵构建功能很强大。比如你想在 Python 3.8、3.9、3.10 上都跑一遍测试,只需要这样写:
strategy:
  matrix:
    python-version: [3.8, 3.9, 3.10]
它会自动生成 3 个并行 job,省时省力。

4.3.2 模型部署的进阶用法

对于模型部署,我经常用 GitHub Actions 配合 Docker。比如构建镜像并推送到 Docker Hub:

jobs:
  docker-build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: yourusername/model-server:latest

这里要注意:docker/login-actiondocker/build-push-action 都是社区维护的 action,用之前最好看看文档。我曾经因为版本号写错,导致 action 不兼容……嗯,从那以后我学会了锁定版本。

4.4 三个工具的对比与选择

说了这么多,到底该选哪个?我整理了一个表格,方便你对比:

特性 Jenkins GitLab CI GitHub Actions
部署方式 自建服务器 自带 Runner 云原生
配置复杂度 高(需维护 Jenkins 服务)
生态丰富度 插件多,但质量参差不齐 中等 丰富,官方维护的 action 多
GPU 支持 需要手动配置节点标签 通过 Runner 标签支持 需要自建 Runner
适合场景 大型企业,已有 Jenkins 基础设施 使用 GitLab 的团队 开源项目、小团队、个人

我个人建议:如果你从零开始,优先选 GitHub Actions。它免费、易用、生态好。如果公司已经用了 GitLab,那就用 GitLab CI,省去维护 Jenkins 的麻烦。至于 Jenkins……嗯,除非你团队有专门的运维人员,否则不太推荐。

避坑指南: 我曾经在 Jenkins 里配置了复杂的 Pipeline,结果 Jenkins 服务器挂了,所有流水线都跑不了。从那以后,我养成了一个习惯:CI/CD 工具本身也要做高可用。比如 Jenkins 用主从架构,GitLab Runner 多部署几个。

好了,CI/CD 基础就聊到这儿。下一章咱们会深入讲讲「模型版本管理与回滚」,到时候会用到今天学的这些流水线知识。你先把 Jenkinsfile 和 .gitlab-ci.yml 练熟,后面就轻松了。