Tự động hóa Docker với Python: Từ Bash Script đến SDK chuyên nghiệp

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

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.errors như NotFound hoặc APIError để 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àm smart_run sẽ 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é!

Share: