| Paket | NuGet |
|---|---|
| FluentDocker | |
| Microsoft-Test | |
| XUnit-Test |
Diese Bibliothek ermöglicht docker und docker-compose Interaktionen mithilfe einer Fluent-API . Es wird auf Linux, Windows und Mac unterstützt. Es unterstützt auch die alten docker-machine Interaktionen.
Beispiel für die Nutzung der Fluent API
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ )
. Build ( )
. Start ( ) )
{
var config = container . GetConfiguration ( true ) ;
Assert . AreEqual ( ServiceRunningState . Running , config . State . ToServiceState ( ) ) ;
}Dadurch wird ein Postgres gestartet und darauf gewartet, dass es bereit ist. Um Compose zu verwenden, gehen Sie einfach so vor:
HINWEIS: Verwenden Sie AssumeComposeVersion(ComposeVersion.V2), um das V2-Verhalten zu verwenden. Der Standardwert ist weiterhin V1 (wird später in diesem Jahr auf den Standardwert V2 geändert).
var file = Path . Combine ( Directory . GetCurrentDirectory ( ) ,
( TemplateString ) "Resources/ComposeTests/WordPress/docker-compose.yml" ) ;
// @formatter:off
using ( var svc = new Builder ( )
. UseContainer ( )
. UseCompose ( )
. FromFile ( file )
. RemoveOrphans ( )
. WaitForHttp ( "wordpress" , "http://localhost:8000/wp-admin/install.php" )
. Build ( ) . Start ( ) )
// @formatter:on
{
// We now have a running WordPress with a MySql database
var installPage = await "http://localhost:8000/wp-admin/install.php" . Wget ( ) ;
Assert . IsTrue ( installPage . IndexOf ( "https://wordpress.org/" , StringComparison . Ordinal ) != - 1 ) ;
Assert . AreEqual ( 1 , svc . Hosts . Count ) ; // The host used by compose
Assert . AreEqual ( 2 , svc . Containers . Count ) ; // We can access each individual container
Assert . AreEqual ( 2 , svc . Images . Count ) ; // And the images used.
}:bulb Hinweis für Linux-Benutzer: Docker erfordert standardmäßig Sudo und die Bibliothek geht standardmäßig davon aus, dass der ausführende Benutzer Sudo nicht ausführen muss, um mit dem Docker-Daemon zu kommunizieren. Weitere Beschreibungen finden Sie im Kapitel „Mit dem Docker-Daemon sprechen“ .
Die Fluent- API baut einen oder mehrere Dienste auf. Jeder Dienst kann zusammengesetzt oder einzeln sein. Daher ist es beispielsweise möglich, mehrere Docker-Compose- basierte Dienste zu starten und jeden von ihnen als einen einzelnen Dienst zu verwalten oder alle zugrunde liegenden Dienste für jeden Docker-Compose -Dienst einzubinden und zu nutzen. Es ist auch möglich, Dienste direkt zu nutzen, z
var file = Path . Combine ( Directory . GetCurrentDirectory ( ) ,
( TemplateString ) "Resources/ComposeTests/WordPress/docker-compose.yml" ) ;
using ( var svc = new DockerComposeCompositeService ( DockerHost , new DockerComposeConfig
{
ComposeFilePath = new List < string > { file } , ForceRecreate = true , RemoveOrphans = true ,
StopOnDispose = true
} ) )
{
svc . Start ( ) ;
// We now have a running WordPress with a MySql database
var installPage = await $ "http://localhost:8000/wp-admin/install.php" . Wget ( ) ;
Assert . IsTrue ( installPage . IndexOf ( "https://wordpress.org/" , StringComparison . Ordinal ) != - 1 ) ;
}Das obige Beispiel erstellt einen Docker-Compose- Dienst aus einer einzelnen Compose-Datei. Wenn der Dienst verworfen wird, werden alle zugrunde liegenden Dienste automatisch gestoppt.
Die Bibliothek wird von .NET Full 4.51 Framework und höher, .NET Standard 1.6, 2.0 unterstützt. Es ist in drei dünne Schichten unterteilt, jede Schicht ist zugänglich:
Bei den meisten Dienstmethoden handelt es sich um Erweiterungsmethoden, die nicht fest in den Dienst selbst integriert sind, wodurch sie leichtgewichtig und anpassbar sind. Da alles zugänglich ist, ist es beispielsweise einfach, eine Erweiterungsmethode für einen Dienst hinzuzufügen, der die Layer-1-Befehle verwendet, um Funktionalität bereitzustellen.
Ich freue mich über Beiträge, obwohl es noch keine Beitragsrichtlinie gibt. Stellen Sie sicher, dass Sie sich bei der Durchführung der Pull-Anfragen an .editorconfig halten. Andernfalls schlägt der Build fehl. Ich werde früher oder später in diesem Jahr mit einer echten Richtlinie aktualisieren.
Für alle Befehle ist eine DockerUri erforderlich. Es ist die URI zum Docker-Daemon, entweder lokal oder remote. Es kann erkennbar oder fest codiert sein. Die Erkennung des lokalen DockerUri kann durch erfolgen
var hosts = new Hosts ( ) . Discover ( ) ;
var _docker = hosts . FirstOrDefault ( x => x . IsNative ) ?? hosts . FirstOrDefault ( x => x . Name == "default" ) ; Das ausgeschnittene Beispiel sucht nach nativen oder „nativen“ Docker-Beta-Hosts. Wenn nicht, wählen Sie die Docker-Maschine „Standard“ als Host aus. Wenn Sie eine Docker-Maschine verwenden und keine Maschine vorhanden ist oder nicht gestartet ist, können Sie ganz einfach eine Docker-Maschine erstellen/starten, z. B. mit "test-machine".Create(1024,20000000,1) . Dadurch wird eine Docker-Maschine mit dem Namen „test-machine“ mit 1 GB RAM, 20 GB Festplatte und einer CPU erstellt.
Es ist nun möglich, die URI zur Kommunikation über die Befehle zu verwenden. Um beispielsweise die Version der Client- und Server-Docker-Binärdateien abzurufen:
var result = _docker . Host . Version ( _docker . Certificates ) ;
Debug . WriteLine ( result . Data ) ; // Will Print the Client and Server Version and API Versions respectively. Alle Befehle geben eine CommandResponse zurück, so dass der Erfolgsfaktor anhand von response.Success überprüft werden kann. Wenn mit dem Befehl Daten verknüpft sind, werden diese in der Eigenschaft response.Data “ zurückgegeben.
Dann ist es einfach, wie unten beschrieben, einen Container mithilfe der Befehle zu starten, zu stoppen und zu löschen. Unten wird ein Container gestartet, ein PS darauf ausgeführt und anschließend gelöscht.
var id = _docker . Host . Run ( "nginx:latest" , null , _docker . Certificates ) . Data ;
var ps = _docker . Host . Ps ( null , _docker . Certificates ) . Data ;
_docker . Host . RemoveContainer ( id , true , true , null , _docker . Certificates ) ; Bei der Ausführung unter Windows kann man wählen, ob man Linux oder einen Windows-Container ausführen möchte. Verwenden Sie den LinuxDaemon oder WindowsDaemon um zu steuern, mit welchem Daemon kommuniziert werden soll.
_docker . LinuxDaemon ( ) ; // ensures that it will talk to linux daemon, if windows daemon it will switch Einige Befehle geben einen Datenstrom zurück, wenn z. B. Ereignisse oder Protokolle mithilfe eines kontinuierlichen Datenstroms benötigt werden. Streams können in Hintergrundaufgaben verwendet werden und unterstützen CancellationToken . Das folgende Beispiel erstellt ein Protokoll.
using ( var logs = _docker . Host . Logs ( id , _docker . Certificates ) )
{
while ( ! logs . IsFinished )
{
var line = logs . TryRead ( 5000 ) ; // Do a read with timeout
if ( null == line )
{
break ;
}
Debug . WriteLine ( line ) ;
}
}Für Befehle gibt es Hilfsmethoden. Es gibt sie in verschiedenen Ausprägungen wie Networking usw. Zum Beispiel beim Lesen eines Protokolls bis zum Ende:
using ( var logs = _docker . Host . Logs ( id , _docker . Certificates ) )
{
foreach ( var line in logs . ReadToEnd ( ) )
{
Debug . WriteLine ( line ) ;
}
} Die höchste Ebene dieser Bibliothek ist die Fluent-API, mit der Sie Maschinen, Bilder und Container definieren und steuern können. Um beispielsweise einen Load Balancer mit zwei NodeJS-Servern einzurichten, kann das Lesen von einem Redis-Server wie folgt aussehen (Knoten-Image wird benutzerdefiniert erstellt, wenn es nicht im Repository gefunden wird).
var fullPath = ( TemplateString ) @"${TEMP}/fluentdockertest/${RND}" ;
var nginx = Path . Combine ( fullPath , "nginx.conf" ) ;
Directory . CreateDirectory ( fullPath ) ;
typeof ( NsResolver ) . ResourceExtract ( fullPath , "index.js" ) ;
using ( var services = new Builder ( )
// Define custom node image to be used
. DefineImage ( "mariotoffia/nodetest" ) . ReuseIfAlreadyExists ( )
. From ( "ubuntu" )
. Maintainer ( "Mario Toffia <[email protected]>" )
. Run ( "apt-get update &&" ,
"apt-get -y install curl &&" ,
"curl -sL https://deb.nodesource.com/setup | sudo bash - &&" ,
"apt-get -y install python build-essential nodejs" )
. Run ( "npm install -g nodemon" )
. Add ( "emb:Ductus.FluentDockerTest/Ductus.FluentDockerTest.MultiContainerTestFiles/package.txt" ,
"/tmp/package.json" )
. Run ( "cd /tmp && npm install" )
. Run ( "mkdir -p /src && cp -a /tmp/node_modules /src/" )
. UseWorkDir ( "/src" )
. Add ( "index.js" , "/src" )
. ExposePorts ( 8080 )
. Command ( "nodemon" , "/src/index.js" ) . Builder ( )
// Redis Db Backend
. UseContainer ( ) . WithName ( "redis" ) . UseImage ( "redis" ) . Builder ( )
// Node server 1 & 2
. UseContainer ( ) . WithName ( "node1" ) . UseImage ( "mariotoffia/nodetest" ) . Link ( "redis" ) . Builder ( )
. UseContainer ( ) . WithName ( "node2" ) . UseImage ( "mariotoffia/nodetest" ) . Link ( "redis" ) . Builder ( )
// Nginx as load balancer
. UseContainer ( ) . WithName ( "nginx" ) . UseImage ( "nginx" ) . Link ( "node1" , "node2" )
. CopyOnStart ( nginx , "/etc/nginx/nginx.conf" )
. ExposePort ( 80 ) . Builder ( )
. Build ( ) . Start ( ) )
{
Assert . AreEqual ( 4 , services . Containers . Count ) ;
var ep = services . Containers . First ( x => x . Name == "nginx" ) . ToHostExposedEndpoint ( "80/tcp" ) ;
Assert . IsNotNull ( ep ) ;
var round1 = $ "http:// { ep . Address } : { ep . Port } " . Wget ( ) ;
Assert . AreEqual ( "This page has been viewed 1 times!" , round1 ) ;
var round2 = $ "http:// { ep . Address } : { ep . Port } " . Wget ( ) ;
Assert . AreEqual ( "This page has been viewed 2 times!" , round2 ) ;
}Im obigen Beispiel wird eine Docker-Datei für das Knoten-Image definiert und erstellt. Anschließend werden Vanilla Redis und Nginx verwendet. Wenn Sie nur eine vorhandene Docker-Datei verwenden möchten, können Sie dies wie folgt tun.
using ( var services = new Builder ( )
. DefineImage ( "mariotoffia/nodetest" ) . ReuseIfAlreadyExists ( )
. FromFile ( "/tmp/Dockerfile" )
. Build ( ) . Start ( ) )
{
// Container either build to reused if found in registry and started here.
} Die Fluent-API unterstützt die Definition einer Docker-Maschine bis hin zu einer Reihe von Docker-Instanzen. Es verfügt über integrierte Unterstützung, um beispielsweise auf einen bestimmten Port oder einen Prozess innerhalb des Containers zu warten, bevor Build() abgeschlossen ist, und kann daher sicher innerhalb einer using-Anweisung verwendet werden. Wenn Sie Wartezeiten usw. speziell verwalten, können Sie den Container jederzeit erstellen und starten und Erweiterungsmethoden verwenden, um das Warten auf den Container selbst durchzuführen.
Um einen Container zu erstellen, lassen Sie einfach den Anfang weg. Zum Beispiel:
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( ) )
{
Assert . AreEqual ( ServiceRunningState . Stopped , container . State ) ;
} In diesem Beispiel wird ein Container mit Postgres erstellt und eine Umgebungsvariable konfiguriert. Innerhalb der using-Anweisung ist es möglich, den IContainerService zu starten. Daher wird jeder erstellte Container in einen IContainerService eingeschlossen. Es ist auch möglich IHostService.GetContainers(...) zu verwenden, um die erstellten, laufenden und beendeten Container abzurufen. Über den IHostService ist es auch möglich, alle Bilder im lokalen Repository abzurufen, um daraus Container zu erstellen.
Wenn Sie einen einzelnen Container ausführen möchten, verwenden Sie die Startmethode Fluent oder Container Service. Zum Beispiel:
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( )
. Start ( ) )
{
var config = container . GetConfiguration ( ) ;
Assert . AreEqual ( ServiceRunningState . Running , container . State ) ;
Assert . IsTrue ( config . Config . Env . Any ( x => x == "POSTGRES_PASSWORD=mysecretpassword" ) ) ;
} Standardmäßig wird der Container gestoppt und gelöscht, wenn die Dispose-Methode ausgeführt wird. Um den Container im Archiv zu behalten, verwenden Sie KeepContainer() auf der Fluent-API. Wenn Dispose() aufgerufen wird, wird es gestoppt, aber nicht gelöscht. Es ist auch möglich, den Betrieb nach der Entsorgung fortzusetzen.
Es ist möglich, Ports sowohl explizit als auch zufällig freizugeben. In beiden Fällen ist es möglich, die IP (im Falle einer Maschine) und den Port (im Falle eines zufälligen Ports) aufzulösen, die im Code verwendet werden sollen. Zum Beispiel:
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 40001 , 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( )
. Start ( ) )
{
var endpoint = container . ToHostExposedEndpoint ( "5432/tcp" ) ;
Assert . AreEqual ( 40001 , endpoint . Port ) ;
} Hier ordnen wir den Container-Port 5432 explizit dem Host-Port 40001 zu. Beachten Sie die Verwendung von container.ToHostExposedEndpoint(...) . Dies dient dazu, immer eine funktionierende IP und einen funktionierenden Port für die Kommunikation mit dem Docker-Container aufzulösen. Es ist auch möglich, einen zufälligen Port zuzuordnen, dh Docker einen verfügbaren Port auswählen zu lassen. Zum Beispiel:
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( )
. Start ( ) )
{
var endpoint = container . ToHostExposedEndpoint ( "5432/tcp" ) ;
Assert . AreNotEqual ( 0 , endpoint . Port ) ;
} Der einzige Unterschied besteht darin, dass nur ein Argument verwendet wird, wenn ExposePort(...) zum Konfigurieren des Containers verwendet wurde. Ansonsten gilt die gleiche Verwendung und ist somit für den Code transparent.
Um zu wissen, wann ein bestimmter Dienst aktiv ist, bevor Sie beispielsweise eine Verbindung zu ihm herstellen. Es ist möglich, darauf zu warten, dass ein bestimmter Port geöffnet wird. Zum Beispiel:
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ )
. Build ( )
. Start ( ) )
{
var config = container . GetConfiguration ( true ) ;
Assert . AreEqual ( ServiceRunningState . Running , config . State . ToServiceState ( ) ) ;
}Im obigen Beispiel warten wir darauf, dass der Container-Port 5432 innerhalb von 30 Sekunden geöffnet wird. Wenn dies fehlschlägt, wird eine Ausnahme ausgelöst und der Container wird entsorgt und entfernt (da wir keine Konfiguration für die Aufbewahrung von Containern usw. haben).
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ , "127.0.0.1" )
. Build ( )
. Start ( ) )
{
var config = container . GetConfiguration ( true ) ;
Assert . AreEqual ( ServiceRunningState . Running , config . State . ToServiceState ( ) ) ;
}Manchmal ist es nicht möglich, den Container direkt über die lokale IP und den lokalen Port zu erreichen, stattdessen hat der Container beispielsweise einen offengelegten Port auf der Loopback-Schnittstelle ( 127.0.0.1 ) und das ist die einzige Möglichkeit, den Container vom Programm aus zu erreichen. Das obige Beispiel erzwingt, dass die Adresse 127.0.0.1 ist, löst aber dennoch den Host-Port auf. Standardmäßig verwendet FluentDocker die Netzwerkprüfung des Containers, um die Netzwerkkonfiguration zu ermitteln.
Manchmal reicht es nicht aus, nur auf einen Port zu warten. Manchmal ist es viel wichtiger, auf einen Containerprozess zu warten. Daher gibt es in der Fluent-API eine Methode zum Warten auf den Prozess sowie eine Erweiterungsmethode für das Containerobjekt. Zum Beispiel:
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. WaitForProcess ( "postgres" , 30000 /*30s*/ )
. Build ( )
. Start ( ) )
{
var config = container . GetConfiguration ( true ) ;
Assert . AreEqual ( ServiceRunningState . Running , config . State . ToServiceState ( ) ) ;
} Im obigen Beispiel gibt Build() die Kontrolle zurück, wenn der Prozess „postgres“ im Container gestartet wurde.
Um Container nutzen zu können, ist es manchmal erforderlich, Volumes im Container auf dem Host bereitzustellen oder einfach vom oder zum Container zu kopieren. Abhängig davon, ob Sie eine Maschine oder Docker ausführen, unterliegt die native Volume-Zuordnung der Einschränkung, dass sie von der virtuellen Maschine aus erreichbar sein muss.
Ein normaler Anwendungsfall besteht beispielsweise darin, dass ein Webserver Inhalte auf einem Docker-Container bereitstellt und der Benutzer Dateien im Host-Dateisystem bearbeitet. In einem solchen Szenario ist es notwendig, ein Docker-Container-Volume auf dem Host bereitzustellen. Zum Beispiel:
const string html = "<html><head>Hello World</head><body><h1>Hello world</h1></body></html>" ;
var hostPath = ( TemplateString ) @"${TEMP}/fluentdockertest/${RND}" ;
Directory . CreateDirectory ( hostPath ) ;
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "nginx:latest" )
. ExposePort ( 80 )
. Mount ( hostPath , "/usr/share/nginx/html" , MountType . ReadOnly )
. Build ( )
. Start ( )
. WaitForPort ( "80/tcp" , 30000 /*30s*/ ) )
{
File . WriteAllText ( Path . Combine ( hostPath , "hello.html" ) , html ) ;
var response = $ "http:// { container . ToHostExposedEndpoint ( "80/tcp" ) } /hello.html" . Wget ( ) ;
Assert . AreEqual ( html , response ) ;
}Im obigen Beispiel wird ein Nginx-Container gestartet und mountet „/usr/share/nginx/html“ auf einem (zufälligen, im temporären Verzeichnis) Hostpfad. Eine HTML-Datei wird in den Hostpfad kopiert und wenn ein HTTP-Abruf zum Nginx-Docker-Container durchgeführt wird, wird dieselbe Datei bereitgestellt.
Manchmal ist es notwendig, Dateien in und aus einem Container zu kopieren. Kopieren Sie beispielsweise eine Konfigurationsdatei, konfigurieren Sie sie und kopieren Sie sie zurück. Ein häufigeres Szenario besteht darin, eine Konfigurationsdatei direkt vor dem Start in den Container zu kopieren. Das Multi-Container-Beispiel kopiert eine Nginx-Konfigurationsdatei unmittelbar vor dem Start. Somit ist es möglich, die manuelle Erstellung einer Docker-Datei und eines Images für eine so einfache Aufgabe zu vermeiden. Verwenden Sie stattdessen einfach z. B. ein offizielles oder benutzerdefiniertes Image, kopieren Sie die Konfiguration und führen Sie es aus.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( )
. Start ( )
. CopyFrom ( "/etc/conf.d" , fullPath ) )
{
var files = Directory . EnumerateFiles ( Path . Combine ( fullPath , "conf.d" ) ) . ToArray ( ) ;
Assert . IsTrue ( files . Any ( x => x . EndsWith ( "pg-restore" ) ) ) ;
Assert . IsTrue ( files . Any ( x => x . EndsWith ( "postgresql" ) ) ) ;
}Das obige Beispiel kopiert ein Verzeichnis aus einem laufenden Container in einen Hostpfad (fullPath). Beachten Sie die Verwendung der Erweiterungsmethode hier, also nicht die Verwendung der Fluent-API (da CopyFrom nach Start() steht). Wenn Sie direkt vor dem Start Dateien aus dem Container kopieren möchten, verwenden Sie stattdessen die Fluent-API.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. CopyOnStart ( "/etc/conf.d" , fullPath )
. Build ( )
. Start ( ) )
{
}Das folgende Beispiel veranschaulicht ein viel häufigeres Szenario, bei dem Dateien in den Container kopiert werden. In diesem Beispiel wird die Erweiterungsmethode anstelle der Fluent-API-Version verwendet. Vor dem Kopieren und direkt nach dem Kopieren wird ein Diff-Snapshot erstellt. In letzterem ist die hello.html vorhanden.
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( )
. Start ( )
. WaitForProcess ( "postgres" , 30000 /*30s*/ )
. Diff ( out before )
. CopyTo ( "/bin" , fullPath ) )
{
var after = container . Diff ( ) ;
Assert . IsFalse ( before . Any ( x => x . Item == "/bin/hello.html" ) ) ;
Assert . IsTrue ( after . Any ( x => x . Item == "/bin/hello.html" ) ) ;
} Manchmal ist es sinnvoll, Dateien in IContainerService.Dispose() zu kopieren (kurz bevor der Container gestoppt wurde). Daher gibt es eine Fluent-API, um sicherzustellen, dass genau das funktioniert.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. CopyOnDispose ( "/etc/conf.d" , fullPath )
. Build ( )
. Start ( ) )
{
} Um einen Container zu analysieren, gibt es Exporterweiterungsmethoden und Fluent-API-Methoden. Am bemerkenswertesten ist die Möglichkeit, einen Container zu exportieren, wenn ein IContainerService entsorgt wird.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. ExportOnDispose ( fullPath )
. Build ( )
. Start ( ) )
{
} Dadurch wird ein Containerexport (TAR-Datei) auf dem Host (fullPath) erstellt. Wenn Sie es lieber explodieren (nicht tarieren) möchten, verwenden Sie stattdessen die Methode ExportExplodedOnDispose . Natürlich können Sie den Container jederzeit exportieren, indem Sie eine Erweiterungsmethode für den Container verwenden.
Ein nützlicher Trick beim Unit-Testen besteht darin, den Containerstatus zu exportieren, wenn der Unit-Test aus irgendeinem Grund fehlschlägt. Daher gibt es eine Fluent-API, die exportiert, wenn eine bestimmte Lambda-Bedingung erfüllt ist. Zum Beispiel:
var failure = false ;
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. ExportOnDispose ( fullPath , svc => failure )
. Build ( )
. Start ( ) )
{
failure = true ;
} Dieses Snippet exportiert den Container, wenn die using-Anweisung den Container entsorgt, da die Fehlervariable auf „true“ gesetzt ist und im ExportOnDispose -Ausdruck verwendet wird.
Alle Dienste können mit Hooks erweitert werden. In ExportOnDispose(path, lambda) wird ein Hook installiert, wenn der Dienststatus festgelegt ist, um das Lambda auszuführen, wenn der Status Removing ist. Es ist möglich, Haken im Handumdrehen zu installieren und zu entfernen. Wenn mehrere Hooks auf derselben Dienstinstanz mit demselben ServiceRunningState registriert sind, werden sie in der Installationsreihenfolge ausgeführt.
Die Hooks eignen sich besonders gut, wenn Sie möchten, dass etwas ausgeführt wird, wenn ein Status für den Dienst festgelegt (oder ausgeführt) wird, z. B. Starting . Die Fluent-API nutzt diese in einigen Situationen, z. B. beim Kopieren von Dateien, beim Exportieren usw.
FluentDocker unterstützt alle Docker-Netzwerkbefehle. Es kann Netzwerke mit _docker.NetworkLs() erkennen, wobei es alle Netzwerke und einige einfache Parameter erkennt, die in NetworkRow definiert sind. Mit _docker.NetworkInspect(network:"networkId") kann es auch prüfen, um tiefere Informationen über das Netzwerk zu erhalten (z. B. welche Container sich im Netzwerk befinden und welche IPAM-Konfiguration).
Um ein neues Netzwerk zu erstellen, verwenden Sie _docker.NetworkCreate("name_of_network") . Es ist auch möglich, NetworkCreateParams bereitzustellen, wo alles angepasst werden kann, z. B. das Erstellen eines Overlay -Netzwerks oder das Ändern der Ipam-Konfiguration. Um ein Netzwerk zu löschen, verwenden Sie einfach _docker.NetworkRm(network:"networkId") .
Beachten Sie, dass Netzwerke nicht gelöscht werden, wenn daran Container angehängt sind!
Wenn ein Netzwerk erstellt wird, ist es möglich, mithilfe von _docker.NetworkConnect("containerId","networkId") einen oder mehrere Container darin abzulegen. Beachten Sie, dass sich Container gleichzeitig in mehreren Netzwerken befinden können und daher Anfragen zwischen isolierten Netzwerken weiterleiten können. Um einen Container von einem Netzwerk zu trennen, führen Sie einfach einen _docker.NetworkDisconnect("containerId","networkId") aus.
Das folgende Beispiel führt einen Container aus, erstellt ein neues Netzwerk und verbindet den laufenden Container mit dem Netzwerk. Anschließend wird der Container getrennt, gelöscht und das Netzwerk gelöscht.
var cmd = _docker . Run ( "postgres:9.6-alpine" , new ContainerCreateParams
{
PortMappings = new [ ] { "40001:5432" } ,
Environment = new [ ] { "POSTGRES_PASSWORD=mysecretpassword" }
} , _certificates ) ;
var container = cmd . Data ;
var network = string . Empty ;
var created = _docker . NetworkCreate ( "test-network" ) ;
if ( created . Success )
network = created . Data [ 0 ] ;
_docker . NetworkConnect ( container , network ) ;
// Container is now running and has address in the newly created 'test-network'
_docker . NetworkDisconnect ( container , id , true /*force*/ ) ;
_docker . RemoveContainer ( container , true , true ) ;
// Now it is possible to delete the network since it has been disconnected from the network
_docker . NetworkRm ( network : network ) ;Es ist auch möglich, einen Fluent Builder zu verwenden, um neue Docker-Netzwerke aufzubauen oder bestehende wiederzuverwenden. Diese können dann beim Erstellen von Containern referenziert werden. Es ist möglich, mehr als ein Docker-Netzwerk aufzubauen und einen Container gleichzeitig an mehr als ein Netzwerk anzuschließen.
using ( var nw = new Builder ( ) . UseNetwork ( "test-network" ) )
{
using (
var container =
new DockerBuilder ( )
. WithImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. ExposePorts ( "5432" )
. UseNetwork ( nw )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ )
. Build ( ) )
{
container . Create ( ) . Start ( ) ;
}
} Der obige Codeausschnitt erstellt ein neues Netzwerk namens test-network und erstellt dann einen Container, der an das test-network angehängt wird. Wenn Dispose() auf nw aufgerufen wird, wird das Netzwerk entfernt. Es ist auch möglich, statische IP- Container-Zuweisungen innerhalb des Netzwerks durch UseIpV4 oder UseIpV6 vorzunehmen. Zum Beispiel:
using ( var nw = Fd . UseNetwork ( "unit-test-nw" )
. UseSubnet ( "10.18.0.0/16" ) . Build ( ) )
{
using (
var container =
Fd . UseContainer ( )
. UseImage ( "postgres:9.6-alpine" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. ExposePort ( 5432 )
. UseNetwork ( nw )
. UseIpV4 ( "10.18.0.22" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ )
. Build ( )
. Start ( ) )
{
var ip = container . GetConfiguration ( ) . NetworkSettings . Networks [ "unit-test-nw" ] . IPAddress ;
Assert . AreEqual ( "10.18.0.22" , ip ) ;
}
} Das obige Beispiel erstellt ein neues Netzwerk „unit-test-nw“ mit dem IP-Bereich 10.18.0.0/16 . Es wird im neuen Behälter verwendet. Die IP für den Container ist auf 10.18.0.22 eingestellt und aufgrund des UseIpV4 -Befehls statisch.
FluentDocker unterstützt die Docker-Volume-Verwaltung sowohl über Befehle als auch über eine Fluent-API. Daher ist es möglich, die im Container verwendete Menge vollständig zu kontrollieren, z. B. ob er entsorgt oder wiederverwendet werden soll, welcher Treiber verwendet werden soll usw.
var volume = _docker . VolumeCreate ( "test-volume" , "local" , opts : {
{ "type" , "nfs" } ,
{ "o=addr" , "192.168.1.1,rw" } ,
{ "device" , ":/path/to/dir" }
} ) ;
var cfg = _docker . VolumeInspect ( _certificates , "test-volume" ) ;
_docker . VolumeRm ( force : true , id : "test-volume" ) ;Das obige Snippet erstellt ein neues Volume mit dem Namen test-volume und ist vom Typ NFS . Anschließend wird das gerade erstellte Volume überprüft und schließlich das Löschen des Volumes erzwungen.
Es ist auch möglich, eine Fluent-API zum Erstellen oder Verwenden von Volumes zu verwenden. Sie können dann beim Bau eines Containers verwendet werden. Dies ist besonders nützlich, wenn die Erstellung von Volumes speziell ist oder die Lebensdauer kontrolliert werden muss.
using ( var vol = new Builder ( ) . UseVolume ( "test-volume" ) . RemoveOnDispose ( ) . Build ( ) )
{
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "postgres:9.6-alpine" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. MountVolume ( vol , "/var/lib/postgresql/data" , MountType . ReadWrite )
. Build ( )
. Start ( ) )
{
var config = container . GetConfiguration ( ) ;
Assert . AreEqual ( 1 , config . Mounts . Length ) ;
Assert . AreEqual ( "test-volume" , config . Mounts [ 0 ] . Name ) ;
}
} Das obige Beispiel erstellt ein neues Volume mit dem Namen test-volume und soll gelöscht werden, wenn Dispose() für IVolumeService aufgerufen wird. Der Container wird erstellt und stellt das neu erstellte Volume im Lese-/Schreibzugriffsmodus in /var/lib/postgresql/data bereit. Da sich der Container im Geltungsbereich der using -Anweisung des Volumes befindet, erstreckt sich seine Lebensdauer über die gesamte Lebensdauer des Containers und wird dann gelöscht.
FluentDocker unterstützt die Verbindung zum Docker-Ereignismechanismus, um die von ihm gesendeten Ereignisse abzuhören.
using ( var events = Fd . Native ( ) . Events ( ) )
{
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "postgres:9.6-alpine" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ )
. Build ( )
. Start ( ) )
{
FdEvent e ;
while ( ( e = events . TryRead ( 3000 ) ) != null )
{
if ( e . Type == EventType . Container && e . Action == EventAction . Start )
break ;
}
}
} Der Ereignis-Listener ist global und kann viele EventAction Typen verarbeiten.
Zum Beispiel
Je nach Aktion kann der Ereignistyp unterschiedlich sein, z. B. ContainerKillEvent für EventAction.Kill . Alle Ereignisse leiten sich von FdEvent ab. Das bedeutet, dass alle gemeinsam genutzten Eigenschaften im Basisereignis und die expliziten Eigenschaften im abgeleiteten Ereignis enthalten sind.
Beispielsweise enthält das „ContainerKillEvent“ die folgenden Eigenschaften:
public sealed class ContainerKillActor : EventActor
{
/// <summary>
/// The image name and label such as "alpine:latest".
/// </summary>
public string Image { get ; set ; }
/// <summary>
/// Name of the container.
/// </summary>
public string Name { get ; set ; }
/// <summary>
/// The signal that the container has been signalled.
/// </summary>
public string Signal { get ; set ; }
} Diese Ereignisschleife kann zum Erfassen von Ereignissen und zum Ansteuern Ihrer instanziierten IService Instanzen verwendet werden. Oder wenn Sie reagieren müssen, z. B. wenn ein Netzwerk hinzugefügt oder gelöscht wird.
Im vollständigen Framework wird die ausführliche Protokollierung mithilfe von System.Diagnostics.Debugger.Log verwendet. Für .net Core wird zur Protokollierung das standardmäßige Microsoft.Extensions.Logging.ILog verwendet. Beide verwenden die Kategorie Ductus.FluentDocker und können daher so konfiguriert werden, dass sie an der Protokollierung teilnehmen oder nicht, und unterschiedliche Protokollierungsziele konfigurieren.
In .net Core können Sie das Protokollierungssegment in der Anwendungskonfigurationsdatei bereitstellen.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Ductus.FluentDocker": "None"
}
}
}
Weitere Informationen finden Sie unter https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1. Für das vollständige Framework sehen Sie sich bitte das in der Appconfig benötigte XML für das vollständige Framework an, das unter https://docs.microsoft.com/en-us/dotnet/framework/wcf/diagnostics/tracing/configuring-tracing beschrieben ist.
Es gibt eine schnelle Möglichkeit, die Protokollierung über ( Ductus.FluentDocker.Services ) Logging.Enabled() oder Logging.Disabled() zu deaktivieren/aktivieren. Dadurch wird die Protokollierung zwangsweise aktiviert/deaktiviert.
Es ist möglich, den Standardmechanismus von FluentDocker zu überschreiben, der die Container-IP aus der Client-Perspektive auflöst, z. B. in WaitForPort . Dies kann auf ContainerBuilder -Basis überschrieben werden.
Das folgende Beispiel überschreibt das Standardverhalten . Wenn null zurückgegeben wird, tritt der Standard- Resolver in Kraft.
using (
var container =
Fd . UseContainer ( )
. UseImage ( "postgres:9.6-alpine" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. ExposePort ( 5432 )
. UseCustomResolver ( (
ports , portAndProto , dockerUri ) =>
{
if ( null == ports || string . IsNullOrEmpty ( portAndProto ) )
return null ;
if ( ! ports . TryGetValue ( portAndProto , out var endpoints ) )
return null ;
if ( null == endpoints || endpoints . Length == 0 )
return null ;
if ( CommandExtensions . IsNative ( ) )
return endpoints [ 0 ] ;
if ( CommandExtensions . IsEmulatedNative ( ) )
return CommandExtensions . IsDockerDnsAvailable ( )
? new IPEndPoint ( CommandExtensions . EmulatedNativeAddress ( ) , endpoints [ 0 ] . Port )
: new IPEndPoint ( IPAddress . Loopback , endpoints [ 0 ] . Port ) ;
if ( Equals ( endpoints [ 0 ] . Address , IPAddress . Any ) && null != dockerUri )
return new IPEndPoint ( IPAddress . Parse ( dockerUri . Host ) , endpoints [ 0 ] . Port ) ;
return endpoints [ 0 ] ;
} )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ )
. Build ( )
. Start ( ) )
{
var state = container . GetConfiguration ( true /*force*/ ) . State . ToServiceState ( ) ;
Assert . AreEqual ( ServiceRunningState . Running , state ) ;
} Es gibt nur begrenzte Unterstützung für die Verwendung der FluentAPI zur Kommunikation mit einem Remote-Docker-Daemon, ohne Docker-Machine zu verwenden. Dies geschieht entweder durch manuelles Erstellen einer Instanz eines DockerHostService oder durch die Verwendung FromUri auf HostBuilder .
using ( var container = Fd . UseHost ( ) .
FromUri ( Settings . DockerUri , isWindowsHost : true ) .
UseContainer ( ) .
Build ( ) )
{
} Das obige Beispiel stellt über eine Einstellung eine Verbindung zu einem benutzerdefinierten DockerUri her und ist ein Windows-Container-Docker-Daemon.
FromUri das einen DockerUri verwendet, um einen IHostService zu erstellen. Diese URL ist willkürlich. Es unterstützt auch andere Eigenschaften ( siehe unten ). public HostBuilder FromUri (
DockerUri uri ,
string name = null ,
bool isNative = true ,
bool stopWhenDisposed = false ,
bool isWindowsHost = false ,
string certificatePath = null ) { /*...*/ }Für alle Parameter werden „vernünftige“ Standardeinstellungen verwendet. In den meisten Fällen reicht die URI aus. Wenn Sie beispielsweise den Zertifikatspfad nicht angeben, wird versucht, ihn aus der Umgebung DOCKER_CERT_PATH abzurufen. Wenn es in der Umgebung nicht gefunden wird, wird es standardmäßig auf „Keine“ gesetzt.
UseHost , der eine instanziierte IHostService Implementierung annimmt. Die Bibliothek unterstützt Docker-Compose, um vorhandene Compose-Dateien zum Rendern von Diensten zu verwenden und deren Lebensdauer zu verwalten.
Das folgende Beispiel enthält eine Compose-Datei, die eine MySql- und eine WordPress-Datei startet. Daher verfügt der einzelne Compose-Dienst über zwei Container- Dienste darunter. Standardmäßig werden die Dienste gestoppt und bereinigt, wenn Dispose() aufgerufen wird. Dies kann durch KeepContainers() in der Fluent -Konfiguration überschrieben werden.
version : ' 3.3 '
services :
db :
image : mysql:5.7
volumes :
- db_data:/var/lib/mysql
restart : always
environment :
MYSQL_ROOT_PASSWORD : somewordpress
MYSQL_DATABASE : wordpress
MYSQL_USER : wordpress
MYSQL_PASSWORD : wordpress
wordpress :
depends_on :
- db
image : wordpress:latest
ports :
- " 8000:80 "
restart : always
environment :
WORDPRESS_DB_HOST : db:3306
WORDPRESS_DB_USER : wordpress
WORDPRESS_DB_PASSWORD : wordpress
volumes :
db_data :Die obige Datei ist die Docker-Compose- Datei zum Zusammenfügen des gesamten Dienstes.
var file = Path . Combine ( Directory . GetCurrentDirectory ( ) ,
( TemplateString ) "Resources/ComposeTests/WordPress/docker-compose.yml" ) ;
using ( var svc = new Builder ( )
. UseContainer ( )
. UseCompose ( )
. FromFile ( file )
. RemoveOrphans ( )
. Build ( ) . Start ( ) )
{
var installPage = await "http://localhost:8000/wp-admin/install.php" . Wget ( ) ;
Assert . IsTrue ( installPage . IndexOf ( "https://wordpress.org/" , StringComparison . Ordinal ) != - 1 ) ;
Assert . AreEqual ( 1 , svc . Hosts . Count ) ;
Assert . AreEqual ( 2 , svc . Containers . Count ) ;
Assert . AreEqual ( 2 , svc . Images . Count ) ;
Assert . AreEqual ( 5 , svc . Services . Count ) ;
}Das obige Snippet konfiguriert den Docker-Compose- Dienst reibungslos und ruft die Installationsseite auf, um zu überprüfen, ob WordPress tatsächlich funktioniert.
Es ist auch möglich, alle Vorgänge auszuführen, die ein einzelner Container unterstützt, z. B. Kopieren, Exportieren und Wartevorgänge. Zum Beispiel:
var file = Path . Combine ( Directory . GetCurrentDirectory ( ) ,
( TemplateString ) "Resources/ComposeTests/WordPress/docker-compose.yml" ) ;
// @formatter:off
using ( new Builder ( )
. UseContainer ( )
. UseCompose ( )
. FromFile ( file )
. RemoveOrphans ( )
. WaitForHttp ( "wordpress" , "http://localhost:8000/wp-admin/install.php" , continuation : ( resp , cnt ) =>
resp . Body . IndexOf ( "https://wordpress.org/" , StringComparison . Ordinal ) != - 1 ? 0 : 500 )
. Build ( ) . Start ( ) )
// @formatter:on
{
// Since we have waited - this shall now always work.
var installPage = await "http://localhost:8000/wp-admin/install.php" . Wget ( ) ;
Assert . IsTrue ( installPage . IndexOf ( "https://wordpress.org/" , StringComparison . Ordinal ) != - 1 ) ;
} Das obige Snippet startet das WordPress-Docker-Compose-Projekt und überprüft die URL http://localhost:8000/wp-admin/install.php, um einen bestimmten Wert im Textkörper zurückzugeben (in diesem Fall „https://wordpress.org“) /"). Wenn nicht, wird 500 zurückgegeben und die WaitForHttp -Funktion wartet 500 Millisekunden, bevor sie erneut aufgerufen wird. Dies funktioniert auch für jedes benutzerdefinierte Lambda. Verwenden Sie stattdessen einfach WaitFor . So ist es beispielsweise möglich, eine Datenbankabfrage durchzuführen, bevor innerhalb des Verwendungsbereichs fortgefahren wird.
Für Linux- und Mac-Benutzer gibt es mehrere Möglichkeiten, sich gegenüber dem Socket zu authentifizieren. FluentDocker unterstützt kein Sudo , Sudo ohne Passwort (Benutzer als NOPASSWD in /etc/sudoer hinzugefügt) oder Sudo mit Passwort. Standardmäßig geht FluentDocker davon aus, dass es ohne Sudo kommunizieren kann. Die Optionen sind global, können aber zur Laufzeit geändert werden.
SudoMechanism . None . SetSudo ( ) ; // This is the default
SudoMechanism . Password . SetSudo ( "<my-sudo-password>" ) ;
SudoMechanism . NoPassword . SetSudo ( ) ;Wenn Sie sudo für die Kommunikation mit dem Docker-Daemon deaktivieren möchten, können Sie einem Docker-Tutorial folgen und den letzten Schritt ausführen, indem Sie Ihren Benutzer zur Docker-Gruppe hinzufügen.
FluentDocker unterstützt die Verbindung zu Remote-Docker-Daemons. Die Fluent API unterstützt z.B
new Builder ( ) . UseHost ( ) . UseMachine ( ) . WithName ( "remote-daemon" )Hierfür ist ein bereits vorab eingerichteter Eintrag in der Docker-Machine -Registrierung erforderlich. Es ist auch möglich, SSH -basierte Docker-Maschinen- Registrierungseinheiten zu definieren, um eine Verbindung zum Remote-Daemon herzustellen.
using (
var container =
new Builder ( ) . UseHost ( )
. UseSsh ( "192.168.1.27" ) . WithName ( "remote-daemon" )
. WithSshUser ( "solo" ) . WithSshKeyPath ( "${E_LOCALAPPDATA}/lxss/home/martoffi/.ssh/id_rsa" )
. UseContainer ( )
. UseImage ( "postgres:9.6-alpine" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( ) )
{
Assert . AreEqual ( ServiceRunningState . Stopped , container . State ) ;
}In diesem Beispiel wird ein neuer Docker-Machine- Registrierungseintrag mit dem Namen remote-daemon erstellt, der SSH mit der IP-Adresse 192.168.1.27 und dem SSH- Benutzer solo verwendet. Wenn bereits ein Eintrag mit dem Namen remote-daemon gefunden wird, wird dieser Eintrag einfach wiederverwendet. Dann erhält es einen IHostService mit den richtigen Zertifikaten und der richtigen URL für den Remote-Daemon. Somit ist es dann möglich, einen Docker-Container auf dem Remote-Daemon zu erstellen, in diesem Fall handelt es sich um das Postgres -Image. Wenn der Container entsorgt wird, wird er wie üblich aus dem Remote-Docker gelöscht. Der IHostService stellt sicher, dass er alle erforderlichen Zertifikate abholt, um die Verbindung zu authentifizieren.
Das obige Beispiel erzeugt diesen Docker-Machine- Registrierungseintrag.
C:Usersmartoffi>docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
remote-daemon * generic Running tcp://192.168.1.27:2376 v18.06.1-ce
Um UseSsh(...) nutzen zu können, muss ein SSH- Tunnel ohne Passwort eingerichtet werden. Darüber hinaus muss dem Benutzer, der den Tunnel nutzt, der Zugriff auf den Docker-Daemon gestattet sein.
Befolgen Sie diese Anleitung zum Einrichten des SSH- Tunnels und stellen Sie sicher, dass der Benutzer auf den Docker-Daemon zugreifen kann.
Erstellen Sie grundsätzlich einen neuen RSA-Schlüssel zur Verwendung mit dem SSH -Tunnel mit ssh-keygen -t rsa und kopieren Sie ihn dann mit ssh-copy-id {username}@{host} auf den Remote-Host.
Bearbeiten Sie /etc/sudoers wie im zweiten Tutorial angegeben.
Wenn dies erledigt ist, können Sie nun über den generischen Treiber oder die oben angegebene Fluent-API auf den Remote-Docker-Daemon zugreifen. Um dasselbe manuell wie im Beispiel angegeben durchzuführen, würde es ungefähr so aussehen.
C:Usersmartoffi>docker-machine.exe create --driver generic --generic-ip-address=192.168.1.27 --generic-ssh-key="%localappdata%/lxss/home/martoffi/.ssh/id_rsa" --generic-ssh-user=solo remote-daemon
Running pre-create checks...
Creating machine...
(remote-daemon) Importing SSH key...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with ubuntu(systemd)...
Installing Docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine.exe env remote-daemon
Nachdem der Registrierungseintrag erstellt wurde, ist es möglich, die Umgebung für den Terminal-Docker festzulegen.
C:Usersmartoffi>docker-machine.exe env remote-daemon
SET DOCKER_TLS_VERIFY=1
SET DOCKER_HOST=tcp://192.168.1.24:2376
SET DOCKER_CERT_PATH=C:Usersmartoffi.dockermachinemachinesremote-daemon
SET DOCKER_MACHINE_NAME=remote-daemon
SET COMPOSE_CONVERT_WINDOWS_PATHS=true
REM Run this command to configure your shell:
REM @FOR /f "tokens=*" %i IN ('docker-machine.exe env remote-daemon') DO @%i
Führen Sie dies aus, damit der Docker-Client den Remote-Docker-Daemon verwendet.
@FOR /f "tokens=*" %i IN ('docker-machine.exe env remote-daemon') DO @%i
Alle Befehle, die die docker Binärdatei verwenden, werden jetzt auf dem Remote-Docker-Daemon ausgeführt.
Beim Erstellen und Abfragen einer Hyper-V-Docker-Maschine über eine Maschine muss der Prozess erhöht werden, da Hyper-V im Standardbenutzermodus nicht auf API-Aufrufe reagiert.
Es ist möglich, eine Integritätsprüfung für den Docker-Container festzulegen, um den Status des Containers basierend auf dieser Aktivität zu melden. Im folgenden Beispiel wird eine Integritätsprüfung verwendet, um festzustellen, ob der Container beendet wurde oder nicht. Es ist möglich, die Konfiguration zu überprüfen (stellen Sie sicher, dass Sie eine Aktualisierung erzwingen) und welchen Status die Gesundheitsprüfung meldet.
using (
var container =
Fd . UseContainer ( )
. UseImage ( "postgres:latest" , force : true )
. HealthCheck ( "exit" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( )
. Start ( ) )
{
var config = container . GetConfiguration ( true ) ;
AreEqual ( HealthState . Starting , config . State . Health . Status ) ;
} Über die Fluent-API und ContainerCreateParams ist es möglich, ulimit für den Docker-Container anzugeben, um beispielsweise die Anzahl der geöffneten Dateien usw. zu begrenzen. Die Verwendung der Fluent-API könnte beispielsweise so aussehen, wenn die Anzahl der geöffneten Dateien auf 2048 (sowohl Soft als auch Hard) beschränkt wird. .
using (
var container =
Fd . UseContainer ( )
. UseImage ( "postgres:latest" , force : true )
. UseUlimit ( Ulimit . NoFile , 2048 , 2048 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( )
. Start ( ) )
{
// Do stuff
} Dieses Repo enthält drei Nuget-Pakete, eines für den fließenden Zugriff, eines für MS-Test-Basisklassen und eines für XUnit-Basisklassen, die beim Testen verwendet werden sollen. Beispielsweise ist es in einem Unit-Test möglich, einen Postgres-Container zu starten und zu warten, bis die Datenbank gestartet ist.
public class PostgresXUnitTests : IClassFixture < PostgresTestBase >
{
[ Fact ]
public void Test ( )
{
// We now have a running postgres
// and a valid connection string to use.
}
} using (
var container =
new DockerBuilder ( )
. WithImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. ExposePorts ( "5432" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ )
. Build ( ) )
{
container . Create ( ) . Start ( ) ;
}Es ist auch möglich, abstrakte Basisklassen wiederzuverwenden, beispielsweise die Postgres-Testbasis, um den Unittest für einen Container zu vereinfachen und sauber zu machen.
[ TestClass ]
public class PostgresMsTests : PostgresTestBase
{
[ TestMethod ]
public void Test ( )
{
// We now have a running postgres
// and a valid connection string to use.
}
} Die FluentDockerTestBase ermöglicht einfache Überschreibungen, um jeden benutzerdefinierten, von Docker unterstützten Test problemlos durchzuführen. Erstellen Sie einfach eine Testklasse, leiten Sie sie von FluentDockerTestBase ab und überschreiben Sie geeignete Methoden. Zum Beispiel.
protected override DockerBuilder Build ( )
{
return new DockerBuilder ( )
. WithImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( $ "POSTGRES_PASSWORD= { PostgresPassword } " )
. ExposePorts ( "5432" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ ) ;
} Dadurch wird ein Builder mit dem Docker-Image „postgres:latest“ erstellt und eine Umgebungszeichenfolge festgelegt. Außerdem wird der Postgres-Datenbank-Port 5432 für den Host verfügbar gemacht, sodass eine Verbindung zur Datenbank innerhalb des Containers hergestellt werden kann. Zuletzt wird auf den Port 5432 gewartet. Dadurch wird sichergestellt, dass die Datenbank läuft und ordnungsgemäß gebootet wurde. Bei einer Zeitüberschreitung, in diesem Beispiel auf 30 Sekunden eingestellt, wird eine Ausnahme ausgelöst und der Container wird gestoppt und entfernt. Beachten Sie, dass der Host-Port nicht 5432 ist! Verwenden Sie Container.GetHostPort("5432/tcp") um den Host-Port abzurufen. Die Host-IP kann von Container.Host -Eigenschaft abgerufen werden und wird daher bei der Kommunikation mit der App im Container verwendet.
Wenn ein Rückruf erforderlich ist, wenn der Container erfolgreich abgerufen, erstellt und gestartet wurde.
protected override void OnContainerInitialized ( )
{
ConnectionString = string . Format ( PostgresConnectionString , Container . Host ,
Container . GetHostPort ( "5432/tcp" ) , PostgresUser ,
PostgresPassword , PostgresDb ) ;
}In diesem Beispiel wird eine ordnungsgemäße Verbindungszeichenfolge zur Postgresql-Datenbank im neu erstellten Container gerendert. Dies kann verwendet werden, um eine Verbindung mit Npgsql, EF7, NHibernate, Marten oder anderen kompatiblen Tools herzustellen. Diese Methode wird nicht aufgerufen, wenn das Image aus dem Docker-Repository abgerufen wird oder der Container nicht erstellt/gestartet werden konnte.
Wenn ein Container-Hook vor dem Herunterfahren überschrieben werden soll.
protected virtual void OnContainerTearDown ( )
{
// Do stuff before container is shut down.
} Beachten Sie, dass der Docker-Container weiterhin ausgeführt wird und manuell entfernt werden muss, wenn der Container nicht ordnungsgemäß entsorgt wird. Dies ist eine Funktion und kein Fehler, da Sie in Ihrem Test möglicherweise mehrere Container ausführen möchten. Die DockerContainer -Klasse verwaltet die Instanz-ID des Containers und interagiert daher nur mit diesem und keinem anderen Container.
Beim Erstellen/Starten eines neuen Containers prüft es zunächst im lokalen Repository, ob das Container-Image bereits vorhanden ist, und lädt es herunter, wenn es nicht gefunden wird. Dies kann einige Zeit dauern und es gibt nur ein Debug-Protokoll. Wenn es aktiviert ist, ist es möglich, den Download-Vorgang zu überwachen.
Wenn eine nicht behandelte Ausnahme auftritt und die Anwendung „FailFast “ schnell beendet wird, wird die finally -Klausel nicht aufgerufen. Daher wird ein fehlerhafter WaitForPort in einer using -Anweisung den Containerdienst nicht verwerfen. Daher läuft der Container noch. Um dies zu beheben, verwenden Sie entweder einen globalen Try...Catch oder fügen Sie einen lokal ein, z
try
{
using ( var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "postgres:9.6-alpine" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=postgres" )
. WaitForPort ( "5777/tcp" , 10000 ) // Fail here since 5777 is not valid port
. Build ( ) )
{
container . Start ( ) ; // FluentDockerException is thrown here since WaitForPort is executed
}
} catch { throw ; } Dies ist jedoch nur dann der Fall, wenn die Anwendung aufgrund der in WaitForPort ausgelösten FluentDockerException beendet wird. Andernfalls wird der Container ordnungsgemäß entsorgt, und daher ist try...catch nicht erforderlich.
Dies könnte auch mithilfe der Fd.Build -Funktionen gelöst werden (weitere Informationen finden Sie unter Verwenden von Builder-Erweiterungen ).
Die Klasse Fd ist eine statische Klasse , die praktische Methoden zum Erstellen und Ausführen einzelner und zusammengesetzter Container bereitstellt. Um einen Container zu erstellen, verwenden Sie einfach:
var build = Fd . Build ( c => c . UseContainer ( )
. UseImage ( "postgres:9.6-alpine" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. WaitForPort ( "5432/tcp" , TimeSpan . FromSeconds ( 30 ) ) ) ;
// This is the equivalent of
var build = new Builder ( ) . UseContainer ( )
. UseImage ( "postgres:9.6-alpine" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. WaitForPort ( "5432/tcp" , TimeSpan . FromSeconds ( 30 ) ) ; Dies kann dann verwendet werden, um die Container innerhalb einer Safe- using -Klausel zu starten, die garantiert entsorgt wird, selbst wenn eine Ausnahme nicht abgefangen wird.
build . Container ( svc =>
{
var config = svc . GetConfiguration ( ) ;
// Do stuff...
} ) ; Nach Ausführung der Container -Methode wird in diesem Fall der Container gestoppt und entfernt. Dies ist das Äquivalent von
// This is equivalent of
try
{
using ( var svc = build . Build ( ) )
{
svc . Start ( ) ;
var config = svc . GetConfiguration ( ) ;
// Do stuff...
}
}
catch
{
Log ( .. . ) ;
throw ;
}Es ist auch möglich, Builder und Ausführen zu kombinieren, z. B. über:
Fd . Container ( c => c . UseContainer ( )
. UseImage ( "postgres:9.6-alpine" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. WaitForPort ( "5432/tcp" , TimeSpan . FromSeconds ( 30 ) ) ,
svc =>
{
var config = svc . GetConfiguration ( ) ;
// Do stuff...
} ) ; Im obigen Beispiel wird der Container erstellt, gestartet, gestoppt und schließlich gelöscht. Selbst wenn Exception ausgelöst wird, wird sie als Disposed angezeigt. Natürlich ist es möglich, komponierte Container mithilfe composite Erweiterungsmethoden wie bei container zu verwenden.