Docker Events API: Giám sát Container Real-time thay vì ‘Cầu nguyện’ mỗi đêm

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

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:

  1. 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.
  2. 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.
  3. 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.

Share: