เริ่มต้นใช้งาน Docker: ลดความซับซ้อนของ DevOps
เผยแพร่แล้ว: 2022-03-11หากคุณชอบวาฬหรือเพียงแค่สนใจในการส่งมอบซอฟต์แวร์ของคุณอย่างต่อเนื่องอย่างรวดเร็วและไม่เจ็บปวด ฉันขอเชิญคุณอ่านบทแนะนำ Docker นี้ ดูเหมือนว่าทุกอย่างจะบ่งบอกว่าคอนเทนเนอร์ซอฟต์แวร์คืออนาคตของไอที ดังนั้นเรามาดูวาฬคอนเทนเนอร์ Moby Dock และ Molly กันดีกว่า
Docker ซึ่งมีโลโก้เป็นรูปวาฬที่ดูเป็นมิตร เป็นโครงการโอเพ่นซอร์สที่อำนวยความสะดวกในการปรับใช้แอปพลิเคชันภายในคอนเทนเนอร์ซอฟต์แวร์ ฟังก์ชันพื้นฐานของมันถูกเปิดใช้งานโดยคุณสมบัติการแยกทรัพยากรของเคอร์เนล Linux แต่มี API ที่ใช้งานง่ายอยู่ด้านบน เวอร์ชันแรกเปิดตัวในปี 2013 และนับตั้งแต่นั้นมาก็ได้รับความนิยมอย่างมาก และมีการใช้กันอย่างแพร่หลายโดยผู้เล่นรายใหญ่หลายราย เช่น eBay, Spotify, Baidu และอื่นๆ ในการระดมทุนรอบที่แล้ว Docker ได้รับเงินจำนวน 95 ล้านดอลลาร์ และกำลังจะกลายเป็นบริการหลักของ DevOps
เปรียบเทียบการขนส่งสินค้า
ปรัชญาเบื้องหลัง Docker สามารถอธิบายได้ด้วยการเปรียบเทียบง่ายๆ ดังต่อไปนี้ ในอุตสาหกรรมการขนส่งระหว่างประเทศ สินค้าต้องขนส่งด้วยวิธีการต่างๆ เช่น รถยก รถบรรทุก รถไฟ เครน และเรือ สินค้าเหล่านี้มีรูปร่างและขนาดต่างกัน และมีข้อกำหนดในการจัดเก็บที่แตกต่างกัน: กระสอบน้ำตาล กระป๋องนม พืช ฯลฯ ในอดีต เป็นกระบวนการที่เจ็บปวด ทั้งนี้ขึ้นอยู่กับการแทรกแซงของเจ้าหน้าที่ในทุกจุดขนส่งสำหรับการขนถ่าย
ทุกอย่างเปลี่ยนไปด้วยการดูดซึมของคอนเทนเนอร์แบบอินเตอร์โมดอล เนื่องจากมาในขนาดมาตรฐานและผลิตขึ้นโดยคำนึงถึงการขนส่ง เครื่องจักรที่เกี่ยวข้องทั้งหมดสามารถออกแบบให้จัดการกับสิ่งเหล่านี้ได้โดยมีมนุษย์เข้ามาแทรกแซงเพียงเล็กน้อย ประโยชน์เพิ่มเติมของภาชนะบรรจุที่ปิดสนิทคือสามารถรักษาสภาพแวดล้อมภายใน เช่น อุณหภูมิและความชื้นสำหรับสินค้าที่มีความละเอียดอ่อน ส่งผลให้อุตสาหกรรมการขนส่งสามารถเลิกกังวลเรื่องสินค้าได้เองและมุ่งเน้นที่การรับสินค้าจาก A ไป B
และนี่คือที่มาของ Docker และนำประโยชน์ที่คล้ายคลึงกันมาสู่อุตสาหกรรมซอฟต์แวร์
แตกต่างจากเครื่องเสมือนอย่างไร?
เครื่องเสมือนและคอนเทนเนอร์ Docker อาจดูเหมือนเหมือนกัน อย่างไรก็ตาม ความแตกต่างหลัก ๆ จะชัดเจนขึ้นเมื่อคุณดูแผนภาพต่อไปนี้:
แอปพลิเคชันที่ทำงานในเครื่องเสมือน นอกเหนือจากไฮเปอร์ไวเซอร์ จำเป็นต้องมีอินสแตนซ์ของระบบปฏิบัติการและไลบรารีที่รองรับทั้งหมด ในทางกลับกันคอนเทนเนอร์ใช้ระบบปฏิบัติการร่วมกับโฮสต์ ไฮเปอร์ไวเซอร์เปรียบได้กับเอ็นจิ้นคอนเทนเนอร์ (แสดงเป็น Docker บนรูปภาพ) ในแง่ที่ว่าจัดการวงจรชีวิตของคอนเทนเนอร์ ความแตกต่างที่สำคัญคือ กระบวนการที่ทำงานภายในคอนเทนเนอร์นั้นเหมือนกับกระบวนการดั้งเดิมบนโฮสต์ และไม่แนะนำโอเวอร์เฮดใดๆ ที่เกี่ยวข้องกับการดำเนินการไฮเปอร์ไวเซอร์ นอกจากนี้ แอปพลิเคชันสามารถใช้ไลบรารีซ้ำและแบ่งปันข้อมูลระหว่างคอนเทนเนอร์ได้
เนื่องจากเทคโนโลยีทั้งสองมีจุดแข็งต่างกัน จึงเป็นเรื่องปกติที่จะค้นหาระบบที่รวมเครื่องเสมือนและคอนเทนเนอร์เข้าด้วยกัน ตัวอย่างที่สมบูรณ์แบบคือเครื่องมือที่ชื่อ Boot2Docker ซึ่งอธิบายไว้ในส่วนการติดตั้ง Docker
สถาปัตยกรรมนักเทียบท่า
ที่ด้านบนของไดอะแกรมสถาปัตยกรรมจะมีการลงทะเบียน โดยค่าเริ่มต้น รีจิสทรีหลักคือ Docker Hub ซึ่งโฮสต์รูปภาพสาธารณะและรูปภาพที่เป็นทางการ องค์กรยังสามารถโฮสต์การลงทะเบียนส่วนตัวได้หากต้องการ
ทางด้านขวามือจะมีรูปภาพและคอนเทนเนอร์ สามารถดาวน์โหลดรูปภาพจากรีจิสตรีได้อย่างชัดเจน ( docker pull imageName
) หรือโดยปริยายเมื่อเริ่มต้นคอนเทนเนอร์ เมื่อดาวน์โหลดรูปภาพแล้ว ระบบจะแคชในเครื่อง
คอนเทนเนอร์เป็นตัวอย่างของภาพ - เป็นสิ่งมีชีวิต อาจมีคอนเทนเนอร์หลายตัวที่ทำงานอยู่ตามอิมเมจเดียวกัน
ที่ศูนย์กลาง มี Docker daemon ที่รับผิดชอบในการสร้าง รัน และตรวจสอบคอนเทนเนอร์ ยังดูแลการสร้างและจัดเก็บภาพ สุดท้าย ทางด้านซ้ายมือจะมีไคลเอ็นต์ Docker มันคุยกับภูตผ่าน HTTP ซ็อกเก็ต Unix ถูกใช้เมื่ออยู่บนเครื่องเดียวกัน แต่การจัดการระยะไกลสามารถทำได้ผ่าน API ที่ใช้ HTTP
การติดตั้ง Docker
สำหรับคำแนะนำล่าสุด คุณควรอ้างอิงถึงเอกสารทางการเสมอ
นักเทียบท่าทำงานบน Linux ดังนั้นขึ้นอยู่กับการกระจายเป้าหมาย sudo apt-get install docker.io
ขึ้นอยู่กับการกระจายเป้าหมาย โปรดดูรายละเอียดในเอกสารประกอบ โดยปกติใน Linux คุณจะเติมคำสั่ง Docker ไว้ข้างหน้า sudo
แต่เราจะข้ามไปในบทความนี้เพื่อความชัดเจน
เนื่องจาก Docker daemon ใช้คุณสมบัติเคอร์เนลเฉพาะของ Linux จึงเป็นไปไม่ได้ที่จะเรียกใช้ Docker ใน Mac OS หรือ Windows คุณควรติดตั้งแอปพลิเคชันชื่อ Boot2Docker แทน แอปพลิเคชันประกอบด้วย VirtualBox Virtual Machine, Docker และยูทิลิตี้การจัดการ Boot2Docker คุณสามารถทำตามคำแนะนำในการติดตั้งอย่างเป็นทางการสำหรับ MacOS และ Windows เพื่อติดตั้ง Docker บนแพลตฟอร์มเหล่านี้
การใช้ Docker
ให้เราเริ่มส่วนนี้ด้วยตัวอย่างด่วน:
docker run phusion/baseimage echo "Hello Moby Dock. Hello Molly."
เราควรเห็นผลลัพธ์นี้:
Hello Moby Dock. Hello Molly.
อย่างไรก็ตาม มีอะไรเกิดขึ้นเบื้องหลังมากกว่าที่คุณคิด:
- อิมเมจ 'phusion/baseimage' ถูกดาวน์โหลดจาก Docker Hub (หากยังไม่มีอยู่ในแคชในเครื่อง)
- เริ่มคอนเทนเนอร์ตามภาพนี้
- คำสั่ง echo ถูกดำเนินการภายในคอนเทนเนอร์
- คอนเทนเนอร์หยุดทำงานเมื่อคำสั่งออก
ในการเรียกใช้ครั้งแรก คุณอาจสังเกตเห็นความล่าช้าก่อนที่ข้อความจะถูกพิมพ์บนหน้าจอ หากรูปภาพถูกแคชไว้ในเครื่อง ทุกอย่างจะใช้เวลาเพียงเสี้ยววินาที รายละเอียดเกี่ยวกับคอนเทนเนอร์สุดท้ายสามารถเรียกค้นได้โดยการเรียกใช้ docker ps -l
:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES af14bec37930 phusion/baseimage:latest "echo 'Hello Moby Do 2 minutes ago Exited (0) 3 seconds ago stoic_bardeen
การดำน้ำครั้งต่อไป
อย่างที่คุณบอกได้ การรันคำสั่งง่ายๆ ภายใน Docker นั้นง่ายพอๆ กับการรันคำสั่งโดยตรงบนเทอร์มินัลมาตรฐาน เพื่อแสดงกรณีการใช้งานที่เป็นประโยชน์มากขึ้น ตลอดช่วงที่เหลือของบทความนี้ เราจะมาดูกันว่าเราจะใช้ Docker เพื่อปรับใช้แอปพลิเคชันเว็บเซิร์ฟเวอร์อย่างง่ายได้อย่างไร เพื่อให้ง่ายขึ้น เราจะเขียนโปรแกรม Java ที่จัดการคำขอ HTTP GET ไปที่ '/ping' และตอบกลับด้วยสตริง 'pong\n'
import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; public class PingPong { public static void main(String[] args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); server.createContext("/ping", new MyHandler()); server.setExecutor(null); server.start(); } static class MyHandler implements HttpHandler { @Override public void handle(HttpExchange t) throws IOException { String response = "pong\n"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } }
Dockerfile
ก่อนเริ่มใช้งานและสร้างอิมเมจ Docker ของคุณเอง คุณควรตรวจสอบก่อนว่ามีอิมเมจที่มีอยู่ใน Docker Hub หรือรีจิสตรีส่วนตัวที่คุณมีสิทธิ์เข้าถึงหรือไม่ ตัวอย่างเช่น แทนที่จะติดตั้ง Java เอง เราจะใช้อิมเมจอย่างเป็นทางการ: java:8
ในการสร้างอิมเมจ ก่อนอื่นเราต้องตัดสินใจเลือกอิมเมจพื้นฐานที่เราจะใช้ มันแสดงโดยคำสั่ง FROM นี่เป็นภาพอย่างเป็นทางการสำหรับ Java 8 จาก Docker Hub เราจะคัดลอกลงในไฟล์ Java ของเราโดยออกคำสั่ง COPY ต่อไปเราจะคอมไพล์ด้วย RUN คำสั่ง EXPOSE แสดงว่าภาพจะให้บริการบนพอร์ตเฉพาะ ENTRYPOINT เป็นคำสั่งที่เราต้องการดำเนินการเมื่อมีการเริ่มต้นคอนเทนเนอร์ตามภาพนี้และ CMD ระบุพารามิเตอร์เริ่มต้นที่เราจะส่งต่อ

