So konfigurieren Sie eine Umgebung für kontinuierliche Integrationstests mit Docker und Docker Compose unter Ubuntu 16.04

Einführung

Continuous integration (CI) bezieht sich auf die Praxis, bei der Entwicklerintegrateo oft wie möglich Code verwenden und jedes Commit vor und nach dem Zusammenführen vonautomated build in einem gemeinsam genutzten Repository getestet wird.

CI beschleunigt Ihren Entwicklungsprozess und minimiert das Risiko kritischer Probleme in der Produktion. Die Einrichtung ist jedoch nicht trivial. Automatisierte Builds werden in einer anderen Umgebung ausgeführt, in der die Installation vonruntime dependencies und die Konfiguration vonexternal services möglicherweise anders sind als in Ihrer lokalen und Entwicklungsumgebung.

Docker ist eine Containerisierungsplattform, die darauf abzielt, die Probleme der Standardisierung der Umgebung zu vereinfachen, sodass die Bereitstellung von Anwendungen auch standardisiert werden kann (find out more about Docker). Mit Docker können Entwickler Produktionsumgebungen auf lokalen Computern simulieren, indem Anwendungskomponenten in lokalen Containern ausgeführt werden. Diese Container können mitDocker Compose unabhängig von der Anwendung und dem zugrunde liegenden Betriebssystem leicht automatisiert werden.

In diesem Tutorial wird Docker Compose verwendet, um die Automatisierung von CI-Workflows zu demonstrieren.

Wir werden eine dockerisierte Python-Anwendung vom Typ "Hallo Welt" und ein Bash-Testskript erstellen. Für die Ausführung der Python-Anwendung sind zwei Container erforderlich: einer für die App selbst und ein Redis-Container für den Speicher, der als Abhängigkeit für die App erforderlich ist.

Anschließend wird das Testskript in einem eigenen Container angedockt und die gesamte Testumgebung in einedocker-compose.test.yml-Datei verschoben, damit wir sicherstellen können, dass jede Testausführung in einer neuen und einheitlichen Anwendungsumgebung ausgeführt wird.

Dieser Ansatz zeigt, wie Sie bei jedem Test eine identische, frische Testumgebung für Ihre Anwendung erstellen können, einschließlich ihrer Abhängigkeiten.

So automatisieren wir die CI-Workflows unabhängig von der zu testenden Anwendung und der zugrunde liegenden Infrastruktur.

Voraussetzungen

Bevor Sie beginnen, benötigen Sie:

