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 oENTRYPOINT
. La pipeline viene eseguitadocker 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/data
e sono presenti tre variabili di database passate al contenitore tramiteenv
. - Il
mysql
contenitore usa la porta3306:3306
e sono presenti anche variabili di database passate tramiteenv
. - Il
web
contenitore è aperto con la porta8000
.
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