Contêineres de serviço

Azure DevOps Services

Se o pipeline exigir o suporte de um ou mais serviços, talvez seja necessário criar, conectar-se e limpar os serviços por trabalho. Por exemplo, seu pipeline pode executar testes de integração que exigem acesso a um banco de dados recém-criado e cache de memória 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 criar, conectar em rede e gerenciar automaticamente o ciclo de vida de um serviço em contêiner. Cada contêiner de serviço é acessível apenas para o trabalho que o exige. 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 é executado docker run para o contêiner fornecido sem nenhum argumento.

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

Observação

Não há suporte para contêineres de serviço em pipelines clássicos.

Trabalho de contêiner único

O exemplo de definição de pipeline YAML a seguir mostra um único trabalho 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

O pipeline anterior busca os nginx contêineres 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 nginx host é 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 que não é contêiner

Você também pode usar contêineres de serviço sem um contêiner de trabalho, como no exemplo a seguir.

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

O pipeline anterior inicia os contêineres mais recentes nginx . Como o trabalho não está em execução em um contêiner, não há resolução automática de nomes. Em vez disso, você pode acessar os serviços usando localhosto . O exemplo fornece explicitamente a 8080:80 porta.

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. Essas variáveis usam o seguinte formato: agent.services.<serviceName>.ports.<port>. Em um script Bash, você pode acessar variáveis usando o ambiente de processo.

No exemplo anterior, redis é atribuída uma porta disponível aleatória 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 invocar um recurso de contêiner ou um contêiner embutido, você pode especificar uma matriz para ports expor no contêiner, como no exemplo a seguir.

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, pois os contêineres na mesma rede do Docker expõem automaticamente todas as portas umas às outras por padrão.

Se o seu trabalho estiver em execução no host, ports são necessários para acessar o serviço. Uma porta assume a forma <hostPort>:<containerPort> ou apenas <containerPort> com um opcional /<protocol> no final. Por exemplo, 6379/tcp expõe tcp a porta 6379, vinculada a uma porta aleatória na máquina host.

Para portas associadas a uma porta aleatória na máquina host, o pipeline cria uma variável do formulário agent.services.<serviceName>.ports.<port> para que o trabalho possa acessar a porta. 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 persistir dados entre várias execuções de um trabalho. Você especifica montagens de volume como uma matriz do volumes formulário , onde <source> pode ser um volume nomeado ou um caminho absoluto na máquina host e <destinationPath> é um caminho <source>:<destinationPath>absoluto no contêiner. Os volumes podem ser nomeados volumes do Docker, volumes anônimos do Docker ou montagens de associação no host.

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

Observação

Se você usar pools hospedados pela Microsoft, seus volumes não serão mantidos entre os trabalhos, pois o computador host é limpo após a conclusão de cada trabalho.

Opções de inicialização

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 de inicialização.

Verificação de integridade

Se algum contêiner de serviço especificar um HEALTHCHECK, o agente poderá, opcionalmente, aguardar até que o contêiner esteja íntegro antes de executar o trabalho.

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

O exemplo a seguir tem um contêiner da Web do Django Python conectado a contêineres de banco de dados PostgreSQL e MySQL.

  • O banco de dados PostgreSQL é o banco de dados primário e seu contêiner é denominado db.
  • O db contêiner usa volume /data/db:/var/lib/postgresql/datae há três variáveis de banco de dados passadas para o contêiner por meio de env.
  • O mysql contêiner usa port 3306:3306, e também há variáveis de banco de dados passadas via env.
  • O contêiner web está aberto com a porta 8000.

Nas etapas, instala as dependências e, em seguida, pip os testes do Django são executados.

Para configurar um exemplo de trabalho, você precisa de um site Django configurado com dois bancos de dados. O exemplo pressupõe que seu arquivo manage.py esteja no diretório raiz e seu projeto Django também esteja dentro desse diretório. Caso contrário, talvez seja necessário atualizar o /__w/1/s/ caminho no /__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