Tại sao nên dùng Python thay vì gõ lệnh Docker CLI thủ công?
Bạn đã bao giờ phải setup một cụm 30 container, mỗi cái gánh một cấu hình network, volume và biến môi trường riêng biệt chưa? Nếu chỉ dùng shell script và gõ lệnh docker run, việc quản lý lỗi (error handling) sẽ sớm trở thành một mớ hỗn độn.
Mình từng duy trì một hệ thống CI/CD với script Bash dài 200 dòng. Mọi thứ vẫn ổn cho đến khi yêu cầu phức tạp hơn: Container A phải healthcheck thành công thì Container B mới được phép start. Lúc này, Bash bắt đầu “hụt hơi”. Mình quyết định chuyển sang Docker SDK cho Python. Dự án đó hiện đã cán mốc 2000 dòng code. Tuy nhiên, nhờ cấu trúc hướng đối tượng, việc bảo trì vẫn cực kỳ nhẹ nhàng.
Dùng code điều khiển Docker giúp bạn tự động hóa các tác vụ lặp lại. Thậm chí, bạn có thể tích hợp Docker vào web app để tạo sandbox cho người dùng hoặc xây dựng tool giám sát riêng.
Cơ chế hoạt động: Docker Client và Docker Daemon
Trước khi gõ code, bạn cần nắm rõ cách Python giao tiếp với Docker. Docker chạy theo mô hình Client-Server. Khi cài Docker, một service chạy ngầm gọi là Docker Daemon sẽ lắng nghe qua Unix socket tại /var/run/docker.sock.
Thư viện docker-py đóng vai trò là một Client. Nó gửi các yêu cầu HTTP REST API đến Daemon để ra lệnh tạo container hoặc xóa image. Hiểu đơn giản: bất cứ lệnh terminal nào bạn hay gõ, Python SDK đều có hàm tương ứng.
Cài đặt thư viện
Chỉ cần một dòng lệnh pip quen thuộc:
pip install docker
Thực hành: Viết script điều khiển Docker
1. Kết nối Docker Client
Bước đầu tiên là thiết lập kết nối. Thông thường, SDK sẽ tự quét và lấy cấu hình mặc định từ biến môi trường của hệ thống.
import docker
try:
client = docker.from_env()
# Kiểm tra kết nối bằng cách lấy thông tin phiên bản
v = client.version()
print(f"Kết nối thành công! Docker Engine v{v['Version']}")
except Exception as e:
print(f"Lỗi kết nối: {e}")
2. Quản lý Image: Pull và Kiểm tra
Thay vì gõ docker pull nginx:latest, chúng ta sẽ thực hiện qua code. Một mẹo nhỏ: hãy kiểm tra image đã có ở local chưa trước khi pull để tiết kiệm băng thông và giảm 3-5 giây chờ đợi không cần thiết.
# Tải image từ Docker Hub
print("Đang check và tải image nginx...")
image = client.images.pull("nginx", tag="latest")
print(f"Đã sẵn sàng Image ID: {image.short_id}")
# Liệt kê các image đang có
for img in client.images.list():
print(f"Tag: {img.tags}")
3. Thao tác Container: Run, Stop và Logs
Đây là phần “ăn tiền” nhất. Khi chạy container qua SDK, bạn có thể kiểm soát chặt chẽ các tham số như detach, ports, hay mem_limit.
# Chạy container (tương đương docker run -d -p 8080:80)
container = client.containers.run(
"nginx",
detach=True,
ports={'80/tcp': 8080},
name="web_app_prod"
)
print(f"Container {container.name} đang chạy.")
# Lấy log thời gian thực
logs = container.logs(tail=10)
print(f"10 dòng log cuối: {logs.decode('utf-8')}")
# Dọn dẹp
container.stop()
container.remove()
Kinh nghiệm xương máu: Luôn đặt thuộc tính name cho container. Nếu để Docker tự sinh tên ngẫu nhiên như “agitated_hopper”, việc truy vết và quản lý container bằng code sau này sẽ là một thảm họa.
4. Thiết lập Network riêng biệt
Để các dịch vụ giao tiếp an toàn, mình luôn khuyến khích tạo network riêng thay vì dùng bridge mặc định của Docker.
# Tạo network nếu chưa tồn tại
net_name = "internal_api_net"
existing_nets = client.networks.list(names=[net_name])
if not existing_nets:
client.networks.create(net_name, driver="bridge")
print(f"Đã tạo network: {net_name}")
Bài học thực tế: Đừng để script của bạn bị “crash” giữa chừng
Khi script phình to, cách tổ chức code sẽ quyết định bạn có được ngủ ngon mỗi đêm hay không. Dưới đây là 3 quy tắc mình luôn áp dụng:
- Bắt lỗi chi tiết: Đừng chỉ dùng
except Exception. Hãy bắt các lỗi cụ thể từdocker.errorsnhưNotFoundhoặcAPIErrorđể có phương án xử lý riêng cho từng trường hợp. - Xây dựng Wrapper Class: Đừng gọi trực tiếp lệnh run ở khắp nơi. Hãy viết một class
DockerManager. Ví dụ: hàmsmart_runsẽ tự động xóa container cũ trùng tên trước khi khởi chạy cái mới. - Dọn dẹp tài nguyên (Housekeeping): Quá trình automation thường để lại các container rác hoặc image “dangling”. Hãy tận dụng
client.containers.prune()để giải phóng dung lượng ổ cứng định kỳ.
def clean_system():
client = docker.from_env()
# Dọn dẹp các container đã stop để giải phóng RAM/Disk
deleted_containers = client.containers.prune()
print(f"Đã dọn dẹp: {deleted_containers.get('SpaceReclaimed', 0)} bytes")
Kết bài
Chuyển từ gõ lệnh thủ công sang Docker SDK là bước đi đầu tiên để bạn tiến tới tư duy Infrastructure as Code. Ban đầu có thể khó hơn viết file .sh, nhưng khả năng kiểm soát của Python sẽ giúp hệ thống của bạn ổn định hơn nhiều. Hãy thử bắt đầu bằng việc viết một script nhỏ để dọn dẹp hệ thống ngay hôm nay nhé!

