YAML 管道中的容器作业

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

本文介绍了 Azure Pipelines 中的容器作业。

默认情况下,Azure Pipelines 作业会直接在安装了代理的主机上运行。 托管代理作业很方便,几乎不需要初始设置和基础结构维护,非常适合基本项目。

要想更好地控制任务上下文,则可以在容器中定义和运行作业。 容器是主机操作系统上的轻型抽象,可提供与主机的隔离。 在容器中运行作业时,可以选择生成所需的操作系统、工具和依赖项的精确版本。

Linux 和 Windows 代理可以直接在主机或容器中运行管道作业。 容器作业在 macOS 上不可用。

对于容器作业,代理会首先提取并启动容器。 然后,作业的每一步都在容器内运行。

如果需要在单个生成步骤级别进行精细控制,则步骤目标允许你为每个步骤选择容器或主机。

先决条件

  • 使用 YAML 管道。 经典管道不支持容器作业。
  • 使用托管的 Windows 或 Ubuntu 代理。 仅 windows-*ubuntu-* 代理映像支持运行容器。 macos-* 代理不支持运行容器。
  • 代理是为容器作业设置的。
    • Windows 和 Linux 代理必须安装 Docker,并需要访问 Docker 守护进程的权限。
    • 当代理已在容器中运行时,不支持容器。 不能有嵌套容器。

其他容器要求

基于 Linux 的容器有以下要求。 有关解决方法,请参阅基于 Nonglibc 的容器

  • 已安装 Bash
  • 基于 GNU C 库 (glibc)
  • ENTRYPOINT
  • USER 提供对 groupadd 和其他特权命令的访问权限,而无需使用 sudo
  • 可以运行代理提供的 Node.js

    注意

    Windows 主机上的 Linux 容器必须预装 Node.js。

Docker Hub 上的一些精简版容器,尤其是基于 Alpine Linux 的容器,并不满足这些要求。 带有 ENTRYPOINT 的容器可能无法运行,因为 Azure Pipelines docker createdocker exec 希望容器始终处于运行状态。

单个作业示例

以下示例为单个作业定义了 Windows 或 Linux 容器。

以下简单示例定义了一个 Linux 容器:

pool:
  vmImage: 'ubuntu-latest'

container: ubuntu:18.04

steps:
- script: printenv

上例会告知系统从 Docker Hub 获取标记为 18.04ubuntu 映像,然后启动容器。 printenv 命令在 ubuntu:18.04 容器内运行。

多个作业

可以使用容器在多个作业中运行同一个步骤。 下面的示例在多个版本的 Ubuntu Linux 中运行相同的步骤。 无需提及 jobs 关键字,因为只定义了一个作业。

pool:
  vmImage: 'ubuntu-latest'

strategy:
  matrix:
    ubuntu16:
      containerImage: ubuntu:16.04
    ubuntu18:
      containerImage: ubuntu:18.04
    ubuntu20:
      containerImage: ubuntu:20.04

container: $[ variables['containerImage'] ]

steps:
- script: printenv

单个代理主机上具有代理池的多个作业

容器作业使用基础主机代理的 Docker 配置文件进行映像注册表授权。 此文件会在 Docker 注册表容器初始化结束时注销。 由于并行运行的另一个作业已经注销了 Docker 配置文件,因此后续容器作业的注册表镜像拉取可能会为 unauthorized authentication 拒绝。

解决方法是设置一个 Docker 环境变量 DOCKER_CONFIG,该变量针对在托管代理上运行的每个代理池。 导出每个代理池的 runsvc.sh 脚本中的 DOCKER_CONFIG,如下所示:

export DOCKER_CONFIG=./.docker

启动选项

可以指定 options 来控制容器的启动,如下例所示:

container:
  image: ubuntu:18.04
  options: --hostname container-test --ip 192.168.0.1

steps:
- script: echo hello

运行 docker create --help 就会看到可以传递给 Docker 调用的选项列表。 并非所有这些选项都能保证与 Azure DevOps 配合使用。 首先检查是否可以使用 container 属性来实现同样的目标。

有关详细信息,请参阅 Azure DevOps YAML 模式参考中的 docker create 命令参考和 resources.containers.container 定义

可重用容器定义

以下示例在 resources 部分中定义了容器,然后用指定的别名来引用它们。 为明确起见,明确列出了 jobs 关键字。

resources:
  containers:
  - container: u16
    image: ubuntu:16.04

  - container: u18
    image: ubuntu:18.04

  - container: u20
    image: ubuntu:20.04

jobs:
- job: RunInContainer
  pool:
    vmImage: 'ubuntu-latest'

  strategy:
    matrix:
      ubuntu16:
        containerResource: u16
      ubuntu18:
        containerResource: u18
      ubuntu20:
        containerResource: u20

  container: $[ variables['containerResource'] ]

  steps:
  - script: printenv

服务终结点

可以在公共 Docker Hub 以外的其他注册表托管容器。 要在 Azure 容器注册表或其他私有容器注册表(包括专用 Docker Hub 注册表)上托管映像,请添加服务连接以访问注册表。 然后,可以在容器定义中引用终结点。

专用 Docker Hub 连接:

container:
  image: registry:ubuntu1804
  endpoint: private_dockerhub_connection

Azure 容器注册表连接:

container:
  image: myprivate.azurecr.io/windowsservercore:1803
  endpoint: my_acr_connection

注意

Azure Pipelines 无法为 Amazon Elastic Container Registry (ECR) 设置服务连接,因为 Amazon ECR 需要其他客户端工具将 AWS 凭据转换为 Docker 可以用来进行身份验证的内容。

基于 Nonglibc 的容器

Azure Pipelines 代理提供 Node.js 副本,这是运行任务和脚本所必需的。 要了解托管代理的 Node.js 版本,请参阅 Microsoft 托管代理

Node.js 的版本根据托管云中使用的 C 运行时(通常是 glibc)进行编译。 有些 Linux 变体使用其他 C 语言运行时。 例如,Alpine Linux 使用 musl。

如果要使用基于 nonglibc 的容器,则需要:

  • 提供自己的 Node.js 副本。
  • 在图片上添加一个标签,告诉代理在哪里可以找到 Node.js 二进制文件。
  • 提供 Azure Pipelines 依赖的其他依赖项:bashsudowhichgroupadd

提供自己的 Node.js

如果使用基于 nonglibc 的容器,则需要在容器中添加 Node 二进制文件。 Node.js 18 是一个安全的选择。 从 node:18-alpine 图像开始。

告知代理有关 Node.js 的信息

代理会读取容器标签 "com.azure.dev.pipelines.handler.node.path"。 如果此标签存在,则它必须是指向 Node.js 二进制文件的路径。

例如,在基于 node:18-alpine 的映像中,在 Dockerfile 中添加以下一行:

LABEL "com.azure.dev.pipelines.agent.handler.node.path"="/usr/local/bin/node"

添加所需的包

Azure Pipelines 假定使用基于 Bash 的系统,并安装了常用管理软件包。 需要特别指出的是,Alpine Linux 未附带所需的多个包。 安装 bashsudoshadow 以满足基本需求。

RUN apk add bash sudo shadow

如果你依赖于任何内置任务或市场任务,则还要提供它们所需的二进制文件。

完整 Dockerfile 示例

FROM node:18-alpine

RUN apk add --no-cache --virtual .pipeline-deps readline linux-pam \
  && apk add bash sudo shadow \
  && apk del .pipeline-deps

LABEL "com.azure.dev.pipelines.agent.handler.node.path"="/usr/local/bin/node"

CMD [ "node" ]