使用 Azure Batch 运行容器工作负载

可以通过 Azure Batch 在 Azure 上运行和缩放大量批处理计算作业。 Batch 任务可直接在 Batch 池中的虚拟机(节点)上运行;但也可以设置一个 Batch 池,以便在节点上的 Docker 兼容容器中运行任务。 本文介绍如何创建支持运行容器任务的计算节点池,然后在池中运行容器任务。

此处的代码示例使用 Batch .NET 和 Python SDK。 也可以使用其他 Batch SDK 和工具,包括 Azure 门户,来创建支持容器的 Batch 池,以及运行容器任务。

为何使用容器?

通过容器可以方便地运行 Batch 任务,无需管理环境和依赖项即可运行应用程序。 容器将应用程序部署为轻量级、可移植、自给自足的单元,可以在各种不同的环境中运行。 例如,在本地构建和测试容器,然后将容器映像上传到 Azure 或其他位置的注册表中。 容器部署模型可确保始终正确安装和配置应用程序的运行时环境,而不考虑在何处托管应用程序。 Batch 中基于容器的任务也可利用非容器任务的功能,包括应用程序包以及资源文件和输出文件的管理。

先决条件

读者应熟悉容器的概念,并知道如何创建 Batch 池和作业。

  • SDK 版本:Batch SDK 支持到以下版本为止的容器映像:

    • Batch REST API 版本 2017-09-01.6.0
    • Batch .NET SDK 版本 8.0.0
    • Batch Python SDK 版本 4.0
    • Batch Java SDK 版本 3.0
    • Batch Node.js SDK 版本 3.0
  • 帐户:在 Azure 订阅中,需要创建 Batch 帐户和 Azure 存储帐户(后者为可选)。

  • 支持的虚拟机 (VM) 映像:容器仅在使用来自受支持映像(下一节列出)的虚拟机配置创建的池中受支持。 如果提供自定义映像,请参阅以下部分所述的注意事项,以及使用托管映像创建自定义映像池中所述的要求。

注意

从 Batch SDK 版本:

  • Batch .NET SDK 版本 16.0.0
  • Batch Python SDK 版本 14.0.0
  • Batch Java SDK 版本 11.0.0
  • Batch Node.js SDK 版本 11.0.0

目前,containerConfiguration 需要传递 Type 属性,支持的值为:ContainerType.DockerCompatibleContainerType.CriCompatible

请记住以下限制:

  • Batch 仅对 Linux 池上运行的容器提供远程直接内存访问 (RDMA) 支持。
  • 对于 Windows 容器工作负载,应为你的池选择多核 VM 大小。

重要

默认情况下,Docker 会创建一个子网规格为 172.17.0.0/16 的网桥。 如果要为池指定虚拟网络,请确保没有冲突的 IP 范围。

支持的 VM 映像

使用以下受支持的 Windows 或 Linux 映像之一,为容器工作负荷创建 VM 计算节点池。 有关与 Batch 兼容的市场映像的详细信息,请参阅虚拟机映像列表

Windows 支持

Batch 支持被指派了容器支持的 Windows Server 映像。 如果映像支持 Docker 容器,则列出 Batch 中支持的所有映像的 API 会指示 DockerCompatible 功能。 Batch 允许但不直接支持 Mirantis 发布的映像,其功能标记为 DockerCompatible。 这些映像只能部署在用户订阅池分配模式 Batch 帐户下。

此外,还可以创建一个自定义映像,以便在 Windows 上启用容器功能。

注意

映像 SKU -with-containers-with-containers-smalldisk 已停用。 有关详细信息和备用容器运行时选项,请参阅公告

Linux 支持

对于 Linux 容器工作负载,Batch 目前支持 Azure 市场中发布的下列 Linux 映像,无需自定义映像。

  • 发布者:microsoft-dsvm
    • 产品/服务:ubuntu-hpc
  • 发布者:almalinux
    • 产品/服务:8-hpc-gen1
    • 产品/服务:8-hpc-gen2

