CI系统中的Docker标记策略(GitLab)

Posted

技术标签:

【中文标题】CI系统中的Docker标记策略(GitLab)【英文标题】:Docker tagging strategy in CI systems (GitLab) 【发布时间】:2021-12-31 09:41:34 【问题描述】:

我们正在使用 GitLab,因此可能会有一些特定于该平台的答案,但我认为这个问题在其他 CI 系统上也同样相关。

在我们的 CI 系统中,我们有一个步骤来构建 Docker 映像。实际上我们有两个不同的工作:

第一个作业将构建一个包含所有外部依赖项的基础映像。此作业仅在 Dockerfile 已更改时才会运行。这很少见。

然后我们有一些使用最新版本的基本映像的测试作业。

如果这也通过了,我们还有第二个工作是使用最新的基础镜像来创建一个已经包含我们的源代码以及需要编译的代码的编译版本的镜像。到目前为止,我们将所有内容都标记为“最新”,但这不是一个好的解决方案。我想知道是否有推荐的方法来标记图像。特别是考虑到我们也开始使用分支和合并请求(GitHub 中的拉取请求),所以我们必须决定如何标记可能已经在分支中创建的图像以及在合并请求期间使用什么?

对此是否有“最佳实践”或有几个“最佳实践”?

【问题讨论】:

我不完全确定这是否是最佳实践,但我们使用短提交 ID (git rev-parse --short HEAD) 标记图像。根据正在运行的作业,标签以 ci_ 为前缀(任何短暂的并在一段时间后自动删除,例如 MR-images)、develop_(develop 的构建)、staging_(符合条件的图像运行 E2E-Tes​​ts,主要是候选版本)和prod_(生产分支的构建)。 @Turing85 谢谢。您是否将这些图像用于单元/集成测试?发送 PR 时使用哪张图片? 单元/集成测试在管道中运行(Java 应用程序/maven,不需要额外的基础设施)。对于 E2E-Tes​​ts,我们使用staging_ 镜像在 K8s 集群中部署类似于生产环境的环境。如果您需要启动一些基础设施进行集成测试,您可以使用ci_ - 图像。但这意味着您需要为每个 MR 部署基础架构,可能是并行的,并在测试通过后销毁它。 当前日期、短提交 ID 或长提交 ID 或 CI 系统构建号都是合理的选择。在我的日常工作中,我们在构建 Docker 映像之前对每个 PR 运行单元测试,然后将每个 PR 部署到测试环境中以运行集成测试。除了“不要使用latest 或其他固定名称”之外,我认为没有单一的“最佳实践”本身 【参考方案1】:

根据您的问题,我尝试简要解释一下我们如何使用 docker 标记以及如何从管道中引用正确的图像。我们在这方面遇到了很多问题,因为我们使用了多个注册表(包括 GitLab 自己的)。每个环境都必须创建自定义标签并将图像推送到不同的注册表。为了解决所有问题,我们执行了以下操作(希望这些信息对您有所帮助!):

使用 GitLab 变量文件来区分不同环境和注册表的变量 使用 Git 提交 SHA 标记 Docker 映像 在部署文件中使用变量字符串 通过 bash 或节点脚本插入变量

使用 GitLab 变量文件

我们总是使用 3 个文件作为管道:

.gitlab-ci-vars.yml .gitlab-ci-jobs.yml .gitlab-ci.yml

我们使用它是因为我们可以在每个环境中拥有不同的 docker 注册表。因此,如果我们愿意,现在我们可以轻松地定位我们的 dev、acc 和 prod 注册表。这样可以很好地将 dev、acc 和 prod 图像分开。

例子.gitlab-ci-vars.yml:

# .gitlab-ci-vars.yml

variables:
  FORCE_COLOR: 1
  NODE_ENV: dev
  CI_DOCKER_NAME: $CI_REGISTRY_IMAGE

.vars-dev:
  variables:
    NODE_ENV: dev
    REGISTRY: my.dev.registry
    IMAGE_NAME: $NODE_ENV-api:$CI_COMMIT_SHA

.vars-acc:
  variables:
    NODE_ENV: acc
    REGISTRY: my.acc.registry
    IMAGE_NAME: $NODE_ENV-api:$CI_COMMIT_SHA

