Dienstcontainer

Azure DevOps Services

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 Container bietet eine einfache und portierbare Möglichkeit zum Ausführen eines Diensts, von dem Ihre Pipeline abhängt. Mit einem Dienstcontainer können Sie den Lebenszyklus eines containerisierten Diensts automatisch erstellen, netzwerken und verwalten. Auf jeden Dienstcontainer kann nur auf den Auftrag zugegriffen werden, für den er erforderlich ist. Dienstcontainer funktionieren mit jeder Art von Auftrag, werden jedoch am häufigsten mit Containeraufträgen verwendet.

Anforderungen

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

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

Hinweis

Dienstcontainer werden in klassischen Pipelines nicht unterstützt.

Auftrag mit einem einzelnen Container

Das folgende Beispiel für die YAML-Pipelinedefinition zeigt einen einzelnen Containerauftrag.

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

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

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

Einzelner Nichtcontainerauftrag

Sie können auch Dienstcontainer ohne Auftragscontainer verwenden, wie im folgenden Beispiel gezeigt.

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

Die vorangehende Pipeline startet die neuesten nginx Container. Da der Auftrag nicht in einem Container ausgeführt wird, gibt es keine automatische Namensauflösung. Stattdessen können Sie Dienste mithilfe localhostvon . Das Beispiel stellt den 8080:80 Port explizit bereit.

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. Diese Variablen haben das folgende Format: agent.services.<serviceName>.ports.<port>. In einem Bash-Skript können Sie mithilfe der Prozessumgebung auf Variablen zugreifen.

Im vorherigen Beispiel redis wird dem Host ein zufällig verfügbarer Port 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 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

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.

Wenn Ihr Auftrag auf dem Host ausgeführt wird, ports müssen Sie auf den Dienst zugreifen. 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.

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 geben Volume-Bereitstellungen als Array des volumes Formulars <source>:<destinationPath>an, wobei <source> es sich um ein benanntes Volume oder einen absoluten Pfad auf dem Hostcomputer 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

Wenn Sie von Microsoft gehostete Pools verwenden, werden Ihre Volumes nicht zwischen Aufträgen beibehalten, da der Hostcomputer nach Abschluss der einzelnen Aufträge bereinigt wird.

Startoptionen

Dienstcontainer nutzen dieselben Containerressourcen wie Containeraufträge. Dies bedeutet, dass Sie dieselben Startoptionen verwenden können.

Integritätsprüfung

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

Beispiel für mehrere Container mit Diensten

Im folgenden Beispiel ist ein Django Python-Webcontainer mit PostgreSQL- und MySQL-Datenbankcontainern verbunden.

  • Die PostgreSQL-Datenbank ist die primäre Datenbank, und ihr Container wird benannt db.
  • Der db Container verwendet Volume /data/db:/var/lib/postgresql/data, und es gibt drei Datenbankvariablen, die an envden Container übergeben werden.
  • Der mysql Container verwendet Port 3306:3306, und es werden auch Datenbankvariablen übergeben 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 im Stammverzeichnis befindet und sich Ihr Django-Projekt ebenfalls in diesem Verzeichnis befindet. 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