备用映像选项

目前,microsoft-azure-batch 发布了支持容器工作负载的其他映像:

  • 发布者:microsoft-azure-batch
    • 产品/服务:ubuntu-server-container
    • 产品/服务:ubuntu-server-container-rdma(专门用于具有 Infiniband 的 VM SKU)

警告

建议使用非 microsoft-azure-batch 发布的映像,因为它发布的映像即将因生命周期结束而被弃用。

备注

上述映像的 docker 数据根位于不同位置:

  • 对于 Azure Batch 发布的 microsoft-azure-batch 映像(产品/服务:centos-container-rdma 等),Docker 数据根将映射到临时磁盘上的 /mnt/batch/docker。

对于非 Batch 发布的映像,当下载容器映像时,OS 磁盘存在可能会被迅速填满的风险。

客户的可能解决方案

在 BatchExplorer 中创建池时,更改启动任务中的 docker 数据根。 下面是“启动任务”命令的示例:

1)  sudo systemctl stop docker
2)  sudo vi /lib/systemd/system/docker.service
    +++
    FROM:
    ExecStart=/usr/bin/docker daemon -H fd://
    TO:
    ExecStart=/usr/bin/docker daemon -g /new/path/docker -H fd://
    +++
3)  sudo systemctl daemon-reload
4)  sudo systemctl start docker

这些映像只能在 Azure Batch 池中使用,适用于 Docker 容器执行。 这些映像具有以下特性:

  • 预装了与 Docker 兼容的 Moby 容器运行时
  • 预装了 NVIDIA GPU 驱动程序和 NVIDIA 容器运行时,可简化 Azure N 系列 VM 上的部署。
  • 后缀为 -rdma 的 VM 映像已预先配置了对 InfiniBand RDMA VM 大小的支持。 这些 VM 映像不应与没有 InfiniBand 支持的 VM 大小一起使用。

也可以在与 Batch 兼容的 Linux 发行版之一上创建与 Batch 容器兼容的自定义映像。 若要在自定义映像上支持 Docker,请安装合适的 Docker 兼容运行时,例如 Docker 版本或 Mirantis 容器运行时。 仅安装 Docker-CLI 兼容工具是不够的;还需要 Docker 引擎兼容运行时。

重要

Azure 或 Azure Batch 均不会为与 Docker(任何版本)、Mirantis 容器运行时或 Moby 运行时相关的问题提供支持。 选择在其映像中使用这些运行时的客户应联系为运行时问题提供支持的公司或实体。

使用自定义 Linux 映像时的其他注意事项:

  • 若要在使用自定义映像时利用 Azure N 系列大小的 GPU 性能,请预装 NVIDIA 驱动程序。 此外,还需要安装 NVIDIA GPU 的 Docker 引擎实用工具 NVIDIA Docker
  • 若要访问 Azure RDMA 网络,请使用支持 RDMA 的 VM 大小。 Batch 支持的 CentOS HPC 和 Ubuntu 映像中已安装所需的 RDMA 驱动程序。 可能还需要其他配置来运行 MPI 工作负载。 请参阅在 Batch 池中使用 RDMA 或 GPU 实例

批处理池的容器配置

若要启用运行容器工作负荷的 Batch 池,必须在池的 VirtualMachineConfiguration 对象中指定 ContainerConfiguration 设置。 本文提供了 Batch .NET API 参考的链接。 Batch Python API 中提供了相应的设置。

可以创建启用容器的池,可以带或不带预提取容器映像,如以下示例所示。 可以通过拉取(或预提取)过程从 Docker 中心预加载容器映像,或在 Internet 上预加载另一个容器注册表。 为获得最佳性能,请使用 Batch 帐户所在的同一区域中的 Azure 容器注册表

