Contentores de serviço
Serviços de DevOps do Azure
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 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 devem definir um
CMD
ouENTRYPOINT
. O pipeline é executadodocker run
para o contêiner fornecido sem quaisquer argumentos.Os Pipelines do Azure podem executar contêineres Linux ou 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 macOS hospedado não suporta contêineres em execução.
Nota
Os contêineres de serviço não são suportados em pipelines clássicos.
Trabalho de contêiner único
O exemplo a seguir definição de pipeline YAML 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 contêineres e buildpack-deps
do nginx
Docker Hub e, em seguida, inicia os contêineres. Os contentores estão ligados em rede para que possam chegar uns aos outros pelo seu services
nome.
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 entre si.
Trabalho único sem 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á sendo executado em um contêiner, não há resolução automática de nomes. Em vez disso, você pode acessar os serviços usando localhost
o . O exemplo fornece explicitamente a 8080:80
porta.
Uma abordagem alternativa é permitir que uma porta aleatória seja atribuída dinamicamente em tempo de execução. Em seguida, você pode acessar essas portas dinâmicas usando variáveis. Estas variáveis assumem a forma: 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 agent.services.redis.ports.6379
variável contém o número da porta.
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 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
A especificação ports
não é necessária se o trabalho estiver sendo 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.
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
sobre a porta 6379
, vinculado a uma porta aleatória na máquina host.
Para portas vinculadas 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 na máquina 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 chamados de volumes do Docker, volumes anônimos do Docker ou montagens de ligação no host.
services:
my_service:
image: myservice:latest
volumes:
- mydockervolume:/data/dir
- /data/dir
- /src/dir:/dst/dir
Nota
Se você usar pools hospedados pela Microsoft, seus volumes não serão persistidos entre os trabalhos, porque a máquina host será limpa 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 estado de funcionamento
Se qualquer contêiner de serviço especificar um HEALTHCHECK, o agente pode, 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 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 é chamado
db
. - O
db
contêiner usa volume/data/db:/var/lib/postgresql/data
e há três variáveis de banco de dados passadas para o contêiner viaenv
. - O
mysql
contêiner usa porta3306:3306
, e também há variáveis de banco de dados passadas viaenv
. - O
web
contêiner está aberto com porta8000
.
Nas etapas, instala 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 assume que seu arquivo manage.py está no diretório raiz e seu projeto Django também está 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