Xưởng
KHOÁ DOCKERTỜ 01 / 34CHƯƠNG 01SỬA ĐỔI C
Tờ 01 · Chương 01Nhập môn

ContainerVì sao thứ này thay đổi hẳn cách chúng ta giao phần mềm cho thế giới

Bài này không bắt đầu bằng định nghĩa. Nó bắt đầu bằng một tối thứ Sáu hỏng việc — rồi đi ngược lại để tìm ra nguyên nhân, bằng sáu bản vẽ bạn tự bấm để mổ xẻ.

Thời lượng15 phút
Bản vẽ06 hình
Trình độNhập môn
Truy cậpMiễn phí
OS CƠ SỞ · ALPINERUNTIME · NODE 20THƯ VIỆNMÃ ỨNG DỤNG185MEGABYTECHỈ ĐỌC1MỖI DÒNG DOCKERFILESINH RA MỘT LỚPDÙNG CHUNG KERNEL CỦA MÁY CHỦ
01Hiện trường

Tối thứ Sáu, và câu nói kinh điển

Bạn vừa xong tính năng upload ảnh. Trên laptop mọi thứ mượt: ảnh resize đúng, watermark đóng đúng góc, chạy đi chạy lại chục lần không lỗi. Commit, push, báo team lên staging.

Mười lăm phút sau, tin nhắn từ tester: "Anh ơi, upload ảnh lỗi 500."

Bạn mở log. Một dòng lạnh lùng: ImageMagick: command not found. Máy bạn có. Server không có. Và bạn nói ra câu mà mọi lập trình viên đều đã từng nói ít nhất một lần trong đời:

"Nhưng trên máy em nó chạy được mà?"

Chuyện này không phải xui xẻo. Nó là hệ quả tất yếu của cách ta đóng gói phần mềm suốt mấy chục năm: ta chỉ chuyển đi mã nguồn, còn môi trường để mã nguồn chạy được thì bỏ lại phía sau.

HÌNH 01
Phân tích sai hỏng — vì sao cùng mã nguồn lại hai kết quả
Bước 1/4Tỉ lệ 1:1
MÁY 01 · MÔI TRƯỜNG PHÁT TRIỂNLaptop của bạnNode.js20.11.0OpenSSL3.0.13glibc2.39ImageMagick7.1.1✓ ỨNG DỤNG CHẠY ĐÚNGsrc/GIT PUSHMÔI TRƯỜNG Ở LẠI1MÁY 02 · MÔI TRƯỜNG CHẠY THẬTMáy chủ stagingNode.js18.19.0OpenSSL1.1.1wglibc2.35ImageMagickkhông có2✕ HTTP 500 — COMMAND NOT FOUND3BA TIẾNG DEBUG LÚC 11 GIỜ ĐÊM
Bước 1 — Hiện trạng máy phát triểnLaptop của bạn tích tụ hàng chục thứ theo thời gian. Ứng dụng chạy được là nhờ tất cả những thứ đó cộng lại, chứ không chỉ nhờ mã nguồn.
1 / 4
Đừng nhầm

Vấn đề này không giải quyết được bằng "viết tài liệu cài đặt cẩn thận hơn". Tài liệu sẽ lạc hậu, người mới sẽ cài thiếu bước, và server production thứ ba sẽ lại khác server thứ hai. Cần một cơ chế máy móc tự đảm bảo, không dựa vào kỷ luật con người.

02Ẩn dụ

Ý tưởng đã cứu cả ngành vận tải biển

Trước 1956, xếp hàng lên tàu biển là một cơn ác mộng. Mỗi món hàng một hình dạng: bao gạo, thùng rượu, kiện vải, máy móc. Công nhân bốc vác xếp thủ công từng món, nhét chèn cho khỏi xê dịch. Một con tàu mất cả tuần để bốc dỡ, và chi phí bốc xếp chiếm gần một nửa giá vận chuyển.

Rồi Malcom McLean đặt câu hỏi ngược:

