Contenedores de servicio

Azure DevOps Services

Si la canalización requiere la compatibilidad de uno o varios servicios, en muchos casos querrá crear, conectar y limpiar cada servicio por trabajo. Por ejemplo, una canalización puede ejecutar pruebas de integración que requieran acceso a una base de datos y una memoria caché. La base de datos y la memoria caché deben crearse de nuevo para cada trabajo de la canalización.

Un contenedor proporciona una manera sencilla y portátil de ejecutar un servicio del que depende la canalización. Un contenedor de servicio le permite crear, conectar en red y administrar automáticamente el ciclo de vida del servicio de contenedor. Solo el trabajo que lo requiere puede acceder a cada contenedor de servicios. Los contenedores de servicio funcionan con cualquier tipo de trabajo, pero se usan con mayor frecuencia con trabajos de contenedor.

Requisitos

Los contenedores de servicio deben definir CMD o ENTRYPOINT. La canalización ejecutará docker run para el contenedor proporcionado sin argumentos adicionales.

Azure Pipelines puede ejecutar contenedores Windows o Linux. Use Ubuntu hospedado para contenedores Linux o el grupo de contenedores Windows hospedados para contenedores de Windows. (El grupo de macOS hospedado no admite la ejecución de contenedores).

Trabajo en un contenedor único

Un ejemplo sencillo de uso de trabajos de contenedor:

resources:
  containers:
  - container: my_container
    image: buildpack-deps:focal
  - container: nginx
    image: nginx


pool:
  vmImage: 'ubuntu-latest'

container: my_container
services:
  nginx: nginx

steps:
- script: |
    curl nginx
  displayName: Show that nginx is running

Esta canalización captura los contenedores nginx y buildpack-deps de Docker Hub y, a continuación, los inicia. Los contenedores están conectados en red para que puedan comunicarse entre sí por su nombre services.

Desde dentro de este contenedor de trabajo, el nombre de host nginx se resuelve en los servicios correctos mediante redes de Docker. Todos los contenedores de la red exponen automáticamente todos los puertos entre sí.

Trabajo individual

También puede usar contenedores de servicio sin un contenedor de trabajo. Un ejemplo sencillo:

resources:
  containers:
  - container: nginx
    image: nginx
    ports:
    - 8080:80
    env:
      NGINX_PORT: 80
  - container: redis
    image: redis
    ports:
    - 6379

pool:
  vmImage: 'ubuntu-latest'

services:
  nginx: nginx
  redis: redis

steps:
- script: |
    curl localhost:8080
    echo $AGENT_SERVICES_REDIS_PORTS_6379

Esta canalización inicia los contenedores nginx más recientes. Dado que el trabajo no se está ejecutando en un contenedor, no hay ninguna resolución de nombres automática. En este ejemplo se muestra cómo puede llegar a los servicios mediante localhost. En el ejemplo anterior, se proporciona explícitamente el puerto (por ejemplo, 8080:80).

Un enfoque alternativo consiste en permitir que un puerto aleatorio se asigne dinámicamente en tiempo de ejecución. A continuación, puede acceder a estos puertos dinámicos mediante variables. En un script de Bash, puede acceder a una variable mediante el entorno de proceso. Estas variables tienen el formato: agent.services.<serviceName>.ports.<port>. En el ejemplo anterior, redis se asigna un puerto disponible aleatorio en el host. La variable agent.services.redis.ports.6379 contiene el número de puerto.

Varios trabajos

Los contenedores de servicio también son útiles para ejecutar los mismos pasos en varias versiones del mismo servicio. En el siguiente ejemplo, los mismos pasos se ejecutan en varias versiones de PostgreSQL.

resources:
  containers:
  - container: my_container
    image: ubuntu:22.04
  - container: pg15
    image: postgres:15
  - container: pg14
    image: postgres:14

pool:
  vmImage: 'ubuntu-latest'

strategy:
  matrix:
    postgres15:
      postgresService: pg15
    postgres14:
      postgresService: pg14

container: my_container

services:
  postgres: $[ variables['postgresService'] ]
steps:
- script: printenv

Puertos

Al especificar un recurso de contenedor o un contenedor alineado, puede especificar una matriz de ports para exponer en el contenedor.

