Compose chạy được trên máy chủ thật, chỉ là nó không tự nói cho bạn biết mấy điều. Tôi dựng năm dịch vụ với năm cấu hình restart: khác nhau rồi khởi động lại daemon — mô phỏng đúng một lần máy chủ reboot.
restart: |
Dừng tay trước? | Sau khi daemon lên lại |
|---|---|---|
| (không khai) | không | Nằm im |
on-failure |
không | Nằm im |
always |
không | Tự lên |
unless-stopped |
không | Tự lên |
unless-stopped |
có | Nằm im (đúng ý) |
Hai dòng đầu là hai cách mất dịch vụ sau mỗi lần reboot.
Không khai gì là mặc định, và mặc định nghĩa là container không bao giờ tự lên lại. Bạn up -d một lần, mọi thứ chạy hàng tháng, rồi nhà cung cấp bảo trì hạ tầng và sáng hôm sau website biến mất.
on-failure cũng không cứu bạn, và đây là chỗ dễ hiểu nhầm nhất. Nghe tên thì tưởng "hỏng thì dậy", nhưng khi máy chủ tắt, Docker gửi SIGTERM và container thoát với mã 0 — đó là thoát bình thường, không phải lỗi. on-failure để yên, đúng như đặc tả của nó.
unless-stopped nhớ đúng ý định của bạn. Dịch vụ bị bạn dừng tay để bảo trì thì sau reboot vẫn nằm im; dịch vụ đang chạy thì lên lại. Đó là hành vi đúng cho gần như mọi thứ trên máy chủ, và là lý do phần 28 khuyên dùng nó thay cho always.
Nên dòng đầu tiên bạn thêm vào mọi service trên máy chủ:
services:
web:
image: vi-du:1.0
restart: unless-stopped
Cập nhật gây gián đoạn bao lâu
Câu hỏi thứ hai của máy chủ thật: gõ docker compose up -d để đưa phiên bản mới lên thì người dùng mất dịch vụ bao lâu? Tôi cho một bộ dò gọi 50 lần mỗi giây trong lúc cập nhật.
| Lệnh | Kết quả |
|---|---|
up -d khi cấu hình không đổi |
447 lần gọi, 0 lần hỏng |
up -d sau khi đổi cấu hình |
518 lần gọi, 5 lần hỏng — gián đoạn 0,59 s |
compose restart |
401 lần gọi, 4 lần hỏng — gián đoạn 0,56 s |
Dòng đầu xác nhận điều phần 49 đã đo: up -d khi không có gì đổi là hoàn toàn vô hại, Compose so config-hash rồi bỏ qua. Chạy nó trong cron cũng được.
Hai dòng sau cho cùng một con số vì cùng một việc: container cũ dừng, container mới lên. Compose không có cập nhật cuốn chiếu — không có giai đoạn hai container cùng chạy.
Và 0,6 giây đó là của nginx. Ứng dụng Java khởi động 30 giây thì gián đoạn là 30 giây. Muốn không gián đoạn thì cần thứ khác: một reverse proxy phía trước cùng thao tác đổi cổng, hoặc Swarm, hoặc Kubernetes. Đừng mong Compose làm việc đó.
Những gì nên khác so với máy phát triển
Ghim phiên bản image, đừng dùng latest.
image: nginx:1.27.3-alpine # khong phai nginx:latest
latest nghĩa là hai máy chủ up -d cách nhau một tuần chạy hai phiên bản khác nhau, và bạn không có cách nào biết. Phần 4 đã đo được rằng chỉ có digest mới thật sự bất biến.
Đừng build: trên máy chủ. Build ở CI, đẩy lên registry, máy chủ chỉ pull. Build trên máy chủ nghĩa là máy chủ cần mã nguồn, cần công cụ build, và cần đủ đĩa cho build cache — thứ mà phần 45 đo được là vùng phình nhanh nhất.
Giới hạn nhật ký. Mặc định json-file không xoay vòng, và phần 46 đo được 53,5 MB log vô hình với mọi lệnh đếm dung lượng:
logging:
driver: local
options: { max-size: "10m", max-file: "3" }
Bind cổng vào loopback rồi để nginx ra ngoài. ports: "127.0.0.1:8080:8080". Lý do đầy đủ ở phần 35: luật ufw không chạm được vào cổng Docker publish.
Đặt giới hạn tài nguyên. Một container rò bộ nhớ sẽ kéo cả máy chủ xuống nếu không có trần:
deploy:
resources:
limits: { memory: 512M, cpus: "1.0" }
Đưa Compose vào systemd
Để cả hệ thống lên cùng máy chủ mà không phụ thuộc vào việc ai đó up -d bằng tay:
[Unit]
Description=He thong cua toi
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/hethong
ExecStart=/usr/bin/docker compose up -d --wait
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
Cờ --wait đáng chú ý: nó khiến lệnh chờ tới khi mọi dịch vụ có healthcheck báo khoẻ, đúng cơ chế mà phần 50 đã đo. Không có nó, systemd coi như xong ngay khi container được tạo.
Nói vậy nhưng nếu mọi service đều có restart: unless-stopped thì Docker tự lo phần khởi động rồi — unit systemd chủ yếu có ích để systemctl stop dừng gọn cả hệ thống.
Thử ba mươi giây
Kiểm tra hệ thống của bạn có sống sót qua reboot không, không cần reboot thật:
docker compose ps --format '{{.Service}}' | while read s; do
p=$(docker inspect "$(docker compose ps -q "$s")" \
--format '{{.HostConfig.RestartPolicy.Name}}')
printf '%-20s %s\n' "$s" "${p:-KHONG KHAI}"
done
Dòng nào ra no, KHONG KHAI hoặc on-failure là dòng sẽ không tự lên lại sau lần reboot tới. Sửa thành unless-stopped rồi docker compose up -d — mất đúng 0,6 giây gián đoạn, một lần.
Phần sau đo docker compose watch, cơ chế đồng bộ mã mà Compose mới đưa vào.