[[Schritt-1 - Erstellen der Python-Anwendung "Hallo Welt"] == Schritt 1 - Erstellen Sie die Python-Anwendung "Hallo Welt"

In diesem Schritt erstellen wir eine einfache Python-Anwendung als Beispiel für den Anwendungstyp, den Sie mit diesem Setup testen können.

Erstellen Sie ein neues Verzeichnis für unsere Anwendung, indem Sie Folgendes ausführen:

cd ~
mkdir hello_world
cd hello_world

Bearbeiten Sie eine neue Dateiapp.py mitnano:

nano app.py

Fügen Sie den folgenden Inhalt hinzu:

app.py

from flask import Flask
from redis import Redis




app = Flask(__name__)
redis = Redis(host="redis")




@app.route("/")
def hello():
    visits = redis.incr('counter')
    html = "

Hello World!

" \ "Visits: {visits}" \ "
" return html.format(visits=visits) if __name__ == "__main__": app.run(host="0.0.0.0", port=80)

Speichern und schließen Sie die Datei, wenn Sie fertig sind.

app.py ist eine Webanwendung, die aufFlask basiert und eine Verbindung zu einem Redis-Datendienst herstellt. Die Zeilevisits = redis.incr('counter') erhöht die Anzahl der Besuche und behält diesen Wert in Redis bei. Schließlich wird eineHello World-Nachricht mit der Anzahl der Besuche in HTML zurückgegeben.

Unsere Anwendung hat zwei Abhängigkeiten,Flask undRedis, die Sie in den ersten beiden Zeilen sehen können. Diese Abhängigkeiten müssen definiert werden, bevor wir die Anwendung ausführen können.

Öffne eine neue Datei:

nano requirements.txt

Fügen Sie den Inhalt hinzu:

requirements.txt

Flask
Redis

Speichern und schließen Sie die Datei, wenn Sie fertig sind. Nachdem wir unsere Anforderungen definiert haben, die wir später indocker-compose.yml festlegen werden, sind wir bereit für den nächsten Schritt.

[[Schritt-2 - Dockerize-the-Hello-World-Quot-Anwendung]] == Schritt 2 - Dockerisieren Sie die "Hello World" -Anwendung

Docker verwendet eine Datei mit dem NamenDockerfile, um die erforderlichen Schritte zum Erstellen eines Docker-Images für eine bestimmte Anwendung anzugeben. Bearbeiten Sie eine neue Datei:

nano Dockerfile

Fügen Sie den folgenden Inhalt hinzu:

Dockerfile

FROM python:2.7


WORKDIR /app


ADD requirements.txt /app/requirements.txt
RUN pip install -r requirements.txt


ADD app.py /app/app.py


EXPOSE 80


CMD ["python", "app.py"]

Analysieren wir die Bedeutung jeder Zeile:

  • FROM python:2.7: Gibt an, dass unser Anwendungsbild "Hello World" aus dem offiziellen Docker-Image vonpython:2.7erstellt wurde

  • WORKDIR /app: Setzt das Arbeitsverzeichnis im Docker-Image auf/app

  • ADD requirements.txt /app/requirements.txt: Fügt die Dateirequirements.txt zu unserem Docker-Image hinzu

  • RUN pip install -r requirements.txt: Installiert diepip-Abhängigkeiten der Anwendung

  • ADD app.py /app/app.py: Fügt unseren Anwendungsquellcode zum Docker-Image hinzu

  • EXPOSE 80: Gibt an, dass unsere Anwendung über Port 80 (den öffentlichen Standard-Webport) erreichbar ist.

  • CMD ["python", "app.py"]: Der Befehl, der unsere Anwendung startet

Speichern und schließen Sie die Datei. DieseDockerfile-Datei enthält alle Informationen, die zum Erstellen der Hauptkomponente unserer Anwendung „Hello World“ erforderlich sind.

Die Abhängigkeit

Jetzt kommen wir zum komplexeren Teil des Beispiels. Unsere Anwendung erfordert Redis als externen Dienst. Dies ist die Art von Abhängigkeit, die in einer herkömmlichen Linux-Umgebung möglicherweise nur schwer auf die gleiche Weise eingerichtet werden kann. Mit Docker Compose können wir sie jedoch jedes Mal auf wiederholbare Weise einrichten.

Erstellen Sie einedocker-compose.yml-Datei, um Docker Compose zu verwenden.

Bearbeiten Sie eine neue Datei:

nano docker-compose.yml

Fügen Sie den folgenden Inhalt hinzu:

docker-compose.yml

web:
  build: .
  dockerfile: Dockerfile
  links:
    - redis
  ports:
    - "80:80"
redis:
  image: redis

In dieser Docker Compose-Datei wird angegeben, wie die Anwendung „Hello World“ lokal in zwei Docker-Containern hochgefahren wird.

Es definiert zwei Container,web undredis.

  • web verwendet das aktuelle Verzeichnis für den Kontext vonbuildund erstellt unsere Python-Anwendung aus der soeben erstellten DateiDockerfile. Dies ist ein lokales Docker-Image, das wir nur für unsere Python-Anwendung erstellt haben. Es definiert eine Verknüpfung zumredis-Container, um Zugriff auf dieredis-Container-IP zu erhalten. Über die öffentliche IP-Adresse Ihres Ubuntu-Servers können Sie auch über das Internet auf Port 80 zugreifen

  • redis wird von einem öffentlichen Standard-Docker-Image mit dem Namenredis ausgeführt.

Speichern und schließen Sie die Datei, wenn Sie fertig sind.

[[Schritt 3 - Bereitstellung der "Hallo Welt" -Anwendung]] == Schritt 3 - Bereitstellung der Anwendung "Hallo Welt"

In diesem Schritt stellen wir die Anwendung bereit und am Ende wird sie über das Internet zugänglich sein. Für die Zwecke Ihres Bereitstellungsworkflows können Sie dies als Entwicklungs-, Staging- oder Produktionsumgebung betrachten, da Sie die Anwendung mehrmals auf dieselbe Weise bereitstellen können.

Mit den Dateiendocker-compose.yml undDockerfile können Sie die Bereitstellung lokaler Umgebungen automatisieren, indem Sie Folgendes ausführen:

docker-compose -f ~/hello_world/docker-compose.yml build
docker-compose -f ~/hello_world/docker-compose.yml up -d

In der ersten Zeile wird unser lokales Anwendungsimage aus der DateiDockerfileerstellt. In der zweiten Zeile werden die Containerweb undredis im Dämonmodus (-d) ausgeführt, wie in der Dateidocker-compose.ymlangegeben.

Überprüfen Sie, ob die Anwendungscontainer erstellt wurden, indem Sie Folgendes ausführen:

docker ps

Dies sollte zwei laufende Container mit den Namenhelloworld_web_1 undhelloworld_redis_1 anzeigen.

Überprüfen wir, ob die Anwendung aktiv ist. Wir können die IP deshelloworld_web_1 Containers erhalten, indem wir Folgendes ausführen:

WEB_APP_IP=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' helloworld_web_1)
echo $WEB_APP_IP

Überprüfen Sie, ob die Webanwendung die richtige Nachricht zurückgibt:

curl http://${WEB_APP_IP}:80

Dies sollte ungefähr so ​​aussehen:

Ausgabe

Hello World!

Visits: 2

Die Anzahl der Besuche wird jedes Mal erhöht, wenn Sie diesen Endpunkt erreichen. Sie können auch über Ihren Browser auf die Anwendung „Hello World“ zugreifen, indem Sie die öffentliche IP-Adresse Ihres Ubuntu-Servers aufrufen.

Anpassen für Ihre eigene Anwendung

Der Schlüssel zum Einrichten Ihrer eigenen Anwendung besteht darin, Ihre Anwendung in einem eigenen Docker-Container abzulegen und jede Abhängigkeit in einem eigenen Container auszuführen. Anschließend können Sie die Beziehungen zwischen den Containern mit Docker Compose definieren, wie im Beispiel gezeigt. Docker Compose wird in diesenDocker Compose article ausführlicher behandelt.

Ein weiteres Beispiel dafür, wie eine Anwendung über mehrere Container ausgeführt werden kann, finden Sie in diesem Artikel zum Ausführen vonWordPress and phpMyAdmin with Docker Compose.

[[Schritt 4 - Erstellen des Testskripts] == Schritt 4 - Erstellen des Testskripts

Jetzt erstellen wir ein Testskript für Ihre Python-Anwendung. Dies ist ein einfaches Skript, das die HTTP-Ausgabe der Anwendung überprüft. Das Skript ist ein Beispiel für den Testtyp, den Sie möglicherweise als Teil Ihres Bereitstellungsprozesses für die kontinuierliche Integration ausführen möchten.

Bearbeiten Sie eine neue Datei:

nano test.sh

Fügen Sie den folgenden Inhalt hinzu:

test.sh

sleep 5
if curl web | grep -q 'Visits: '; then
  echo "Tests passed!"
  exit 0
else
  echo "Tests failed!"
  exit 1
fi

test.sh Tests für die grundlegende Webkonnektivität unserer Anwendung "Hello World". Es verwendet cURL, um die Anzahl der Besuche abzurufen und darüber zu berichten, ob der Test bestanden wurde oder nicht.

[[Schritt-5 - Erstellen der Testumgebung]] == Schritt 5 - Erstellen der Testumgebung

Um unsere Anwendung zu testen, müssen wir eine Testumgebung bereitstellen. Außerdem möchten wir sicherstellen, dass es mit der Live-Anwendungsumgebung identisch ist, die wir inStep 3 erstellt haben.

Zuerst müssen wir unser Testskript andocken, indem wir eine neue Dockerfile-Datei erstellen. Bearbeiten Sie eine neue Datei:

nano Dockerfile.test

Fügen Sie den folgenden Inhalt hinzu:

Dockerfile.test

FROM ubuntu:xenial


RUN apt-get update && apt-get install -yq curl && apt-get clean


WORKDIR /app


ADD test.sh /app/test.sh


CMD ["bash", "test.sh"]

Dockerfile.test erweitert das offizielle Image vonubuntu:xenial, um die Abhängigkeit voncurlzu installieren, fügttests.sh zum Image-Dateisystem hinzu und gibt den BefehlCMDan, mit dem das Testskript ausgeführt wird Bash.

Sobald unsere Tests Dockerized sind, können sie auf reproduzierbare und agnostische Weise ausgeführt werden.

Der nächste Schritt ist die Verknüpfung unseres Testcontainers mit unserer Anwendung „Hello World“. Hier kommt Docker Compose wieder zur Hilfe. Bearbeiten Sie eine neue Datei:

nano docker-compose.test.yml

Fügen Sie den folgenden Inhalt hinzu:

docker-compose.test.yml

sut:
  build: .
  dockerfile: Dockerfile.test
  links:
    - web
web:
  build: .
  dockerfile: Dockerfile
  links:
    - redis
redis:
  image: redis

In der zweiten Hälfte der Docker Compose-Datei werden die Hauptanwendungwebund ihre Abhängigkeitredisauf dieselbe Weise wie in der vorherigen Dateidocker-compose.ymlbereitgestellt. Dies ist der Teil der Datei, der die Containerweb undredis angibt. Der einzige Unterschied besteht darin, dass derweb-Container Port 80 nicht mehr verfügbar macht, sodass die Anwendung während der Tests nicht über das öffentliche Internet verfügbar ist. Sie sehen also, dass wir die Anwendung und ihre Abhängigkeiten genauso erstellen wie in der Live-Bereitstellung.

Diedocker-compose.test.yml-Datei definiert auch einensut-Container (benannt nachsystem under tests), der für die Ausführung unserer Integrationstests verantwortlich ist. Der Containersut gibt das aktuelle Verzeichnis als unser Verzeichnisbuildund unsere DateiDockerfile.testan. Es ist mit dem Containerwebverknüpft, sodass die IP-Adresse des Anwendungscontainers für unser Skripttest.shzugänglich ist.

Anpassen für Ihre eigene Anwendung

Beachten Sie, dassdocker-compose.test.yml möglicherweise Dutzende externer Dienste und mehrere Testcontainer enthält. Docker kann alle diese Abhängigkeiten auf einem einzelnen Host ausführen, da jeder Container das zugrunde liegende Betriebssystem gemeinsam nutzt.

Wenn Sie mehr Tests für Ihre Anwendung ausführen müssen, können Sie zusätzliche Docker-Dateien für diese erstellen, ähnlich der oben gezeigtenDockerfile.test-Datei.

Anschließend können Sie zusätzliche Container unterhalb dessut-Containers in derdocker-compose.test.yml-Datei hinzufügen und auf die zusätzlichen Docker-Dateien verweisen.

[[Schritt-6 -—- Test-the-Hello-World-Quot-Anwendung]] == Schritt 6 - Testen Sie die Anwendung „Hello World“

Wenn wir die Docker-Ideen von lokalen Umgebungen auf Testumgebungen ausweiten, können wir unsere Anwendung mithilfe von Docker automatisiert testen, indem wir Folgendes ausführen:

docker-compose -f ~/hello_world/docker-compose.test.yml -p ci build

Dieser Befehl erstellt die lokalen Bilder, die vondocker-compose.test.yml benötigt werden. Beachten Sie, dass wir-f verwenden, um aufdocker-compose.test.yml zu zeigen, und-p, um einen bestimmten Projektnamen anzugeben.

Starten Sie jetzt Ihre neue Testumgebung, indem Sie Folgendes ausführen:

docker-compose -f ~/hello_world/docker-compose.test.yml -p ci up -d
OutputCreating ci_redis_1
Creating ci_web_1
Creating ci_sut_1

Überprüfen Sie die Ausgabe dessut-Containers, indem Sie Folgendes ausführen:

docker logs -f ci_sut_1

Ausgabe

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    42  100    42    0     0   3902      0 --:--:-- --:--:-- --:--:--  4200
Tests passed!

Überprüfen Sie abschließend den Exit-Code dessut-Containers, um festzustellen, ob Ihre Tests bestanden wurden:

docker wait ci_sut_1

Ausgabe

0

Nach der Ausführung dieses Befehls beträgt der Wert von$?0, wenn die Tests bestanden wurden. Ansonsten sind unsere Anwendungstests fehlgeschlagen.

Beachten Sie, dass andere CI-Tools unser Code-Repository klonen und diese wenigen Befehle ausführen können, um zu überprüfen, ob die Tests mit den neuesten Bits Ihrer Anwendung bestanden werden, ohne sich um Laufzeitabhängigkeiten oder externe Dienstkonfigurationen zu sorgen.

Das ist es! Wir haben unseren Test erfolgreich in einer frisch gebauten Umgebung durchgeführt, die mit unserer Produktionsumgebung identisch ist.

Fazit

Dank Docker und Docker Compose konnten wir das Erstellen einer Anwendung (Dockerfile), das Bereitstellen einer lokalen Umgebung (docker-compose.yml), das Erstellen eines Testabbilds (Dockerfile.test) und das Ausführen automatisieren (Integrations-) Tests (docker-compose.test.yml) für jede Anwendung.

Die Verwendung derdocker-compose.test.yml-Datei zum Testen hat insbesondere folgende Vorteile: Der Testprozess ist:

  • Automatable: Die Art und Weise, wie ein Tooldocker-compose.test.yml ausführt, ist unabhängig von der zu testenden Anwendung

  • Light-weight: Hunderte von externen Diensten können auf einem einzelnen Host bereitgestellt werden, wodurch komplexe (Integrations-) Testumgebungen simuliert werden

  • Agnostic: Vermeiden Sie die Sperrung von CI-Anbietern, und Ihre Tests können in jeder Infrastruktur und auf jedem Betriebssystem ausgeführt werden, das Docker unterstützt

  • Immutable: Tests, die auf Ihrem lokalen Computer bestanden werden, werden in Ihrem CI-Tool bestanden

Dieses Tutorial zeigt ein Beispiel für das Testen einer einfachen „Hello World“ -Anwendung.

Jetzt ist es an der Zeit, Ihre eigenen Anwendungsdateien zu verwenden, Ihre eigenen Anwendungstestskripte anzudocken und Ihre eigenendocker-compose.test.ymlzu erstellen, um Ihre Anwendung in einer frischen und unveränderlichen Umgebung zu testen.