resources:
  containers:
  - container: my_service
    image: my_service:latest
    ports:
    - 8080:80
    - 5432

services:
  redis:
    image: redis
    ports:
    - 6379/tcp

No es necesario especificar ports si el trabajo se ejecuta en un contenedor porque los contenedores de la misma red de Docker exponen automáticamente todos los puertos entre sí de forma predeterminada.

Si el trabajo se ejecuta en el host, entonces ports es necesario para acceder al servicio. Un puerto toma el formato <hostPort>:<containerPort> o simplemente <containerPort>, con un /<protocol> opcional al final, por ejemplo 6379/tcp para exponer tcp a través del puerto 6379, enlazado a un puerto aleatorio en el equipo host.

Para los puertos enlazados a un puerto aleatorio en el equipo host, la canalización crea una variable del formato agent.services.<serviceName>.ports.<port> para que el trabajo pueda acceder a él. Por ejemplo, agent.services.redis.ports.6379 se resuelve en el puerto asignado aleatoriamente en el equipo host.

Volúmenes

Los volúmenes son útiles para compartir datos entre servicios o para conservar datos entre varias ejecuciones de un trabajo.

Puede especificar montajes de volumen como una matriz de volumes. Los volúmenes pueden ser volúmenes Docker con nombre, anónimos o montajes de enlace en el host.

services:
  my_service:
    image: myservice:latest
    volumes:
    - mydockervolume:/data/dir
    - /data/dir
    - /src/dir:/dst/dir

Los volúmenes tienen el formato <source>:<destinationPath>, donde <source> puede ser un volumen con nombre o una ruta de acceso absoluta en el equipo host y <destinationPath> es una ruta de acceso absoluta en el contenedor.

Nota:

Si usa nuestros grupos hospedados, los volúmenes no se conservarán entre los trabajos porque el equipo host se limpia una vez completado el trabajo.

Otras opciones

Los contenedores de servicio comparten los mismos recursos de contenedor que los trabajos de contenedor. Esto significa que puede usar las mismas opciones adicionales.

Healthcheck

Si lo desea, si algún contenedor de servicios especifica un HEALTHCHECK, el agente espera hasta que el contenedor esté en buen estado antes de ejecutar el trabajo.

Ejemplo de varios contenedores con servicios

En este ejemplo, hay un contenedor web de Django de Python conectado a dos contenedores de base de datos: PostgreSQL y MySQL. La base de datos PostgreSQL es la base de datos principal y su contenedor tiene el nombre db. El contenedor db usa el volumen /data/db:/var/lib/postgresql/data y hay tres variables de base de datos que se pasan al contenedor a través de env. El contenedor mysql usa el puerto 3306:3306 y también hay variables de base de datos pasadas a través de env. El contenedor web está abierto con el puerto 8000. En los pasos, pip instala las dependencias y, a continuación, se ejecuta la prueba de Django. Si quiere configurar un ejemplo de trabajo, necesitará un sitio de Django configurado con dos bases de datos. En este ejemplo se supone que el archivo manage.py está en el directorio raíz y que el proyecto de Django está dentro de ese directorio. Es posible que tenga que actualizar el trazado /__w/1/s/ en /__w/1/s/manage.py test.

resources:
  containers:
    - container: db
      image: postgres
      volumes:
          - '/data/db:/var/lib/postgresql/data'
      env:
        POSTGRES_DB: postgres
        POSTGRES_USER: postgres
        POSTGRES_PASSWORD: postgres
    - container: mysql
      image: 'mysql:5.7'
      ports:
         - '3306:3306'
      env:
        MYSQL_DATABASE: users
        MYSQL_USER: mysql
        MYSQL_PASSWORD: mysql
        MYSQL_ROOT_PASSWORD: mysql
    - container: web
      image: python
      volumes:
      - '/code'
      ports:
        - '8000:8000'

pool:
  vmImage: 'ubuntu-latest'

container: web
services:
  db: db
  mysql: mysql

steps:
    - script: |
        pip install django
        pip install psycopg2
        pip install mysqlclient
      displayName: set up django
    - script: |
          python /__w/1/s/manage.py test