.vars-prod:
  variables:
    NODE_ENV: prod
    REGISTRY: my.prod.registry
    IMAGE_NAME: $NODE_ENV-api:$CI_COMMIT_SHA

.gitlab-jobs.yml 中可重用作业的示例:

# .gitlab-jobs.yml

.publish_gitlab:
  stage: publish
    image: docker
    services:
      - name: docker:dind
        alias: docker
    before_script:
      - echo -n $CI_JOB_TOKEN | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
    script:
      - docker build -t $REGISTRY/$IMAGE_NAME .
      - docker push $REGISTRY/$IMAGE_NAME

我们现在可以从.gitlab-ci.yml 调用它,如下所示:

include:
  - ".gitlab-ci-vars.yml"
  - ".gitlab-ci-jobs.yml"

publish:image:dev:
  extends: .publish_gitlab
  # target dev env vars
  variables: !reference [.vars-dev, variables]
  resource_group: dev
  environment:
    name: dev
  only:
    - develop

publish:image:acc:
  extends: .publish_gitlab
  variables: !reference [.vars-acc, variables]
  resource_group: acc
  environment:
    name: acc
  only:
    - /^release.*$/

publish:image:prod:
  extends: .publish_gitlab
  variables: !reference [.vars-prod, variables]
  resource_group: prod
  environment:
    name: prod
  only:
    - master

在部署文件中使用变量字符串

由于标签是随机的(Git SHA),您不知道确切的名称。因此,我们在 K8s 或任何其他文件中使用我们需要使用标签来定位我们的新 docker 映像,我们使用 nodejs 脚​​本插入的字符串。在这种情况下,我们始终以正确的图像为目标,并且永远不会与其他标签发生冲突。

示例 k8s 文件:

apiVersion: apps/v1
...
    spec:
      containers:
        - name: my-image-name
          # target the name we defined in .gitlab-ci-vars.yml
          image: __REPO__/__IMAGE_NAME__
          ports:
            - containerPort: __CONTAINER_PORT__
          env:
            - name: NODE_ENV
              value: __NODE_ENV__

我们现在通过在应用部署(或运行测试或其他任何方式)之前覆盖它们来配置所有这些变量。

const fs = require('fs');
const path = require('path');

const 
  NODE_ENV,
  REGISTRY,
  IMAGE_NAME,
 = process.env

const DEFAULT_PROVISION_MAP = 
  __REPO__: REGISTRY,
  __IMAGE_NAME__: IMAGE_NAME,
  __CONTAINER_PORT__: 3000,
  __NODE_ENV__: NODE_ENV,


async function provisionK8sFiles() 
  const K8S_TEMPLATE_DIR = 'k8s';
  const DIR = path.join('gitlab', K8S_TEMPLATE_DIR);
  const K8S_FILES = await fs.promises.readdir(DIR);

  const MAPPING = 
    ...DEFAULT_PROVISION_MAP,
  

  for (const file of K8S_FILES) 
    const filePath = path.join(DIR, file);

    let content = await fs.promises.readFile(filePath, 
      encoding: 'utf-8'
    );

    content = Object.keys(MAPPING).reduce((acc, curr) => 
      return acc.replace(new RegExp(curr, 'g'), MAPPING[curr])
    , content)

    fs.writeFileSync(filePath, content, 'utf8')
  


provisionK8sFiles()

为了更新管道中的所有变量,我们使用before_script 来定位正确的图像。

  before_script:
    - node ./bin/provision-k8s.js

结论

尝试使一切动态化,并通过 bash 或节点脚本插入正确的图像和 sha。

【讨论】:

以上是关于CI系统中的Docker标记策略(GitLab)的主要内容,如果未能解决你的问题,请参考以下文章

我需要通过 gitlab-ci 中的 ssh 将 env 变量传递给 docker

如何将存储库中的文件复制到用于作业的 Docker 容器中,在 gitlab-ci.yml

.gitlab-ci.yml配置参数

Linux系统:第十五章:gitlab集成ci

Linux系统:第十五章:gitlab集成CI

GitLab CI 对 Docker 构建作业的无效参数