预提取容器映像的优点是,当任务开始运行时,它们不必等待容器映像下载完毕。 容器配置在创建池时将容器映像拉取到 VM。 然后,在池中运行的任务即可引用容器映像和容器运行选项列表。

注意

Docker Hub 限制了映像拉取的数量。 请确保工作负载不会超出为基于 Docker Hub 的映像发布的速率限制。 建议直接使用 Azure 容器注册表或利用

不带预提取容器映像的池

若要配置启用了容器但不带预提取容器映像的池,请定义 ContainerConfigurationVirtualMachineConfiguration 对象,如以下示例所示。 这些示例使用市场中提供的适用于 Azure Batch 容器池映像的 Ubuntu Server。

注意:示例中使用的 Ubuntu 服务器版本仅用于演示目的。 将 node_agent_sku_id 更改为你使用的版本。

image_ref_to_use = batch.models.ImageReference(
    publisher='microsoft-dsvm',
    offer='ubuntu-hpc',
    sku='2204',
    version='latest')

"""
Specify container configuration. This is required even though there are no prefetched images.
"""

container_conf = batch.models.ContainerConfiguration()

new_pool = batch.models.PoolAddParameter(
    id=pool_id,
    virtual_machine_configuration=batch.models.VirtualMachineConfiguration(
        image_reference=image_ref_to_use,
        container_configuration=container_conf,
        node_agent_sku_id='batch.node.ubuntu 22.04'),
    vm_size='STANDARD_D2S_V3',
    target_dedicated_nodes=1)
...
ImageReference imageReference = new ImageReference(
    publisher: "microsoft-dsvm",
    offer: "ubuntu-hpc",
    sku: "2204",
    version: "latest");

// Specify container configuration. This is required even though there are no prefetched images.
ContainerConfiguration containerConfig = new ContainerConfiguration();

// VM configuration
VirtualMachineConfiguration virtualMachineConfiguration = new VirtualMachineConfiguration(
    imageReference: imageReference,
    nodeAgentSkuId: "batch.node.ubuntu 22.04");
virtualMachineConfiguration.ContainerConfiguration = containerConfig;

// Create pool
CloudPool pool = batchClient.PoolOperations.CreatePool(
    poolId: poolId,
    targetDedicatedComputeNodes: 1,
    virtualMachineSize: "STANDARD_D2S_V3",
    virtualMachineConfiguration: virtualMachineConfiguration);

容器配置的预提取映像

若要在池中预提取容器映像,请将容器映像列表(在 Python 中为 container_image_names)添加到 ContainerConfiguration

以下基本 Python 示例演示如何从 Docker 中心预提取一个标准的 Ubuntu 容器映像。

image_ref_to_use = batch.models.ImageReference(
    publisher='microsoft-dsvm',
    offer='ubuntu-hpc',
    sku='2204',
    version='latest')

"""
Specify container configuration, fetching the official Ubuntu container image from Docker Hub.
"""

container_conf = batch.models.ContainerConfiguration(
    container_image_names=['ubuntu'])

new_pool = batch.models.PoolAddParameter(
    id=pool_id,
    virtual_machine_configuration=batch.models.VirtualMachineConfiguration(
        image_reference=image_ref_to_use,
        container_configuration=container_conf,
        node_agent_sku_id='batch.node.ubuntu 22.04'),
    vm_size='STANDARD_D2S_V3',
    target_dedicated_nodes=1)
...

以下 C# 示例假设要从 Docker 中心预提取一个 TensorFlow 映像。 本示例包含一个在池节点的 VM 主机中运行的启动任务。 可以在主机中运行具有特定目的(例如,装载可以从容器访问的文件服务器)的启动任务。

ImageReference imageReference = new ImageReference(
    publisher: "microsoft-dsvm",
    offer: "ubuntu-hpc",
    sku: "2204",
    version: "latest");

ContainerRegistry containerRegistry = new ContainerRegistry(
    registryServer: "https://hub.docker.com",
    userName: "UserName",
    password: "YourPassword"
);

