Упаковка | NuGet |
---|---|
FluentDocker | |
Microsoft Тест | |
XUnit-тест |
Эта библиотека обеспечивает взаимодействие docker
и docker-compose
с использованием Fluent API . Он поддерживается в Linux, Windows и Mac. Он также поддерживает устаревшие взаимодействия docker-machine
.
Пример использования Fluent API
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ )
. Build ( )
. Start ( ) )
{
var config = container . GetConfiguration ( true ) ;
Assert . AreEqual ( ServiceRunningState . Running , config . State . ToServiceState ( ) ) ;
}
Это запускает postgres и ожидает его готовности. Чтобы использовать композицию, просто сделайте это следующим образом:
ПРИМЕЧАНИЕ. Используйте AssumeComposeVersion(ComposeVersion.V2), чтобы использовать поведение V2, по умолчанию по-прежнему используется V1 (позже в этом году будет изменено на значение по умолчанию на V2).
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 Примечание для пользователей Linux: Docker по умолчанию требует sudo , а библиотека по умолчанию ожидает, что исполняющему пользователю не нужно выполнять sudo для связи с демоном docker. Более подробное описание можно найти в главе «Разговор с Docker Daemon» .
Свободный API создает один или несколько сервисов. Каждая услуга может быть составной или единичной. Таким образом, можно, например, запустить несколько служб на основе docker-compose и управлять каждым из них как единым сервисом или изучить и использовать все базовые службы в каждой службе docker-compose . Также можно напрямую использовать услуги, например
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 ) ;
}
В приведенном выше примере создается служба Docker-Compose из одного файла Compose. Когда служба удаляется, все базовые службы автоматически останавливаются.
Библиотека поддерживается полной платформой .NET 4.51 и выше, стандартом .NET 1.6, 2.0. Он разделен на три тонких слоя, каждый слой доступен:
Большинство методов службы являются методами расширения и не встроены в саму службу, что делает их легкими и настраиваемыми. Поскольку все доступно, например, легко добавить метод расширения для службы, которая использует команды уровня 1 для обеспечения функциональности.
Я приветствую вклад, хотя на данный момент не существует рекомендаций по вкладу, обязательно придерживайтесь .editorconfig при выполнении запросов на извлечение. В противном случае сборка завершится неудачно. Рано или поздно в этом году я обновлю настоящее руководство.
Для работы всех команд требуется DockerUri
. Это Uri демона Docker, локального или удаленного. Он может быть обнаруживаемым или жестко запрограммированным. Обнаружение локального DockerUri
можно выполнить с помощью
var hosts = new Hosts ( ) . Discover ( ) ;
var _docker = hosts . FirstOrDefault ( x => x . IsNative ) ?? hosts . FirstOrDefault ( x => x . Name == "default" ) ;
В вырезанном примере будут проверяться «родные» хосты или хосты Docker Beta, если не выбрать Docker-машину «по умолчанию» в качестве хоста. Если вы используете докер-машину и ни одна машина не существует или не запущена, легко создать/запустить докер-машину, например, "test-machine".Create(1024,20000000,1)
. Это создаст докер-машину с именем «test-machine» с 1 ГБ ОЗУ, 20 ГБ диска и одним процессором.
Теперь можно использовать Uri для связи с помощью команд. Например, чтобы получить версию двоичных файлов Docker клиента и сервера:
var result = _docker . Host . Version ( _docker . Certificates ) ;
Debug . WriteLine ( result . Data ) ; // Will Print the Client and Server Version and API Versions respectively.
Все команды возвращают CommandResponse, так что можно проверить фактор успеха по response.Success
. Если какие-либо данные связаны с командой, они возвращаются в свойстве response.Data
.
Тогда просто, как показано ниже, можно запустить и остановить, включая удаление контейнера, с помощью команд. Ниже запускается контейнер и выполняется PS, а затем удаляется.
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 ) ;
При работе в Windows можно выбрать запуск Linux или контейнера Windows. Используйте LinuxDaemon
или WindowsDaemon
, чтобы контролировать, с каким демоном общаться.
_docker . LinuxDaemon ( ) ; // ensures that it will talk to linux daemon, if windows daemon it will switch
Некоторые команды возвращают поток данных, когда, например, события или журналы требуют использования непрерывного потока. Потоки могут использоваться в фоновых задачах и поддерживают CancellationToken
. Ниже приведен пример журнала.
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 ) ;
}
}
Для команд существуют служебные методы. Они бывают разных видов, например, сетевые и т. д. Например, при чтении журнала до конца:
using ( var logs = _docker . Host . Logs ( id , _docker . Certificates ) )
{
foreach ( var line in logs . ReadToEnd ( ) )
{
Debug . WriteLine ( line ) ;
}
}
Самый высокий уровень этой библиотеки — это свободный API, с помощью которого вы можете определять машины, образы и контейнеры и управлять ими. Например, настройка балансировщика нагрузки с двумя серверами nodejs, считывающими данные с сервера Redis, может выглядеть так (образ узла создается вручную, если его нет в репозитории).
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 ) ;
}
В приведенном выше примере определяется Dockerfile и создается его для образа узла. Затем он использует ванильный Redis и nginx. Если вы просто хотите использовать существующий файл Dockerfile, это можно сделать следующим образом.
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.
}
Свободный API поддерживает определение Docker-машины и набора экземпляров Docker. Он имеет встроенную поддержку, например, ожидания определенного порта или процесса внутри контейнера до завершения Build()
и поэтому его можно безопасно использовать в операторе using. Если есть специальное управление таймаутами ожидания и т. д., вы всегда можете создать и запустить контейнер и использовать методы расширения для ожидания самого контейнера.
Чтобы создать контейнер, просто опустите начало. Например:
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( ) )
{
Assert . AreEqual ( ServiceRunningState . Stopped , container . State ) ;
}
В этом примере создается контейнер с postgres, настраивается одна переменная среды. В инструкции using можно запустить IContainerService
. Таким образом, каждый построенный контейнер обертывается в IContainerService
. Также можно использовать IHostService.GetContainers(...)
для получения созданных, запущенных и закрытых контейнеров. Из IHostService
также можно получить все изображения в локальном репозитории для создания контейнеров.
Если вы хотите запустить один контейнер, используйте метод запуска Fluent или контейнерной службы. Например:
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" ) ) ;
}
По умолчанию контейнер останавливается и удаляется при запуске метода Dispose. Чтобы сохранить контейнер в архиве, используйте KeepContainer()
в свободном API. Когда вызывается Dispose()
он будет остановлен, но не удален. Также возможно оставить его включенным после утилизации.
Порты можно выставлять как явно, так и случайным образом. В любом случае можно разрешить IP-адрес (в случае машины) и порт (в случае случайного порта) для использования в коде. Например:
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 ) ;
}
Здесь мы явно сопоставляем порт контейнера 5432 с портом хоста 40001. Обратите внимание на использование container.ToHostExposedEndpoint(...)
. Это необходимо для того, чтобы всегда разрешать рабочий IP-адрес и порт для связи с контейнером докеров. Также можно сопоставить случайный порт, т. е. позволить Docker выбрать доступный порт. Например:
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 ) ;
}
Единственное отличие здесь заключается в том, что при использовании ExposePort(...)
для настройки контейнера используется только один аргумент. То же использование применяется и в других случаях и, таким образом, прозрачно для кода.
Чтобы узнать, когда определенная служба запущена и работает, прежде чем начинать, например, подключаться к ней. Можно дождаться открытия определенного порта. Например:
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 ( ) ) ;
}
В приведенном выше примере мы ждем открытия порта контейнера 5432 в течение 30 секунд. В случае сбоя будет выдано исключение, и, таким образом, контейнер будет удален (поскольку у нас нет конфигурации сохранения контейнера и т. д.).
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 ( ) ) ;
}
Иногда невозможно напрямую связаться с контейнером по локальному IP-адресу и порту, вместо этого, например, у контейнера есть открытый порт в интерфейсе обратной связи ( 127.0.0.1 ), и это единственный способ добраться до контейнера из программы. В приведенном выше примере адрес должен быть 127.0.0.1, но при этом разрешается порт хоста. По умолчанию FluentDocker использует проверку сети в контейнере для определения конфигурации сети.
Иногда недостаточно просто дождаться порта. Иногда гораздо важнее дождаться контейнерного процесса. Поэтому в свободном API существует метод ожидания процесса, а также метод расширения объекта-контейнера. Например:
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 ( ) ) ;
}
В приведенном выше примере Build()
вернет управление, когда процесс «postgres» будет запущен внутри контейнера.
Чтобы использовать контейнеры, иногда необходимо смонтировать тома в контейнере на хост или просто скопировать из контейнера или в контейнер. В зависимости от того, используете ли вы машину или докер, изначальное сопоставление томов имеет ограничение, согласно которому оно должно быть доступно с виртуальной машины.
Обычный вариант использования — это, например, веб-сервер, обслуживающий контент в контейнере докеров, а пользователь редактирует файлы в файловой системе хоста. В таком случае необходимо смонтировать том докер-контейнера на хост. Например:
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 ) ;
}
В приведенном выше примере контейнер nginx запускается и монтирует /usr/share/nginx/html по пути хоста (случайный, во временном каталоге). HTML-файл копируется в путь хоста, и когда HTTP-переход к контейнеру докеров nginx выполняется, тот же файл обслуживается.
Иногда необходимо скопировать файлы в контейнер и из него. Например, скопируйте файл конфигурации, настройте его и скопируйте обратно. Более распространенный сценарий — копирование файла конфигурации в контейнер непосредственно перед его запуском. В примере с несколькими контейнерами файл конфигурации nginx копируется непосредственно перед его запуском. Таким образом, можно избежать создания Dockerfile и образа вручную для такой простой задачи. Вместо этого просто используйте, например, официальный или собственный образ, скопируйте конфигурацию и запустите.
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" ) ) ) ;
}
В приведенном выше примере каталог копируется в путь хоста (fullPath) из работающего контейнера. Обратите внимание, что здесь используется метод расширения, поэтому не используется свободный API (поскольку CopyFrom находится после Start()). Если вы хотите скопировать файлы из контейнера непосредственно перед запуском, используйте вместо этого Fluent API.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. CopyOnStart ( "/etc/conf.d" , fullPath )
. Build ( )
. Start ( ) )
{
}
В приведенном ниже примере показан гораздо более распространенный сценарий, когда файлы копируются в контейнер. В этом примере используется метод расширения вместо свободной версии API. Перед копированием и сразу после копирования создается снимок различий. В последнем присутствует 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" ) ) ;
}
Иногда бывает полезно скопировать файлы в IContainerService.Dispose()
(непосредственно перед остановкой контейнера). Поэтому существует свободный API, гарантирующий, что он сделает именно это.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. CopyOnDispose ( "/etc/conf.d" , fullPath )
. Build ( )
. Start ( ) )
{
}
Для анализа контейнера существует метод расширения экспорта и свободные методы API. В частности, это возможность экспортировать контейнер при удалении IContainerService
.
using ( new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. ExposePort ( 5432 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. ExportOnDispose ( fullPath )
. Build ( )
. Start ( ) )
{
}
Это создаст экспорт контейнера (tar-файл) на хосте (fullPath). Если вы предпочитаете развернуть его (без тарирования), используйте вместо этого метод ExportExplodedOnDispose
. Конечно, вы можете экспортировать контейнер в любое время, используя метод расширения контейнера.
Когда дело доходит до модульного тестирования, полезным трюком является экспорт состояния контейнера, когда модульный тест по какой-либо причине завершается неудачей, поэтому существует Fluent API, который будет экспортировать, когда будет выполнено определенное условие Lambda. Например:
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 ;
}
Этот фрагмент будет экспортировать контейнер, когда оператор using удаляет контейнер, поскольку для переменной error установлено значение true и она используется в выражении ExportOnDispose
.
Все сервисы можно расширить с помощью хуков. В ExportOnDispose(path, lambda)
устанавливается перехватчик, когда состояние службы настроено на выполнение лямбда-выражения, когда состояние Removing
. Возможна установка и снятие крючков на лету. Если в одном экземпляре службы зарегистрировано несколько перехватчиков с одним и тем же ServiceRunningState
, они будут выполняться в порядке установки.
Перехватчики особенно хороши, если вы хотите, чтобы что-то выполнялось, когда в сервисе будет установлено (или выполнено) состояние, например Starting
. Fluent API использует их в некоторых ситуациях, таких как копирование файлов, экспорт и т. д.
FluentDocker поддерживает все сетевые команды Docker. Он может обнаруживать сети с помощью _docker.NetworkLs()
, где он обнаруживает все сети и некоторые простые параметры, определенные в NetworkRow
. Он также может проверять, чтобы получить более подробную информацию о сети (например, какие контейнеры находятся в сети и конфигурации Ipam) с помощью _docker.NetworkInspect(network:"networkId")
.
Чтобы создать новую сеть, используйте _docker.NetworkCreate("name_of_network")
. Также можно предоставить NetworkCreateParams
, где все можно настроить, например создать оверлейную сеть или изменить конфигурацию Ipam. Чтобы удалить сеть, просто используйте _docker.NetworkRm(network:"networkId")
.
Обратите внимание, что сети не удаляются, если к ним подключены какие-либо контейнеры!
Когда сеть создана, в нее можно поместить один или несколько контейнеров, используя _docker.NetworkConnect("containerId","networkId")
. Обратите внимание, что контейнеры могут находиться в нескольких сетях одновременно, поэтому запрос прокси может осуществляться между изолированными сетями. Чтобы отключить контейнер от сети, просто выполните _docker.NetworkDisconnect("containerId","networkId")
.
В следующем примере запускается контейнер, создается новая сеть и подключаются к сети работающий контейнер. Затем он отключает контейнер, удаляет его и удаляет сеть.
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 ) ;
Также можно использовать Fluent builder для создания новых или повторного использования существующих сетей докеров. Затем на них можно будет ссылаться при создании контейнеров . Можно построить более одной сети докеров и подключить контейнер к более чем одной сети одновременно.
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 ( ) ;
}
}
Приведенный выше фрагмент кода создает новую сеть под названием test-network , а затем создает контейнер, подключенный к test-network . Когда Dispose()
вызывается в nw, он удаляет сеть. Также возможно выполнять назначения статических IP- контейнеров внутри сети с помощью UseIpV4
или UseIpV6
. Например:
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 ) ;
}
}
В приведенном выше примере создается новая сеть unit-test-nw с диапазоном IP-адресов 10.18.0.0/16 . Он используется в новом контейнере. IP-адрес контейнера установлен на 10.18.0.22 и является статическим из-за команды UseIpV4
.
FluentDocker поддерживает управление томами Docker как с помощью команд, так и с помощью свободного API. Таким образом, можно полностью контролировать объемы, используемые в контейнере, например, следует ли его утилизировать, повторно использовать, какой драйвер использовать и т. д.
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" ) ;
Приведенный выше фрагмент создает новый том с именем test-volume и имеет тип NFS . Затем он проверяет только что созданный том и, наконец, принудительно удаляет том.
Также можно использовать свободный API для создания или использования томов. Затем их можно использовать при создании контейнера. Это особенно полезно, когда создание томов является особенным или необходимо контролировать срок службы.
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 ) ;
}
}
В приведенном выше примере создается новый том с именем test-volume , и его удаление запланировано при вызове Dispose()
в IVolumeService
. Контейнер создается и монтирует вновь созданный том в /var/lib/postgresql/data в режиме доступа для чтения/записи . Поскольку контейнер находится в области using
оператора тома, его время жизни охватывает все время жизни контейнера, а затем удаляется.
FluentDocker поддерживает подключение к механизму событий Docker для прослушивания отправляемых им событий.
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 ;
}
}
}
Прослушиватель событий является глобальным и может обрабатывать множество типов EventAction
.
Например
В зависимости от действия тип события может отличаться, например ContainerKillEvent
для EventAction.Kill
. Все события происходят от FdEvent
. Это означает, что все общие свойства находятся в базовом событии, а явные — в производном.
Например, ContainerKillEvent содержит следующие свойства:
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 ; }
}
Этот цикл событий можно использовать для сбора событий и управления созданными экземплярами IService
. Или, если вам нужно отреагировать, например, на добавление или удаление сети.
В полной версии он использует подробное ведение журнала с помощью System.Diagnostics.Debugger.Log
. Для ядра .net для регистрации используется стандартный Microsoft.Extensions.Logging.ILog
. Оба используют категорию Ductus.FluentDocker и поэтому могут быть настроены для участия в ведении журнала или нет, а также для настройки различных мест назначения журнала.
В ядре .net вы можете указать сегмент журнала в файле конфигурации приложения.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Ductus.FluentDocker": "None"
}
}
}
Пожалуйста, проверьте https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1 для получения дополнительной информации. Чтобы получить полную структуру, ознакомьтесь с XML-файлом, необходимым в конфигурации приложения для полной платформы, описанной в https://docs.microsoft.com/en-us/dotnet/framework/wcf/diagnostics/tracing/configuring-tracing.
Существует быстрый способ отключить/включить ведение журнала с помощью ( Ductus.FluentDocker.Services
) Logging.Enabled()
или Logging.Disabled()
. Это принудительно включит/отключит ведение журнала.
Можно переопределить механизм FluentDocker по умолчанию, который разрешает IP-адрес контейнера с точки зрения клиентов, например, в WaitForPort
. Это можно переопределить на основе ContainerBuilder
.
В приведенном ниже примере переопределяется поведение по умолчанию . Когда он возвращает null
срабатывает преобразователь по умолчанию .
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 ) ;
}
Поддержка использования FluentAPI для связи с удаленным демоном Docker без использования docker-machine ограничена. Это делается либо путем создания экземпляра DockerHostService
вручную, либо с помощью FromUri
в HostBuilder
.
using ( var container = Fd . UseHost ( ) .
FromUri ( Settings . DockerUri , isWindowsHost : true ) .
UseContainer ( ) .
Build ( ) )
{
}
Приведенный выше пример подключается к пользовательскому DockerUri
из настройки и является демоном Docker для контейнера Windows.
FromUri
, который использует DockerUri
для создания IHostService
. Этот URI является произвольным. Он также поддерживает другие свойства ( см. ниже ). public HostBuilder FromUri (
DockerUri uri ,
string name = null ,
bool isNative = true ,
bool stopWhenDisposed = false ,
bool isWindowsHost = false ,
string certificatePath = null ) { /*...*/ }
Он будет использовать «разумные» значения по умолчанию для всех параметров. В большинстве случаев URI достаточно. Например, если не предоставить путь к сертификату, он попытается получить его из среды DOCKER_CERT_PATH . Если он не найден в среде, по умолчанию он будет равен нулю.
UseHost
, который принимает экземпляр реализации IHostService
. Библиотека поддерживает docker-compose, позволяющую использовать существующие файлы компоновки для предоставления услуг и управления их сроком службы.
В следующем примере будет файл Compose, который запускает MySql и WordPress . Таким образом, под одной службой создания будут находиться две службы- контейнера . По умолчанию службы останавливаются и очищаются при вызове Dispose()
. Это можно переопределить с помощью KeepContainers()
в свободной конфигурации.
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 :
Вышеупомянутый файл представляет собой файл docker-compose для объединения всего сервиса.
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 ) ;
}
Приведенный выше фрагмент плавно настраивает службу docker-compose и вызывает страницу установки, чтобы убедиться, что WordPress действительно работает.
Также возможно выполнять все операции, которые поддерживает один контейнер, такие как копирование, экспорт и ожидание. Например:
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 ) ;
}
Приведенный выше фрагмент запускает проект создания докера WordPress и проверяет URL-адрес http://localhost:8000/wp-admin/install.php и возвращает определенное значение в теле (в данном случае «https://wordpress.org) /"). В противном случае она возвращает 500 , и функция WaitForHttp
будет ждать 500 миллисекунд перед повторным вызовом. Это работает и для любой пользовательской лямбды, просто используйте вместо этого WaitFor
. Таким образом, можно, например, запросить базу данных, прежде чем продолжить использование области действия.
Для пользователей Linux и Mac есть несколько вариантов аутентификации в сокете. FluentDocker не поддерживает sudo , sudo без пароля (пользователь добавляется как NOPASSWD в /etc/sudoer) или sudo с паролем. По умолчанию FluentDocker ожидает, что сможет общаться без использования sudo . Параметры являются глобальными, но их можно изменить во время выполнения.
SudoMechanism . None . SetSudo ( ) ; // This is the default
SudoMechanism . Password . SetSudo ( "<my-sudo-password>" ) ;
SudoMechanism . NoPassword . SetSudo ( ) ;
Если вы хотите отключить sudo для связи с демоном Docker, вы можете следовать руководству по Docker и выполнить последний шаг по добавлению пользователя в группу Docker.
FluentDocker поддерживает подключение к удаленным демонам Docker. Свободный API поддерживает, например
new Builder ( ) . UseHost ( ) . UseMachine ( ) . WithName ( "remote-daemon" )
где для этого требуется уже предварительно настроенная запись в реестре докер-машины . Также можно определить весь реестр докер-машины на основе SSH для подключения к удаленному демону.
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 ) ;
}
В этом примере будет создана новая запись реестра докер-машины с именем Remote-daemon , которая использует SSH с IP-адресом 192.168.1.27 и пользователем SSH Solo . Если запись с именем Remote-daemon уже найдена, она просто будет повторно использовать эту запись. Затем он получает IHostService с правильными сертификатами и URL-адресом для удаленного демона. Таким образом, можно создать Docker-контейнер на удаленном демоне, в данном случае это образ postgres . Когда он удаляет контейнер, он, как обычно, удаляет его из удаленного докера. IHostService обязательно получит все необходимые сертификаты для аутентификации соединения.
В приведенном выше примере создается эта запись реестра докер-машины .
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
Чтобы использовать UseSsh(...)
необходимо настроить SSH- туннель без пароля. Кроме того, пользователю, использующему туннель, должен быть разрешен доступ к демону Docker.
Следуйте этому руководству, чтобы настроить туннель SSH и убедиться, что пользователь может получить доступ к демону Docker.
По сути, создайте новый ключ rsa для использования с туннелем SSH, используя ssh-keygen -t rsa
, а затем скопируйте его на удаленный хост с помощью ssh-copy-id {username}@{host}
.
Отредактируйте файл /etc/sudoers, как указано во втором руководстве.
Когда это будет сделано, вы сможете получить доступ к удаленному демону Docker с помощью универсального драйвера или гибкого API, указанного выше. Чтобы сделать то же самое вручную, как указано в примере, это будет выглядеть примерно так.
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
Теперь запись реестра создана, и можно установить среду для докера терминала.
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
Запустите это, чтобы клиент Docker использовал удаленный демон Docker.
@FOR /f "tokens=*" %i IN ('docker-machine.exe env remote-daemon') DO @%i
Все команды, использующие двоичный файл docker
, теперь будут выполняться на удаленном демоне docker.
При создании и запросе через компьютер док-машины Hyper-V необходимо повысить уровень процесса, поскольку Hyper-V не будет отвечать на вызовы API в стандартном пользовательском режиме.
Для Docker-контейнера можно указать проверку работоспособности, чтобы сообщать о состоянии контейнера на основе такой активности. В следующем примере используется проверка работоспособности контейнера. Можно проверить конфигурацию (не забудьте принудительно обновить), о каком статусе сообщает проверка работоспособности.
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 ) ;
}
Это возможно через Fluent API и ContainerCreateParams
указать ulimit для контейнера докера, например, ограничить количество открытых файлов и т. д. Например, использование Fluent API может выглядеть так при ограничении количества открытых файлов до 2048 (как мягких, так и жестких) .
using (
var container =
Fd . UseContainer ( )
. UseImage ( "postgres:latest" , force : true )
. UseUlimit ( Ulimit . NoFile , 2048 , 2048 )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( )
. Start ( ) )
{
// Do stuff
}
Этот репозиторий содержит три пакета nuget: один для свободного доступа, один для базовых классов ms-test и другой для базовых классов xunit, которые будут использоваться во время тестирования. Например, в модульном тесте можно запустить контейнер postgres и дождаться загрузки базы данных.
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 ( ) ;
}
Также можно повторно использовать абстрактные базовые классы, например тестовую базу postgres, чтобы упростить и сделать чистый модульный тест для контейнера.
[ TestClass ]
public class PostgresMsTests : PostgresTestBase
{
[ TestMethod ]
public void Test ( )
{
// We now have a running postgres
// and a valid connection string to use.
}
}
FluentDockerTestBase
позволяет легко переопределять любые пользовательские тесты, поддерживаемые Docker. Просто создайте тестовый класс, наследуйте его от FluentDockerTestBase
и переопределите подходящие методы. Например.
protected override DockerBuilder Build ( )
{
return new DockerBuilder ( )
. WithImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( $ "POSTGRES_PASSWORD= { PostgresPassword } " )
. ExposePorts ( "5432" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ ) ;
}
Это создаст сборщик с изображением докера postgres:latest и установит одну строку среды, а также предоставит хосту порт 5432 базы данных postgres, чтобы можно было подключиться к базе данных внутри контейнера. Наконец, он будет ждать порта 5432. Это гарантирует, что база данных работает и правильно загрузилась. Если время ожидания в этом примере установлено равным 30 секундам, будет выдано исключение, и контейнер будет остановлен и удален. Обратите внимание, что порт хоста не 5432! Используйте Container.GetHostPort("5432/tcp")
чтобы получить порт хоста. IP-адрес хоста можно получить с помощью свойства Container.Host
и, следовательно, он должен использоваться при взаимодействии с приложением в контейнере.
Если обратный вызов необходим, когда контейнер был успешно извлечен, создан и запущен.
protected override void OnContainerInitialized ( )
{
ConnectionString = string . Format ( PostgresConnectionString , Container . Host ,
Container . GetHostPort ( "5432/tcp" ) , PostgresUser ,
PostgresPassword , PostgresDb ) ;
}
В этом примере отображается правильная строка подключения к базе данных postgresql внутри вновь развернутого контейнера. Это можно использовать для подключения с помощью Npgsql, EF7, NHibernate, Marten или других совместимых инструментов. Этот метод не будет вызываться, если образ был извлечен из репозитория докера или не удалось создать/запустить контейнер.
Если необходимо переопределить перехват контейнера перед завершением работы.
protected virtual void OnContainerTearDown ( )
{
// Do stuff before container is shut down.
}
Обратите внимание: если безымянный контейнер не удален должным образом, Docker-контейнер все равно будет работать, и его необходимо удалить вручную. Это функция, а не ошибка, поскольку вам может понадобиться, чтобы в вашем тесте работало несколько контейнеров. Класс DockerContainer
управляет идентификатором экземпляра контейнера и, таким образом, взаимодействует только с ним, а не с другим контейнером.
При создании/запуске нового контейнера он сначала проверит локальный репозиторий, существует ли уже образ контейнера, и загрузит его, если не найден. Это может занять некоторое время, и есть только журнал отладки, если он включен, и можно отслеживать процесс загрузки.
Когда возникает необработанное исключение и приложение FailFast, т.е. быстро завершает работу, оно не вызывает предложение finally
. Таким образом, неудачный вариант WaitForPort
внутри оператора using
не приведет к удалению службы контейнера. Следовательно, контейнер все еще работает. Чтобы это исправить, либо выполните глобальную попытку...catch, либо внедрите ее локально, например
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 ; }
Но это происходит только тогда, когда завершение приложения происходит из-за исключения FluentDockerException
, созданного в WaitForPort
, в противном случае контейнер будет правильно удален, и, следовательно, try...catch
не потребуется.
Эту проблему также можно решить с помощью функций Fd.Build
(дополнительную информацию см. в разделе «Использование расширений Builder »).
Класс Fd
— это статический класс , предоставляющий удобные методы для создания и запуска одиночных и составных контейнеров. Чтобы создать контейнер, просто используйте:
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 ) ) ;
Затем это можно использовать для запуска контейнеров в предложении безопасного using
, которое гарантированно будет удалено, даже если неперехваченное исключение.
build . Container ( svc =>
{
var config = svc . GetConfiguration ( ) ;
// Do stuff...
} ) ;
После выполнения метода Container
контейнер в этом случае останавливается и удаляется. Это эквивалент
// This is equivalent of
try
{
using ( var svc = build . Build ( ) )
{
svc . Start ( ) ;
var config = svc . GetConfiguration ( ) ;
// Do stuff...
}
}
catch
{
Log ( .. . ) ;
throw ;
}
Также возможно объединить сборщик и запуск, например, через:
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...
} ) ;
В приведенном выше примере контейнер создается, запускается, останавливается и, наконец, удаляется. Даже если и будет выбрано Exception
, оно будет Disposed
. Конечно, можно использовать составной контейнер, используя composite
методы расширения, как и в случае с container
.