Python pyudev Linux: Tự động xử lý dữ liệu khi cắm USB vào Server

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Ba cách tiếp cận để “bắt” sự kiện thiết bị ngoại vi

Nếu đang quản trị server Linux, chắc hẳn bạn đã từng gặp ca khó: Làm sao để khi cắm một ổ cứng 2TB chứa đầy file log vào, hệ thống phải tự nhận diện, mount đúng chỗ và chạy script xử lý ngay lập tức?

Dưới đây là 3 phương án phổ biến mà các admin thường cân nhắc:

  1. Shell Script + Cronjob: Cứ 60 giây lại chạy lsblk một lần. Cách này vừa chậm chạp vừa làm tăng load hệ thống không đáng có.
  2. Udev Rules thuần túy: Cấu hình trực tiếp trong /etc/udev/rules.d/. Phương pháp này rất nhẹ nhưng cực kỳ khó debug. Việc viết logic phức tạp như gọi API hay parse file JSON bằng Shell script bên trong udev rules là một cực hình.
  3. Python + thư viện pyudev: Đây là lựa chọn tối ưu nhất. Nó kết hợp sự mạnh mẽ của hệ thống udev Linux với khả năng xử lý linh hoạt của Python.

Tại sao pyudev lại vượt trội hơn Shell script?

Dự án đầu tiên của mình chỉ cần copy vài file từ USB vào server. Lúc đó, script Python chỉ dài khoảng 50 dòng. Tuy nhiên, khi yêu cầu tăng lên như phải kiểm tra checksum MD5, phân loại 500GB dữ liệu theo ngày tháng, và gửi thông báo qua Slack, mọi thứ bắt đầu phức tạp.

Nếu dùng Shell script, việc bảo trì sẽ trở thành ác mộng. pyudev giải quyết vấn đề này bằng cách quản lý thiết bị dưới dạng object. Bạn có thể lọc thiết bị theo Serial Number hoặc Vendor ID cực kỳ gọn gàng. Vì lắng nghe trực tiếp từ Kernel qua Netlink, Python sẽ nhận tín hiệu ngay khi đầu USB vừa chạm cổng, độ trễ thường dưới 10ms.

Triển khai script giám sát trong thực tế

Thiết lập môi trường

Trước tiên, bạn cần cài đặt thư viện. Hầu hết các bản phân phối như Ubuntu hay CentOS đều đã có sẵn libudev nên việc cài đặt rất nhanh gọn.

pip install pyudev

Code mẫu: Theo dõi sự kiện Plug & Play

Đoạn code dưới đây đóng vai trò như một “lính canh” chạy ngầm. Nó sẽ in ra thông tin chi tiết ngay khi có bất kỳ thiết bị lưu trữ (block device) nào kết nối vào hệ thống.

import pyudev

def monitor_devices():
    context = pyudev.Context()
    monitor = pyudev.Monitor.from_netlink(context)
    monitor.filter_by(subsystem='block')

    print("--- Đang đợi thiết bị... ---")
    
    for device in iter(monitor.poll, None):
        if device.action == 'add':
            print(f"Phát hiện: {device.device_node}")
            print(f"File System: {device.get('ID_FS_TYPE')}")
            print(f"UUID: {device.get('ID_FS_UUID')}")
        elif device.action == 'remove':
            print(f"Đã rút thiết bị: {device.device_node}")

if __name__ == "__main__":
    monitor_devices()

Tự động Mount và xử lý dữ liệu

Để đưa vào sản xuất, mình thường dùng module subprocess để thực hiện lệnh mount. Một mẹo nhỏ: hãy luôn kiểm tra thuộc tính ID_FS_USAGE. Việc này giúp bạn tránh mount nhầm các phân vùng swap hoặc phân vùng hệ thống không mong muốn.

import pyudev
import subprocess
import os

def handle_device_event(device):
    if device.action == 'add' and device.get('ID_FS_USAGE') == 'filesystem':
        node = device.device_node
        label = device.get('ID_FS_LABEL') or "usb_disk"
        mount_path = f"/mnt/external/{label}"
        
        os.makedirs(mount_path, exist_ok=True)
        
        try:
            subprocess.run(["mount", node, mount_path], check=True)
            print(f"Mount thành công {node} vào {mount_path}")
            # Thêm logic xử lý file (ví dụ: rsync dữ liệu) tại đây
        except subprocess.CalledProcessError as e:
            print(f"Lỗi Mount: {e}")

def main():
    context = pyudev.Context()
    monitor = pyudev.Monitor.from_netlink(context)
    monitor.filter_by(subsystem='block')

    observer = pyudev.MonitorObserver(monitor, handle_device_event)
    observer.start()
    
    try:
        while True:
            import time
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()

if __name__ == "__main__":
    main()

Kinh nghiệm thực chiến khi vận hành hệ thống lớn

Khi script phát triển từ vài chục lên hàng nghìn dòng code, có những vấn đề sống còn bạn cần lưu ý để tránh treo server:

  • Tuyệt đối không chạy tác vụ nặng trực tiếp: Đừng bao giờ copy 100GB dữ liệu ngay trong hàm callback của udev. Việc này sẽ làm block toàn bộ tiến trình lắng nghe. Hãy đẩy task vào threading hoặc các queue như Celery/Redis.
  • Xử lý lỗi “rút nóng”: Người dùng thường rút USB mà không unmount. Bạn cần bao bọc các thao tác đọc/ghi file trong khối try...except cực kỳ chặt chẽ.
  • Quản lý quyền hạn: Script chạy quyền user thường không thể mount. Bạn nên cấu hình sudoers cho phép user thực hiện lệnh mount/umount mà không cần password.
  • Định danh bằng UUID: Đừng bao giờ tin vào những tên gọi như /dev/sdb1 vì chúng có thể thay đổi sau mỗi lần khởi động. Hãy dùng ID_FS_UUID để định danh chính xác thiết bị của từng khách hàng.

Kỹ thuật này đã giúp mình tự động hóa hoàn toàn quy trình đổ dữ liệu offline từ các trạm quan trắc về server trung tâm. Hy vọng giải pháp này sẽ giúp bộ công cụ DevOps của bạn trở nên mạnh mẽ hơn.

Share: