Contenitori del servizio

Servizi di Azure DevOps

Se la pipeline richiede il supporto di uno o più servizi, potrebbe essere necessario creare, connettersi e pulire i servizi per processo. Ad esempio, la pipeline potrebbe eseguire test di integrazione che richiedono l'accesso a un database e una cache di memoria appena creati per ogni processo nella pipeline.

Un contenitore offre un modo semplice e portabile per eseguire un servizio da cui dipende la pipeline. Un contenitore di servizi consente di creare, rete e gestire automaticamente il ciclo di vita di un servizio in contenitori. Ogni contenitore del servizio è accessibile solo al processo che lo richiede. I contenitori di servizi funzionano con qualsiasi tipo di processo, ma vengono usati più comunemente con i processi del contenitore.

Requisiti

  • I contenitori del servizio devono definire un CMD oggetto o ENTRYPOINT. La pipeline viene eseguita docker run per il contenitore fornito senza argomenti.

  • Azure Pipelines può eseguire contenitori Linux o Windows. È possibile usare il pool di contenitori Ubuntu ospitato per i contenitori Linux o il pool di Windows ospitato per i contenitori Windows. Il pool macOS ospitato non supporta l'esecuzione di contenitori.

Nota

I contenitori del servizio non sono supportati nelle pipeline classiche.

Processo a contenitore singolo

La definizione della pipeline YAML di esempio seguente mostra un singolo processo del contenitore.

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

La pipeline precedente recupera i contenitori e buildpack-deps dall'hub nginx Docker e quindi avvia i contenitori. I contenitori vengono collegati insieme in modo che possano raggiungere l'uno dall'altro in base al nome services .

All'interno di questo contenitore di processi, il nginx nome host viene risolto nei servizi corretti usando la rete Docker. Tutti i contenitori della rete espongono automaticamente tutte le porte l'una all'altra.

Singolo processo non contenitore

È anche possibile usare contenitori di servizi senza un contenitore di processi, come nell'esempio seguente.

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

La pipeline precedente avvia i contenitori più recenti nginx . Poiché il processo non è in esecuzione in un contenitore, non esiste alcuna risoluzione automatica dei nomi. È invece possibile raggiungere i servizi usando localhost. L'esempio fornisce in modo esplicito la 8080:80 porta.

Un approccio alternativo consiste nel consentire l'assegnazione dinamica di una porta casuale in fase di esecuzione. È quindi possibile accedere a queste porte dinamiche usando le variabili. Queste variabili hanno il formato : agent.services.<serviceName>.ports.<port>. In uno script Bash è possibile accedere alle variabili usando l'ambiente del processo.

Nell'esempio redis precedente viene assegnata una porta disponibile casuale nell'host. La agent.services.redis.ports.6379 variabile contiene il numero di porta.

Più processi

I contenitori di servizi sono utili anche per eseguire gli stessi passaggi su più versioni dello stesso servizio. Nell'esempio seguente vengono eseguiti gli stessi passaggi su più versioni di 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

Porti

Quando si richiama una risorsa contenitore o un contenitore inline, è possibile specificare una matrice di ports da esporre nel contenitore, come nell'esempio seguente.

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

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

Se il processo è in esecuzione in un contenitore, non è necessario specificare ports perché i contenitori nella stessa rete Docker espongono automaticamente tutte le porte l'una all'altra per impostazione predefinita.

Se il processo è in esecuzione nell'host, ports è necessario accedere al servizio. Una porta assume la forma <hostPort>:<containerPort> o solo <containerPort> con un oggetto facoltativo /<protocol> alla fine. Ad esempio, 6379/tcp espone tcp sulla porta 6379, associata a una porta casuale nel computer host.

Per le porte associate a una porta casuale nel computer host, la pipeline crea una variabile del modulo agent.services.<serviceName>.ports.<port> in modo che il processo possa accedere alla porta. Ad esempio, agent.services.redis.ports.6379 viene risolto nella porta assegnata in modo casuale nel computer host.

Volumi

I volumi sono utili per condividere dati tra servizi o per rendere persistenti i dati tra più esecuzioni di un processo. È possibile specificare i montaggi del volume come matrice del volumes formato <source>:<destinationPath>, dove <source> può essere un volume denominato o un percorso assoluto nel computer host ed <destinationPath> è un percorso assoluto nel contenitore. I volumi possono essere denominati volumi Docker, volumi Docker anonimi o montaggi di associazione nell'host.

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

Nota

Se si usano pool ospitati da Microsoft, i volumi non vengono mantenuti tra i processi, perché il computer host viene pulito dopo il completamento di ogni processo.

Opzioni di avvio

I contenitori del servizio condividono le stesse risorse del contenitore dei processi del contenitore. Ciò significa che è possibile usare le stesse opzioni di avvio.

Controllo integrità

Se un contenitore di servizi specifica healthCHECK, l'agente può attendere facoltativamente finché il contenitore non è integro prima di eseguire il processo.

Esempio di più contenitori con servizi

L'esempio seguente include un contenitore Web Python Django connesso ai contenitori di database PostgreSQL e MySQL.

  • Il database PostgreSQL è il database primario e il relativo contenitore è denominato db.
  • Il db contenitore usa il volume /data/db:/var/lib/postgresql/datae sono presenti tre variabili di database passate al contenitore tramite env.
  • Il mysql contenitore usa la porta 3306:3306e sono presenti anche variabili di database passate tramite env.
  • Il web contenitore è aperto con la porta 8000.

Nei passaggi pip installa le dipendenze e quindi i test Django vengono eseguiti.

Per configurare un esempio funzionante, è necessario configurare un sito Django con due database. Nell'esempio si presuppone che il file manage.py si trova nella directory radice e che anche il progetto Django si trova all'interno di tale directory. In caso contrario, potrebbe essere necessario aggiornare il /__w/1/s/ percorso in /__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