Thay vì tối ưu việc xếp hàng, sao không chuẩn hoá cái hộp đựng hàng?

Ông làm ra thùng thép kích thước cố định. Bên trong đựng gì cũng được — gạo, rượu, vải, máy móc, không ai quan tâm. Bên ngoài thì luôn giống hệt nhau: cùng kích thước, cùng bốn góc khoá chuẩn. Nhờ vậy cần cẩu ở bất kỳ cảng nào cũng nhấc được, xe tải nào cũng chở được, toa tàu nào cũng xếp được. Thời gian bốc dỡ rơi từ một tuần xuống vài giờ.

HÌNH 02
Chuẩn hoá cái vỏ, không chuẩn hoá món hàng
Bước 1/3Hình chiếu trục đo
A · HÀNG RỜIbao gạothùng rượumáy móckiện vảiXẾP THỦ CÔNG · MỘT TÀU MẤT CẢ TUẦNĐÓNGB · THÙNG TIÊU CHUẨNISO 668 · 6,06 m1BÊN NGOÀI LUÔN GIỐNG NHAUC · MỌI PHƯƠNG TIỆN ĐỀU NHẬNTÀU BIỂNXE TẢITÀU HOẢ
A — Cơn ác mộng bốc xếpMỗi món hàng một hình dạng nên không tồn tại quy trình chung. Mọi thứ phải làm thủ công: chậm, đắt, dễ hỏng hàng.
1 / 3
Hiểu nôm na

Container là suất cơm hộp mang đi

Bạn không mang theo cái bếp, cũng không mang theo đầu bếp. Bạn mang theo một hộp đã có sẵn cơm, thức ăn, nước chấm, đũa — đúng khẩu phần, đúng thứ tự, đóng kín. Mở ra ở văn phòng, ở công viên hay trên tàu thì món ăn vẫn y hệt lúc đóng hộp.

Container phần mềm cũng vậy: nó gói ứng dụng cùng với runtime, thư viện, cấu hình và biến môi trường mà ứng dụng cần. Mở ra ở máy nào cũng cho đúng một kết quả.

Ẩn dụ này sai ở chỗHộp cơm mang theo cả bếp thu nhỏ — container thì không. Container vẫn dùng chung "bếp" (kernel Linux) của máy chủ. Nó mang theo nguyên liệu và công thức, chứ không mang theo gian bếp. Đây chính là điểm khác biệt cốt lõi với máy ảo, mổ xẻ ngay dưới đây.
03Đối chiếu

Máy ảo và container khác nhau ở đúng một chỗ

Trước khi có container, cách phổ biến để "đóng gói cả môi trường" là dùng máy ảo. Bạn tạo một máy tính ảo hoàn chỉnh, cài hệ điều hành riêng, cài ứng dụng vào đó, rồi bê nguyên máy ảo đi. Cách này giải quyết được vấn đề — nhưng rất tốn kém.

Khác biệt cốt lõi

Máy ảo và container khác nhau ở độ sâu của lớp ảo hoá. Máy ảo ảo hoá phần cứng: mỗi máy ảo tin rằng nó có CPU, RAM, ổ cứng riêng, nên nó cần một hệ điều hành đầy đủ của riêng mình. Container ảo hoá hệ điều hành: mọi container dùng chung nhân Linux của máy chủ, chỉ bị cách ly về góc nhìn — nó tưởng mình có filesystem riêng, danh sách tiến trình riêng, mạng riêng.

Kéo thanh chọn trên bản vẽ để thấy chi phí phình lên thế nào khi số ứng dụng tăng.

