บรรจุุภัณฑ์ | นูเก็ต |
---|---|
FluentDocker | |
การทดสอบของไมโครซอฟต์ | |
การทดสอบ 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 daemon คำอธิบายเพิ่มเติมสามารถพบได้ในบท Talking to Docker Daemon
API ได้อย่างคล่องแคล่วสร้างบริการหนึ่งรายการขึ้นไป แต่ละบริการอาจเป็นแบบประกอบหรือเอกพจน์ ดังนั้นจึงเป็นไปได้ที่จะเริ่มให้บริการที่ใช้ นักเทียบท่าเขียน หลายบริการและจัดการแต่ละบริการเป็นบริการเดียวหรือเจาะลึกและใช้บริการพื้นฐานทั้งหมดบนบริการ เขียนนักเทียบท่า แต่ละบริการ นอกจากนี้ยังสามารถใช้บริการได้โดยตรงเช่น
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 ) ;
}
ตัวอย่างข้างต้นสร้างบริการ เขียนนักเทียบท่า จากไฟล์เขียนไฟล์เดียว เมื่อบริการถูกกำจัด บริการพื้นฐานทั้งหมดจะหยุดทำงานโดยอัตโนมัติ
ไลบรารีนี้รองรับ .NET เวอร์ชันเต็ม 4.51 และสูงกว่า .NET มาตรฐาน 1.6, 2.0 แบ่งออกเป็นสามชั้นบาง ๆ แต่ละชั้นสามารถเข้าถึงได้:
วิธีการบริการส่วนใหญ่เป็นวิธีการขยายและไม่ได้เดินสายเข้ากับบริการโดยตรง ทำให้มีน้ำหนักเบาและปรับแต่งได้ เนื่องจากทุกสิ่งสามารถเข้าถึงได้ จึงเป็นเรื่องง่าย เช่น การเพิ่มวิธีการขยายสำหรับบริการที่ใช้คำสั่งเลเยอร์ 1 เพื่อให้มีฟังก์ชันการทำงาน
ฉันยินดีต้อนรับการมีส่วนร่วม แม้ว่ายังไม่มีแนวทางการสนับสนุนในขณะนี้ แต่อย่าลืมปฏิบัติตาม .editorconfig เมื่อดำเนินการ Pull Requests มิฉะนั้นงานสร้างจะล้มเหลว ฉันจะอัปเดตพร้อมแนวทาง ที่แท้จริง ไม่ช้าก็เร็วในปีนี้
คำสั่งทั้งหมดต้องใช้ DockerUri
เพื่อใช้งาน มันคือ Uri ของ docker daemon ทั้งแบบโลคัลหรือแบบรีโมต สามารถค้นพบได้หรือฮาร์ดโค้ดได้ การค้นพบ 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 และไม่มีเครื่องอยู่หรือไม่ได้เริ่มทำงาน เป็นเรื่องง่ายที่จะสร้าง / เริ่มต้น docker-machine เช่น "test-machine".Create(1024,20000000,1)
สิ่งนี้จะสร้างเครื่องเทียบท่าชื่อ "test-machine" พร้อม RAM 1GB, ดิสก์ 20GB และใช้ CPU หนึ่งตัว
ตอนนี้คุณสามารถใช้ Uri เพื่อสื่อสารโดยใช้คำสั่งได้แล้ว ตัวอย่างเช่นหากต้องการรับเวอร์ชันของไบนารีไคลเอ็นต์และเซิร์ฟเวอร์นักเทียบท่า:
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 Container ได้ ใช้ LinuxDaemon
หรือ WindowsDaemon
เพื่อควบคุมว่าจะคุยกับ daemon ใด
_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 สร้างมันขึ้นมาสำหรับอิมเมจของโหนด จากนั้นใช้ vanilla 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 ได้อย่างคล่องแคล่วรองรับตั้งแต่การกำหนดเครื่องเทียบท่าไปจนถึงชุดอินสแตนซ์นักเทียบท่า มีการรองรับในตัว เช่น การรอพอร์ตเฉพาะหรือกระบวนการภายในคอนเทนเนอร์ก่อนที่ Build()
จะเสร็จสมบูรณ์ ดังนั้นจึงสามารถใช้งานได้อย่างปลอดภัยภายในคำสั่งการใช้งาน หากมีการจัดการเฉพาะเกี่ยวกับการหมดเวลารอ ฯลฯ คุณสามารถสร้างและเริ่มต้นคอนเทนเนอร์ได้ตลอดเวลา และใช้วิธีการขยายเพื่อดำเนินการรอบนคอนเทนเนอร์นั้นเอง
หากต้องการสร้างคอนเทนเนอร์ ให้ละเว้นจุดเริ่มต้น ตัวอย่างเช่น:
using (
var container =
new Builder ( ) . UseContainer ( )
. UseImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( "POSTGRES_PASSWORD=mysecretpassword" )
. Build ( ) )
{
Assert . AreEqual ( ServiceRunningState . Stopped , container . State ) ;
}
ตัวอย่างนี้สร้างคอนเทนเนอร์ด้วย postgres กำหนดค่าตัวแปรสภาพแวดล้อมหนึ่งรายการ ภายในคำสั่งการใช้งาน คุณสามารถเริ่ม IContainerService
ได้ ดังนั้นแต่ละคอนเทนเนอร์ที่สร้างขึ้นจึงถูกห่อไว้ใน IContainerService
นอกจากนี้ยังเป็นไปได้ที่จะใช้ IHostService.GetContainers(...)
เพื่อรับคอนเทนเนอร์ที่สร้างขึ้น รัน และออกแล้ว จาก IHostService
คุณยังสามารถรับอิมเมจทั้งหมดในพื้นที่เก็บข้อมูลในเครื่องเพื่อสร้างคอนเทนเนอร์ได้
เมื่อคุณต้องการเรียกใช้คอนเทนเนอร์เดียว ให้ใช้วิธีการเริ่มต้นบริการแบบคล่องแคล่วหรือแบบคอนเทนเนอร์ ตัวอย่างเช่น:
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 และพอร์ตที่ใช้งานได้เสมอเพื่อสื่อสารกับคอนเทนเนอร์นักเทียบท่า นอกจากนี้ยังเป็นไปได้ที่จะแมปพอร์ตแบบสุ่ม เช่น ให้นักเทียบท่าเลือกพอร์ตที่พร้อมใช้งาน ตัวอย่างเช่น:
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 วินาที หากล้มเหลว จะเกิดข้อยกเว้น และคอนเทนเนอร์จะถูกกำจัดและลบออก (เนื่องจากเราไม่มีการกำหนดค่า Keep Container ฯลฯ)
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 ได้อย่างคล่องแคล่ว จะใช้สแน็ปช็อต 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) บนโฮสต์ (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 ;
}
ตัวอย่างนี้จะส่งออกคอนเทนเนอร์เมื่อคำสั่งการใช้งานกำลังกำจัดคอนเทนเนอร์ เนื่องจากตัวแปรความล้มเหลวถูกตั้งค่าเป็นจริงและใช้ในนิพจน์ ExportOnDispose
บริการทั้งหมดสามารถขยายได้ด้วย hooks ใน ExportOnDispose(path, lambda)
จะติดตั้ง Hook เมื่อสถานะบริการถูกตั้งค่าให้ดำเนินการ lambda เมื่อสถานะกำลัง Removing
สามารถติดตั้งและถอดตะขอได้ทันที หากมีการลงทะเบียน hook หลายรายการบนอินสแตนซ์บริการเดียวกัน โดยมี ServiceRunningState
เดียวกัน ตะขอเหล่านั้นจะถูกดำเนินการตามลำดับการติดตั้ง
hooks นั้นดีเป็นพิเศษถ้าคุณต้องการให้บางสิ่งถูกดำเนินการเมื่อสถานะกำลังจะถูกตั้งค่า (หรือดำเนินการ) บนบริการเช่น Starting
Fluent API ใช้ประโยชน์จากสิ่งเหล่านั้นในบางสถานการณ์ เช่น คัดลอกไฟล์ ส่งออก ฯลฯ
FluentDocker รองรับคำสั่งเครือข่ายนักเทียบท่าทั้งหมด สามารถค้นพบเครือข่ายโดย _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 ) ;
นอกจากนี้ยังสามารถใช้ตัวสร้างที่คล่องแคล่วเพื่อสร้างเครือข่ายนักเทียบท่าใหม่หรือนำกลับมาใช้ใหม่ได้ สิ่งเหล่านี้สามารถอ้างอิงได้ในขณะที่สร้าง คอนเทนเนอร์ เป็นไปได้ที่จะสร้างเครือข่ายนักเทียบท่ามากกว่าหนึ่งเครือข่ายและแนบคอนเทนเนอร์เข้ากับเครือข่ายมากกว่าหนึ่งเครือข่ายในแต่ละครั้ง
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 ) ;
}
}
ตัวอย่างข้างต้นสร้าง หน่วยเครือข่ายใหม่-test-nw ด้วยช่วง IP 10.18.0.0/16 เป็นของที่ใช้ในภาชนะใหม่ IP สำหรับคอนเทนเนอร์ถูกตั้งค่าเป็น 10.18.0.22 และเป็นแบบคงที่เนื่องจากคำสั่ง UseIpV4
FluentDocker รองรับการจัดการโวลุ่มของนักเทียบท่าทั้งจากคำสั่งและจาก 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 โดยเชื่อมต่อกับกลไกเหตุการณ์นักเทียบท่าเพื่อฟังเหตุการณ์ที่ส่ง
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 ;
}
}
}
Event Listener เป็นแบบสากลและอาจรองรับ 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 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สำหรับข้อมูลเพิ่มเติม สำหรับเฟรมเวิร์กแบบเต็ม โปรดตรวจสอบ XML ที่จำเป็นใน appconfig สำหรับเฟรมเวิร์กแบบเต็มที่อธิบายไว้ใน 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 เพื่อพูดคุยกับดีมอนนักเทียบท่าระยะไกลโดยไม่ต้องใช้เครื่องนักเทียบท่า ซึ่งทำได้โดยการสร้างอินสแตนซ์ของ DockerHostService
ด้วยตนเอง หรือใช้ FromUri
บน HostBuilder
using ( var container = Fd . UseHost ( ) .
FromUri ( Settings . DockerUri , isWindowsHost : true ) .
UseContainer ( ) .
Build ( ) )
{
}
ตัวอย่างด้านบนเชื่อมต่อกับ DockerUri
ที่กำหนดเองจากการตั้งค่าและเป็น Windows Container Docker Daemon
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 เพื่อใช้ไฟล์เขียนที่มีอยู่เพื่อให้บริการและจัดการอายุการใช้งานของไฟล์ดังกล่าว
ตัวอย่างต่อไปนี้จะมีไฟล์เขียนที่สร้าง 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 :
ไฟล์ด้านบนเป็นไฟล์ นักเทียบท่า เพื่อต่อยอดบริการที่สมบูรณ์
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 ) ;
}
ตัวอย่างข้างต้นกำลังกำหนดค่าบริการ เขียนนักเทียบท่า อย่างคล่องแคล่วและเรียกใช้หน้าการติดตั้งเพื่อตรวจสอบว่า 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 รองรับ no 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 daemon คุณสามารถทำตามบทช่วยสอนนักเทียบท่าและทำตามขั้นตอนสุดท้ายในการเพิ่มผู้ใช้ของคุณไปยังกลุ่มนักเทียบท่า
FluentDocker รองรับการเชื่อมต่อกับ daemons นักเทียบท่าระยะไกล API ได้อย่างคล่องแคล่วรองรับเช่น
new Builder ( ) . UseHost ( ) . UseMachine ( ) . WithName ( "remote-daemon" )
โดยที่จำเป็นต้องมีรายการการตั้งค่าล่วงหน้าในรีจิสทรีของ docker-machine นอกจากนี้ยังสามารถกำหนดรีจิสทรี นักเทียบท่า-เครื่อง ที่ใช้ 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 ) ;
}
ตัวอย่างนี้จะสร้างรายการรีจิสทรี docker-machine ใหม่ชื่อ remote-daemon ที่ใช้ SSH พร้อมที่อยู่ IP 192.168.1.27 และผู้ใช้ SSH Solo หากพบรายการชื่อ remote-daemon แล้ว รายการดังกล่าวจะถูกนำมาใช้ซ้ำ จากนั้นจะได้รับ IHostService พร้อมใบรับรองและ URL ที่ถูกต้องสำหรับภูตระยะไกล ดังนั้นจึงเป็นไปได้ที่จะสร้างคอนเทนเนอร์นักเทียบท่าบนเดมอนระยะไกล ในกรณีนี้คืออิมเมจ 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 daemon
ทำตามบทช่วยสอนเหล่านี้วิธีตั้งค่าอุโมงค์ SSH และตรวจสอบให้แน่ใจว่าผู้ใช้สามารถเข้าถึง docker daemon ได้
โดยพื้นฐานแล้วสร้างคีย์ rsa ใหม่เพื่อใช้กับอุโมงค์ SSH โดยใช้ ssh-keygen -t rsa
จากนั้นคัดลอกไปยังโฮสต์ระยะไกลโดย ssh-copy-id {username}@{host}
แก้ไข /etc/sudoers ตามที่ระบุไว้ในบทช่วยสอนที่สอง
เมื่อดำเนินการเสร็จแล้ว ตอนนี้คุณสามารถเข้าถึง Remote Docker Daemon ได้ด้วยไดรเวอร์ทั่วไปหรือ 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
เรียกใช้สิ่งนี้เพื่อให้ไคลเอ็นต์นักเทียบท่าใช้ภูตนักเทียบท่าระยะไกล
@FOR /f "tokens=*" %i IN ('docker-machine.exe env remote-daemon') DO @%i
คำสั่งทั้งหมดที่ใช้ไบนารี docker
จะดำเนินการบนดีมอนนักเทียบท่าระยะไกล
เมื่อสร้างและสืบค้นผ่านเครื่อง เครื่องนักเทียบท่า Hyper-v กระบวนการจะต้องได้รับการยกระดับเนื่องจาก Hyper-V จะไม่ตอบสนองต่อการเรียก API ในโหมดผู้ใช้มาตรฐาน
คุณสามารถระบุการตรวจสอบสภาพสำหรับคอนเทนเนอร์นักเทียบท่าเพื่อรายงานสถานะของคอนเทนเนอร์ตามกิจกรรมดังกล่าวได้ ตัวอย่างต่อไปนี้คือการใช้การตรวจสอบสภาพว่าคอนเทนเนอร์ออกแล้วหรือไม่ คุณสามารถตรวจสอบการกำหนดค่าได้ (อย่าลืมบังคับรีเฟรช) ว่าการตรวจสอบสภาพกำลังรายงานสถานะใด
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
}
repo นี้มีแพ็คเกจ nuget สามแพ็คเกจ หนึ่งแพ็คเกจสำหรับการเข้าถึงอย่างคล่องแคล่ว หนึ่งแพ็คเกจสำหรับคลาสฐาน ms-test และอีกแพ็คเกจสำหรับคลาสฐาน xunit ที่จะใช้ในขณะทดสอบ ตัวอย่างเช่น ในการทดสอบหน่วย เป็นไปได้ที่จะเปิดคอนเทนเนอร์ postgres และรอเมื่อ db บูตแล้ว
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
ช่วยให้สามารถแทนที่อย่างง่ายเพื่อทำการทดสอบใดๆ ที่สนับสนุนนักเทียบท่าแบบกำหนดเองได้อย่างง่ายดาย เพียงสร้างคลาสการทดสอบและรับมาจาก FluentDockerTestBase
และแทนที่วิธีที่เหมาะสม ตัวอย่างเช่น.
protected override DockerBuilder Build ( )
{
return new DockerBuilder ( )
. WithImage ( "kiasaki/alpine-postgres" )
. WithEnvironment ( $ "POSTGRES_PASSWORD= { PostgresPassword } " )
. ExposePorts ( "5432" )
. WaitForPort ( "5432/tcp" , 30000 /*30s*/ ) ;
}
สิ่งนี้จะสร้างตัวสร้างด้วย docker image postgres:latest และตั้งค่าสตริงสภาพแวดล้อมหนึ่งรายการ นอกจากนี้ยังจะแสดงพอร์ต postgres db 5432 ไปยังโฮสต์เพื่อให้สามารถเชื่อมต่อกับ db ภายในคอนเทนเนอร์ได้ สุดท้ายจะรอพอร์ต 5432 เพื่อให้แน่ใจว่า db กำลังทำงานและบูตอย่างถูกต้อง หากการหมดเวลาในตัวอย่างนี้ตั้งค่าเป็น 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 หรือเครื่องมืออื่นๆ ที่เข้ากันได้ เมธอดนี้จะไม่ถูกเรียกใช้หากดึงอิมเมจจากที่เก็บนักเทียบท่า หรือไม่สามารถสร้าง/เริ่มคอนเทนเนอร์ได้
หากต้องการแทนที่ hook คอนเทนเนอร์ก่อนปิดระบบ
protected virtual void OnContainerTearDown ( )
{
// Do stuff before container is shut down.
}
โปรดทราบว่าหากคอนเทนเนอร์ที่ไม่มีชื่อ หากไม่ได้กำจัดอย่างเหมาะสม คอนเทนเนอร์นักเทียบท่าจะยังคงทำงานและจะต้องถูกลบออกด้วยตนเอง นี่เป็นฟีเจอร์ไม่ใช่จุดบกพร่อง เนื่องจากคุณอาจต้องการให้คอนเทนเนอร์หลายตัวทำงานในการทดสอบของคุณ คลาส DockerContainer
จัดการรหัสอินสแตนซ์ของคอนเทนเนอร์ และโต้ตอบกับมันเท่านั้น และไม่มีคอนเทนเนอร์อื่นใด
เมื่อสร้าง / เริ่มต้นคอนเทนเนอร์ใหม่ ก่อนอื่นจะตรวจสอบพื้นที่เก็บข้อมูลในเครื่องว่ามีอิมเมจคอนเทนเนอร์อยู่แล้วหรือไม่ และจะดาวน์โหลดหากไม่พบ การดำเนินการนี้อาจใช้เวลาสักครู่และมีเพียงบันทึกการแก้ไขข้อบกพร่องหากเปิดใช้งานไว้ ก็สามารถตรวจสอบกระบวนการดาวน์โหลดได้
เมื่อเกิดข้อยกเว้นที่ไม่สามารถจัดการได้และแอปพลิเคชัน FailFast กล่าวคือยุติลงอย่างรวดเร็ว มันจะ ไม่ เรียกใช้ส่วนคำสั่ง finally
ดังนั้น WaitForPort
ที่ล้มเหลวภายในคำสั่ง using
จะ ไม่ กำจัดบริการคอนเทนเนอร์ ดังนั้นคอนเทนเนอร์จึงยังคงทำงานอยู่ หากต้องการแก้ไขปัญหานี้ ให้ลองใช้ทั่วโลก 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 ; }
แต่นี่เป็นเพียงเมื่อการยกเลิกแอปพลิเคชันเสร็จสิ้นเนื่องจาก 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...
} ) ;
ตัวอย่างข้างต้นจะสร้างคอนเทนเนอร์ เริ่มต้น หยุด และสุดท้ายจะลบคอนเทนเนอร์ แม้ว่า and Exception
จะถูกส่งออกไป มันก็จะถูก Disposed
แน่นอนว่ามันเป็นไปได้ที่จะใช้ compsed คอนเทนเนอร์โดยใช้วิธีการขยาย composite
เช่นเดียวกับ container