Contentores de serviço

Azure DevOps Services

Se o seu oleoduto necessitar do apoio de um ou mais serviços, em muitos casos irá querer criar, conectar-se e limpar cada serviço por trabalho. Por exemplo, um gasoduto pode executar testes de integração que requerem acesso a uma base de dados e a uma cache de memória. A base de dados e a cache de memória precisam de ser criadas recentemente para cada trabalho no oleoduto.

Um recipiente fornece uma forma simples e portátil de executar um serviço do que o seu oleoduto depende. Um recipiente de serviço permite-lhe criar, rede e gerir automaticamente o ciclo de vida do seu serviço contentorizado. Cada recipiente de serviço é acessível apenas pelo trabalho que o exija. Os contentores de serviço trabalham com qualquer tipo de trabalho, mas são mais usados com trabalhos de contentores.

Requisitos

Os recipientes de serviço devem definir um CMD ou .ENTRYPOINT O gasoduto funcionará docker run para o recipiente fornecido sem argumentos adicionais.

Os gasodutos Azure podem executar Linux ou Windows Containers. Utilize ubuntu alojado para recipientes Linux ou a piscina de contentores hosted Windows para recipientes Windows. (A piscina do macOS hospedada não suporta recipientes de funcionamento.)

Trabalho de contentor único

Um exemplo simples de utilização de trabalhos de contentores:

resources:
  containers:
  - container: my_container
    image: buildpack-deps:focal
  - container: nginx
    image: nginx


pool:
  vmImage: 'ubuntu-20.04'

container: my_container
services:
  nginx: nginx

steps:
- script: |
    curl nginx
  displayName: Show that nginx is running

Este oleoduto recolhe os nginx contentores e buildpack-deps os contentores do Docker Hub e, em seguida, inicia os contentores. Os contentores estão em rede para que possam alcançar-se pelo nome services .

A partir de dentro deste recipiente de trabalho, o nome do nginx anfitrião resolve-se para os serviços corretos usando a rede Docker. Todos os contentores da rede expõem automaticamente todas as portas umas às outras.

Trabalho único

Também pode utilizar recipientes de serviço sem um recipiente 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-18.04'

services:
  nginx: nginx
  redis: redis

steps:
- script: |
    curl localhost:8080
    echo $AGENT_SERVICES_REDIS_PORTS_6379

Este oleoduto inicia os mais recentes nginx contentores. Como o trabalho não está a funcionar num contentor, não há resolução automática de nomes. Este exemplo mostra como pode, em vez disso, chegar aos serviços utilizando 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 no tempo de execução. Em seguida, pode aceder a estas portas dinâmicas utilizando variáveis. Num script Bash, você pode aceder a uma variável usando o ambiente do processo. Estas variáveis assumem o formulário: agent.services.<serviceName>.ports.<port>. No exemplo acima, redis é atribuída uma porta disponível aleatoriamente no anfitrião. A agent.services.redis.ports.6379 variável contém o número da porta.

Múltiplos empregos

Os recipientes de serviço também são úteis para executar os mesmos passos contra várias versões do mesmo serviço. No exemplo seguinte, os mesmos passos correm contra várias versões de PostgreSQL.

resources:
  containers:
  - container: my_container
    image: ubuntu:18.04
  - container: pg12
    image: postgres:12
  - container: pg11
    image: postgres:11

pool:
  vmImage: 'ubuntu-18.04'

strategy:
  matrix:
    postgres12:
      postgresService: pg12
    postgres11:
      postgresService: pg11

container: my_container

services:
  postgres: $[ variables['postgresService'] ]
steps:
- script: printenv

Portas

Ao especificar um recurso de contentor ou um recipiente de linha, pode especificar uma matriz para ports expor no recipiente.

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

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

Não é necessário especificar ports se o seu trabalho está a funcionar num contentor porque os contentores da mesma rede Docker expõem automaticamente todas as portas umas às outras por defeito.

Se o seu trabalho estiver a decorrer no anfitrião, então ports é necessário aceder ao serviço. Uma porta toma o formulário <hostPort>:<containerPort> ou apenas <containerPort>, com uma opcional /<protocol> no final, por exemplo 6379/tcp , para expor tcp sobre a porta 6379, ligada a uma porta aleatória na máquina hospedeira.

Para portas ligadas a uma porta aleatória na máquina hospedeira, o gasoduto cria uma variável do formulário agent.services.<serviceName>.ports.<port> para que possa ser acedido pelo trabalho. Por exemplo, agent.services.redis.ports.6379 resolve-se a porta atribuída aleatoriamente na máquina hospedeira.

Volumes

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

Pode especificar os suportes de volume como uma matriz de volumes. Os volumes podem ser chamados volumes Docker, volumes anónimos do Docker, ou montar suportes no hospedeiro.

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

Os volumes tomam a forma <source>:<destinationPath>, onde <source> pode ser um volume nomeado ou um caminho absoluto na máquina hospedeira, e <destinationPath> é um caminho absoluto no recipiente.

Nota

Se utilizar as nossas piscinas hospedadas, então os seus volumes não serão persistidos entre os trabalhos porque a máquina hospedeira é limpa após o trabalho estar concluído.

Outras opções

Os contentores de serviço partilham os mesmos recursos de contentores que os postos de trabalho dos contentores. Isto significa que pode usar as mesmas opções adicionais.

Verificação de saúde

Opcionalmente, se algum recipiente de serviço especificar um HEALTHCHECK, o agente aguarda até que o recipiente esteja saudável antes de executar o trabalho.

Vários contentores com exemplo de serviços

Neste exemplo, há um recipiente web Django Python ligado a dois contentores de base de dados - PostgreSQL e MySQL. A base de dados PostgreSQL é a base de dados primária e o seu contentor tem o nome db. O db recipiente utiliza volume /data/db:/var/lib/postgresql/data e há três variáveis de base de dados passadas para o contentor via env. O mysql contentor utiliza a porta 3306:3306 e há também variáveis de base de dados passadas via env. O web recipiente está aberto com porta 8000. Nos degraus, pip instala-se dependências e, em seguida, o teste de Django são executados. Se quiser ter um exemplo de trabalho, precisará de um site django configurado com duas bases de dados. Este exemplo pressupõe que o seu manage.py ficheiro está no diretório de raiz e o seu projeto Django está dentro desse diretório. Pode ser necessário atualizar o /__w/1/s/ caminho 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