Skip to main content

CI/CD 使用指南

先选方式

需求 方式 runner tag
编译 / 打包 / 测试 / 部署等日常作业 常规 job 不用写(公共 runner)
只构建并推送镜像 kaniko 不用写(公共 runner)
docker run / compose 等 Docker in Docker DinD dind(公共 runner)

tags 怎么填:

  • 用公共(共享)runner:不写 tags(常规 job、kaniko 就是这种;也可显式写 tags: [docker])。
  • 用公共 DinD:必须写 tags: [dind](dind)——只有带这个 tag 的 runner 提供 job 内 Docker。
  • 用你自己的 runner(自建本地 runner):写你注册时设的 tag,例如 tags: [myrunner]。 注意:项目里有自己的 runner 且开了 "Run untagged jobs" 时,不写 tags 的 job 会优先被它接走(通常正是你想要的)。

常规 job

  • 编译、构建产物、打包、测试、代码检查、部署……这些都是同一类「都属于「常规 job」:,在 job 容器里跑命令、不构建镜像。
在
job

下面示例以 Node 前端为例:

compile:
  stage: build容器里跑命令,用 image: node:lts        # 换成你需要的通用镜像
  script:
    - npm ci
    - npm run build
    - npm test选工具链,用 artifacts: paths:把产物传给后面的 [dist/]job。它们的差别只是 