// Specify container configuration, prefetching Docker images
ContainerConfiguration containerConfig = new ContainerConfiguration();
containerConfig.ContainerImageNames = new List<string> { "tensorflow/tensorflow:latest-gpu" };
containerConfig.ContainerRegistries = new List<ContainerRegistry> { containerRegistry };

// VM configuration
VirtualMachineConfiguration virtualMachineConfiguration = new VirtualMachineConfiguration(
    imageReference: imageReference,
    nodeAgentSkuId: "batch.node.ubuntu 22.04");
virtualMachineConfiguration.ContainerConfiguration = containerConfig;

// Set a native host command line start task
StartTask startTaskContainer = new StartTask( commandLine: "<native-host-command-line>" );

// Create pool
CloudPool pool = batchClient.PoolOperations.CreatePool(
    poolId: poolId,
    virtualMachineSize: "Standard_NC6S_V3",
    virtualMachineConfiguration: virtualMachineConfiguration);

// Start the task in the pool
pool.StartTask = startTaskContainer;
...

从专用容器注册表中预提取映像

另外,也可以通过对专有容器注册表服务器进行身份验证来预提取容器映像。 在以下示例中,ContainerConfigurationVirtualMachineConfiguration 对象从专用的 Azure 容器注册表预提取专用的 TensorFlow 映像。 图像中的参考信息与前面的示例相同。

image_ref_to_use = batch.models.ImageReference(
    publisher='microsoft-dsvm',
    offer='ubuntu-hpc',
    sku='2204',
    version='latest')

# Specify a container registry
container_registry = batch.models.ContainerRegistry(
        registry_server="myRegistry.azurecr.cn",
        user_name="myUsername",
        password="myPassword")

# Create container configuration, prefetching Docker images from the container registry
container_conf = batch.models.ContainerConfiguration(
        container_image_names = ["myRegistry.azurecr.cn/samples/myImage"],
        container_registries =[container_registry])

new_pool = batch.models.PoolAddParameter(
            id="myPool",
            virtual_machine_configuration=batch.models.VirtualMachineConfiguration(
                image_reference=image_ref_to_use,
                container_configuration=container_conf,
                node_agent_sku_id='batch.node.ubuntu 22.04'),
            vm_size='STANDARD_D2S_V3',
            target_dedicated_nodes=1)
// Specify a container registry
ContainerRegistry containerRegistry = new ContainerRegistry(
    registryServer: "myContainerRegistry.azurecr.cn",
    userName: "myUserName",
    password: "myPassword");

// Create container configuration, prefetching Docker images from the container registry
ContainerConfiguration containerConfig = new ContainerConfiguration();
containerConfig.ContainerImageNames = new List<string> {
        "myContainerRegistry.azurecr.cn/tensorflow/tensorflow:latest-gpu" };
containerConfig.ContainerRegistries = new List<ContainerRegistry> { containerRegistry } );

// VM configuration
VirtualMachineConfiguration virtualMachineConfiguration = new VirtualMachineConfiguration(
    imageReference: imageReference,
    nodeAgentSkuId: "batch.node.ubuntu 22.04");
virtualMachineConfiguration.ContainerConfiguration = containerConfig;

// Create pool
CloudPool pool = batchClient.PoolOperations.CreatePool(
    poolId: poolId,
    targetDedicatedComputeNodes: 2,
    virtualMachineSize: "Standard_NC6S_V3",
    virtualMachineConfiguration: virtualMachineConfiguration);
...

对 ACR 的托管标识支持

当你访问在 Azure 容器注册表中存储的容器时,可以使用用户名/密码或托管标识通过服务进行身份验证。 若要使用托管标识,请先确保该标识已分配给池,并且该标识已分配有你要访问的容器注册表的 AcrPull 角色。 然后,只需指示 Batch 向 ACR 进行身份验证时要使用的标识。