HÌNH 03
Chi phí thật khi chạy N ứng dụng trên một máy
Số ứng dụng
A · MÁY ẢO — ẢO HOÁ PHẦN CỨNG3,9 GBOS kháchthư việnApp 1OS kháchthư việnApp 2OS kháchthư việnApp 3HYPERVISORHẠ TẦNG VẬT LÝB · CONTAINER — ẢO HOÁ HỆ ĐIỀU HÀNH0,30 GBthư việnApp 1thư việnApp 2thư việnApp 3DOCKER ENGINE · CONTAINERDMỘT HỆ ĐIỀU HÀNH DUY NHẤT — KERNEL DÙNG CHUNGHẠ TẦNG VẬT LÝThanh đo phía trên dùng chung một tỉ lệ cho cả hai bên
Chỉ sốMáy ảoContainerChênh lệch
Dung lượng ổ đĩa3,9 GB0,30 GB13×
Thời gian khởi động~90 s~1,2 s75×
RAM tối thiểu5,0 GB0,71 GB7×
Điều đáng chú ýBa dịch vụ là kịch bản thường gặp nhất: web, API, cơ sở dữ liệu. Chênh lệch dung lượng đã hơn mười lần, chênh lệch thời gian khởi động còn lớn hơn nữa.
Tiêu chíMáy ảoContainer
Ảo hoá ở tầngPhần cứngHệ điều hành
Hệ điều hành riêngCó — mỗi máy ảo một bản đầy đủKhông — dùng chung kernel máy chủ
Dung lượng điển hình1 – 20 GB5 – 300 MB
Thời gian khởi động30 giây – vài phútdưới 1 giây
Mức cách lyRất mạnh — ranh giới ở phần cứng ảoKhá mạnh — ranh giới ở namespace & cgroup
Chạy được OS khácCó — Windows trên host LinuxKhông — container Linux cần kernel Linux
Hợp với việc gìCách ly nhiều khách hàng, chạy OS khácĐóng gói & giao ứng dụng, CI/CD, co giãn
Ẩn dụ nguy hiểm nhất trong ngành

"Container là máy ảo nhẹ hơn." Nghe thì tiện, nhưng nó dẫn tới ba sai lầm rất tốn thời gian: chạy nhiều dịch vụ trong một container như thể nó là server; dùng docker exec sửa trực tiếp rồi tưởng thay đổi còn mãi; và tin rằng container cách ly mạnh ngang máy ảo. Container không phải máy nhỏ — nó là một tiến trình bị kernel giới hạn tầm nhìn.

Không phải chọn một trong hai

Thực tế phổ biến nhất hiện nay là container chạy bên trong máy ảo: nhà cung cấp cloud cho thuê một máy ảo, bạn cài Docker lên đó và chạy hàng chục container. Máy ảo lo cách ly giữa các khách hàng, container lo đóng gói ứng dụng.

04Mổ xẻ

Bên trong một lệnh docker run

Bạn gõ một dòng lệnh, mười giây sau có một web server đang chạy. Giữa hai thời điểm đó là một chuỗi việc khá thú vị. Hiểu được luồng này thì phần lớn lỗi Docker sau này bạn sẽ tự đoán được nguyên nhân.

bash · máy của bạn
$ docker run -d -p 8080:80 --name web nginx:1.27-alpine
Unable to find image 'nginx:1.27-alpine' locally
1.27-alpine: Pulling from library/nginx
a0d0a0d46f8b: Pull complete
e7b6b1ef4a0e: Pull complete
Status: Downloaded newer image for nginx:1.27-alpine
3f1a9c04b7e2d8a5c6f0b31e7d9a4c28f5b6e1d0a3c7b249e8f5d1a6c0b3e7f42
HÌNH 04
Hành trình của lệnh
Bước 1/6Cuộn để chạy

Bước 1. Chương trình docker bạn gõ không tự tạo container. Nó dịch lệnh của bạn thành một lời gọi HTTP rồi gửi đi.

MÁY CỦA BẠN · DOCKER HOSTcó sẵn chưa?pullDocker CLIchương trình bạn gõREST API/var/run/docker.sockdockerdtiến trình nềnKho image ở máychưa có nginx:1.27Container “web”chưa tồn tạicổng 8080 chưa mởRegistrydocker.iolayer · alpinelayer · nginxlayer · cấu hình123456
05Danh mục

