Dienstcontainer

Azure DevOps Services

Wenn Ihre Pipeline durch Dienste unterstützt werden muss, sollten Sie in vielen Fällen auf Auftragsbasis jeden Dienst erstellen, eine Verbindung mit ihm herstellen und die Bereinigung durchführen. Beispielsweise kann eine Pipeline Integrationstests ausführen, die Zugriff auf eine Datenbank und einen Speichercache erfordern. Die Datenbank und der Arbeitsspeichercache müssen für jeden Auftrag in der Pipeline neu erstellt werden.

Ein Container bietet eine einfache und portierbare Möglichkeit zum Ausführen eines Diensts, von dem Ihre Pipeline abhängig ist. Mit einem Dienstcontainer können Sie den Lebenszyklus Ihres Containerdiensts automatisch erstellen, vernetzen und verwalten. Auf jeden Dienstcontainer kann nur der Auftrag zugreifen, der ihn benötigt. Dienstcontainer funktionieren mit jeder Art von Auftrag, werden aber am häufigsten mit Containeraufträgen verwendet.

Anforderungen

Dienstcontainer müssen CMD oder ENTRYPOINT definieren. Die Pipeline führt docker run für den bereitgestellten Container ohne zusätzliche Argumente aus.

Azure Pipelines kann Linux- oder Windows-Container ausführen. Verwenden Sie gehostete Ubuntu-für-Linux-Container oder den gehosteten Windows-Containerpool für Windows-Container. (Der gehostete macOS-Pool unterstützt keine ausgeführten Container.)

Auftrag mit einem einzelnen Container

Ein einfaches Beispiel für die Verwendung von Containeraufträgen:

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

Diese Pipeline ruft die Container nginx und buildpack-deps von Docker Hub ab und startet sie dann. Die Container sind miteinander vernetzt, sodass sie sich über ihren services-Namen erreichen können.

Innerhalb dieses Auftragscontainers wird der nginx-Hostname mithilfe des Docker-Netzwerks in die richtigen Dienste aufgelöst. Alle Container im Netzwerk machen automatisch alle Ports gegenseitig verfügbar.

Einzelner Auftrag

Sie können Dienstcontainer auch ohne Auftragscontainer verwenden. Ein einfaches Beispiel:

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

Diese Pipeline startet die neuesten nginx-Container. Da der Auftrag nicht in einem Container ausgeführt wird, gibt es keine automatische Namensauflösung. In diesem Beispiel wird gezeigt, wie Sie Dienste stattdessen über localhost erreichen können. Im obigen Beispiel wird der Port explizit angegeben (z. B. 8080:80).

Ein alternativer Ansatz besteht darin, einen zufälligen Port dynamisch zur Laufzeit zuzuweisen. Sie können dann mithilfe von Variablen auf diese dynamischen Ports zugreifen. In einem Bash-Skript können Sie über die Prozessumgebung auf eine Variable zugreifen. Diese Variablen haben das folgende Format: agent.services.<serviceName>.ports.<port>. Im obigen Beispiel wird redis ein zufällig verfügbarer Port auf dem Host zugewiesen. Die Variable agent.services.redis.ports.6379 enthält die Portnummer.

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

Wenn Sie eine Containerressource oder einen Inlinecontainer angeben, können Sie ein Array von ports angeben, die für den Container verfügbar gemacht werden sollen.

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

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

Die Angabe von ports ist nicht erforderlich, wenn der Auftrag in einem Container ausgeführt wird, da Container im selben Docker-Netzwerk standardmäßig automatisch alle Ports füreinander verfügbar machen.

Wenn der Auftrag auf dem Host ausgeführt wird, sind ports zu Zugreifen auf den Dienst erforderlich. Ein Port hat die Form <hostPort>:<containerPort> oder einfach <containerPort> mit einem optionalen /<protocol> am Ende, z. B. 6379/tcp, um tcp über Port 6379, gebunden an einen zufälligen Port auf dem Hostcomputer, verfügbar zu machen.

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

Volumes

Volumes sind nützlich für die gemeinsame Nutzung von Daten zwischen Diensten oder das Beibehalten von Daten zwischen mehreren Ausführungen eines Auftrags.

Sie können Volumeeinbindungen als Array von volumes angeben. Volumes können benannte Docker-Volumes, anonyme Docker-Volumes oder Einbindungen auf dem Host sein.

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

Volumes haben die Form <source>:<destinationPath>, wobei <source> ein benanntes Volume oder ein absoluter Pfad auf dem Hostcomputer sein kann und <destinationPath> ein absoluter Pfad im Container ist.

Hinweis

Wenn Sie unsere gehosteten Pools verwenden, werden Ihre Volumes nicht zwischen Aufträgen beibehalten, da der Hostcomputer nach Abschluss des Auftrags bereinigt wird.

Weitere Optionen

Dienstcontainer nutzen dieselben Containerressourcen wie Containeraufträge. Sie können daher dieselben zusätzlichen Optionen verwenden.

Healthcheck

Wenn ein Dienstcontainer HEALTHCHECK angibt, wartet der Agent optional, bis der Container fehlerfrei ist, bevor er den Auftrag ausführt.

Beispiel für mehrere Container mit Diensten

In diesem Beispiel ist ein Django-Python-Webcontainer mit zwei Datenbankcontainern verbunden: PostgreSQL und MySQL. Die PostgreSQL-Datenbank ist die primäre Datenbank, deren Container den Namen db hat. Der Container db verwendet das Volume /data/db:/var/lib/postgresql/data, und es werden drei Datenbankvariablen über env an den Container übergeben. Der Container mysql verwendet das Port 3306:3306, und es werden außerdem Datenbankvariablen über env übergeben. Der Container web ist an Port 8000 geöffnet. In den Schritten installiert pip Abhängigkeiten, und dann werden Django-Tests ausgeführt. Wenn Sie ein funktionierendes Beispiel einrichten möchten, benötigen Sie eine Django-Site mit zwei Datenbanken. In diesem Beispiel wird davon ausgegangen, dass sich Ihre Datei manage.py im Stammverzeichnis und Ihr Django-Projekt ebenfalls in diesem Verzeichnis befindet. Möglicherweise müssen Sie den Pfad /__w/1/s/ in /__w/1/s/manage.py test aktualisieren.

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