stage
    和 script 里跑什么。 一个 job = 一个镜像 + 一串命令 +(可选)产物。 特点:最快、最省资源、隔离最简单(无 Docker daemon)。 不能 docker build;要产出镜像请看 kaniko / dind 两节。 任意公共镜像都可用(Docker Hub / gcr.io / ghcr.io / quay.io / nvcr.io / registry.k8s.io / registry.gitlab.com 均已加速)。

    编译 / 构建产物

    build:
      stage: build
      image: node:lts
      script:
        - npm ci
        - npm run build
      artifacts:
        paths: [dist/]
    

    测试

    test:
      stage: test
      image: node:lts
      needs: [build]
      script:
        - npm ci
        - npm test -- --reporter=junit --outputFile=report.xml
      artifacts:
        when: always
        reports:
          junit: report.xml
    

    代码检查 / 静态分析

    lint:
      stage: test
      image: node:lts
      script:
        - npm ci
        - npx eslint .
    

    打包归档

    package:
      stage: package
      image: alpine:latest
      needs: [build]
      script:
        - tar -czf myapp.tar.gz -C dist .
      artifacts:
        paths: [myapp.tar.gz]
    

    用到 package 这类非默认 stage 时,要在文件顶部声明:stages: [build, test, package, deploy](GitLab 默认只有 .pre / build / test / deploy / .post)。

    部署(deployment job)

    部署方式很多(rsync、scp、sftp、WebDAV、对象存储、kubectl、调用发布接口……),这里只给一个最常见的例子,换传输方式只需改 script,凭据的处理方式不变。

    核心原则:敏感信息(私钥、密码、Token)只放 GitLab 的 CI/CD 变量里,绝不写进仓库、.gitlab-ci.yml 或提交的文件。

    deploy:
      stage: deploy
      image: alpine:latest
      variables:
        GIT_STRATEGY: none
      environment:
        name: production
        url: https://myapp.example.com
      resource_group: production
      rules:
        - if: $CI_COMMIT_BRANCH == "main"
          when: manual
      before_script:
        - apk add --no-cache openssh-client rsync
        - mkdir -p ~/.ssh && chmod 700 ~/.ssh
        - install -m 600 "$SSH_PRIVATE_KEY" ~/.ssh/id_ed25519
        - install -m 644 "$SSH_KNOWN_HOSTS" ~/.ssh/known_hosts
      script:
        - rsync -az --delete -e "ssh -i ~/.ssh/id_ed25519" dist/ deploy@my-server:/srv/myapp/
        - ssh -i ~/.ssh/id_ed25519 deploy@my-server 'cd /srv/myapp && docker compose up -d'
    

    敏感信息存哪里(Settings → CI/CD → Variables):

    变量 类型 说明 SSH_PRIVATE_KEY File 部署专用私钥(单独生成,不要用个人密钥)。File 类型下 $SSH_PRIVATE_KEY 是临时文件路径,用 install -m 600 "$SSH_PRIVATE_KEY" ~/.ssh/id_ed25519 复制,私钥内容不会出现在日志里 SSH_KNOWN_HOSTS Variable 或 File 目标机公钥指纹(ssh-keyscan my-server);不要用 StrictHostKeyChecking=no 密码 / Token Masked 勾 Masked;生产再勾 Protected(只在受保护分支的 job 里可见)

    要点:

      私钥只存在于 job 的临时容器里(权限 600),job 结束随容器销毁;仓库里永远没有它。 目标机不要开密码登录,使用最小权限的专用部署账号。 生产发布建议限定 protected branch + when: manual + environment + resource_group。 拉私有仓库代码用 deploy token / deploy key,不要用个人 PAT。

      kaniko

      • 只构建并推送镜像只能构建并推送镜像,不能 docker run / compose;不支持 RUN --mount=type=secret;不支持跨架构构建。
      不需要特权、不需要 DinD、任意 runner 都能跑。 缓存:--cache-repo 指向本项目 registry。 关于 tag:debug 是浮动 tag(自动跟随上游最新版),不要用 latest(那是 scratch 镜像,没有 shell,CI 脚本根本跑不起来)。 上游 GoogleContainerTools/kaniko 已归档(2025-06,冻结在 v1.24.0);martizih/kaniko 属于仍活跃的替代分支 osscontainertools/kaniko。
      build-image:
        stage: build
        image:
          name: martizih/kaniko:debug        # 浮动 tag,始终带 shell;等价 ghcr.io/osscontainertools/kaniko:debug
          entrypoint: [""]
        script:
          - mkdir -p /kaniko/.docker
          - |
            cat > /kaniko/.docker/config.json <<CONF
            {"auths":{"${CI_REGISTRY}":{"username":"${CI_REGISTRY_USER}","password":"${CI_REGISTRY_PASSWORD}"}}}
            CONF
          - /kaniko/executor
            --context "${CI_PROJECT_DIR}"
            --dockerfile "${CI_PROJECT_DIR}/Dockerfile"
            --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}"
            --destination "${CI_REGISTRY_IMAGE}:latest"
            --snapshot-mode=redo
            --compressed-caching=false
            --cache=true
            --cache-repo="${CI_REGISTRY_IMAGE}/cache"
      
        不需要特权、不需要 dind、任意 runner 都能跑。 限制:不能 docker run / compose;不支持 RUN --mount=type=secret;不支持跨架构构建。 缓存:--cache-repo 指向本项目 registry。 关于 tag:debug 是浮动 tag(自动跟随上游最新版),不要用 latest(那是 scratch 镜像,没有 shell,CI 脚本根本跑不起来)。 上游 GoogleContainerTools/kaniko 已归档(2025-06,冻结在 v1.24.0);martizih/kaniko 属于仍活跃的替代分支 osscontainertools/kaniko。

        DinD

        • 在 job 里跑完整 Docker
        无需 privileged(runner 侧使用隔离运行时,容器是真正的 user-namespace 隔离)。 支持 docker build / run / compose,以及同架构的 docker buildx;隔离运行时里 binfmt_misc 不可用,跨架构(multi-arch / QEMU 模拟)构建做不了;需要多架构镜像请使用自己的构建机。 内层 daemon 每个 job 都是全新的,镜像层不跨 job 保留;要复用请用 registry 缓存/artifacts,不要共享 docker 数据目录。
        build-and-test:
          stage: test
          tags: [dind]                     # ← 必须,否则会被派到没有 Docker daemon 的 runner
          image: docker:29-cli
          services:
            - name: docker:dind
              alias: docker
          variables:
            DOCKER_HOST: tcp://docker:2375
            DOCKER_TLS_CERTDIR: ""         # 明文 2375,仅 job 内部网络
          script:
            - docker build -t myapp:ci .
            - docker run --rm myapp:ci ./run-tests.sh
            - docker compose up -d --build && docker compose ps
        
          无需 privileged(runner 侧使用隔离运行时,容器是真正的 user-namespace 隔离)。 支持 docker build / run / compose,以及同架构的 docker buildx。注意:隔离运行时里 binfmt_misc 不可用,跨架构(multi-arch / QEMU 模拟)构建做不了;需要多架构镜像请使用自己的构建机。 内层 daemon 每个 job 都是全新的,镜像层不跨 job 保留;要复用请用 registry 缓存/artifacts,不要共享 docker 数据目录。 想用 TLS 而不是明文:不要设 DOCKER_TLS_CERTDIR,改设 DOCKER_HOST=tcp://docker:2376、DOCKER_TLS_VERIFY=1、DOCKER_CERT_PATH=/certs/client。

          自建本地 runner 并注册到 git.nju.edu.cn

          什么时候用:不方便用共享 runner(需要特殊硬件、本地数据、自定义镜像环境),或想把构建放在自己的机器上。

          在 GitLab 上创建 runner、拿 token

          • 位置二选一:项目 → Settings → CI/CD → Runners → New project runner。
          • 勾选 Tags,例如 myrunner —— 这是 job 匹配 runner 的唯一依据;不建议勾 "Run untagged jobs"(会跟共享 runner 抢任务)。
          • 创建后复制 runner authentication token(glrt-...,只显示一次,等同凭据,不要提交进仓库、不要写进 .gitlab-ci.yml)。

          在目标机器上安装和注册 gitlab-runner

          安装与注册方式很多(Docker、deb/rpm 包、二进制、Kubernetes 等),请按你的环境选择,参考 GitLab 官方文档:

          • 安装:https://docs.gitlab.com/runner/install/
          • 注册:https://docs.gitlab.com/runner/register/

          注册时 --url 填 https://git.nju.edu.cn/;GitLab 19.x 使用 runner authentication token,即上一步拿到的 glrt-...(对应 --token glrt-...);--executor 按需选择(例如 docker)。

          使用与验证

          • 只有写了 tags: [myrunner] 的 job 才会派到你的 runner。
          • GitLab → Runners 页面应显示 online;跑一条最小 job 验证即可。

          命名与凭据

          • 推送目标统一用 ${CI_REGISTRY_IMAGE}(= reg.nju.edu.cn/<namespace>/<project>),不要硬编码仓库地址。
          • 拉取本项目/组内私有镜像用 $CI_JOB_TOKEN;外部 registry 凭据放 Masked + Protected 的 CI 变量。
          • 凭据不要写进 .gitlab-ci.yml,也不要 --build-arg SECRET=... 烧进镜像层(dind 可用 RUN --mount=type=secret,kaniko 不支持)。
          • Docker Hub 上游图片可走 Dependency Proxy(需要改镜像名)。

          常见问题

          • 忘了写 tags: [dind] → job 会被派到没有 Docker daemon 的 runner,docker 命令连不上。runner。
          • 忘了写 services: [docker:dind] → 连不上 tcp://docker:2375。
          • 用 martizih/kaniko:latest → 那是 scratch 镜像,没有 shell,CI 脚本根本跑不起来;请用 martizih/kaniko:debug。
          • 把凭据写进 .gitlab-ci.yml,或用 --build-arg SECRET=... 烧进镜像层 → 凭据会永久留在仓库或镜像历史里;请一律使用 CI/CD 变量(DinD 里可用 RUN --mount=type=secret,kaniko 不支持这种写法)。