Năm chi tiết bạn sẽ gặp mỗi ngày

Docker chỉ có vài danh từ cốt lõi. Nắm chắc năm cái dưới đây là bạn đọc hiểu được hầu hết tài liệu và thông báo lỗi sau này. Bấm vào từng dòng để mở rộng.

SỐCHI TIẾTMÔ TẢ & ẨN DỤ
06Sơ đồ trạng thái

Vòng đời của một container

Container không chỉ có "chạy" và "tắt". Nó đi qua một số trạng thái, mỗi lệnh Docker đưa nó sang một trạng thái cụ thể. Rất nhiều nhầm lẫn của người mới đến từ việc không phân biệt được đã dừngđã xoá.

Bấm thử các lệnh trên bản vẽ để xem container di chuyển — kể cả những lệnh sai.

HÌNH 05
Sơ đồ trạng thái và lệnh chuyển trạng thái
Tương tácCREATED
startstopstartpauseunpausermrm — container biến mất cùng lớp ghi của nócreatedđã tạo, chưa chạyrunningtiến trình đang sốngpausedđóng băng, giữ RAMstoppedcòn tồn tại, còn dữ liệuremovedkhông còn gì
Đang ở trạng thái createdContainer đã được tạo: filesystem đã dựng, cấu hình và tên đã ghi lại — nhưng chưa có tiến trình nào chạy. Lệnh docker create dừng đúng ở đây, còn docker run làm luôn cả bước tiếp theo.
Sai lầm rất phổ biến

docker stop db rồi tưởng dữ liệu đã mất. Không hề — container ở trạng thái stopped vẫn giữ nguyên lớp ghi, docker start db là chạy lại như cũ. Chỉ khi docker rm db thì lớp ghi mới thực sự bị xoá. Và nếu dữ liệu nằm trong volume thì ngay cả docker rm cũng không đụng tới.

07Cấu tạo

Vì sao image nhẹ đến vậy

Đến đây bạn có thể thắc mắc: nếu mỗi container mang theo cả môi trường, chạy 20 container thì chẳng phải tốn 20 lần dung lượng sao? Câu trả lời nằm ở cách image được xây: xếp chồng từng lớp, và các lớp được dùng chung.

Mỗi chỉ thị trong Dockerfile tạo một lớp chứa đúng phần khác biệt so với lớp dưới. Các lớp này chỉ đọc. Khi container chạy, Docker đặt thêm một lớp ghi mỏng lên trên cùng — mọi thay đổi lúc chạy chỉ nằm ở lớp đó.

HÌNH 06
Dockerfile dựng nên các layer — hình chiếu trục đo
Bước 1/7Bấm dòng lệnh để nhảy
DOCKERFILEFROM node:20-alpineWORKDIR /appCOPY package*.json ./RUN npm ci --omit=devCOPY . .CMD ["node","server.js"]MỖI DÒNG → MỘT LỚP CHỈ ĐỌC130 MBNode 20 trên Alpine130 MBthư mục /app0 MBpackage.json2 MBnode_modules48 MBmã nguồn ứng dụng5 MBmetadata · CMD0 MBBA CONTAINER — MỖI CÁI MỘT LỚP GHI RIÊNGweb-1web-2web-3
Lớp 1 — NềnChỉ thị FROM lấy một image có sẵn làm nền. Đây thường là lớp nặng nhất — và cũng là lớp được chia sẻ nhiều nhất giữa các image khác nhau trên cùng máy.
1 / 7
bash · kiểm chứng cơ chế dùng chung
$ docker image ls
REPOSITORY   TAG         IMAGE ID       SIZE
myapp        v1          8c2f1a9d3e40   185MB
myapp        v2          b71e04a5c9d2   186MB
node         20-alpine   2ad9f0c81b57   130MB

$ docker system df
TYPE       TOTAL  ACTIVE  SIZE     RECLAIMABLE
Images     3      2       196MB    12.4MB (6%)
Containers 3      3       1.1MB    0B

