Hướng dẫn tích hợp Docker Container với systemd trên Linux: Chạy container như một Service thực thụ

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Tại sao phải dùng systemd khi Docker đã có Restart Policy?

Hồi mới bắt đầu làm Lab, mình cứ đinh ninh rằng chỉ cần thêm flag --restart always vào lệnh docker run hoặc khai báo restart: always trong Docker Compose là xong. Container sập thì Docker tự kéo lại, server reboot thì Docker daemon tự bật container lên. Nghe thì có vẻ ổn. Nhưng thực tế ở Production, không phải lúc nào cũng vậy.

Mình từng gặp trường hợp server bị mất điện đột ngột. Khi khởi động lại, Docker daemon lên trước khi các ổ đĩa mạng (NFS) được mount xong. Kết quả là container khởi động lỗi vì không tìm thấy dữ liệu. Hay một lần khác, mình cần container ứng dụng phải chạy sau khi database cài trực tiếp trên Host OS (không dùng Docker) đã sẵn sàng. Những kịch bản này, Docker Restart Policy gần như bó tay.

Đó là lý do mình chuyển sang dùng systemd để quản lý Docker container. Systemd là trình quản lý hệ thống mặc định trên hầu hết các bản phân phối Linux hiện nay (Ubuntu, Debian, CentOS…). Khi tích hợp Docker vào systemd, container được quản lý như mọi dịch vụ OS khác — kiểm soát thứ tự khởi động, log tập trung qua journald, và dễ monitor hơn nhiều.

So sánh các phương pháp quản lý khởi động

Tính năng Docker Restart Policy Docker Compose Systemd Integration
Dễ sử dụng Rất cao Cao Trung bình
Quản lý Dependency Không Chỉ trong nội bộ file Compose Rất mạnh (với mọi dịch vụ OS)
Tự động restart Có (nhiều tùy chọn hơn)
Logging Docker logs Docker logs Journald (tập trung)

Ưu điểm của systemd

  • Kiểm soát thứ tự (Dependencies): Bạn có thể yêu cầu container chỉ khởi động sau khi Network, MySQL, hoặc một ổ đĩa cụ thể đã sẵn sàng.
  • Tự động hồi phục: Systemd có cơ chế restart thông minh hơn, ví dụ: thử lại 5 lần, mỗi lần cách nhau 10 giây, nếu vẫn lỗi thì dừng hẳn để tránh vòng lặp crash vô tận.
  • Tích hợp giám sát: Các công cụ như Zabbix, Prometheus dễ dàng check trạng thái service qua systemctl.

Nhược điểm

  • Phải viết file cấu hình unit hơi rườm rà hơn so với gõ một dòng lệnh.
  • Mỗi container cần một file service riêng.

Hướng dẫn triển khai: Biến Container thành Systemd Service

Giả sử mình có một ứng dụng web đơn giản chạy bằng Nginx. Thay vì docker run, mình sẽ tạo một service cho nó.

Bước 1: Chuẩn bị file Unit

Tạo một file có đuôi .service trong thư mục /etc/systemd/system/. Mình đặt tên là webapp.service.

sudo nano /etc/systemd/system/webapp.service

Bước 2: Viết nội dung cấu hình

Đây là cấu hình mình đang dùng thực tế cho mấy dự án VPS cá nhân. Copy vào file:

[Unit]
Description=My Web Application Container
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
# Xóa container cũ nếu còn tồn tại trước khi start mới
ExecStartPre=-/usr/bin/docker stop %p
ExecStartPre=-/usr/bin/docker rm %p
ExecStartPre=/usr/bin/docker pull nginx:latest

# Lệnh khởi chạy chính
ExecStart=/usr/bin/docker run --name %p \
    -p 8080:80 \
    nginx:latest

# Lệnh dừng container
ExecStop=/usr/bin/docker stop %p

[Install]
WantedBy=multi-user.target

Giải thích các tham số quan trọng:

  • After=docker.service: Đảm bảo Docker daemon phải chạy trước thì service này mới chạy.
  • ExecStartPre=-/usr/bin/docker stop %p: Dấu trừ - ở trước lệnh có nghĩa là nếu lệnh này lỗi (ví dụ container chưa tồn tại), systemd vẫn tiếp tục chạy lệnh tiếp theo mà không dừng lại.
  • %p: Biến systemd lấy tên service không có đuôi (ở đây là webapp). Dùng làm tên container để dễ nhận biết.
  • Restart=always: Tự động bật lại nếu container crash.

Bước 3: Kích hoạt Service

Sau khi lưu file, bạn cần báo cho systemd biết là có sự thay đổi và kích hoạt nó:

# Reload để hệ thống nhận diện file mới
sudo systemctl daemon-reload

# Cho phép service khởi động cùng OS
sudo systemctl enable webapp.service

# Khởi chạy service ngay lập tức
sudo systemctl start webapp.service

Bước 4: Kiểm tra trạng thái

Kiểm tra container đang chạy chưa:

sudo systemctl status webapp.service

Dòng active (running) màu xanh là thành công. Nếu thấy failed, chạy journalctl -u webapp.service -n 50 để xem lỗi cụ thể.

Mẹo thực chiến: Quản lý Log và Debug

Chuyển sang systemd, bạn cũng không cần docker logs nữa. Toàn bộ output của container đổ vào journald. Tiện nhất là khi debug: log ứng dụng và log hệ thống cùng timeline, không cần đoán thứ tự.

Xem log real-time:

journalctl -u webapp.service -f

Khi cần cập nhật code hoặc image mới, chỉ cần chạy:

sudo systemctl restart webapp.service

Systemd tự chạy qua các bước ExecStartPre — pull image mới, xóa container cũ, khởi động lại. Không cần nhớ thứ tự thủ công.

Khi nào KHÔNG nên dùng cách này?

Systemd giải quyết đúng bài toán của nó — nhưng không phải bài toán nào cũng cần đến nó.

  1. Môi trường Development: Hãy cứ dùng Docker Compose cho nhanh. Việc sửa file service rồi reload daemon mỗi khi đổi port rất mất thời gian.
  2. Hệ thống Microservices phức tạp: Nếu bạn có hàng chục container liên kết với nhau, việc quản lý hàng chục file service thủ công sẽ là thảm họa. Lúc này Docker Compose hoặc Kubernetes là lựa chọn đúng đắn.

Cách này phù hợp nhất cho các single-instance services trên VPS. Ví dụ: một Telegram Bot, một blog cá nhân, hay Nginx/Traefik làm reverse proxy. Nếu chỉ có 2–3 container độc lập, đây là lựa chọn đơn giản và ổn định hơn Docker Compose nhiều.

Mình đang dùng đúng setup này để chạy Nginx reverse proxy và một Telegram Bot trên VPS cá nhân — mấy tháng nay chưa cần can thiệp thủ công lần nào. Nếu bạn đang gặp vấn đề container không chịu khởi động sau reboot, thử cách này xem.

Share: