Emballer | NuGet |
---|---|
CourantDocker | |
Test Microsoft | |
Test XUnit |
Cette bibliothèque permet les interactions docker
et docker-compose
à l'aide d' une API Fluent . Il est pris en charge sous Linux, Windows et Mac. Il prend également en charge les interactions docker-machine
héritées.
Exemple d'utilisation de l'API Fluent
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 ( ) ) ;
}
Cela lance un postgres et attend qu'il soit prêt. Pour utiliser Compose, procédez comme ceci :
REMARQUE : utilisez AssumeComposeVersion(ComposeVersion.V2) pour utiliser le comportement V2, la valeur par défaut est toujours V1 (à modifier par défaut en V2 plus tard cette année)
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 Remarque pour les utilisateurs Linux : Docker nécessite sudo par défaut et la bibliothèque s'attend par défaut à ce que l'utilisateur en cours d'exécution n'ait pas besoin de faire sudo pour parler au démon Docker. Plus de description peut être trouvée dans le chapitre Parler au démon Docker .
L' API fluide crée un ou plusieurs services. Chaque service peut être composite ou singulier. Par conséquent, il est possible, par exemple, de lancer plusieurs services basés sur Docker-compose et de gérer chacun d'eux comme un seul service ou de creuser et d'utiliser tous les services sous-jacents sur chaque service Docker-compose . Il est également possible d'utiliser les services directement, par exemple
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 ) ;
}
L'exemple ci-dessus crée un service docker-compose à partir d'un seul fichier de composition. Lorsque le service est supprimé, tous les services sous-jacents sont automatiquement arrêtés.
La bibliothèque est prise en charge par le framework .NET full 4.51 et supérieur, .NET standard 1.6, 2.0. Il est divisé en trois fines couches, chaque couche est accessible :
La majorité des méthodes de service sont des méthodes d'extension et ne sont pas câblées au service lui-même, ce qui les rend légères et personnalisables. Puisque tout est accessible, il est par exemple facile d'ajouter une méthode d'extension pour un service qui utilise les commandes de couche 1 pour fournir des fonctionnalités.
Je suis heureux de contribuer, bien qu'il n'y ait pas encore de directive de contribution, assurez-vous de respecter .editorconfig lorsque vous effectuez les demandes d'extraction. Sinon, la construction échouera. Je mettrai à jour avec une vraie ligne directrice tôt ou tard cette année.
Toutes les commandes nécessitent un DockerUri
pour fonctionner. Il s'agit de l'Uri du démon Docker, soit localement, soit à distance. Il peut être détectable ou codé en dur. La découverte du DockerUri
local peut être effectuée par
var hosts = new Hosts ( ) . Discover ( ) ;
var _docker = hosts . FirstOrDefault ( x => x . IsNative ) ?? hosts . FirstOrDefault ( x => x . Name == "default" ) ;
L'exemple extrait vérifiera les hôtes natifs ou Docker bêta "natifs", sinon, choisira la machine Docker "par défaut" comme hôte. Si vous utilisez docker-machine et qu'aucune machine n'existe ou n'est démarrée, il est facile de créer/démarrer une docker-machine en utilisant par exemple "test-machine".Create(1024,20000000,1)
. Cela créera une machine Docker nommée « machine de test » avec 1 Go de RAM, 20 Go de disque et utilisera un processeur.
Il est désormais possible d'utiliser l'Uri pour communiquer à l'aide des commandes. Par exemple pour obtenir la version des binaires Docker client et serveur :
var result = _docker . Host . Version ( _docker . Certificates ) ;
Debug . WriteLine ( result . Data ) ; // Will Print the Client and Server Version and API Versions respectively.
Toutes les commandes renvoient une CommandResponse telle qu'il est possible de vérifier le facteur de réussite par response.Success
. Si des données sont associées à la commande, elles sont renvoyées dans la propriété response.Data
.
Ensuite, il est simple comme ci-dessous de démarrer et d'arrêter la suppression d'un conteneur à l'aide des commandes. Ci-dessous démarre un conteneur et fait un PS dessus, puis le supprime.
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 ) ;
Lors de l'exécution sous Windows, on peut choisir d'exécuter un conteneur Linux ou Windows. Utilisez LinuxDaemon
ou WindowsDaemon
pour contrôler à quel démon communiquer.
_docker . LinuxDaemon ( ) ; // ensures that it will talk to linux daemon, if windows daemon it will switch
Certaines commandes renvoient un flux de données lorsque, par exemple, des événements ou des journaux sont souhaités en utilisant un flux continu. Les flux peuvent être utilisés dans les tâches en arrière-plan et prennent en charge CancellationToken
. L'exemple ci-dessous suit un journal.
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 ) ;
}
}
Des méthodes utilitaires existent pour les commandes. Ils se présentent sous différentes formes, telles que la mise en réseau, etc. Par exemple, lors de la lecture d'un journal jusqu'à la fin :
using ( var logs = _docker . Host . Logs ( id , _docker . Certificates ) )
{
foreach ( var line in logs . ReadToEnd ( ) )
{
Debug . WriteLine ( line ) ;
}
}
La couche la plus élevée de cette bibliothèque est l'API fluide où vous pouvez définir et contrôler des machines, des images et des conteneurs. Par exemple, configurer un équilibreur de charge avec deux serveurs nodejs lisant à partir d'un serveur Redis peut ressembler à ceci (l'image du nœud est créée sur mesure si elle n'est pas trouvée dans le référentiel).
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 ) ;
}
L'exemple ci-dessus définit un Dockerfile et le construit pour l'image du nœud. Il utilise ensuite Vanilla Redis et Nginx. Si vous souhaitez simplement utiliser un Dockerfile existant, cela peut être fait comme ceci.
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.
}
L'API fluide prend en charge la définition d'une machine Docker jusqu'à un ensemble d'instances Docker. Il prend en charge par exemple l'attente d'un port spécifique ou d'un processus dans le conteneur avant la fin Build()
et peut donc être utilisé en toute sécurité dans une instruction using. En cas de gestion spécifique des délais d'attente, etc., vous pouvez toujours créer et démarrer le conteneur et utiliser des méthodes d'extension pour effectuer l'attente sur le conteneur lui-même.
Pour créer un conteneur, omettez simplement le début. Par exemple:
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( ) )
{
Assert . AreEqual ( ServiceRunningState . Stopped , container . State ) ;
}
Cet exemple crée un conteneur avec postgres, configure une variable d'environnement. Dans l'instruction using, il est possible de démarrer IContainerService
. Ainsi, chaque conteneur construit est enveloppé dans un IContainerService
. Il est également possible d'utiliser IHostService.GetContainers(...)
pour obtenir les conteneurs créés, en cours d'exécution et quittés. Depuis IHostService
il est également possible d'obtenir toutes les images du référentiel local à partir desquelles créer des conteneurs.
Si vous souhaitez exécuter un seul conteneur, utilisez la méthode de démarrage du service fluide ou conteneur. Par exemple:
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" ) ) ;
}
Par défaut, le conteneur est arrêté et supprimé lorsque la méthode Dispose est exécutée, afin de conserver le conteneur dans l'archive, utilisez KeepContainer()
sur l'API fluide. Lorsque Dispose()
est invoqué, il sera arrêté mais pas supprimé. Il est également possible de le faire fonctionner après sa suppression.
Il est possible d'exposer les ports de manière explicite ou aléatoire. Quoi qu'il en soit, il est possible de résoudre l'IP (en cas de machine) et le port (en cas de port aléatoire) à utiliser dans le code. Par exemple:
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 ) ;
}
Ici, nous mappons explicitement le port de conteneur 5432 sur le port hôte 40001. Notez l'utilisation de container.ToHostExposedEndpoint(...)
. Il s'agit de toujours résoudre une adresse IP et un port fonctionnels pour communiquer avec le conteneur Docker. Il est également possible de mapper un port aléatoire, c'est à dire de laisser Docker choisir un port disponible. Par exemple:
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 ) ;
}
La seule différence ici est qu'un seul argument est utilisé lorsque ExposePort(...)
a été utilisé pour configurer le conteneur. Le même usage s’applique autrement et est donc transparent pour le code.
Afin de savoir quand un certain service est opérationnel avant de commencer, par exemple, à s'y connecter. Il est possible d'attendre qu'un port spécifique soit ouvert. Par exemple:
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 ( ) ) ;
}
Dans l'exemple ci-dessus, nous attendons que le port à conteneurs 5432 soit ouvert dans les 30 secondes. S'il échoue, il lèvera une exception et le conteneur sera donc supprimé et supprimé (puisque nous n'avons pas de configuration de conteneur de conservation, etc.).
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 ( ) ) ;
}
Parfois, il n'est pas possible d'atteindre directement le conteneur, par adresse IP et port locaux, par exemple, le conteneur a un port exposé sur l'interface de bouclage ( 127.0.0.1 ) et c'est le seul moyen d'atteindre le conteneur à partir du programme. L'exemple ci-dessus force l'adresse à 127.0.0.1 mais résout toujours le port hôte. Par défaut, FluentDocker utilise l'inspection du réseau sur le conteneur pour déterminer la configuration du réseau.
Parfois, il ne suffit pas d’attendre un port. Parfois, il est bien plus vital d’attendre un processus de conteneur. Par conséquent, une méthode d'attente de processus existe dans l'API fluide ainsi qu'une méthode d'extension sur l'objet conteneur. Par exemple:
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 ( ) ) ;
}
Dans l'exemple ci-dessus, Build()
renverra le contrôle lorsque le processus "postgres" aura été démarré dans le conteneur.
Afin d'utiliser des conteneurs, il est parfois nécessaire de monter les volumes du conteneur sur l'hôte ou simplement de copier depuis ou vers le conteneur. Selon que vous exécutez une machine ou un docker, le mappage de volume a la contrainte d'être accessible depuis la machine virtuelle.
Un cas d'utilisation normal consiste, par exemple, à avoir un serveur Web diffusant du contenu sur un conteneur Docker et à ce que l'utilisateur modifie les fichiers sur le système de fichiers hôte. Dans un tel scénario, il est nécessaire de monter un volume de conteneur Docker sur l'hôte. Par exemple:
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 ) ;
}
Dans l'exemple ci-dessus, un conteneur nginx est démarré et monte « /usr/share/nginx/html » sur un chemin d'hôte (aléatoire, dans le répertoire temporaire). Un fichier HTML est copié dans le chemin de l'hôte et lorsqu'une requête HTTP vers le conteneur Docker nginx est effectuée, ce même fichier est servi.
Parfois, il est nécessaire de copier des fichiers vers et depuis un conteneur. Par exemple, copiez un fichier de configuration, configurez-le et recopiez-le. Le scénario le plus courant consiste à copier un fichier de configuration dans le conteneur, juste avant son démarrage. L'exemple multi-conteneur copie un fichier de configuration nginx juste avant son démarrage. Il est ainsi possible d'éviter de créer manuellement un Dockerfile et une image pour une tâche aussi simple. Au lieu de cela, utilisez simplement, par exemple, une image officielle ou personnalisée, copiez la configuration et exécutez.
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" ) ) ) ;
}
L'exemple ci-dessus copie un répertoire vers un chemin d'hôte (fullPath) à partir d'un conteneur en cours d'exécution. Notez l'utilisation de la méthode d'extension ici, n'utilisant donc pas l'API fluide (puisque CopyFrom est après Start()). Si vous souhaitez copier des fichiers du conteneur juste avant de commencer, utilisez plutôt l'API Fluent.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. CopyOnStart ( "/etc/conf.d" , fullPath )
. Build ( )
. Start ( ) )
{
}
L'exemple ci-dessous illustre un scénario beaucoup plus courant dans lequel les fichiers sont copiés dans le conteneur. Cet exemple utilise la méthode d'extension au lieu de la version fluide de l'API. Il prend un instantané Diff avant la copie, puis juste après la copie. Dans ce dernier le hello.html est présent.
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" ) ) ;
}
Il est parfois utile de copier des fichiers dans IContainerService.Dispose()
(juste avant que le conteneur ne s'arrête). Par conséquent, une API fluide existe pour garantir qu’elle fera exactement cela.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. CopyOnDispose ( "/etc/conf.d" , fullPath )
. Build ( )
. Start ( ) )
{
}
Afin d'analyser un conteneur, il existe une méthode d'extension d'exportation et des méthodes API fluides. Il s'agit notamment de la possibilité d'exporter un conteneur lorsqu'un IContainerService
est supprimé.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. ExportOnDispose ( fullPath )
. Build ( )
. Start ( ) )
{
}
Cela produira une exportation de conteneur (fichier tar) sur l'hôte (fullPath). Si vous préférez le faire exploser (non taré), utilisez plutôt la méthode ExportExplodedOnDispose
. Bien entendu, vous pouvez exporter le conteneur à tout moment en utilisant une méthode d'extension sur le conteneur.
Une astuce utile en matière de tests unitaires consiste à exporter l'état du conteneur lorsque le test unitaire échoue pour une raison quelconque. Il existe donc une API Fluent qui exportera lorsqu'une certaine condition Lambda est remplie. Par exemple:
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 ;
}
Cet extrait exportera le conteneur lorsque l'instruction using supprimera le conteneur puisque la variable d'échec est définie sur true et est utilisée dans l'expression ExportOnDispose
.
Tous les services peuvent être étendus avec des hooks. Dans ExportOnDispose(path, lambda)
installe un Hook lorsque l'état du service est défini pour exécuter le lambda lorsque l'état est Removing
. Il est possible d'installer et de retirer des crochets à la volée. Si plusieurs hooks sont enregistrés sur la même instance de service, avec le même ServiceRunningState
, ils seront exécutés dans l'ordre d'installation.
Les hooks sont particulièrement utiles si vous souhaitez que quelque chose soit exécuté lorsqu'un état est sur le point d'être défini (ou exécuté) sur le service tel que Starting
. L'API Fluent les utilise dans certaines situations telles que la copie de fichiers, l'exportation, etc.
FluentDocker prend en charge toutes les commandes réseau Docker. Il peut découvrir les réseaux par _docker.NetworkLs()
où il découvre tous les réseaux et quelques paramètres simples définis dans NetworkRow
. Il peut également inspecter pour obtenir des informations plus approfondies sur le réseau (telles que les conteneurs présents dans le réseau et la configuration Ipam) par _docker.NetworkInspect(network:"networkId")
.
Afin de créer un nouveau réseau, utilisez _docker.NetworkCreate("name_of_network")
. Il est également possible de fournir NetworkCreateParams
où tout peut être personnalisé, comme créer un réseau superposé et modifier la configuration Ipam. Pour supprimer un réseau, utilisez simplement le _docker.NetworkRm(network:"networkId")
.
Notez que les réseaux ne sont pas supprimés si des conteneurs y sont attachés !
Lorsqu'un réseau est créé, il est possible d'y placer un ou plusieurs conteneurs à l'aide de _docker.NetworkConnect("containerId","networkId")
. Notez que les conteneurs peuvent se trouver dans plusieurs réseaux à la fois, et peuvent donc effectuer des requêtes proxy entre des réseaux isolés. Pour déconnecter un conteneur d'un réseau, effectuez simplement un _docker.NetworkDisconnect("containerId","networkId")
.
L'exemple suivant exécute un conteneur, crée un réseau et connecte le conteneur en cours d'exécution au réseau. Il déconnecte ensuite le conteneur, le supprime et supprime le réseau.
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 ) ;
Il est également possible d'utiliser un générateur fluide pour créer de nouveaux réseaux Docker ou réutiliser des réseaux existants. Ceux-ci peuvent ensuite être référencés lors de la construction de conteneurs . Il est possible de créer plusieurs réseaux Docker et d'attacher un conteneur à plusieurs réseaux à la fois.
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 ( ) ;
}
}
L'extrait de code ci-dessus crée un nouveau réseau appelé test-network , puis crée un conteneur attaché au test-network . Lorsque Dispose()
est appelé sur nw, il supprimera le réseau. Il est également possible d'effectuer des attributions de conteneurs IP statiques au sein du réseau par UseIpV4
ou UseIpV6
. Par exemple:
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 ) ;
}
}
L'exemple ci-dessus crée un nouveau réseau unit-test-nw avec ip-range 10.18.0.0/16 . C'est celui utilisé dans le nouveau conteneur. L'adresse IP du conteneur est définie sur 10.18.0.22 et est statique en raison de la commande UseIpV4
.
FluentDocker prend en charge la gestion des volumes Docker à la fois à partir de commandes et d'une API fluide. Par conséquent, il est possible d'avoir un contrôle total sur les volumes utilisés dans le conteneur, par exemple s'il doit être éliminé, réutilisé, quel pilote utiliser, etc.
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" ) ;
L'extrait ci-dessus crée un nouveau volume nommé test-volume et est de type NFS . Il inspecte ensuite le volume qui vient d'être créé et force enfin la suppression du volume.
Il est également possible d'utiliser une API fluide pour créer ou utiliser des volumes. Ils peuvent ensuite être utilisés lors de la construction d’un conteneur. Ceci est particulièrement utile lorsque la création de volumes est spéciale ou que la durée de vie doit être contrôlée.
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 ) ;
}
}
L'exemple ci-dessus crée un nouveau volume appelé test-volume et sa suppression est planifiée lorsque Dispose()
est invoquée sur IVolumeService
. Le conteneur est créé et monte le volume nouvellement créé sur /var/lib/postgresql/data en mode d'accès en lecture/écriture . Étant donné que le conteneur est dans la portée de l'instruction using
du volume, sa durée de vie s'étend sur toute la durée de vie du conteneur, puis est supprimée.
FluentDocker prend en charge la connexion au mécanisme d'événements Docker pour écouter les événements qu'il envoie.
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 ;
}
}
}
L'écouteur d'événements est global et peut gérer de nombreux types EventAction
.
Par exemple
En fonction de l'action, le type d'événement peut différer, par exemple ContainerKillEvent
pour EventAction.Kill
. Tous les événements dérivent de FdEvent
. Cela signifie que toutes les propriétés partagées se trouvent dans l'événement de base et que les propriétés explicites sont dans la dérivée.
Par exemple, « ContainerKillEvent » contient les propriétés suivantes :
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 ; }
}
Cette boucle d'événements peut être utilisée pour récupérer des événements et piloter vos instances IService
instanciées. Ou si vous devez réagir, par exemple, si un réseau est ajouté ou supprimé.
Dans le framework complet, il utilise une journalisation détaillée à l'aide de System.Diagnostics.Debugger.Log
. Pour le noyau .net, il utilise le standard Microsoft.Extensions.Logging.ILog
pour se connecter. Les deux utilisent la catégorie Ductus.FluentDocker et peuvent donc être configurés pour participer ou non à la journalisation et configurer différentes destinations de journalisation.
Dans .net core, vous pouvez fournir le segment de journalisation dans le fichier de configuration de l'application.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Ductus.FluentDocker": "None"
}
}
}
Veuillez consulter https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1 pour plus d'informations. Pour le framework complet, veuillez consulter le XML nécessaire dans l'appconfig pour le framework complet décrit dans https://docs.microsoft.com/en-us/dotnet/framework/wcf/diagnostics/tracing/configuring-tracing.
Il existe un moyen rapide de désactiver/activer la journalisation via ( Ductus.FluentDocker.Services
) Logging.Enabled()
ou Logging.Disabled()
. Cela activera/désactivera de force la journalisation.
Il est possible de remplacer le mécanisme par défaut de FluentDocker qui résout l'adresse IP du conteneur du point de vue des clients, par exemple WaitForPort
. Cela peut être remplacé sur la base de ContainerBuilder
.
L'exemple ci-dessous remplace le comportement par défaut . Lorsqu'il renvoie null
le résolveur par défaut entre en jeu.
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 ) ;
}
Il existe une prise en charge limitée pour utiliser FluentAPI pour parler à un démon Docker distant sans utiliser Docker-machine. Cela se fait soit en créant manuellement une instance de DockerHostService
, soit en utilisant FromUri
sur HostBuilder
.
using ( var container = Fd . UseHost ( ) .
FromUri ( Settings . DockerUri , isWindowsHost : true ) .
UseContainer ( ) .
Build ( ) )
{
}
L'exemple ci-dessus se connecte à un DockerUri
personnalisé à partir d'un paramètre et est un démon Docker de conteneur Windows.
FromUri
qui utilise un DockerUri
pour créer un IHostService
. Cet uri est arbitraire. Il prend également en charge d'autres propriétés ( voir ci-dessous ). public HostBuilder FromUri (
DockerUri uri ,
string name = null ,
bool isNative = true ,
bool stopWhenDisposed = false ,
bool isWindowsHost = false ,
string certificatePath = null ) { /*...*/ }
Il utilisera des valeurs par défaut « raisonnables » sur tous les paramètres. Dans la plupart des cas, l' uri est suffisant. Par exemple, si vous ne fournissez pas le certificatePath, il essaiera de l'obtenir à partir de l'environnement DOCKER_CERT_PATH . S'il n'est pas trouvé dans l'environnement, il sera par défaut nul.
UseHost
qui prend une implémentation IHostService
instanciée. La bibliothèque prend en charge docker-compose pour utiliser les fichiers de composition existants pour rendre les services et en gérer la durée de vie.
L'exemple suivant contiendra un fichier de composition qui lance un MySql et un WordPress . Par conséquent, le service de composition unique aura deux services de conteneur en dessous. Par défaut, il arrêtera les services et nettoiera lorsque Dispose()
sera invoqué. Cela peut être remplacé par KeepContainers()
dans la configuration fluide .
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 :
Le fichier ci-dessus est le fichier docker-compose permettant d'assembler le service complet.
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 ) ;
}
L'extrait ci-dessus configure couramment le service docker-compose et appelle la page d'installation pour vérifier que WordPress fonctionne effectivement.
Il est également possible d'effectuer toutes les opérations prises en charge par un seul conteneur, telles que les opérations de copie, d'exportation et d'attente. Par exemple:
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 ) ;
}
L'extrait ci-dessus lance le projet WordPress Docker Compose et vérifie l' URL http://localhost:8000/wp-admin/install.php, il renvoie une certaine valeur dans le corps (dans ce cas "https://wordpress.org /"). Sinon, il renvoie 500 et la fonction WaitForHttp
attendra 500 millisecondes avant de l'appeler à nouveau. Cela fonctionne également pour n'importe quel lambda personnalisé, utilisez simplement WaitFor
à la place. Ainsi, il est possible, par exemple, d'interroger une base de données avant de continuer dans le cadre d'utilisation.
Pour les utilisateurs Linux et Mac, il existe plusieurs options pour s'authentifier auprès du socket. FluentDocker ne prend en charge aucun sudo , sudo sans mot de passe (utilisateur ajouté en tant que NOPASSWD dans /etc/sudoer) ou sudo avec mot de passe. La valeur par défaut est que FluentDocker s'attend à pouvoir parler sans sudo . Les options sont globales mais peuvent être modifiées au moment de l'exécution.
SudoMechanism . None . SetSudo ( ) ; // This is the default
SudoMechanism . Password . SetSudo ( "<my-sudo-password>" ) ;
SudoMechanism . NoPassword . SetSudo ( ) ;
Si vous souhaitez désactiver sudo pour communiquer avec le démon Docker, vous pouvez suivre un didacticiel Docker et effectuer la dernière étape d'ajout de votre utilisateur au groupe Docker.
FluentDocker prend en charge la connexion aux démons Docker distants. L'API fluide prend en charge par exemple
new Builder ( ) . UseHost ( ) . UseMachine ( ) . WithName ( "remote-daemon" )
où cela nécessite une entrée déjà pré-configurée dans le registre de la machine docker . Il est également possible de définir des ensembles de registre Docker-machine basés sur SSH pour se connecter au démon distant.
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 ) ;
}
Cet exemple créera une nouvelle entrée de registre Docker-machine nommée remote-daemon qui utilise SSH avec l'adresse IP de 192.168.1.27 et l'utilisateur SSH solo . Si une entrée nommée démon-distant est déjà trouvée, il réutilisera simplement cette entrée. Ensuite, il obtient un IHostService avec les certificats et l'URL corrects pour le démon distant. Ainsi, il est possible alors de créer un conteneur docker sur le démon distant, dans ce cas il s'agit de l'image postgres . Lorsqu'il supprime le conteneur, comme d'habitude, il le supprime du docker distant. Le IHostService s'assure de récupérer tous les certificats nécessaires afin d'authentifier la connexion.
L'exemple ci-dessus produit cette entrée de registre Docker-machine .
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
Pour utiliser UseSsh(...)
un tunnel SSH sans mot de passe doit être configuré. De plus, l'utilisateur qui utilise le tunnel doit être autorisé à accéder au démon Docker.
Suivez ce tutoriel pour configurer le tunnel SSH et assurez-vous que l'utilisateur peut accéder au démon Docker.
Créez essentiellement une nouvelle clé rsa à utiliser avec le tunnel SSH à l'aide ssh-keygen -t rsa
, puis copiez-la sur l'hôte distant par ssh-copy-id {username}@{host}
.
Modifiez le /etc/sudoers comme spécifié dans le deuxième didacticiel.
Lorsque cela est fait, vous pouvez désormais accéder au démon Docker distant via le pilote générique ou l'API fluide spécifiée ci-dessus. Pour faire manuellement la même chose que celle spécifiée dans l’exemple, cela ressemblerait à ceci.
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
Maintenant que l'entrée de registre est créée, il est possible de définir l'environnement pour le menu fixe du terminal.
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
Exécutez ceci pour que le client Docker utilise le démon Docker distant.
@FOR /f "tokens=*" %i IN ('docker-machine.exe env remote-daemon') DO @%i
Toutes les commandes utilisant le binaire docker
s'exécuteront désormais sur le démon docker distant.
Lors de la création et de l'interrogation, via une machine, d'une machine Docker Hyper-V, le processus doit être élevé car Hyper-V ne répondra pas aux appels d'API en mode utilisateur standard.
Il est possible de spécifier un contrôle de santé pour le conteneur Docker afin de signaler l'état du conteneur en fonction de cette activité. L'exemple suivant utilise une vérification de l'état indiquant que le conteneur est terminé ou non. Il est possible de vérifier la configuration (assurez-vous de forcer l'actualisation) quel est l'état signalé par la vérification de l'état.
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 ) ;
}
Il est possible via l' API Fluent et ContainerCreateParams
de spécifier ulimit au conteneur Docker pour, par exemple, limiter le nombre de fichiers ouverts, etc. Par exemple, l'utilisation de l' API Fluent pourrait ressembler à ceci en limitant le nombre de fichiers ouverts à 2048 (à la fois logiciels et matériels) .
using (
var container =
Fd . UseContainer ( )
. UseImage ( "postgres:latest" , force : true )
. UseUlimit ( Ulimit . NoFile , 2048 , 2048 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( )
. Start ( ) )
{
// Do stuff
}
Ce référentiel contient trois packages nuget, un pour l'accès fluide, un pour les classes de base ms-test et un autre pour les classes de base xunit à utiliser lors des tests. Par exemple, dans un test unitaire, il est possible de lancer un conteneur Postgres et d'attendre que la base de données ait démarré.
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 ( ) ;
}
Il est également possible de réutiliser des classes de base abstraites, par exemple la base de test postgres pour simplifier et réaliser des tests unitaires propres vers un conteneur.
[ TestClass ]
public class PostgresMsTests : PostgresTestBase
{
[ TestMethod ]
public void Test ( )
{
// We now have a running postgres
// and a valid connection string to use.
}
}
Le FluentDockerTestBase
permet des remplacements simples pour effectuer facilement n'importe quel test personnalisé soutenu par Docker. Créez simplement une classe de test, dérivez-la de FluentDockerTestBase
et remplacez les méthodes appropriées. Par exemple.
protected override DockerBuilder Build ( )
{
return new DockerBuilder ( )
. WithImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( $ "POSTGRES_PASSWORD= { PostgresPassword } " )
. ExposePorts ( "5432" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ ) ;
}
Cela créera un constructeur avec l'image docker postgres:latest et définira une chaîne d'environnement, il exposera également le port 5432 de la base de données postgres à l'hôte afin que l'on puisse se connecter à la base de données dans le conteneur. Enfin, il attendra le port 5432. Cela garantit que la base de données est en cours d'exécution et a correctement démarré. Si le délai d'attente, dans cet exemple, est défini sur 30 secondes, une exception sera levée et le conteneur sera arrêté et supprimé. Notez que le port hôte n’est pas 5432 ! Utilisez Container.GetHostPort("5432/tcp")
pour obtenir le port hôte. L'adresse IP de l'hôte peut être récupérée par la propriété Container.Host
et doit donc être utilisée lors de la communication avec l'application dans le conteneur.
Si un rappel est nécessaire lorsque le conteneur a été extrait, créé et démarré avec succès.
protected override void OnContainerInitialized ( )
{
ConnectionString = string . Format ( PostgresConnectionString , Container . Host ,
Container . GetHostPort ( "5432/tcp" ) , PostgresUser ,
PostgresPassword , PostgresDb ) ;
}
Cet exemple restitue une chaîne de connexion appropriée à la base de données postgresql dans le conteneur nouvellement lancé. Cela peut être utilisé pour se connecter à l'aide de Npgsql, EF7, NHibernate, Marten ou d'autres outils compatibles. Cette méthode ne sera pas appelée si vous extrayez l'image du référentiel Docker ou si elle ne parvient pas à créer/démarrer le conteneur.
Si un hook de conteneur avant l’arrêt est souhaité, remplacez-le.
protected virtual void OnContainerTearDown ( )
{
// Do stuff before container is shut down.
}
Notez que s'il n'est pas nommé, s'il n'est pas correctement éliminé, le conteneur Docker fonctionnera toujours et devra être supprimé manuellement. Il s'agit d'une fonctionnalité et non d'un bug puisque vous souhaiterez peut-être que plusieurs conteneurs soient exécutés dans votre test. La classe DockerContainer
gère l'identifiant d'instance du conteneur et n'interagit donc qu'avec lui et avec aucun autre conteneur.
Lors de la création/démarrage d'un nouveau conteneur, il vérifiera d'abord le référentiel local si l'image du conteneur est déjà présente et la téléchargera si elle n'est pas trouvée. Cela peut prendre un certain temps et il n'y a qu'un journal de débogage. Si cette option est activée, il est possible de surveiller le processus de téléchargement.
Lorsqu'une exception non gérée se produit et que l'application FailFast se termine rapidement, elle n'invoquera pas la clause finally
. Ainsi, un WaitForPort
défaillant dans une instruction using
ne supprimera pas le service de conteneur. Le conteneur est donc toujours en cours d’exécution. Pour résoudre ce problème, effectuez un try...catch global ou injectez-en un localement, par exemple
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 ; }
Mais ce n'est que lorsque l'arrêt de l'application est effectué en raison de l' FluentDockerException
lancée dans WaitForPort
, sinon le conteneur sera éliminé correctement et le try...catch
n'est donc pas nécessaire.
Cela pourrait également être résolu en utilisant les fonctions Fd.Build
( voir Utilisation des extensions Builder pour plus d'informations).
La classe Fd
est une classe statique qui fournit des méthodes pratiques pour créer et exécuter des conteneurs simples et composés. Pour créer un conteneur, utilisez simplement :
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 ) ) ;
Cela peut ensuite être utilisé pour démarrer les conteneurs dans le cadre d'une clause using
sécurisée dont la suppression est garantie même en cas d'exception non détectée.
build . Container ( svc =>
{
var config = svc . GetConfiguration ( ) ;
// Do stuff...
} ) ;
Une fois la méthode Container
exécutée, le conteneur est dans ce cas arrêté et supprimé. C'est l'équivalent de
// This is equivalent of
try
{
using ( var svc = build . Build ( ) )
{
svc . Start ( ) ;
var config = svc . GetConfiguration ( ) ;
// Do stuff...
}
}
catch
{
Log ( .. . ) ;
throw ;
}
Il est également possible de combiner builder et running par exemple via :
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...
} ) ;
L'exemple ci-dessus construira le conteneur, démarrera, arrêtera et enfin supprimera le conteneur. Même si une Exception
est levée, elle sera Disposed
. Bien sûr, il est possible d'utiliser compsed conteneur en utilisant des méthodes d'extension composite
comme avec container
.