Partager via


Conteneurs de service

Azure DevOps Services

Cet article décrit l’utilisation de conteneurs de service dans Azure Pipelines. Si votre pipeline nécessite la prise en charge d’un ou plusieurs services, vous devrez peut-être créer, vous connecter et libérer les services pour chaque tâche. Par exemple, votre pipeline peut exécuter des tests d’intégration qui nécessitent l’accès à une base de données et un cache mémoire nouvellement créés pour chaque travail du pipeline.

Un conteneur de services offre un moyen simple et portable d’exécuter des services dans votre pipeline. Le conteneur de service est accessible uniquement au travail qui le nécessite.

Les conteneurs de service vous permettent de créer, de réseau et de gérer automatiquement les cycles de vie des services dont dépendent vos pipelines. Les conteneurs de service fonctionnent avec n’importe quel type de travail, mais sont les plus couramment utilisés avec les travaux de conteneur.

Remarque

Les pipelines classiques ne prennent pas en charge les conteneurs de service.

Conditions et limitations

  • Les conteneurs de service doivent définir un CMD ou ENTRYPOINT. Le pipeline s’exécute docker run sans argument pour le conteneur fourni.

  • Azure Pipelines peut exécuter des conteneurs Linux ou Windows . Vous utilisez le pool Ubuntu hébergé pour les conteneurs Linux ou le pool Windows hébergé pour les conteneurs Windows. Le pool macOS hébergé ne prend pas en charge l’exécution de conteneurs.

  • Les conteneurs de service partagent les mêmes ressources de conteneur que les travaux de conteneur, ce qui leur permet d’utiliser les mêmes options de démarrage.

  • Si un conteneur de service spécifie un HEALTHCHECK, l’agent peut éventuellement attendre que le conteneur soit sain avant d’exécuter le travail.

Travail de conteneur unique

L’exemple de pipeline YAML suivant définit un job de conteneur unique qui utilise un conteneur de service. Le pipeline récupère les conteneurs buildpack-deps et nginx à partir de Docker Hub, puis démarre tous les conteneurs. Les conteneurs sont en réseau afin qu’ils puissent se joindre les uns aux autres par leurs services noms.

Depuis le conteneur de tâches, le nom d’hôte nginx se résout vers les services appropriés grâce au réseau Docker. Tous les conteneurs du réseau exposent automatiquement tous les ports les uns aux autres.

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

Travail unique noncontainer

Vous pouvez également utiliser des conteneurs de service dans des travaux non-conteneurs. Le pipeline démarre les derniers conteneurs, mais, étant donné que le travail ne s’exécute pas dans un conteneur, il n’existe aucune résolution automatique de noms. Au lieu de cela, vous atteignez les services à l’aide de localhost. L’exemple de pipeline suivant spécifie explicitement le 8080:80 port pour nginx.

Une autre approche consiste à affecter dynamiquement un port aléatoire au moment de l’exécution. Pour autoriser le travail à accéder au port, le pipeline crée une variable de la forme agent.services.<serviceName>.ports.<port>. Vous pouvez accéder au port dynamique à l’aide de cette variable d’environnement dans un script Bash.

Dans le pipeline suivant, redis obtient un port disponible aléatoire sur l’hôte et la agent.services.redis.ports.6379 variable contient le numéro de port.

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

Plusieurs travaux

Les conteneurs de service sont également utiles pour exécuter les mêmes étapes sur plusieurs versions du même service. Dans l’exemple suivant, les mêmes étapes s’exécutent sur plusieurs versions de 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

Ports

Les travaux qui s’exécutent directement sur l’hôte nécessitent ports d’accéder au conteneur de service. La spécification ports n’est pas nécessaire si votre travail s’exécute dans un conteneur, car les conteneurs sur le même réseau Docker exposent automatiquement tous les ports les uns aux autres par défaut.

Un port prend la forme <hostPort>:<containerPort> ou simplement <containerPort> avec une option facultative /<protocol> à la fin. Par exemple, 6379/tcp expose tcp sur le port 6379, lié à un port aléatoire sur l’ordinateur hôte.

Lorsque vous appelez une ressource de conteneur ou un conteneur inline, vous pouvez spécifier un tableau de ports données à exposer sur le conteneur, comme dans l’exemple suivant.

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

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

Pour les ports liés à un port aléatoire sur l’ordinateur hôte, le pipeline crée une variable du formulaire agent.services.<serviceName>.ports.<port> afin que le travail puisse accéder au port. Par exemple, agent.services.redis.ports.6379 résout le port attribué de manière aléatoire sur l’ordinateur hôte.

Volumes

Les volumes sont utiles pour partager des données entre des services ou pour conserver des données entre plusieurs exécutions d’un travail. Vous spécifiez des montages de volume sous la forme d’un tableau de volumes.

Chaque volume prend la forme <source>:<destinationPath>, où <source> est un volume nommé ou un chemin absolu sur l’hôte, et <destinationPath> est un chemin absolu dans le conteneur. Les volumes peuvent être nommés volumes Docker, volumes Docker anonymes ou montages de liaison sur l’hôte.

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

Remarque

Les pools hébergés par Microsoft ne conservent pas les volumes entre les travaux, car la machine hôte est nettoyée après chaque travail.

Exemple de plusieurs conteneurs avec services

L’exemple de pipeline suivant contient un conteneur web Python Django connecté aux conteneurs de base de données PostgreSQL et MySQL.

  • La base de données PostgreSQL est la base de données primaire et son conteneur est nommé db.
  • Le conteneur utilise le db volume /data/db:/var/lib/postgresql/dataet transmet trois variables de base de données au conteneur via env.
  • Le conteneur utilise le mysql port 3306:3306et transmet également des variables de base de données via env.
  • Le conteneur web est ouvert avec le port 8000.

Dans les étapes, pip installe les dépendances, puis les tests Django s’exécutent.

Pour configurer un exemple de travail, vous avez besoin d’un site Django configuré avec deux bases de données. L’exemple suppose que votre fichier manage.py et votre projet Django se trouvent dans le répertoire racine. Si ce n’est pas le cas, vous devrez peut-être mettre à jour le chemin d’accès /__w/1/s/ dans /__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