Paquete | NuGet |
---|---|
FluentDocker | |
Prueba de Microsoft | |
XPrueba unitaria |
Esta biblioteca permite interacciones docker
y docker-compose
mediante una API Fluent . Es compatible con Linux, Windows y Mac. También es compatible con las antiguas interacciones entre la docker-machine
acoplable.
Ejemplo de uso fluido de 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 ( ) ) ;
}
Esto activa un postgres y espera a que esté listo. Para usar redactar, simplemente hazlo así:
NOTA: Utilice AssumeComposeVersion(ComposeVersion.V2) para utilizar el comportamiento V2, el valor predeterminado sigue siendo V1 (se cambiará al valor predeterminado V2 más adelante este año).
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 Nota para usuarios de Linux: Docker requiere sudo de forma predeterminada y la biblioteca espera de forma predeterminada que el usuario ejecutante no necesite realizar sudo para comunicarse con el demonio de Docker. Puede encontrar más descripción en el capítulo Hablando con Docker Daemon .
La API fluida crea uno o más servicios. Cada servicio podrá ser compuesto o singular. Por lo tanto, es posible, por ejemplo, activar varios servicios basados en Docker Compose y administrar cada uno de ellos como un solo servicio o profundizar y utilizar todos los servicios subyacentes en cada servicio de Docker Compose . También es posible utilizar servicios directamente, por ejemplo.
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 ) ;
}
El ejemplo anterior crea un servicio de composición acoplable a partir de un único archivo de redacción. Cuando se elimina el servicio, todos los servicios subyacentes se detienen automáticamente.
La biblioteca es compatible con el marco .NET completo 4.51 y superior, el estándar .NET 1.6, 2.0. Está dividido en tres capas finas, cada capa es accesible:
La mayoría de los métodos de servicio son métodos de extensión y no están integrados en el servicio en sí, lo que los hace livianos y personalizables. Dado que todo es accesible, es fácil, por ejemplo, agregar un método de extensiones para un servicio que utiliza los comandos de la capa 1 para proporcionar funcionalidad.
Acepto las contribuciones, aunque todavía no existe una guía de contribución; asegúrese de cumplir con .editorconfig al realizar las solicitudes de extracción. De lo contrario, la compilación fallará. Lo actualizaré con una guía real tarde o temprano este año.
Todos los comandos necesitan un DockerUri
para funcionar. Es el Uri del demonio acoplable, ya sea local o remoto. Puede ser detectable o codificado. El descubrimiento de DockerUri
local se puede realizar mediante
var hosts = new Hosts ( ) . Discover ( ) ;
var _docker = hosts . FirstOrDefault ( x => x . IsNative ) ?? hosts . FirstOrDefault ( x => x . Name == "default" ) ;
El ejemplo recortado buscará hosts nativos o "nativos" de Docker Beta; de lo contrario, elegirá la máquina acoplable "predeterminada" como host. Si está utilizando docker-machine y no existe ninguna máquina o no está iniciada, es fácil crear/iniciar una docker-machine, por ejemplo, "test-machine".Create(1024,20000000,1)
. Esto creará una máquina acoplable llamada "máquina de prueba" con 1 GB de RAM, 20 GB de disco y utilizará una CPU.
Ahora es posible utilizar el Uri para comunicarse mediante los comandos. Por ejemplo, para obtener la versión de los archivos binarios de Docker del cliente y del servidor:
var result = _docker . Host . Version ( _docker . Certificates ) ;
Debug . WriteLine ( result . Data ) ; // Will Print the Client and Server Version and API Versions respectively.
Todos los comandos devuelven un CommandResponse de modo que es posible verificar el factor de éxito mediante response.Success
. Si hay algún dato asociado con el comando, se devuelve en la propiedad response.Data
.
Entonces es tan simple como se muestra a continuación para iniciar y detener, incluir eliminar un contenedor usando los comandos. A continuación se inicia un contenedor, se le hace una PS y luego se elimina.
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 ) ;
Cuando se ejecuta en Windows, se puede optar por ejecutar Linux o el contenedor de Windows. Utilice LinuxDaemon
o WindowsDaemon
para controlar con qué demonio hablar.
_docker . LinuxDaemon ( ) ; // ensures that it will talk to linux daemon, if windows daemon it will switch
Algunos comandos devuelven un flujo de datos cuando, por ejemplo, se desean eventos o registros utilizando un flujo continuo. Las transmisiones se pueden utilizar en tareas en segundo plano y admiten CancellationToken
. El siguiente ejemplo sigue un registro.
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 ) ;
}
}
Existen métodos de utilidad para los comandos. Vienen en diferentes estilos, como networking, etc. Por ejemplo, al leer un registro hasta el final:
using ( var logs = _docker . Host . Logs ( id , _docker . Certificates ) )
{
foreach ( var line in logs . ReadToEnd ( ) )
{
Debug . WriteLine ( line ) ;
}
}
La capa más alta de esta biblioteca es la API fluida donde puede definir y controlar máquinas, imágenes y contenedores. Por ejemplo, configurar un equilibrador de carga con dos servidores nodejs que leen desde un servidor redis puede verse así (la imagen del nodo se crea de forma personalizada si no se encuentra en el repositorio).
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 ) ;
}
El ejemplo anterior define un Dockerfile y lo construye para la imagen del nodo. Luego usa vanilla redis y nginx. Si solo desea utilizar un Dockerfile existente, puede hacerlo así.
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.
}
La API fluida admite desde la definición de una máquina acoplable hasta un conjunto de instancias acoplables. Tiene soporte incorporado para, por ejemplo, esperar un puerto específico o un proceso dentro del contenedor antes de que se complete Build()
y, por lo tanto, se puede usar de manera segura dentro de una declaración de uso. Si se realiza una gestión específica de los tiempos de espera, etc., siempre puede crear e iniciar el contenedor y utilizar métodos de extensión para realizar la espera en el propio contenedor.
Para crear un contenedor simplemente omita el inicio. Por ejemplo:
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( ) )
{
Assert . AreEqual ( ServiceRunningState . Stopped , container . State ) ;
}
Este ejemplo crea un contenedor con postgres, configura una variable de entorno. Dentro de la declaración de uso es posible iniciar IContainerService
. Por lo tanto, cada contenedor construido está envuelto en un IContainerService
. También es posible utilizar IHostService.GetContainers(...)
para obtener los contenedores creados, en ejecución y salidos. Desde IHostService
también es posible obtener todas las imágenes en el repositorio local para crear contenedores.
Cuando desee ejecutar un solo contenedor, utilice el método de inicio del servicio fluido o de contenedor. Por ejemplo:
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" ) ) ;
}
De forma predeterminada, el contenedor se detiene y elimina cuando se ejecuta el método Dispose; para mantener el contenedor en el archivo, use KeepContainer()
en la API fluida. Cuando se invoca Dispose()
se detendrá pero no se eliminará. También es posible mantenerlo funcionando después de desecharlo.
Es posible exponer puertos tanto de forma explícita como aleatoria. De cualquier manera es posible resolver la IP (en el caso de una máquina) y el puerto (en el caso de un puerto aleatorio) a usar en el código. Por ejemplo:
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 ) ;
}
Aquí asignamos el puerto de contenedor 5432 al puerto de host 40001 explícitamente. Tenga en cuenta el uso de container.ToHostExposedEndpoint(...)
. Esto es para resolver siempre una IP y un puerto que funcionen para comunicarse con el contenedor acoplable. También es posible asignar un puerto aleatorio, es decir, dejar que Docker elija un puerto disponible. Por ejemplo:
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 única diferencia aquí es que solo se usa un argumento cuando se usó ExposePort(...)
para configurar el contenedor. De lo contrario se aplica el mismo uso y, por lo tanto, es transparente para el código.
Para saber cuándo un determinado servicio está en funcionamiento antes de comenzar, por ejemplo, a conectarse a él. Es posible esperar a que se abra un puerto específico. Por ejemplo:
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 ( ) ) ;
}
En el ejemplo anterior, esperamos a que se abra el puerto del contenedor 5432 en 30 segundos. Si falla, generará una excepción y, por lo tanto, el contenedor se desechará y eliminará (ya que no tenemos ninguna configuración de conservación de contenedor, 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 ( ) ) ;
}
A veces no es posible llegar directamente al contenedor, por IP y puerto local; en cambio, por ejemplo, el contenedor tiene un puerto expuesto en la interfaz loopback ( 127.0.0.1 ) y esa es la única forma de llegar al contenedor desde el programa. El ejemplo anterior fuerza que la dirección sea 127.0.0.1 pero aún resuelve el puerto del host. De forma predeterminada, FluentDocker utiliza la inspección de red en el contenedor para determinar la configuración de la red.
A veces no basta con esperar a que llegue el puerto. A veces es mucho más importante esperar por un proceso de contenedor. Por lo tanto, existe un método de espera de proceso en la API fluida, así como un método de extensión en el objeto contenedor. Por ejemplo:
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 ( ) ) ;
}
En el ejemplo anterior, Build()
devolverá el control cuando el proceso "postgres" se haya iniciado dentro del contenedor.
Para utilizar contenedores, a veces es necesario montar volúmenes en el contenedor en el host o simplemente copiar desde o hacia el contenedor. Dependiendo de si está ejecutando la máquina o la ventana acoplable de forma nativa, la asignación de volúmenes tiene la restricción de que debe ser accesible desde la máquina virtual.
Un caso de uso normal es tener, por ejemplo, un servidor web que proporcione contenido en un contenedor acoplable y el usuario edite archivos en el sistema de archivos del host. En tal escenario, es necesario montar un volumen de contenedor acoplable en el host. Por ejemplo:
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 ) ;
}
En el ejemplo anterior, se inicia un contenedor nginx y se monta '/usr/share/nginx/html' en una ruta de host (aleatoria, en el directorio temporal). Se copia un archivo HTML en la ruta del host y cuando se realiza una transferencia HTTP hacia el contenedor acoplable nginx, se sirve ese mismo archivo.
A veces es necesario copiar archivos hacia y desde un contenedor. Por ejemplo, copie un archivo de configuración, configúrelo y cópielo nuevamente. El escenario más común es copiar un archivo de configuración al contenedor, justo antes de que se inicie. El ejemplo de varios contenedores copia un archivo de configuración de nginx justo antes de iniciarlo. Por lo tanto, es posible evitar la creación manual de un Dockerfile y una imagen para una tarea tan sencilla. En su lugar, utilice, por ejemplo, una imagen oficial o personalizada, copie la configuración y ejecútela.
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" ) ) ) ;
}
El ejemplo anterior copia un directorio a una ruta de host (fullPath) desde un contenedor en ejecución. Tenga en cuenta el uso del método de extensión aquí, por lo que no se utiliza la API fluida (ya que CopyFrom está después de Start()). Si desea copiar archivos del contenedor justo antes de comenzar, utilice la API Fluent.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. CopyOnStart ( "/etc/conf.d" , fullPath )
. Build ( )
. Start ( ) )
{
}
El siguiente ejemplo ilustra un escenario mucho más común en el que los archivos se copian al contenedor. Este ejemplo utiliza el método de extensión en lugar de la versión API fluida. Toma una instantánea Diff antes de la copia y luego justo después de la copia. En este último está presente hello.html.
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" ) ) ;
}
En algún momento es útil copiar archivos en IContainerService.Dispose()
(justo antes de que el contenedor se detenga). Por lo tanto, existe una API fluida para garantizar que simplemente haga eso.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. CopyOnDispose ( "/etc/conf.d" , fullPath )
. Build ( )
. Start ( ) )
{
}
Para analizar un contenedor, existen métodos de extensión de exportación y métodos API fluidos. Lo más notable es la posibilidad de exportar un contenedor cuando se elimina un IContainerService
.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. ExportOnDispose ( fullPath )
. Build ( )
. Start ( ) )
{
}
Esto producirá una exportación de contenedor (archivo tar) en el host (fullPath). Si prefiere explotarlo (sin tararlo), utilice el método ExportExplodedOnDispose
. Por supuesto, puede exportar el contenedor en cualquier momento utilizando un método de extensión en el contenedor.
Un truco útil cuando se trata de pruebas unitarias es exportar el estado del contenedor cuando la prueba unitaria falla por algún motivo; por lo tanto, existe una API Fluent que exportará cuando se cumpla una determinada condición Lambda. Por ejemplo:
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 ;
}
Este fragmento exportará el contenedor cuando la instrucción de uso elimine el contenedor, ya que la variable de falla está establecida en verdadero y se usa en la expresión ExportOnDispose
.
Todos los servicios se pueden ampliar con ganchos. En ExportOnDispose(path, lambda)
se instala un Hook cuando el estado del servicio está configurado para ejecutar lambda cuando el estado es Removing
. Es posible instalar y quitar ganchos sobre la marcha. Si se registran varios enlaces en la misma instancia de servicio, con el mismo ServiceRunningState
, se ejecutarán en el orden de instalación.
Los ganchos son particularmente buenos si desea que se ejecute algo cuando un estado está a punto de establecerse (o ejecutarse) en el servicio, como Starting
. La API Fluent los utiliza en algunas situaciones, como copiar archivos, exportar, etc.
FluentDocker admite todos los comandos de red de Docker. Puede descubrir redes mediante _docker.NetworkLs()
donde descubre todas las redes y algunos parámetros simples definidos en NetworkRow
. También puede inspeccionar para obtener información más profunda sobre la red (como qué contenedores están en la red y la configuración de Ipam) mediante _docker.NetworkInspect(network:"networkId")
.
Para crear una nueva red, utilice _docker.NetworkCreate("name_of_network")
. También es posible proporcionar NetworkCreateParams
donde todo se puede personalizar, como crear una red superpuesta y cambiar la configuración de Ipam. Para eliminar una red, simplemente use _docker.NetworkRm(network:"networkId")
.
Tenga en cuenta que las redes no se eliminan si hay contenedores adjuntos.
Cuando se crea una red, es posible colocar uno o más contenedores en ella usando _docker.NetworkConnect("containerId","networkId")
. Tenga en cuenta que los contenedores pueden estar en varias redes a la vez, por lo que pueden realizar solicitudes de proxy entre redes aisladas. Para desconectar un contenedor de una red, simplemente haga _docker.NetworkDisconnect("containerId","networkId")
.
El siguiente ejemplo ejecuta un contenedor, crea una nueva red y conecta el contenedor en ejecución a la red. Luego desconecta el contenedor, lo elimina y elimina la red.
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 ) ;
También es posible utilizar un constructor fluido para crear redes acoplables nuevas o reutilizar redes acoplables existentes. Luego se puede hacer referencia a ellos mientras se construyen contenedores . Es posible crear más de una red acoplable y conectar un contenedor a más de una red a la vez.
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 ( ) ;
}
}
El fragmento de código anterior crea una nueva red llamada test-network y luego crea un contenedor que se adjunta a test-network . Cuando se llama a Dispose()
en nw, se eliminará la red. También es posible realizar asignaciones de contenedores de IP estáticas dentro de la red mediante UseIpV4
o UseIpV6
. Por ejemplo:
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 ) ;
}
}
El ejemplo anterior crea una nueva red unit-test-nw con ip-range 10.18.0.0/16 . Es el usado en el nuevo contenedor. La IP del contenedor está configurada en 10.18.0.22 y es estática debido al comando UseIpV4
.
FluentDocker admite la gestión de volúmenes de Docker tanto desde comandos como desde una API fluida. Por lo tanto, es posible tener un control total sobre los volúmenes utilizados en el contenedor, como si se eliminará, se reutilizará, qué controlador utilizar, 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" ) ;
El fragmento anterior crea un nuevo volumen con el nombre volumen-prueba y es de tipo NFS . Luego inspecciona el volumen recién creado y, por último, fuerza la eliminación del volumen.
También es posible utilizar una API fluida para crear o utilizar volúmenes. Luego se pueden utilizar para construir un contenedor. Esto es especialmente útil cuando la creación de volúmenes es especial o es necesario controlar la vida útil.
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 ) ;
}
}
El ejemplo anterior crea un nuevo volumen llamado test-volume y está programado para eliminarse cuando se invoca Dispose()
en IVolumeService
. El contenedor se crea y monta el volumen recién creado en /var/lib/postgresql/data como modo de acceso de lectura/escritura . Dado que el contenedor está dentro del alcance de la declaración de using
del volumen, su vida útil abarca toda la vida útil del contenedor y luego se elimina.
FluentDocker admite la conexión al mecanismo de eventos de Docker para escuchar los eventos que envía.
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 ;
}
}
}
El detector de eventos es global y puede manejar muchos tipos EventAction
.
Por ejemplo
Dependiendo de la acción, el tipo de evento puede diferir, como ContainerKillEvent
para EventAction.Kill
. Todos los eventos derivan de FdEvent
. Eso significa que todas las propiedades compartidas están en el evento base y las explícitas están en el derivado.
Por ejemplo, ´ContainerKillEvent` contiene las siguientes propiedades:
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 ; }
}
Este bucle de eventos se puede utilizar para recoger eventos y controlar sus instancias IService
instanciadas. O si necesita reaccionar, por ejemplo, si se agrega o elimina una red.
En el marco completo, utiliza el registro detallado mediante System.Diagnostics.Debugger.Log
. Para .net core, utiliza el estándar Microsoft.Extensions.Logging.ILog
para iniciar sesión. Ambos utilizan la categoría Ductus.FluentDocker y, por lo tanto, pueden configurarse para participar o no en el registro y configurar diferentes destinos de registro.
En .net core, puede proporcionar el segmento de registro en el archivo de configuración de la aplicación.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Ductus.FluentDocker": "None"
}
}
}
Consulte https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1 para obtener más información. Para obtener el marco completo, consulte el XML necesario en appconfig para conocer el marco completo descrito en https://docs.microsoft.com/en-us/dotnet/framework/wcf/diagnostics/tracing/configuring-tracing.
Existe una forma rápida de deshabilitar/habilitar el registro mediante ( Ductus.FluentDocker.Services
) Logging.Enabled()
o Logging.Disabled()
. Esto habilitará/deshabilitará el registro a la fuerza.
Es posible anular el mecanismo predeterminado de FluentDocker para resolver la IP del contenedor desde la perspectiva del cliente, por ejemplo, en WaitForPort
. Esto se puede anular según ContainerBuilder
.
El siguiente ejemplo anula el comportamiento predeterminado . Cuando devuelve null
se activa el solucionador predeterminado .
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 ) ;
}
Hay soporte limitado para usar FluentAPI para hablar con un demonio acoplable remoto sin usar Docker-machine. Esto se hace creando manualmente una instancia de DockerHostService
o usando FromUri
en HostBuilder
.
using ( var container = Fd . UseHost ( ) .
FromUri ( Settings . DockerUri , isWindowsHost : true ) .
UseContainer ( ) .
Build ( ) )
{
}
El ejemplo anterior se conecta a un DockerUri
personalizado desde una configuración y es un demonio acoplable de contenedor de Windows.
FromUri
que usa DockerUri
para crear un IHostService
. Este uri es arbitrario. También admite otras propiedades ( ver más abajo ). public HostBuilder FromUri (
DockerUri uri ,
string name = null ,
bool isNative = true ,
bool stopWhenDisposed = false ,
bool isWindowsHost = false ,
string certificatePath = null ) { /*...*/ }
Utilizará valores predeterminados "sensibles" en todos los parámetros. En la mayoría de los casos, la uri es suficiente. Por ejemplo, si no proporciona la ruta del certificado, intentará obtenerla del entorno DOCKER_CERT_PATH . Si no se encuentra en el entorno, el valor predeterminado será ninguno.
UseHost
que requiere una implementación IHostService
instanciada. La biblioteca admite docker-compose para utilizar archivos de redacción existentes para prestar servicios y gestionar la vida útil de los mismos.
El siguiente ejemplo tendrá un archivo de redacción que activa MySql y WordPress . Por lo tanto, el servicio de redacción único tendrá dos servicios de contenedor debajo. De forma predeterminada, detendrá los servicios y los limpiará cuando se invoque Dispose()
. KeepContainers()
puede anular esto en la configuración fluida .
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 :
El archivo anterior es el archivo docker-compose para unir el servicio completo.
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 ) ;
}
El fragmento anterior configura con fluidez el servicio Docker-Compose e invoca la página de instalación para verificar que WordPress realmente esté funcionando.
También es posible realizar todas las operaciones que admite un solo contenedor, como operaciones de copia, exportación y espera. Por ejemplo:
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 ) ;
}
El fragmento anterior inicia el proyecto de redacción de WordPress Docker y verifica la URL http://localhost:8000/wp-admin/install.php y devuelve un cierto valor en el cuerpo (en este caso "https://wordpress.org /"). De lo contrario, devuelve 500 y la función WaitForHttp
esperará 500 milisegundos antes de volver a invocarse. Esto también funciona para cualquier lambda personalizada, simplemente use WaitFor
en su lugar. Por lo tanto, es posible, por ejemplo, consultar una base de datos antes de continuar dentro del alcance de uso.
Para los usuarios de Linux y Mac, existen varias opciones para autenticarse en el socket. FluentDocker no admite sudo , sudo sin contraseña (el usuario se agrega como NOPASSWD en /etc/sudoer) o sudo con contraseña. El valor predeterminado es que FluentDocker espera poder hablar sin ningún sudo . Las opciones son globales pero se pueden cambiar en tiempo de ejecución.
SudoMechanism . None . SetSudo ( ) ; // This is the default
SudoMechanism . Password . SetSudo ( "<my-sudo-password>" ) ;
SudoMechanism . NoPassword . SetSudo ( ) ;
Si desea desactivar sudo para comunicarse con el demonio de Docker, puede seguir un tutorial de Docker y realizar el último paso para agregar su usuario al grupo de Docker.
FluentDocker admite la conexión a demonios acoplables remotos. La API fluida admite, por ejemplo,
new Builder ( ) . UseHost ( ) . UseMachine ( ) . WithName ( "remote-daemon" )
donde esto requiere una entrada ya preconfigurada en el registro de la máquina acoplable . También es posible definir registros completos de la máquina acoplable basados en SSH para conectarse a un demonio remoto.
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 ) ;
}
Este ejemplo creará una nueva entrada de registro de Docker-machine denominada daemon remoto que usa SSH con la dirección IP 192.168.1.27 y el usuario SSH solo . Si ya se encuentra una entrada llamada daemon remoto, simplemente reutilizará esta entrada. Luego obtiene un IHostService con los certificados y la URL correctos para el demonio remoto. Por lo tanto, es posible crear un contenedor acoplable en el demonio remoto, en cuyo caso es la imagen de Postgres . Cuando desecha el contenedor, como de costumbre, lo elimina de la ventana acoplable remota. IHostService se asegura de recoger todos los certificados necesarios para autenticar la conexión.
El ejemplo anterior genera esta entrada de registro de 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
Para utilizar UseSsh(...)
se debe configurar un túnel SSH sin contraseña. Además, el usuario que utiliza el túnel debe poder acceder al demonio acoplable.
Siga este tutorial sobre cómo configurar el túnel SSH y asegúrese de que el usuario pueda acceder al demonio de Docker.
Básicamente, cree una nueva clave rsa para usar con el túnel SSH usando ssh-keygen -t rsa
y luego cópiela en el host remoto mediante ssh-copy-id {username}@{host}
.
Edite /etc/sudoers como se especifica en el segundo tutorial.
Una vez hecho esto, ahora podrá acceder al demonio acoplable remoto mediante el controlador genérico o la API fluida especificada anteriormente. Para hacer lo mismo manualmente como se especifica en el ejemplo, se vería así.
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
Ahora que se crea la entrada del registro, es posible configurar el entorno para la ventana acoplable del 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
Ejecute esto para que el cliente Docker utilice el demonio Docker remoto.
@FOR /f "tokens=*" %i IN ('docker-machine.exe env remote-daemon') DO @%i
Todos los comandos que utilizan el binario docker
ahora se ejecutarán en el demonio de Docker remoto.
Al crear y consultar, a través de una máquina, una máquina acoplable Hyper-V, el proceso debe elevarse, ya que Hyper-V no responderá a las llamadas API en el modo de usuario estándar.
Es posible especificar una verificación de estado para el contenedor acoplable para informar el estado del contenedor en función de dicha actividad. El siguiente ejemplo utiliza una verificación de estado para determinar si el contenedor ha salido o no. Es posible verificar la configuración (asegúrese de forzar la actualización) qué estado informa la verificación de estado.
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 ) ;
}
Es posible a través de Fluent API y ContainerCreateParams
especificar ulimit en el contenedor de la ventana acoplable para, por ejemplo, limitar la cantidad de archivos abiertos, etc. Por ejemplo, el uso de Fluent API podría verse así al restringir la cantidad de archivos abiertos a 2048 (tanto blandos como duros) .
using (
var container =
Fd . UseContainer ( )
. UseImage ( "postgres:latest" , force : true )
. UseUlimit ( Ulimit . NoFile , 2048 , 2048 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( )
. Start ( ) )
{
// Do stuff
}
Este repositorio contiene tres paquetes nuget, uno para acceso fluido, uno para clases base ms-test y otro para clases base xunit que se usarán durante las pruebas. Por ejemplo, en una prueba unitaria es posible iniciar un contenedor de Postgres y esperar a que se inicie la base de datos.
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 ( ) ;
}
También es posible reutilizar clases base abstractas, por ejemplo, la base de prueba de Postgres para simplificar y realizar pruebas unitarias limpias en un contenedor.
[ TestClass ]
public class PostgresMsTests : PostgresTestBase
{
[ TestMethod ]
public void Test ( )
{
// We now have a running postgres
// and a valid connection string to use.
}
}
FluentDockerTestBase
permite anulaciones simples para realizar fácilmente cualquier prueba personalizada respaldada por Docker. Simplemente cree una clase de prueba y derivela de FluentDockerTestBase
y anule los métodos adecuados. Por ejemplo.
protected override DockerBuilder Build ( )
{
return new DockerBuilder ( )
. WithImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( $ "POSTGRES_PASSWORD= { PostgresPassword } " )
. ExposePorts ( "5432" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ ) ;
}
Esto creará un generador con la imagen de la ventana acoplable postgres:latest y establecerá una cadena de entorno, también expondrá el puerto 5432 de la base de datos de Postgres al host para que uno pueda conectarse a la base de datos dentro del contenedor. Por último, esperará el puerto 5432. Esto garantiza que la base de datos se esté ejecutando y se haya iniciado correctamente. Si se agota el tiempo de espera, en este ejemplo establecido en 30 segundos, se generará una excepción y el contenedor se detendrá y se eliminará. Tenga en cuenta que el puerto del host no es 5432. Utilice Container.GetHostPort("5432/tcp")
para obtener el puerto del host. La propiedad Container.Host
puede recuperar la IP del host y, por lo tanto, se utilizará al comunicarse con la aplicación en el contenedor.
Si se necesita una devolución de llamada cuando el contenedor se haya extraído, creado e iniciado correctamente.
protected override void OnContainerInitialized ( )
{
ConnectionString = string . Format ( PostgresConnectionString , Container . Host ,
Container . GetHostPort ( "5432/tcp" ) , PostgresUser ,
PostgresPassword , PostgresDb ) ;
}
Este ejemplo genera una cadena de conexión adecuada a la base de datos postgresql dentro del contenedor recién creado. Esto se puede utilizar para conectarse mediante Npgsql, EF7, NHibernate, Marten u otras herramientas compatibles. No se llamará a este método si se extrae la imagen del repositorio de la ventana acoplable o si no se puede crear/iniciar el contenedor.
Si se desea anular un gancho de contenedor antes del cierre.
protected virtual void OnContainerTearDown ( )
{
// Do stuff before container is shut down.
}
Tenga en cuenta que, si el contenedor no tiene nombre, si no se elimina correctamente, el contenedor acoplable seguirá ejecutándose y deberá eliminarse manualmente. Esta es una característica, no un error, ya que es posible que desee ejecutar varios contenedores en su prueba. La clase DockerContainer
administra la identificación de instancia del contenedor y, por lo tanto, solo interactúa con él y con ningún otro contenedor.
Al crear/iniciar un nuevo contenedor, primero comprobará el repositorio local si la imagen del contenedor ya está presente y la descargará si no la encuentra. Esto puede llevar algún tiempo y solo hay un registro de depuración, si está habilitado, es posible monitorear el proceso de descarga.
Cuando se produce una excepción no controlada y la aplicación FailFast, es decir, finaliza rápidamente, no invocará la cláusula finally
. Por lo tanto, un WaitForPort
fallido dentro de una declaración using
no eliminará el servicio de contenedor. Por lo tanto, el contenedor todavía está ejecutándose. Para solucionar este problema, realice un intento global... capture o inyecte uno localmente, por ejemplo
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 ; }
Pero esto es solo cuando la terminación de la aplicación se realiza debido a la FluentDockerException
lanzada en WaitForPort
; de lo contrario, eliminará el contenedor correctamente y, por lo tanto, no se necesita try...catch
.
Esto también podría resolverse usando las funciones Fd.Build
( consulte Uso de extensiones de Builder para obtener más información).
La clase Fd
es una clase estática que proporciona métodos convenientes para crear y ejecutar contenedores individuales y compuestos. Para construir un contenedor simplemente use:
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 ) ) ;
Luego, esto se puede usar para iniciar los contenedores dentro de una cláusula using
seguro que se garantiza que se eliminará incluso si no se detecta una excepción.
build . Container ( svc =>
{
var config = svc . GetConfiguration ( ) ;
// Do stuff...
} ) ;
Después de ejecutar el método Container
, en este caso el contenedor se detiene y se elimina. Este es el equivalente de
// This is equivalent of
try
{
using ( var svc = build . Build ( ) )
{
svc . Start ( ) ;
var config = svc . GetConfiguration ( ) ;
// Do stuff...
}
}
catch
{
Log ( .. . ) ;
throw ;
}
También es posible combinar el constructor y la ejecución, por ejemplo mediante:
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...
} ) ;
El ejemplo anterior construirá el contenedor, lo iniciará, lo detendrá y finalmente lo eliminará. Incluso si se lanza Exception
, se Disposed
. Por supuesto, es posible utilizar un contenedor comprimido utilizando métodos de extensión composite
como con container
.