包裹 | 努格特 |
---|---|
流暢的Docker | |
微軟測試 | |
XUnit測試 |
該程式庫支援使用Fluent API進行docker
和docker-compose
互動。 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 並等待它準備好。要使用 compose,只需這樣做:
注意:使用 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 守護程序對話一章中找到。
Fluent 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服務。當服務被處置時,所有底層服務都會自動停止。
此函式庫受 .NET 完整 4.51 框架及更高版本、.NET 標準 1.6、2.0 支援。它分為三層,每層都是可訪問的:
大多數服務方法都是擴展方法,沒有硬連線到服務本身,這使得它們輕量級且可自訂。由於一切都是可存取的,因此很容易為使用第 1 層命令提供功能的服務添加擴充方法。
我確實歡迎貢獻,儘管目前還沒有貢獻指南,但請確保在執行 Pull 請求時遵守.editorconfig 。否則建置將會失敗。今年遲早我會更新一個真正的指南。
所有指令都需要DockerUri
才能使用。它是本地或遠端 docker 守護程式的 Uri。它可以是可發現的或硬編碼的。可以透過以下方式發現本地DockerUri
var hosts = new Hosts ( ) . Discover ( ) ;
var _docker = hosts . FirstOrDefault ( x => x . IsNative ) ?? hosts . FirstOrDefault ( x => x . Name == "default" ) ;
截斷的範例將檢查本機或 docker beta「本機」主機,如果沒有選擇 docker-machine「預設」作為主機。如果您使用 docker-machine 且沒有機器存在或未啟動,則可以透過例如"test-machine".Create(1024,20000000,1)
輕鬆建立/啟動 docker-machine。這將建立一台名為「test-machine」的 docker 機器,具有 1GB RAM、20GB 磁碟並使用一個 CPU。
現在可以使用 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
當需要使用連續流的事件或日誌時,某些指令會傳回資料流。 Streams 可用於後台任務並支援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.
}
Fluent API 支援從定義一個 docker-machine 到一組 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 方法時容器會停止並刪除,為了將容器保留在 archve 中,請使用 Fluent API 上的KeepContainer()
。當呼叫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 容器進行通訊。也可以對應隨機端口,即讓 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使用容器上的網路檢查來確定網路配置。
有時僅僅等待端口是不夠的。有時,等待容器進程更為重要。因此,Fluent 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 ( ) ) ;
}
在上面的範例中,當容器內啟動進程「postgres」時, Build()
將傳回控制權。
為了使用容器,有時需要將容器中的磁碟區安裝到主機上,或只是從容器複製或複製到容器。根據您執行的是機器還是 Docker,本機磁碟區對應具有必須可從虛擬機器存取的限制。
正常的用例是讓網路伺服器在 Docker 容器上提供內容,並且使用者在主機檔案系統上編輯檔案。在這種情況下,需要將 docker 容器磁碟區安裝到主機上。例如:
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 檔案會複製到主機路徑中,當完成對 nginx docker 容器的 HTTP 取得時,將提供相同的檔案。
有時需要將檔案複製到容器中或從容器複製檔案。例如,複製配置文件,對其進行配置然後將其複製回來。更常見的場景是在容器啟動之前將設定檔複製到容器中。多容器範例在啟動之前複製 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)。注意這裡使用了擴充方法,因此沒有使用 Fluent 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 ( ) )
{
}
下面的範例說明了一種更常見的場景,其中檔案被複製到容器中。此範例使用擴充方法而不是 Fluent API 版本。它在複製之前和複製之後拍攝 Diff 快照。後者中存在 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 檔案)。如果您希望將其分解(去皮),請改用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 語句處置容器時,此程式碼片段會匯出容器,因為 failure 變數設為 true 並在ExportOnDispose
表達式中使用。
所有服務都可以透過鉤子進行擴充。在ExportOnDispose(path, lambda)
中,當服務狀態設定為在狀態為Removing
時執行 lambda 時,就會安裝一個 Hook。可即時安裝和移除鉤子。如果在同一個服務實例上註冊了多個鉤子,並且具有相同的ServiceRunningState
,它們將按照安裝順序執行。
如果您希望在服務上設定(或執行)狀態時執行某些操作(例如Starting
),那麼這些鉤子特別有用。 Fluent API 在某些情況下會使用這些 API,例如複製檔案、匯出等。
FluentDocker 支援所有 docker 網路命令。它可以透過_docker.NetworkLs()
發現網絡,它會發現NetworkRow
中定義的所有網路和一些簡單參數。它還可以透過_docker.NetworkInspect(network:"networkId")
進行檢查以獲取有關網路的更深入資訊(例如網路中有哪些容器以及 Ipam 配置)。
為了建立新網絡,請使用_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 建構器建置新的或重複使用現有的 Docker 網路。然後可以在建立容器時引用這些內容。可以建置多個 docker 網絡,並將一個容器一次連接到多個網路。
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 的容器。當在nw上呼叫Dispose()
時,它將刪除網路。也可以透過UseIpV4
或UseIpV6
在網路內進行靜態IP容器分配。例如:
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-range 為10.18.0.0/16 。這是在新容器中使用的。容器的 IP 設定為10.18.0.22 ,並且由於UseIpV4
指令而處於靜態。
FluentDocker 支援透過指令和 Fluent API 進行 docker 磁碟區管理。因此,可以完全控制容器中使用的體積,例如是否應處置、重複使用、使用什麼驅動程式等。
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 的新磁碟區,並計劃在IVolumeService
上呼叫Dispose()
時將其刪除。容器已創建,並將新建立的磁碟區以讀/寫存取模式掛載到/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
類型。
例如
根據操作的不同,事件類型可能有所不同,例如EventAction.Kill
的ContainerKillEvent
。所有事件均源自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 core,它使用標準的Microsoft.Extensions.Logging.ILog
來記錄。兩者都使用Ductus.FluentDocker類別,因此可以配置為參與或不參與日誌記錄並配置不同的日誌記錄目標。
在 .net core 中,您可以在應用程式設定檔中提供日誌記錄段。
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Ductus.FluentDocker": "None"
}
}
}
請查看 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1 以了解更多資訊。有關完整框架,請查看 appconfig 中所需的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 ) ;
}
在不使用 docker-machine 的情況下,使用FluentAPI與遠端 docker 守護程式對話的支援有限。這可以透過手動建立DockerHostService
實例或在HostBuilder
上使用FromUri
來完成。
using ( var container = Fd . UseHost ( ) .
FromUri ( Settings . DockerUri , isWindowsHost : true ) .
UseContainer ( ) .
Build ( ) )
{
}
上面的範例從設定連接到自訂DockerUri
,並且是一個 Windows 容器 Docker 守護程式。
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就足夠了。例如,如果不提供certificatePath,它將嘗試從環境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 docker compose 專案並檢查URL http://localhost:8000/wp-admin/install.php 是否回傳正文中的某個值(在本例中為「https://wordpress.org ” /”)。如果不是,則傳回500 , WaitForHttp
函數將等待 500 毫秒,然後再次呼叫。這也適用於任何自訂 lambda,只需使用WaitFor
即可。因此,可以在繼續使用範圍之前查詢資料庫。
對於 Linux 和 Mac 用戶,有多種選項可用於對套接字進行身份驗證。 FluentDocker支援 no sudo 、不帶任何密碼的sudo (使用者在 /etc/sudoer 中新增為 NOPASSWD )或帶密碼的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" )
這需要在docker-machine註冊表中預先設定條目。也可以定義基於SSH的docker-machine註冊表整體來連接到遠端守護程序。
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 的新docker-machine登錄項,該條目使用 IP 位址為192.168.1.27的SSH和SSH用戶alone 。如果已經找到名為Remote-daemon 的條目,它將重複使用該條目。然後,它會取得具有遠端守護程式的正確憑證和URL 的IHostService 。因此,可以在遠端守護程序上建立一個 docker 容器,在這種情況下它是postgres映像。當它處置容器時,像往常一樣,它會從遠端 Docker 中刪除它。 IHostService確保取得所有必要的憑證以驗證連線。
上面的範例產生此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
為了使用UseSsh(...)
必須設定無密碼的SSH隧道。此外,必須允許使用隧道的使用者存取 docker 守護程式。
請按照這些教學如何設定SSH隧道並確保使用者可以存取 docker 守護程式。
基本上使用ssh-keygen -t rsa
建立一個新的 rsa 金鑰以與SSH隧道一起使用,然後透過ssh-copy-id {username}@{host}
將其複製到遠端主機。
按照第二個教程中的指定編輯 /etc/sudoers。
完成此操作後,您現在可以透過通用驅動程式或上面指定的 Fluent API 存取遠端 docker 守護程式。要按照範例中指定的方式手動執行相同的操作,它看起來像這樣。
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
現在登錄項目已創建,可為終端 docker 設定環境。
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 docker 機器時,需要提升進程,因為 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
向 docker 容器指定 ulimit,例如限制開啟檔案的數量等。
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*/ ) ;
}
這將使用 docker 映像 postgres:latest 建立一個建構器並設定一個環境字串,它還將向主機公開 postgres 資料庫連接埠 5432,以便可以連接到容器內的資料庫。最後它將等待連接埠 5432。如果逾時(在本例中設定為 30 秒),它將引發異常,並且容器將停止並刪除。注意主機連接埠不是5432!使用Container.GetHostPort("5432/tcp")
取得主機連接埠。主機 IP 可以透過Container.Host
屬性檢索,因此在與容器中的應用程式通訊時應使用該主機 IP。
當容器成功拉取、建立和啟動時是否需要回呼。
protected override void OnContainerInitialized ( )
{
ConnectionString = string . Format ( PostgresConnectionString , Container . Host ,
Container . GetHostPort ( "5432/tcp" ) , PostgresUser ,
PostgresPassword , PostgresDb ) ;
}
此範例在新啟動的容器中呈現到 postgresql 資料庫的正確連接字串。這可用於使用 Npgsql、EF7、NHibernate、Marten 或其他相容工具進行連接。如果從 docker 儲存庫中提取映像或無法建立/啟動容器,則不會呼叫此方法。
如果需要覆蓋關閉前容器掛鉤。
protected virtual void OnContainerTearDown ( )
{
// Do stuff before container is shut down.
}
注意,如果未命名容器,如果沒有正確處置,docker容器仍然會運行,必須手動刪除。這是一個功能而不是錯誤,因為您可能想要在測試中運行多個容器。 DockerContainer
類別管理容器的實例 ID,因此只與它交互,而不與其他容器交互。
建立/啟動新容器時,它將首先檢查本機儲存庫是否已存在容器映像,如果未找到,則會下載它。這可能需要一些時間,並且只有一個調試日誌(如果啟用)可以監視下載過程。
當發生未處理的異常並且應用程式FailFast即快速終止時,它不會呼叫finally
子句。因此, using
語句中的WaitForPort
失敗將不會釋放容器服務。因此容器仍在運作。要解決此問題,可以使用全域 try...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 ; }
但這只有當應用程式因WaitForPort
中拋出FluentDockerException
而終止時才會發生,否則它將正確處理容器,因此不需要try...catch
。
這也可以使用Fd.Build
函數來解決(有關更多信息,請參閱使用生成器擴展)。
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
。當然,可以像使用container
一樣使用composite
擴展方法來使用複合容器。