Partilhar via


Contentores de serviço

Serviços de DevOps do Azure

Este artigo descreve o uso de contêineres de serviço no Azure Pipelines. 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 de serviço fornece uma maneira simples e portátil de executar serviços em seu pipeline. O contêiner de serviço é acessível apenas para o trabalho que o exige.

Os contêineres de serviço permitem que você crie, conecte e gerencie automaticamente os ciclos de vida dos serviços dos quais seus pipelines dependem. Os contêineres de serviço funcionam com qualquer tipo de trabalho, mas são mais comumente usados com trabalhos de contêiner.

Nota

Os pipelines clássicos não suportam contêineres de serviço.

Condições e limitações

  • Os contêineres de serviço devem definir um CMD ou ENTRYPOINT. O pipeline é executado docker run sem argumentos para o contêiner fornecido.

  • Os Pipelines do Azure podem executar contêineres Linux ou Windows . Você usa o pool do Ubuntu hospedado para contêineres do Linux ou o pool do Windows hospedado para contêineres do Windows. O pool macOS hospedado não suporta contêineres em execução.

  • Os contêineres de serviço compartilham os mesmos recursos de contêiner que os trabalhos de contêiner, para que possam usar as mesmas opções de inicialização.

  • Se um contentor de serviço especificar um HEALTHCHECK, o agente pode, opcionalmente, aguardar até que o contentor esteja saudável antes de executar a tarefa.

Trabalho de contêiner único

O exemplo de pipeline YAML a seguir define uma tarefa de contêiner único que usa um contêiner de serviço. O pipeline busca os contêineres buildpack-deps e nginx do Docker Hub e, em seguida, inicia todos os contêineres. Os contentores estão ligados em rede para que possam chegar uns aos outros pelos seus services nomes.

De dentro do 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 entre si.

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

Trabalho único sem contêiner

Você também pode usar contêineres de serviço em trabalhos que não sejam de contêiner. O pipeline inicia os contentores mais recentes, mas, como a tarefa não é executada num contentor, não há resolução automática de nomes. Em vez disso, você acessa os serviços usando localhost. O pipeline de exemplo a seguir especifica explicitamente a 8080:80 porta para nginx.

Uma abordagem alternativa é atribuir uma porta aleatória dinamicamente em tempo de execução. Para permitir que a tarefa aceda à porta, o pipeline cria uma variável da forma agent.services.<serviceName>.ports.<port>. Você pode acessar a porta dinâmica usando essa variável de ambiente em um script Bash.

No pipeline a seguir, redis obtém uma porta disponível aleatória no host e a agent.services.redis.ports.6379 variável contém o número da porta.

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

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

Os trabalhos executados diretamente no host exigem o acesso ao contentor de serviços. A especificação ports não é necessária se o trabalho for executado em um contêiner, porque os contêineres na mesma rede do Docker expõem automaticamente todas as portas umas às outras por padrão.

Uma porta assume a forma <hostPort>:<containerPort> ou apenas <containerPort> com um opcional /<protocol> no final. Por exemplo, 6379/tcp expõe tcp sobre a porta 6379, vinculado a uma porta aleatória na máquina host.

Ao invocar um recurso de contêiner ou um contêiner embutido, você pode especificar uma matriz de ports para 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

Para portas vinculadas a uma porta aleatória na máquina host, o pipeline cria uma variável no formato agent.services.<serviceName>.ports.<port>, permitindo que o trabalho aceda à porta. Por exemplo, agent.services.redis.ports.6379 corresponde à porta atribuída aleatoriamente na máquina anfitriã.

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 de volumes.

Cada volume assume a forma <source>:<destinationPath>, onde <source> é um volume nomeado ou um caminho absoluto no host e <destinationPath> é um caminho absoluto no contêiner. Os volumes podem ser chamados volumes do Docker, volumes anónimos do Docker ou bind mounts no host.

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

Nota

Os pools hospedados pela Microsoft não mantêm volumes entre tarefas, porque a máquina anfitriã é limpa após cada tarefa.

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

O exemplo de pipeline a seguir tem um contentor web Python Django conectado aos contentores de base de dados PostgreSQL e MySQL.

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

Nas etapas, pip instala dependências e, em seguida, 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 assume que seu arquivo manage.py e seu projeto Django estão no diretório raiz. 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