ContainerRegistry containerRegistry = new ContainerRegistry(
    registryServer: "myContainerRegistry.azurecr.cn",
    identityReference: new ComputeNodeIdentityReference() { ResourceId = "/subscriptions/SUB/resourceGroups/RG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/identity-name" }
);

// Create container configuration, prefetching Docker images from the container registry
ContainerConfiguration containerConfig = new ContainerConfiguration();
containerConfig.ContainerImageNames = new List<string> {
        "myContainerRegistry.azurecr.cn/tensorflow/tensorflow:latest-gpu" };
containerConfig.ContainerRegistries = new List<ContainerRegistry> { containerRegistry } );

// VM configuration
VirtualMachineConfiguration virtualMachineConfiguration = new VirtualMachineConfiguration(
    imageReference: imageReference,
    nodeAgentSkuId: "batch.node.ubuntu 22.04");
virtualMachineConfiguration.ContainerConfiguration = containerConfig;

// Create pool
CloudPool pool = batchClient.PoolOperations.CreatePool(
    poolId: poolId,
    targetDedicatedComputeNodes: 2,
    virtualMachineSize: "Standard_NC6S_V3",
    virtualMachineConfiguration: virtualMachineConfiguration);
...

任务容器设置

若要在启用了容器的池上运行容器任务,请指定特定于容器的设置。 设置包括要使用的映像、注册表和容器运行选项。

  • 使用任务类中的 ContainerSettings 属性来配置特定于容器的设置。 这些设置由 TaskContainerSettings 类定义。 --rm 容器选项不需要其他 --runtime 选项,因为它由 Batch 处理。

  • 如果在容器映像上运行任务,云任务作业管理器任务将需要容器设置。 但是,启动任务作业准备任务作业发布任务都不需要容器设置(即它们可以在容器上下文中或直接在节点上运行)。

  • 对于 Linux,Batch 会将用户/组权限映射到容器。 如果访问容器中的任何文件夹都需要管理员权限,则可能需要以管理员提升级别在池范围内运行任务。 这可确保 Batch 在容器上下文中以 root 身份运行任务。 否则,非管理员用户可能无权访问这些文件夹。

  • 对于启用了 GPU 的硬件的容器池,Batch 会自动为容器任务启用 GPU,因此不应包含 -gpus 参数。

容器任务命令行

运行容器任务时,Batch 自动使用 docker create 命令通过任务中指定的映像创建容器。 之后,Batch 会控制容器中的任务执行。

与非容器 Batch 任务一样,为容器任务设置命令行。 由于 Batch 自动创建容器,因此命令行仅指定会在容器中运行的命令。

以下是 Batch 应用于 Docker 容器任务的默认行为:

确保查看 ENTRYPOINT 和 CMD 之间的 Docker 文档,以便了解当容器映像具有指定的 ENTRYPOINT 并且你还指定了任务命令行时可能产生的交互影响。

如果想替代容器映像 ENTRYPOINT,可以将 --entrypoint <args> 参数指定为 containerRunOption。 请参阅可选的 ContainerRunOptions,以了解你可以为 Batch 用来创建和运行容器的 docker create 命令提供的参数。 例如,若要为容器设置工作目录,请设置 --workdir <directory> 选项。

