Contenedores de servicio
Azure DevOps Services
Si la canalización requiere la compatibilidad de uno o varios servicios, es posible que tenga que crear, conectarse y limpiar los servicios por trabajo. Por ejemplo, la canalización podría ejecutar pruebas de integración que requieran acceso a una base de datos recién creada y a una caché de memoria 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 servicios le permite crear, red y administrar automáticamente el ciclo de vida de un servicio en contenedor. Solo se puede acceder a cada contenedor de servicios para el trabajo que lo requiere. Los contenedores de servicio funcionan con cualquier tipo de trabajo, pero se usan con más frecuencia con trabajos de contenedor.
Requisitos
Los contenedores de servicio deben definir
CMD
oENTRYPOINT
. La canalización se ejecutadocker run
para el contenedor proporcionado sin ningún argumento.Azure Pipelines puede ejecutar contenedores de Linux o Windows. Puede usar el grupo de contenedores de Ubuntu hospedado para contenedores de Linux o el grupo de Windows hospedado para contenedores de Windows. El grupo de macOS hospedado no admite la ejecución de contenedores.
Nota:
Los contenedores de servicio no se admiten en canalizaciones clásicas.
Trabajo en un contenedor único
En la siguiente definición de canalización YAML de ejemplo se muestra un único trabajo 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
La canalización anterior captura los nginx
contenedores y buildpack-deps
de Docker Hub y, a continuación, inicia los contenedores. Los contenedores están conectados en red para que puedan comunicarse entre sí por su nombre services
.
Desde dentro de este contenedor de trabajos, el nombre de nginx
host 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 de no contenedor único
También puede usar contenedores de servicio sin un contenedor de trabajos, como en el ejemplo siguiente.
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
La canalización anterior inicia los contenedores más recientes nginx
. Dado que el trabajo no se está ejecutando en un contenedor, no hay ninguna resolución de nombres automática. En su lugar, puede acceder a los servicios mediante localhost
. En el ejemplo se proporciona explícitamente el 8080:80
puerto.
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. Estas variables tienen el formato: agent.services.<serviceName>.ports.<port>
. En un script de Bash, puede acceder a variables mediante el entorno de proceso.
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 invocar un recurso de contenedor o un contenedor insertado, puede especificar una matriz de ports
para exponerlo en el contenedor, como en el ejemplo siguiente.
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, ya que 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, ports
es necesario acceder al servicio. Un puerto toma el formato <hostPort>:<containerPort>
o simplemente <containerPort>
con un opcional /<protocol>
al final. Por ejemplo, 6379/tcp
expone 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 formulario agent.services.<serviceName>.ports.<port>
para que el trabajo pueda acceder al puerto. 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. Especifique montajes de volumen como una matriz del volumes
formulario <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. Los volúmenes se pueden denominar volúmenes de Docker, volúmenes anónimos de Docker o enlazar montajes en el host.
services:
my_service:
image: myservice:latest
volumes:
- mydockervolume:/data/dir
- /data/dir
- /src/dir:/dst/dir
Nota:
Si usa grupos hospedados por Microsoft, los volúmenes no se conservan entre trabajos, ya que la máquina host se limpia una vez completado cada trabajo.
Opciones de inicio
Los contenedores de servicio comparten los mismos recursos de contenedor que los trabajos de contenedor. Esto significa que puede usar las mismas opciones de inicio.
Comprobación de estado
Si algún contenedor de servicios especifica healthCHECK, el agente puede esperar opcionalmente hasta que el contenedor esté en buen estado antes de ejecutar el trabajo.
Ejemplo de varios contenedores con servicios
En el ejemplo siguiente se tiene un contenedor web de Python de Django conectado a los contenedores de base de datos postgreSQL y MySQL.
- La base de datos PostgreSQL es la base de datos principal y su contenedor se denomina
db
. - El
db
contenedor 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 deenv
. - El
mysql
contenedor usa el puerto3306:3306
y también hay variables de base de datos pasadas a través deenv
. - El contenedor
web
está abierto con el puerto8000
.
En los pasos, instala las dependencias y, a continuación, pip
se ejecutan las pruebas de Django.
Para configurar un ejemplo de trabajo, necesita un sitio de Django configurado con dos bases de datos. En el ejemplo se supone que el archivo manage.py está en el directorio raíz y el proyecto de Django también está dentro de ese directorio. Si no es así, es posible que tenga que actualizar la /__w/1/s/
ruta de acceso 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