Contêineres de serviço

Azure DevOps Services

Se o pipeline exigir o suporte de um ou mais serviços, em muitos casos você desejará criar, conectar e limpar cada serviço, por trabalho. Por exemplo, um pipeline pode executar testes de integração que exigem acesso a um banco de dados e a um cache de memória. O banco de dados e o cache de memória precisam ser criados recentemente para cada trabalho no pipeline.

Um contêiner fornece uma maneira simples e portátil de executar um serviço do qual seu pipeline depende. Um contêiner de serviço permite que você crie, interaja e gerencie o ciclo de vida do serviço em contêineres. Cada contêiner de serviço é acessível apenas pelo trabalho que o requer. Os contêineres de serviço funcionam com qualquer tipo de trabalho, mas são mais comumente usados com trabalhos de contêiner.

Requisitos

Os contêineres de serviço precisam definir um CMD ou ENTRYPOINT. O pipeline executará docker run para o contêiner fornecido, sem argumentos adicionais.

O Azure Pipelines pode executar contêineres do Linux ou do Windows. Use o Ubuntu hospedado para contêineres do Linux ou o pool de contêineres hospedado do Windows para contêineres do Windows. (O pool de macOS hospedado não dá suporte à execução de contêineres.)

Trabalho de contêiner único

Um exemplo simples de uso de trabalhos de contêiner:

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

Esse pipeline busca os contêineres nginx e buildpack-deps do Docker Hub e, em seguida, inicia os contêineres. Os contêineres são agrupados em rede para que possam alcançar uns aos outros pelo nome services deles.

De dentro desse contêiner de trabalho, o nome do host nginx é resolvido para os serviços corretos usando a rede do Docker. Todos os contêineres na rede expõem automaticamente todas as portas uns para os outros.

Trabalho único

Você também pode usar contêineres de serviço sem um contêiner de trabalho. Um exemplo simples:

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

Esse pipeline inicia os nginx contêineres mais recentes. Como o trabalho não está em execução em um contêiner, não há resolução automática de nomes. Este exemplo mostra como você pode, em vez disso, acessar serviços usando localhost. No exemplo acima, fornecemos a porta explicitamente (por exemplo, 8080:80).

Uma abordagem alternativa é permitir que uma porta aleatória seja atribuída dinamicamente em runtime. Em seguida, você pode acessar essas portas dinâmicas usando variáveis. Em um script Bash, você pode acessar uma variável usando o ambiente de processo. Essas variáveis usam o seguinte formato: agent.services.<serviceName>.ports.<port>. No exemplo acima, uma porta disponível aleatória é atribuída a redis no host. A variável agent.services.redis.ports.6379 contém o número de versão.

Vários trabalhos

Os contêineres de serviço também são úteis para executar as mesmas etapas em várias versões do mesmo serviço. No exemplo a seguir, as mesmas etapas são executadas em várias versões do 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

Portas

Ao especificar um recurso de contêiner ou um contêiner embutido, você pode especificar uma matriz de ports para expor no contêiner.

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

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

A especificação ports não será necessária se o trabalho estiver em execução em um contêiner, porque os contêineres na mesma rede do Docker expõem automaticamente todas as portas entre eles por padrão.

Se o trabalho estiver em execução no host, ports será necessário acessar o serviço. Uma porta usa o formato <hostPort>:<containerPort> ou apenas <containerPort>, com um /<protocol> opcional no final, por exemplo, 6379/tcp, para expor tcp pela porta 6379, associada a uma porta aleatória no computador host.

Para portas associadas a uma porta aleatória no computador host, o pipeline cria uma variável no formato agent.services.<serviceName>.ports.<port> para que possa ser acessado pelo trabalho. Por exemplo, agent.services.redis.ports.6379 resolve para a porta atribuída aleatoriamente no computador host.

Volumes

Os volumes são úteis para compartilhar dados entre serviços ou para manter dados entre várias execuções de um trabalho.

Você pode especificar montagens de volume como uma matriz de volumes. Os volumes podem ser nomeados como volumes do Docker, volumes anônimos do Docker ou montagens de vinculação no host.

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

Os volumes assumem o formato <source>:<destinationPath>, em que <source> pode ser um volume nomeado ou um caminho absoluto no computador host, e <destinationPath> é um caminho absoluto no contêiner.

Observação

Se você usar nossos pools hospedados, os volumes não serão persistidos entre os trabalhos porque o computador host será limpo após a conclusão do trabalho.

Outras opções

Os contêineres de serviço compartilham os mesmos recursos de contêiner que os trabalhos de contêiner. Isso significa que você pode usar as mesmas opções adicionais.

Healthcheck

Opcionalmente, se qualquer contêiner de serviço especificar um HEALTHCHECK, o agente aguardará até que o contêiner esteja íntegro antes de executar o trabalho.

Exemplo de vários contêineres com serviços

Neste exemplo, há um contêiner Web do Django Python conectado a dois contêineres de banco de dados : PostgreSQL e MySQL. O banco de dados PostgreSQL é o banco de dados primário, e o contêiner dele tem o nome db. O contêiner db usa o volume /data/db:/var/lib/postgresql/data e há três variáveis de banco de dados passadas para o contêiner por meio de env. O contêiner mysql usa a porta 3306:3306, e também há variáveis de banco de dados passadas por meio de env. O contêiner web está aberto com a porta 8000. Nas etapas, pip instala dependências e, em seguida, o teste do Django é executado. Se você quiser configurar um exemplo de trabalho, precisará de um site do Django configurado com dois bancos de dados. Este exemplo pressupõe que o arquivo manage.py esteja no diretório raiz e que o projeto do Django esteja dentro desse diretório. Talvez seja necessário atualizar o caminho /__w/1/s/ em /__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