Vấn đề thực tế: Khi container “chết” trong im lặng
Cách đây 2 năm, mình từng vận hành một cụm 20 microservices cho sàn e-commerce. Một đêm nọ, tính năng thanh toán lỗi hoàn toàn. Khách hàng bắt đầu phàn nàn trên Fanpage, sếp gọi cháy máy, nhưng Dashboard monitoring vẫn báo xanh rì. Hóa ra, service bị treo (zombie process) chứ không sập hẳn. Docker liên tục restart nó nhưng lại rơi vào vòng lặp CrashLoopBackOff.
Mình mất hơn 2 tiếng để tìm ra nguyên nhân: Một container bị Memory Leak. Cứ mỗi 5 phút, nó lại ‘ngỏm’ một lần. Lúc đó, mình hoàn toàn bị động. Mình chỉ biết sự cố khi khách hàng lên tiếng, thay vì biết ngay khi hệ thống bắt đầu bất ổn. Bài học rút ra? Đừng tin tuyệt đối vào flag --restart always.
Restart tự động chỉ là phần ngọn. Cái bạn thực sự cần là khả năng quan sát (Observability). Bạn phải phản ứng tức thời với những gì đang diễn ra bên trong Docker Engine.
Tại sao các công cụ truyền thống đôi khi lại “hụt hơi”?
Thông thường, anh em sẽ nghĩ ngay đến Prometheus + Grafana hoặc ELK Stack. Tuy nhiên, các giải pháp này bộc lộ vài nhược điểm trong các dự án quy mô vừa và nhỏ:
- Độ trễ (Latency): Prometheus hoạt động theo cơ chế “pull”. Nếu bạn đặt interval 30 giây, bạn sẽ mất tới nửa phút để biết container đã chết.
- Ngốn tài nguyên: Chạy cả cụm Monitoring tốn ít nhất 1-2GB RAM. Đây là sự lãng phí cực lớn nếu bạn chỉ chạy 5-10 container trên một con VPS cấu hình thấp.
- Khó tự động hóa: Để lập trình các kịch bản phản ứng phức tạp (như: xóa cache Redis rồi mới restart container) bằng các công cụ này thường rất cồng kềnh.
Chúng ta cần một cơ chế Event-driven (hướng sự kiện). Ngay khi Docker Engine thực hiện hành động (die, stop, oom), nó phải bắn tin hiệu ngay lập tức.
3 cách tiếp cận để “bắt bài” sự kiện Docker
Dưới đây là những cách mình từng thử qua:
- Polling
docker ps: Viết script cronjob chạy mỗi phút. Cách này cực tệ vì tốn tài nguyên và độ trễ quá cao. - Sử dụng Log Driver: Đẩy log về server tập trung. Cách này tốt để debug nhưng cực khó để trigger hành động tự động dựa trên trạng thái container.
- Docker Events API: Đây là “vũ khí bí mật” có sẵn. Nó cung cấp stream dữ liệu thời gian thực về mọi biến động của container, image và network.
Giải pháp: Xây dựng hệ thống Automation với Docker Events API
Việc kết hợp Docker Events API với một script Python nhỏ gọn là phương án hiệu quả nhất. Script này lắng nghe trực tiếp từ Docker Socket (/var/run/docker.sock). Sau đó, nó đẩy cảnh báo qua Telegram và thực hiện logic restart thông minh.
1. Lắng nghe Docker Events bằng dòng lệnh
Bạn có thể test nhanh bằng lệnh bash để xem dữ liệu trả về:
docker events --filter 'event=die' --filter 'event=oom'
Khi một container bị kill hoặc hết bộ nhớ (OOM), thông tin chi tiết sẽ hiện ra ngay. Đây chính là nguồn dữ liệu quý giá chúng ta sẽ khai thác.
2. Script giám sát và cảnh báo Telegram
Dưới đây là cấu trúc script Python mình đang dùng cho các dự án thực tế. Nó nhẹ (chỉ tốn khoảng 30-50MB RAM) nhưng cực kỳ mạnh mẽ.
import docker
import requests
import os
# Cấu hình Telegram
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
CHAT_ID = os.getenv("CHAT_ID")
def send_telegram_msg(message):
url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
data = {"chat_id": CHAT_ID, "text": message, "parse_mode": "Markdown"}
try:
requests.post(url, data=data, timeout=5)
except Exception as e:
print(f"Lỗi gửi Telegram: {e}")
def monitor_events():
client = docker.from_env()
print("🚀 Đang lắng nghe Docker events...")
for event in client.events(decode=True):
status = event.get('status')
attributes = event.get('Actor', {}).get('Attributes', {})
container_name = attributes.get('name', 'Unknown')
if status == "die":
exit_code = event.get('Actor', {}).get('Attributes', {}).get('exitCode')
msg = f"🔴 *Container Alert*\n*Service:* {container_name}\n*Status:* Đã dừng (Die)\n*Exit Code:* {exit_code}"
send_telegram_msg(msg)
if exit_code == "137":
send_telegram_msg(f"⚠️ Cảnh báo: {container_name} bị OOM Kill (Hết RAM)!")
elif status == "oom":
send_telegram_msg(f"🔥 *CRITICAL*: {container_name} đã cạn kiệt bộ nhớ!")
if __name__ == "__main__":
monitor_events()
3. Triển khai dưới dạng Sidecar Container
Để script tự chạy và giám sát các container khác, hãy đóng gói nó vào Docker. Đừng quên mount socket vào bên trong file docker-compose.yml:
services:
docker-monitor:
image: my-docker-monitor:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- TELEGRAM_TOKEN=${TELEGRAM_TOKEN}
- CHAT_ID=${CHAT_ID}
restart: always
Cảnh báo bảo mật: Việc mount docker.sock cấp quyền kiểm soát toàn bộ Docker Engine cho script. Bạn tuyệt đối không được public image này lên Docker Hub nếu chứa thông tin nhạy cảm.
Nâng cấp: Tự động hóa thông minh
Thay vì chỉ gửi tin nhắn, bạn có thể thêm logic xử lý tự động. Ví dụ: Nếu container restart quá 5 lần trong 10 phút, hãy tạm dừng nó. Việc này giúp tránh nghẽn CPU cho toàn bộ server.
Bạn cũng có thể dùng Events API làm Audit Log. Nó giúp bạn biết chính xác ai đã xóa container nào và vào lúc nào. Thực tế, giải pháp này giúp mình giảm 80% thời gian phản ứng với sự cố. Thay vì đợi khách hàng gọi, mình nhận tin nhắn và xử lý ngay trên điện thoại.
Tóm lại, nếu bạn quản lý Docker mà chưa dùng Events API, bạn đang bỏ lỡ một công cụ cực phẩm. Nó nhẹ, miễn phí và linh hoạt cho mọi nhu cầu tự động hóa.
