Trabajos de contenedor en canalizaciones YAML

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

En este artículo se explican los trabajos de contenedor en Azure Pipelines.

De forma predeterminada, los trabajos de Azure Pipelines se ejecutan directamente en los equipos host en los que está instalado el agente. Los trabajos del agente hospedado son cómodos, requieren poca configuración inicial e infraestructura para mantener y son adecuados para proyectos básicos.

Si desea tener más control sobre el contexto de la tarea, puede definir y ejecutar trabajos en contenedores. Los contenedores son una abstracción ligera sobre el sistema operativo host que proporciona aislamiento del host. Al ejecutar trabajos en contenedores, puede seleccionar las versiones exactas de los sistemas operativos, las herramientas y las dependencias que requiere la compilación.

Los agentes de Linux y Windows pueden ejecutar trabajos de canalización directamente en el host o en contenedores. Los trabajos de contenedor no están disponibles en macOS.

En el caso de un trabajo de contenedor, el agente primero captura e inicia el contenedor. A continuación, cada paso del trabajo se ejecuta dentro del contenedor.

Si necesita un control específico a nivel de paso de compilación individual, los destinos de paso le permiten elegir el contenedor o el host de cada paso.

Requisitos previos

  • Use una canalización YAML. Las canalizaciones clásicas no admiten trabajos de contenedor.
  • Use un agente de Windows o Ubuntu hospedado. Solo los agentes de windows-* y ubuntu-* admiten la ejecución de contenedores. Los agentes macos-* no admiten la ejecución de contenedores.
  • El agente está configurado para trabajos de contenedor.
    • Los agentes de Windows y Linux deben tener Docker instalado y necesitan permiso para acceder al demonio de Docker.
    • No se admiten contenedores cuando el agente ya se ejecuta dentro de un contenedor. No se pueden tener contenedores anidados.

Requisitos de contenedor adicionales

Los contenedores basados en Linux tienen los siguientes requisitos. Para obtener soluciones alternativas, consulte Contenedores no basados en glibc.

  • Bash instalado
  • Basado en biblioteca GNU C (glibc)
  • No ENTRYPOINT
  • Proporciona USER con acceso a groupadd y otros comandos con privilegios sin usar sudo
  • Puede ejecutar Node.js, que proporciona el agente

    Nota:

    En el caso de los contenedores de Linux basados en hosts de Windows, Node.js debe estar preinstalado.

Algunos de los contenedores eliminados disponibles en Docker Hub, especialmente aquellos basados en Alpine Linux, no cumplen estos requisitos. Es posible que los contenedores con un ENTRYPOINT no funcionen porque Azure Pipelines docker create y docker exec esperan que el contenedor esté siempre en funcionamiento.

Ejemplos de trabajos únicos

En los ejemplos siguientes se define un contenedor de Windows o Linux para un solo trabajo.

En el ejemplo sencillo siguiente se define un contenedor de Linux:

pool:
  vmImage: 'ubuntu-latest'

container: ubuntu:18.04

steps:
- script: printenv

El ejemplo anterior indica al sistema que capture la imagen de ubuntu etiquetada como 18.04 de Docker Hub y, que, luego, inicie el contenedor. El comando printenv se ejecuta dentro del contenedor ubuntu:18.04.

Varios trabajos

Puede usar contenedores para ejecutar el mismo paso en varios trabajos. En el ejemplo siguiente se ejecuta el mismo paso en varias versiones de Ubuntu Linux. No es necesario mencionar la palabra clave jobs porque solo se define un solo trabajo.

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

Varios trabajos con grupos de agentes en un único host de agente

Un trabajo de contenedor usa el archivo de configuración de Docker del agente host subyacente para la autorización del registro de imágenes. Este archivo cierra la sesión al final de la inicialización del contenedor del registro de Docker. Es posible que se deniegue la unauthorized authentication de extracciones de imágenes del Registro para los trabajos de contenedor posteriores porque otro trabajo que se ejecuta en paralelo ya ha cerrado el archivo de configuración de Docker.

La solución consiste en establecer una variable de entorno DOCKER_CONFIG de Docker específica de cada grupo de agentes que se ejecuta en el agente hospedado. Exporte DOCKER_CONFIG en cada script runsvc.sh de cada grupo de agentes como se indica:

export DOCKER_CONFIG=./.docker

Opciones de inicio

Puede especificar options para controlar el inicio del contenedor, como en el ejemplo siguiente:

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

steps:
- script: echo hello

La ejecución de docker create --help proporciona la lista de opciones que puede pasar a la invocación de Docker. No está garantizado que todas estas opciones funcionen con Azure DevOps. Compruebe primero si puede usar una propiedad container para lograr el mismo objetivo.

Para obtener más información, consulte la referencia del comando docker create y la definición resources.containers.container en la referencia de esquema YAML de Azure DevOps.

Definición de contenedor reutilizable

En el ejemplo siguiente se definen los contenedores de la sección resources y, a continuación, se hace referencia a ellos por sus alias asignados. Por claridad, aquí se muestra explícitamente la palabra clave 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

Puntos de conexión del servicio

Puede hospedar contenedores en otros registros distintos de los registros de Docker Hub públicos. Para hospedar una imagen en Azure Container Registry u otro registro de contenedor privado, incluido un registro de Docker Hub privado, agregue una conexión de servicio para acceder al registro. A continuación, puede hacer referencia al punto de conexión en la definición del contenedor.

Conexión privada de Docker Hub:

container:
  image: registry:ubuntu1804
  endpoint: private_dockerhub_connection

Conexión de Azure Container Registry:

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

Nota:

Azure Pipelines no puede configurar una conexión de servicio para Amazon Elastic Container Registry (ECR), ya que Amazon ECR requiere otras herramientas de cliente para convertir credenciales de AWS en algo que Docker puede usar para autenticarse.

Contenedores no basados en glibc

El agente de Azure Pipelines proporciona una copia de Node.js, que es necesaria para ejecutar tareas y scripts. Para obtener información sobre la versión de Node.js de un agente hospedado, consulte Agentes hospedados por Microsoft.

La versión de Node.js se compila en el tiempo de ejecución de C que se usa en la nube hospedada, normalmente glibc. Algunas variantes de Linux usan otros entornos de ejecución de C. Por ejemplo, Alpine Linux usa musl.

Si desea usar un contenedor no basado en glibc, debe hacer lo siguiente:

  • Proporcione su propia copia de Node.js.
  • Agregue una etiqueta a la imagen que indique al agente dónde encontrar el archivo binario Node.js.
  • Proporcione otras dependencias de las que depende Azure Pipelines: bash, sudo, which y groupadd.

Proporcione su propio Node.js

Si usa un contenedor no basado en glibc, es responsable de agregar un archivo binario de nodo al contenedor. Node.js 18 es una opción segura. Empiece por la imagen node:18-alpine.

Informar al agente sobre Node.js

El agente lee la etiqueta de contenedor "com.azure.dev.pipelines.handler.node.path". Si esta etiqueta existe, debe ser la ruta de acceso al archivo binario de Node.js.

Por ejemplo, en una imagen basada en node:18-alpine, agregue la línea siguiente al Dockerfile:

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

Adición de paquetes necesarios

Azure Pipelines da por hecho un sistema basado en Bash con paquetes administrativos comunes instalados. Alpine Linux en particular no incluye varios de los paquetes necesarios. Instale bash, sudo y shadow para cubrir las necesidades básicas.

RUN apk add bash sudo shadow

Si depende de cualquier tarea integrada o de Marketplace, proporcione también los archivos binarios que estos requieran.

Ejemplo de Dockerfile completo

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" ]