Freigeben über


Dienstcontainer

Azure DevOps Services

In diesem Artikel wird die Verwendung von Dienstcontainern in Azure Pipelines beschrieben. Wenn Ihre Pipeline die Unterstützung eines oder mehrerer Dienste erfordert, müssen Sie möglicherweise die Dienste pro Auftrag erstellen, verbinden und bereinigen. Ihre Pipeline kann beispielsweise Integrationstests ausführen, die Zugriff auf eine neu erstellte Datenbank und einen Speichercache für jeden Auftrag in der Pipeline erfordern.

Ein Dienstcontainer bietet eine einfache und portierbare Möglichkeit zum Ausführen von Diensten in Ihrer Pipeline. Auf den Dienstcontainer kann nur für den Auftrag zugegriffen werden, der ihn erfordert.

Mithilfe von Dienstcontainern können Sie die von Ihren Pipelines abhängigen Dienste automatisch erstellen, vernetzen und den Lebenszyklus verwalten. Dienstcontainer funktionieren mit jeder Art von Auftrag, werden jedoch am häufigsten mit Containeraufträgen verwendet.

Hinweis

Klassische Pipelines unterstützen keine Dienstcontainer.

Bedingungen und Einschränkungen

  • Dienstcontainer müssen CMD oder ENTRYPOINT definieren. Die Pipeline wird ohne Argumente für den bereitgestellten Container ausgeführt docker run .

  • Azure-Pipelines können Linux- oder Windows-Container ausführen. Sie verwenden den gehosteten Ubuntu-Pool für Linux-Container oder den gehosteten Windows-Pool für Windows-Container. Der gehostete macOS-Pool unterstützt keine Ausführung von Containern.

  • Dienstcontainer verwenden dieselben Containerressourcen wie Containeraufträge, sodass sie dieselben Startoptionen verwenden können.

  • Wenn ein Dienstcontainer einen HEALTHCHECK angibt, kann der Agent optional darauf warten, bis der Container gesund ist, bevor der Auftrag ausgeführt wird.

Auftrag mit einem einzelnen Container

Im folgenden Beispiel für die YAML-Pipeline wird ein einzelner Containerauftrag definiert, der einen Dienstcontainer verwendet. Die Pipeline ruft die buildpack-deps und nginx Container von Docker Hub ab und startet dann alle Container. Die Container sind vernetzt, damit sie sich über ihre Namen services erreichen können.

Von innerhalb des Auftragscontainers wird mithilfe von Docker-Netzwerkfunktionen der nginx Hostname zu den richtigen Diensten aufgelöst. Alle Container im Netzwerk machen automatisch alle Ports gegenseitig verfügbar.

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

Einzelner Nichtcontainerauftrag

Sie können auch Dienstcontainer in Nicht-Container-Aufträgen verwenden. Die Pipeline startet die neuesten Container, aber da der Job nicht in einem Container ausgeführt wird, gibt es keine automatische Namensauflösung. Stattdessen erreichen Sie Dienste mithilfe von localhost. Die folgende Beispielpipeline gibt explizit den 8080:80 Port für nginx.

Eine alternative Methode besteht darin, zur Laufzeit einen zufälligen Port dynamisch zuzuweisen. Damit der Auftrag auf den Port zugreifen kann, erstellt die Pipeline eine Variable in der Form agent.services.<serviceName>.ports.<port>. Sie können auf den dynamischen Port zugreifen, indem Sie diese Umgebungsvariable in einem Bash-Skript verwenden.

In der folgenden Pipeline erhält redis einen zufällig verfügbaren Port auf dem Host, und die Variable agent.services.redis.ports.6379 enthält die Portnummer.

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

Mehrere Aufträge

Dienstcontainer sind auch nützlich, um dieselben Schritte für mehrere Versionen desselben Diensts auszuführen. Im folgenden Beispiel werden dieselben Schritte für mehrere Versionen von PostgreSQL ausgeführt.

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

Aufträge, die direkt auf dem Host ausgeführt werden, müssen ports auf den Dienstcontainer zugreifen. Die Angabe ports ist nicht erforderlich, wenn Ihr Auftrag in einem Container ausgeführt wird, da Container im selben Docker-Netzwerk standardmäßig alle Ports automatisch füreinander verfügbar machen.

Ein Port übernimmt das Formular <hostPort>:<containerPort> oder nur <containerPort> mit einem optionalen /<protocol> Ende. Beispielsweise 6379/tcp wird tcp über portiert 6379, gebunden an einen zufälligen Port auf dem Hostcomputer.

Wenn Sie eine Containerressource oder einen Inlinecontainer aufrufen, können Sie wie im folgenden Beispiel ein Array angeben, das ports für den Container verfügbar gemacht werden soll.

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

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

Für Ports, die an einen zufälligen Port auf dem Hostcomputer gebunden sind, erstellt die Pipeline eine Variable des Formulars agent.services.<serviceName>.ports.<port> , damit der Auftrag auf den Port zugreifen kann. Beispielsweise wird agent.services.redis.ports.6379 in den zufällig zugewiesenen Port auf dem Hostcomputer aufgelöst.

Volumes

Volumes eignen sich zum Freigeben von Daten zwischen Diensten oder zum Speichern von Daten zwischen mehreren Ausführungsläufen eines Auftrags. Sie definieren Volume-Einhängungen als ein Array von volumes.

Jedes Volume übernimmt die Form <source>:<destinationPath>, wobei <source> es sich entweder um ein benanntes Volume oder einen absoluten Pfad auf dem Host handelt und <destinationPath> ein absoluter Pfad im Container ist. Volumes können Docker-Volumes, anonyme Docker-Volumes oder Bereitstellungen auf dem Host binden.

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

Hinweis

Von Microsoft gehostete Pools speichern keine Volumes zwischen Aufträgen, da der Hostcomputer nach jedem Auftrag bereinigt wird.

Beispiel für mehrere Container mit Diensten

Die folgende Beispielpipeline verfügt über einen Django Python-Webcontainer, der mit PostgreSQL- und MySQL-Datenbankcontainern verbunden ist.

  • Die PostgreSQL-Datenbank ist die primäre Datenbank, und ihr Container wird benannt db.
  • Der db-Container verwendet das Volume /data/db:/var/lib/postgresql/data, und übergibt drei Datenbankvariablen über env.
  • Der mysql Container verwendet Port 3306:3306und übergibt auch Datenbankvariablen über env.
  • Der Container web ist an Port 8000 geöffnet.

In den Schritten pip werden Abhängigkeiten installiert und dann Django-Tests ausgeführt.

Zum Einrichten eines Funktionierenden Beispiels benötigen Sie eine Django-Website, die mit zwei Datenbanken eingerichtet ist. Im Beispiel wird davon ausgegangen, dass sich Ihre manage.py Datei und Ihr Django-Projekt im Stammverzeichnis befinden. Wenn nicht, müssen Sie den /__w/1/s/ Pfad möglicherweise aktualisieren 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