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:
- Shell Script + Cronjob: Cứ 60 giây lại chạy
lsblkmộ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ó. - 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. - 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
threadinghoặ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...exceptcự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
sudoerscho phép user thực hiện lệnhmount/umountmà không cần password. - Định danh bằng UUID: Đừng bao giờ tin vào những tên gọi như
/dev/sdb1vì chúng có thể thay đổi sau mỗi lần khởi động. Hãy dùngID_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.

