gitlab cicd每次运行都要花费大量时间安装一堆依赖,如何提速

使用 GitLab CI/CD 的 Docker Runner 时,经常会看到类似日志:

Using Docker executor with image alpine:3.20 ...

$ apk add --no-cache openssh-client rsync
fetch https://dl-cdn.alpinelinux.org/...
Installing openssh-client...
Installing rsync...

每次流水线都要重新下载和安装软件包,既浪费时间,也容易受到网络波动影响。

本文介绍如何制作一个预装依赖的 CI 基础镜像,让流水线直接使用本地镜像,不再重复执行 apk add

一、为什么每次都重新安装

Docker Runner 每次执行 Job 时,都会创建一个新的临时容器。

如果 CI 配置是:

image: alpine:3.20

before_script:
  - apk add --no-cache openssh-client rsync

实际过程就是:

创建全新的 alpine 容器
→ 下载 APK 软件索引
→ 下载 openssh-client、rsync
→ 安装依赖
→ 执行部署
→ 销毁容器

解决思路是:

提前制作一个包含 SSH 和 rsync 的 Docker 镜像(如果你的cicd需要其他包,就换成其他包,这里以ssh和rsync为例)
→ CI 每次直接启动该镜像
→ 不再执行 apk add

二、适用条件

  • GitLab Runner 使用 docker executor。
  • Runner 服务器已经安装 Docker。
  • CI 中通过 image: 指定运行镜像。

如果使用的是 shell executor,image: 不会生效,本文方案不适用。

三、确认 Runner 配置

登录 GitLab Runner 所在服务器:

ssh root@127.0.0.2

检查 Runner:

gitlab-runner list
docker version

检查 /etc/gitlab-runner/config.toml ,Docker Runner 的配置通常类似:

[[runners]]
  name = "test"
  executor = "docker"

  [runners.docker]
    image = "alpine:3.20"
    pull_policy = ["if-not-present"]
    volumes = ["/cache"]

这里最重要的是:

pull_policy = ["if-not-present"]

它表示:

本机有镜像 → 直接使用
本机没有镜像 → 尝试从镜像仓库下载

因此,只需要在 Runner 服务器上提前构建好镜像,CI 就可以直接使用。

四、创建部署基础镜像

1. 建立镜像目录

在 Runner 服务器执行:

mkdir -p /opt/gitlab-ci-images/deploy-alpine
cd /opt/gitlab-ci-images/deploy-alpine

2. 创建 Dockerfile

执行:

vim Dockerfile

进入 Vim 后:

  1. a 进入编辑模式。
  2. 粘贴下面的内容。
  3. Esc
  4. 输入 :wq 并回车。

Dockerfile 内容:

FROM alpine:3.20

RUN apk add --no-cache openssh-client rsync

这个镜像以 alpine:3.20 为基础,并在构建阶段提前安装:

  • openssh-client
  • rsync

3. 构建镜像

执行:

docker build -t local/gitlab-deploy:alpine3.20-v1 .

参数含义:

docker build       构建镜像
-t                 设置镜像名称和版本
local/gitlab-deploy 镜像名称
alpine3.20-v1      镜像版本
.                  使用当前目录的 Dockerfile

4. 检查镜像

docker image inspect local/gitlab-deploy:alpine3.20-v1

查看镜像列表:

docker images | grep gitlab-deploy

5. 验证工具

docker run --rm local/gitlab-deploy:alpine3.20-v1 \
  sh -c 'ssh -V && rsync --version | head -n 1'

预期能够看到 SSH 和 rsync 的版本信息。

五、修改 .gitlab-ci.yml

原来的 CI 配置可能是:

deploy-master:
  stage: deploy
  image: alpine:3.20

  before_script:
    - apk add --no-cache openssh-client rsync
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh

修改为:

deploy-master:
  stage: deploy
-  image: alpine:3.20
+  image: local/gitlab-deploy:alpine3.20-v1

  before_script:
-   - apk add --no-cache openssh-client rsync
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh

完整部署任务可以类似:

stages:
  - deploy

deploy-master:
  stage: deploy
  image: local/gitlab-deploy:alpine3.20-v1

  rules:
    - if: '$CI_COMMIT_BRANCH == "master"'

  resource_group: production-deploy

  variables:
    DEPLOY_HOST: "127.0.0.2"
    DEPLOY_USER: "root"
    DEPLOY_PATH: "/data/my-project"

  before_script:
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - |
      if [ -n "$GITLAB_CICD_ED25519_PRIVATE_KEY_B64" ]; then
        printf '%s' "$GITLAB_CICD_ED25519_PRIVATE_KEY_B64" |
          base64 -d > ~/.ssh/gitlab_cicd_ed25519
      else
        echo "缺少部署私钥"
        exit 1
      fi
    - chmod 600 ~/.ssh/gitlab_cicd_ed25519
    - ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
    - export DEPLOY_SSH="ssh -i ~/.ssh/gitlab_cicd_ed25519 -o IdentitiesOnly=yes"

  script:
    - $DEPLOY_SSH "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p '$DEPLOY_PATH'"
    - |
      rsync -az --delete \
        -e "$DEPLOY_SSH" \
        --exclude='.git/' \
        --exclude='.env' \
        --exclude='node_modules/' \
        ./ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/"

  environment:
    name: production

六、提交并验证

修改 .gitlab-ci.yml 后提交:

git add .gitlab-ci.yml
git commit -m "优化 CI 部署镜像,避免重复安装依赖"
git push

新流水线日志应该变成:

Using Docker executor with image local/gitlab-deploy:alpine3.20-v1
Using locally found image version due to "if-not-present"
Executing "step_script" stage
$ mkdir -p ~/.ssh

不应再出现:

$ apk add --no-cache openssh-client rsync
fetch https://dl-cdn.alpinelinux.org/...
Installing openssh-client...

七、以后如何更新镜像

如果以后还需要安装 curlbashgit 等工具,修改 Dockerfile:

FROM alpine:3.20

RUN apk add --no-cache \
    bash \
    curl \
    git \
    openssh-client \
    rsync

使用新版本标签构建:

docker build -t local/gitlab-deploy:alpine3.20-v2 .

然后更新 .gitlab-ci.yml

image: local/gitlab-deploy:alpine3.20-v2

建议使用 v1v2 等固定版本,不要长期使用 latest。这样可以明确知道流水线使用了哪个镜像,也方便出现问题时回退。