FROM java:8 COPY PingPong.java / RUN javac PingPong.java EXPOSE 8080 ENTRYPOINT ["java"] CMD ["PingPong"]
หลังจากบันทึกคำแนะนำเหล่านี้ในไฟล์ชื่อ “Dockerfile” เราสามารถสร้างอิมเมจ Docker ที่เกี่ยวข้องได้โดยดำเนินการ:
docker build -t toptal/pingpong .
เอกสารอย่างเป็นทางการสำหรับ Docker มีหัวข้อเกี่ยวกับแนวทางปฏิบัติที่ดีที่สุดเกี่ยวกับการเขียน Dockerfile โดยเฉพาะ
วิ่งตู้คอนเทนเนอร์
เมื่อสร้างภาพแล้ว เราก็สามารถนำมันมามีชีวิตเป็นภาชนะได้ มีหลายวิธีที่เราสามารถเรียกใช้คอนเทนเนอร์ได้ แต่เริ่มจากวิธีง่ายๆ ก่อน:
docker run -d -p 8080:8080 toptal/pingpong
โดยที่ -p [port-on-the-host]:[port-in-the-container] หมายถึงการแมปพอร์ตบนโฮสต์และคอนเทนเนอร์ตามลำดับ นอกจากนี้ เรากำลังบอกให้ Docker รันคอนเทนเนอร์เป็นกระบวนการ daemon ในพื้นหลังโดยระบุ -d คุณสามารถทดสอบว่าแอปพลิเคชันเว็บเซิร์ฟเวอร์กำลังทำงานอยู่หรือไม่โดยพยายามเข้าถึง 'http://localhost:8080/ping' โปรดทราบว่าบนแพลตฟอร์มที่ใช้ Boot2docker คุณจะต้องแทนที่ 'localhost' ด้วยที่อยู่ IP ของเครื่องเสมือนที่ Docker ทำงานอยู่
บนลินุกซ์:
curl http://localhost:8080/ping
บนแพลตฟอร์มที่ต้องใช้ Boot2Docker:
curl $(boot2docker ip):8080/ping
หากทุกอย่างเป็นไปด้วยดี คุณจะเห็นคำตอบ:
pong
ไชโย คอนเทนเนอร์ Docker แบบกำหนดเองตัวแรกของเราที่ยังมีชีวิตอยู่และกำลังว่ายน้ำอยู่! เราสามารถเริ่มคอนเทนเนอร์ในโหมดโต้ตอบ -i -t ในกรณีของเรา เราจะแทนที่คำสั่ง entrypoint เพื่อให้แสดงด้วย bash terminal ตอนนี้ เราสามารถรันคำสั่งอะไรก็ได้ที่เราต้องการ แต่การออกจากคอนเทนเนอร์จะหยุดมัน:
docker run -i -t --entrypoint="bash" toptal/pingpong
มีตัวเลือกอื่นๆ มากมายสำหรับใช้เริ่มต้นคอนเทนเนอร์ ให้เราครอบคลุมอีกไม่กี่ ตัวอย่างเช่น หากเราต้องการคงข้อมูลไว้ภายนอกคอนเทนเนอร์ เราสามารถแชร์ระบบไฟล์โฮสต์กับคอนเทนเนอร์โดยใช้ -v โดยค่าเริ่มต้น โหมดการเข้าถึงจะเป็นแบบอ่าน-เขียน แต่สามารถเปลี่ยนเป็นโหมดอ่านอย่างเดียวได้โดยการผนวก :ro
เข้ากับพาธวอลุ่มภายในคอนเทนเนอร์ ไดรฟ์ข้อมูลมีความสำคัญอย่างยิ่งเมื่อเราต้องใช้ข้อมูลความปลอดภัย เช่น ข้อมูลประจำตัวและคีย์ส่วนตัวภายในคอนเทนเนอร์ ซึ่งไม่ควรจัดเก็บไว้ในอิมเมจ นอกจากนี้ยังสามารถป้องกันการทำซ้ำของข้อมูลได้ เช่น โดยการแมปที่เก็บ Maven ในพื้นที่ของคุณกับคอนเทนเนอร์เพื่อช่วยให้คุณไม่ต้องดาวน์โหลดอินเทอร์เน็ตสองครั้ง
นักเทียบท่ายังมีความสามารถในการเชื่อมโยงคอนเทนเนอร์เข้าด้วยกัน คอนเทนเนอร์ที่เชื่อมโยงสามารถพูดคุยกันได้แม้ว่าจะไม่มีพอร์ตใดที่เปิดอยู่ก็ตาม สามารถทำได้ด้วย –link other-container-name ด้านล่างนี้เป็นตัวอย่างการรวมพารามิเตอร์ที่กล่าวถึงข้างต้น:
docker run -p 9999:8080 --link otherContainerA --link otherContainerB -v /Users/$USER/.m2/repository:/home/user/.m2/repository toptal/pingpong
การทำงานของคอนเทนเนอร์และอิมเมจอื่นๆ
ไม่น่าแปลกใจเลยที่รายการการดำเนินการที่สามารถนำไปใช้กับคอนเทนเนอร์และรูปภาพนั้นค่อนข้างยาว เพื่อความกระชับ ให้เราดูเพียงบางส่วนเท่านั้น:
- หยุด - หยุดคอนเทนเนอร์ที่ทำงานอยู่
- start - เริ่มคอนเทนเนอร์ที่หยุด
- ส่ง - สร้างภาพใหม่จากการเปลี่ยนแปลงของคอนเทนเนอร์
- rm - ลบคอนเทนเนอร์อย่างน้อยหนึ่งรายการ
- rmi - ลบหนึ่งภาพขึ้นไป
- ps - แสดงรายการคอนเทนเนอร์
- ภาพ - แสดงรายการภาพ
- exec - รันคำสั่งในคอนเทนเนอร์ที่กำลังทำงานอยู่
คำสั่งสุดท้ายอาจมีประโยชน์อย่างยิ่งสำหรับวัตถุประสงค์ในการดีบัก เนื่องจากช่วยให้คุณสามารถเชื่อมต่อกับเทอร์มินัลของคอนเทนเนอร์ที่ทำงานอยู่:
docker exec -i -t <container-id> bash
Docker Compose สำหรับ Microservice World
หากคุณมีคอนเทนเนอร์ที่เชื่อมต่อถึงกันมากกว่าสองสามคอนเทนเนอร์ คุณควรใช้เครื่องมืออย่าง Docker-compose ในไฟล์คอนฟิกูเรชัน คุณอธิบายวิธีเริ่มคอนเทนเนอร์และวิธีที่คอนเทนเนอร์ควรเชื่อมโยงถึงกัน โดยไม่คำนึงถึงจำนวนคอนเทนเนอร์ที่เกี่ยวข้องและการพึ่งพา คุณสามารถทำให้คอนเทนเนอร์ทั้งหมดทำงานด้วยคำสั่งเดียว: docker-compose up
นักเทียบท่าในป่า
มาดูสามขั้นตอนของวงจรชีวิตโครงการและดูว่าวาฬที่เป็นมิตรของเราจะช่วยเหลือได้อย่างไร
การพัฒนา
Docker ช่วยให้คุณรักษาสภาพแวดล้อมการพัฒนาในพื้นที่ของคุณให้สะอาดอยู่เสมอ แทนที่จะต้องติดตั้งบริการต่างๆ หลายเวอร์ชัน เช่น Java, Kafka, Spark, Cassandra เป็นต้น คุณสามารถเริ่มและหยุดคอนเทนเนอร์ที่ต้องการได้เมื่อจำเป็น คุณสามารถก้าวไปอีกขั้นและเรียกใช้ซอฟต์แวร์หลายชุดเคียงข้างกันเพื่อหลีกเลี่ยงการผสมผสานของเวอร์ชันการพึ่งพา
ด้วย Docker คุณสามารถประหยัดเวลา แรงกาย และเงินได้ หากโครงการของคุณซับซ้อนมากในการตั้งค่า ให้ "เทียบชิดขอบ" กับมัน ผ่านความเจ็บปวดจากการสร้างอิมเมจ Docker เพียงครั้งเดียว และจากจุดนี้ ทุกคนก็สามารถเริ่มคอนเทนเนอร์ได้ในพริบตา
คุณยังสามารถมี "สภาพแวดล้อมการรวม" ที่ทำงานในเครื่อง (หรือบน CI) และแทนที่ต้นขั้วด้วยบริการจริงที่ทำงานอยู่ในคอนเทนเนอร์ Docker
การทดสอบ / การบูรณาการอย่างต่อเนื่อง
ด้วย Dockerfile มันง่ายที่จะสร้างงานสร้างที่ทำซ้ำได้ สามารถกำหนดค่า Jenkins หรือโซลูชัน CI อื่นๆ เพื่อสร้างอิมเมจ Docker สำหรับทุกบิลด์ คุณสามารถจัดเก็บภาพบางส่วนหรือทั้งหมดในรีจิสทรี Docker ส่วนตัวเพื่อใช้อ้างอิงในอนาคต
ด้วย Docker คุณจะทดสอบเฉพาะสิ่งที่จำเป็นต้องทดสอบและนำสภาพแวดล้อมออกจากสมการ การทดสอบบนคอนเทนเนอร์ที่กำลังทำงานอยู่สามารถช่วยให้คาดเดาสิ่งต่างๆ ได้มากขึ้น
คุณสมบัติที่น่าสนใจอีกประการของการมีคอนเทนเนอร์ซอฟต์แวร์คือง่ายต่อการแยกเครื่องสเลฟออกด้วยการตั้งค่าการพัฒนาที่เหมือนกัน อาจมีประโยชน์อย่างยิ่งสำหรับการทดสอบโหลดของการปรับใช้คลัสเตอร์
การผลิต
นักเทียบท่าสามารถเป็นส่วนต่อประสานทั่วไประหว่างนักพัฒนาและเจ้าหน้าที่ฝ่ายปฏิบัติการเพื่อขจัดแหล่งที่มาของความขัดแย้ง นอกจากนี้ยังสนับสนุนให้ใช้อิมเมจ/ไบนารีเดียวกันในทุกขั้นตอนของไปป์ไลน์ นอกจากนี้ ความสามารถในการปรับใช้คอนเทนเนอร์ที่ทดสอบอย่างสมบูรณ์โดยไม่มีความแตกต่างของสภาพแวดล้อมช่วยให้แน่ใจว่าไม่มีข้อผิดพลาดเกิดขึ้นในกระบวนการสร้าง
คุณสามารถโยกย้ายแอปพลิเคชันไปสู่การใช้งานจริงได้อย่างราบรื่น สิ่งที่ครั้งหนึ่งเคยเป็นกระบวนการที่น่าเบื่อและเป็นขุย ตอนนี้สามารถทำได้ง่ายๆ ดังนี้:
docker stop container-id; docker run new-image
และหากมีข้อผิดพลาดในการปรับใช้เวอร์ชันใหม่ คุณสามารถย้อนกลับหรือเปลี่ยนเป็นคอนเทนเนอร์อื่นได้อย่างรวดเร็ว:
docker stop container-id; docker start other-container-id
… รับประกันว่าจะไม่ทิ้งความยุ่งเหยิงใด ๆ ไว้เบื้องหลังหรือปล่อยให้สิ่งต่าง ๆ อยู่ในสภาพที่ไม่สอดคล้องกัน
สรุป
บทสรุปที่ดีของสิ่งที่ Docker ทำนั้นรวมอยู่ในคำขวัญของตัวเอง: Build, Ship, Run
- Build - Docker ช่วยให้คุณสามารถเขียนแอปพลิเคชันของคุณจากไมโครเซอร์วิส โดยไม่ต้องกังวลเกี่ยวกับความไม่สอดคล้องกันระหว่างสภาพแวดล้อมการพัฒนาและการใช้งานจริง และโดยไม่ต้องล็อกในแพลตฟอร์มหรือภาษาใดๆ
- Ship - Docker ให้คุณออกแบบวงจรทั้งหมดของการพัฒนา ทดสอบ และแจกจ่ายแอปพลิเคชัน และจัดการด้วยอินเทอร์เฟซผู้ใช้ที่สอดคล้องกัน
- Run - Docker มอบความสามารถในการปรับใช้บริการที่ปรับขนาดได้อย่างปลอดภัยและเชื่อถือได้บนแพลตฟอร์มที่หลากหลาย
สนุกกับการว่ายน้ำกับวาฬ!
ส่วนหนึ่งของงานนี้ได้รับแรงบันดาลใจจากหนังสือที่ยอดเยี่ยมเรื่อง Using Docker โดย Adrian Mouat