下面是容器映像和 Batch 容器选项或任务命令行及其效果的一些示例:

  • 未指定容器映像 ENTRYPOINT,Batch 任务命令行是“/bin/sh -c python myscript.py”。
    • Batch 使用指定的 Batch 任务命令行创建容器,并在 Batch 任务工作目录中运行它。 如果“myscript.py”不在 Batch 任务工作目录中,这可能会导致失败。
    • 如果任务命令行指定为“/bin/sh -c python /path/to/script/myscript.py”,那么即使将工作目录设置为 Batch 任务工作目录,只要脚本的所有依赖项都满足,该任务也可以正常运行。
  • 容器映像 ENTRYPOINT 指定为“./myscript.sh”,Batch 任务命令行为空。
    • Batch 依赖于 ENTRYPOINT 创建容器,并在 Batch 任务工作目录中运行它。 如果容器映像 WORKDIR 与 Batch 任务工作目录不一致(这取决于操作系统、作业 ID、任务 ID 等各种因素),则此任务可能会失败。
    • 如果将“--workdir /path/to/script”指定为 containerRunOption,那么如果脚本的所有依赖项都已满足,该任务可以正常运行。
  • 未指定容器映像 ENTRYPOINT,Batch 任务命令行是“./myscript.sh”,且 ContainerRunOptions 中的 WORKDIR 被替代为“--workdir /path/to/script”。
    • Batch 创建工作目录为“/path/to/script”的容器,并执行命令行“./myscript.sh”,由于在指定的工作目录中找到了脚本,因此执行成功。

容器任务工作目录

Batch 容器任务在容器的工作目录中执行,该目录与 Batch 为常规(非容器)任务设置的目录相似。 此工作目录与映像中的 WORKDIR(如果配置)或默认容器工作目录(Windows 容器上的 C:\ 或 Linux 容器上的 /)不同。

对于 Batch 容器任务:

  • 以递归方式位于主机节点上的 AZ_BATCH_NODE_ROOT_DIR(Azure Batch 目录的根)下的所有目录都映射到容器中。
  • 所有任务环境变量都映射到该容器。
  • 节点上的任务工作目录 AZ_BATCH_TASK_WORKING_DIR 设置为与常规任务相同并映射到容器中。

重要

对于具有临时磁盘的 VM 系列上的 Windows 容器池,由于 Windows 容器限制,整个临时磁盘都会映射到容器空间。

这些映射允许以与处理非容器任务大致相同的方式处理容器任务。 例如,使用应用程序包安装应用程序,从 Azure 存储访问资源文件,使用任务环境设置,以及在容器停止后保留任务输出文件。

无论容器映像的 WORKDIR 如何设置,stdout.txtstderr.txt 都会被捕获到 AZ_BATCH_TASK_DIR 中。

对容器任务进行故障排除

如果容器任务未按预期运行,则可能需要获取有关容器映像的 WORKDIR 或 ENTRYPOINT 配置的信息。 若要查看配置,请运行 docker image inspect 命令。

如果需要,请根据映像调整容器任务的设置:

  • 在任务命令行中指定绝对路径。 如果映像的默认 ENTRYPOINT 用于任务命令行,请确保设置了绝对路径。
  • 在任务的容器运行选项中,更改工作目录以匹配映像中的 WORKDIR。 例如,设置 --workdir /app

容器任务示例

以下 Python 代码片段展示了根据从 Docker 中心拉取的虚构映像创建的容器中运行的基本命令行。 此处,--rm 容器选项用于在任务完成后删除容器,--workdir 选项用于设置工作目录。 该命令行使用一个简单的 shell 命令(可将小文件写入主机上的任务工作目录)覆盖容器 ENTRYPOINT。

task_id = 'sampletask'
task_container_settings = batch.models.TaskContainerSettings(
    image_name='myimage',
    container_run_options='--rm --workdir /')
task = batch.models.TaskAddParameter(
    id=task_id,
    command_line='/bin/sh -c \"echo \'hello world\' > $AZ_BATCH_TASK_WORKING_DIR/output.txt\"',
    container_settings=task_container_settings
)

以下 C# 示例演示某个云任务的基本容器设置:

// Simple container task command
string cmdLine = "c:\\app\\myApp.exe";

TaskContainerSettings cmdContainerSettings = new TaskContainerSettings (
    imageName: "myimage",
    containerRunOptions: "--rm --workdir c:\\app"
    );

CloudTask containerTask = new CloudTask (
    id: "Task1",
    commandline: cmdLine);
containerTask.ContainerSettings = cmdContainerSettings;

后续步骤