# Ba image cộng lại "trên giấy" là 501MB.
# Thực tế trên đĩa chỉ 196MB — phần còn lại là layer dùng chung.
08Bảng sửa đổi

Bốn sai lầm tốn thời gian nhất

SỐSAI LẦM & CÁCH SỬA
01

Coi container như một cái server nhỏ

Vào container bằng docker exec, cài thêm gói, sửa file cấu hình, rồi yên tâm. Container tái tạo lại là mọi thứ bay sạch.

Cách sửaMọi thay đổi phải nằm trong Dockerfile hoặc trong volume. Container phải xoá đi tạo lại được bất cứ lúc nào mà không mất gì.

02

Lưu dữ liệu quan trọng bên trong container

Chạy Postgres bằng docker run postgres không kèm volume. Đến lúc nâng cấp phiên bản, docker rm — và toàn bộ cơ sở dữ liệu biến mất cùng lớp ghi.

Cách sửaMọi trạng thái cần sống lâu đều gắn volume. Xem như container có thể bị xoá bất cứ lúc nào mà không báo trước.

03

Dùng tag latest ở production

latest không có nghĩa là "mới nhất" — nó chỉ là tag mặc định, và người khác có thể đẩy nội dung hoàn toàn khác vào đó. Cùng một file compose, hôm nay chạy tốt, tháng sau kéo về một bản khác và hỏng.

Cách sửaGhim phiên bản cụ thể (nginx:1.27-alpine), hoặc chắc chắn hơn nữa là ghim theo mã băm sha256.

04

Nhồi mọi thứ vào một container

Web server, ứng dụng, cơ sở dữ liệu, cron — tất cả trong một container, khởi động bằng script tự chế. Một dịch vụ chết thì Docker không biết, không restart, log trộn lẫn nhau.

Cách sửaMỗi container một việc, một tiến trình chính. Nhiều dịch vụ thì ghép lại bằng Docker Compose.

09Nghiệm thu

Kiểm tra hiểu bài

Năm câu, không tính điểm, không giới hạn thời gian. Mục đích là để bạn tự phát hiện chỗ nào còn mơ hồ — mỗi câu giải thích cả đáp án đúng lẫn vì sao các đáp án kia sai.

Phiếu nghiệm thu · Tờ 010 / 5 câu
01Điều nào sau đây không nằm trong một Docker image?Chọn 1
02Chọn tất cả khẳng định đúng về khác biệt giữa container và máy ảo.Chọn nhiều
03Chạy docker stop web sẽ xoá mọi dữ liệu mà container web đã ghi ra.Đúng / Sai
04Vì sao Dockerfile thường chép package.json vào trước rồi mới COPY . .?Chọn 1
05Sau khi chạy docker rm -f web, những thứ nào vẫn còn trên máy?Chọn nhiều
10Tóm tắt

Năm điều mang về

01

Lỗi "máy tôi chạy được" sinh ra vì ta chỉ chuyển đi mã nguồn mà bỏ lại môi trường. Container đóng gói cả hai vào một thứ.

02

Container không phải máy ảo nhẹ. Máy ảo ảo hoá phần cứng nên cần OS riêng; container ảo hoá hệ điều hành và dùng chung kernel máy chủ.

03

Một lệnh docker run đi qua: CLI → REST API → daemon → kho image ở máy → registry nếu thiếu → tạo container → cấp mạng → chạy tiến trình.

04

Image là khuôn chỉ đọc gồm nhiều lớp dùng chung được; container là image đang chạy, có thêm lớp ghi mỏng sẽ mất khi bị xoá.

05

stop khác rm. Dữ liệu cần sống lâu thì phải nằm trong volume, không nằm trong container.

Xong tờ này rồi?
Đăng nhập để lưu tiến độ vào tài khoản của bạn và mở khoá chứng nhận cuối khoá. Bài học vẫn đọc được đầy đủ khi chưa đăng nhập.
Đăng nhập để lưu tiến độ