Docker Checkpoint và Restore với CRIU: Di chuyển Container không gián đoạn dịch vụ

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

Vấn đề: Migrate container mà không được phép dừng service

Tháng trước mình cần chuyển một con VPS sang server mạnh hơn. Container đang chạy là một job xử lý background — đã chạy được 6 tiếng, đang ở giữa chừng một batch job lớn. Nếu dừng container, toàn bộ progress mất sạch, phải chạy lại từ đầu. Nếu giữ nguyên thì không migrate được.

Đây là tình huống nhiều người đã từng bực bội: Docker container không có cơ chế “tạm dừng và chuyển đi” theo nghĩa đen. Các cách thông thường hay dùng:

  • Stop container → backup data → restore ở server mới → restart (có downtime)
  • Dùng persistent volume và restart (mất in-memory state)
  • Dùng replication phức tạp (tốn tài nguyên, không phải lúc nào cũng khả thi)

Nhưng có một cách ít người biết: CRIU (Checkpoint/Restore In Userspace) — đóng băng toàn bộ trạng thái process, chuyển sang máy khác, tiếp tục chạy như không có gì xảy ra.

Nguyên nhân: Tại sao container không di chuyển được “nóng”

Container về bản chất là một process group được cô lập bằng Linux namespaces và cgroups. Khi container đang chạy, nó có:

  • Memory state: heap, stack, các biến đang xử lý
  • File descriptors: socket đang mở, pipe, file handles
  • Network connections: TCP connections đang established
  • Process state: CPU registers, signal handlers, timers

Tất cả những thứ này chỉ tồn tại trong RAM của server hiện tại. Docker image chỉ lưu filesystem snapshot — không lưu runtime state. Đó là lý do docker commit rồi docker pull sang máy mới sẽ không giúp được gì với in-memory state đang chạy.

Các cách giải quyết

Cách 1: Blue-green deployment (không dùng CRIU)

Nếu ứng dụng stateless hoặc có thể sync state từ database, blue-green deployment là lựa chọn đơn giản hơn nhiều:

  • Start container mới ở server đích
  • Switch traffic qua Nginx hoặc load balancer
  • Stop container cũ khi đã confirm xong

Nhưng nếu container đang giữ in-memory state quan trọng (ML model đã load xong, batch job đang chạy, websocket connections đang duy trì…) thì cách này không phù hợp.

Cách 2: CRIU — Checkpoint/Restore In Userspace

CRIU là công cụ Linux cho phép dump toàn bộ trạng thái process ra file, sau đó restore ở bất kỳ đâu. Docker tích hợp CRIU qua experimental feature với hai lệnh chính:

  • docker checkpoint create — đóng băng container, dump state ra disk
  • docker start --checkpoint — restore từ checkpoint đã tạo

Thực hành: Di chuyển container với Docker Checkpoint + CRIU

Bước 1: Cài đặt CRIU trên cả hai server

# Ubuntu/Debian
sudo apt-get install -y criu

# Kiểm tra phiên bản (cần 3.14+)
criu --version

# Kiểm tra kernel có hỗ trợ đủ features không
criu check --ms

Bước 2: Bật experimental features cho Docker daemon

Mặc định Docker tắt checkpoint feature. Cần bật lên trong daemon config:

sudo nano /etc/docker/daemon.json
{
  "experimental": true
}
sudo systemctl restart docker

# Verify experimental đã bật
docker info | grep -i experimental
# Experimental: true

Bước 3: Chạy container với runc runtime

CRIU chỉ hoạt động với runc runtime — không tương thích với containerd snapshotter mặc định trên Docker 24+. Mình đã mất khá nhiều thời gian debug cái lỗi này:

# Bắt buộc dùng --runtime=runc
docker run -d --name myapp \
  --runtime=runc \
  --security-opt seccomp:unconfined \
  nginx:alpine

# Xác nhận container đang chạy
docker ps

Flag --security-opt seccomp:unconfined cần thiết để CRIU có đủ quyền truy cập system calls khi dump trạng thái.

Bước 4: Tạo checkpoint

# Tạo checkpoint (container pause trong lúc dump)
docker checkpoint create myapp checkpoint1

# Hoặc giữ container tiếp tục chạy sau khi checkpoint
docker checkpoint create --leave-running myapp checkpoint1

# Xem danh sách checkpoints đã tạo
docker checkpoint ls myapp
# CHECKPOINT NAME
# checkpoint1

Checkpoint data mặc định được lưu tại:

/var/lib/docker/containers/<container-id>/checkpoints/checkpoint1/

Bước 5: Chuyển checkpoint sang server mới

# Lấy container ID
CONTAINER_ID=$(docker inspect --format='{{.Id}}' myapp)

# Nén checkpoint directory
tar -czf checkpoint1.tar.gz \
  /var/lib/docker/containers/${CONTAINER_ID}/checkpoints/checkpoint1/

# Copy sang server mới
scp checkpoint1.tar.gz user@new-server:/tmp/

# Export container filesystem (cần để restore đúng environment)
docker export myapp | gzip > myapp-fs.tar.gz
scp myapp-fs.tar.gz user@new-server:/tmp/

Bước 6: Restore ở server mới

# --- Thực hiện trên server mới ---

# 1. Import container filesystem
docker import /tmp/myapp-fs.tar.gz myapp-image:restored

# 2. Tạo container (chưa start) với cùng tên
docker create --name myapp \
  --runtime=runc \
  --security-opt seccomp:unconfined \
  myapp-image:restored

# 3. Lấy container ID mới
NEW_CONTAINER_ID=$(docker inspect --format='{{.Id}}' myapp)

# 4. Copy checkpoint vào đúng vị trí
mkdir -p /var/lib/docker/containers/${NEW_CONTAINER_ID}/checkpoints/
tar -xzf /tmp/checkpoint1.tar.gz \
  -C /var/lib/docker/containers/${NEW_CONTAINER_ID}/checkpoints/

# 5. Restore từ checkpoint
docker start --checkpoint checkpoint1 myapp

Kiểm tra sau restore

# Xem logs để confirm container tiếp tục từ điểm dừng
docker logs myapp

# Xem đầy đủ trạng thái container
docker inspect myapp

Mình hay paste JSON output của docker inspect vào toolcraft.app/vi/tools/developer/json-formatter để đọc cho dễ hơn — nhanh hơn cài extension nhiều, đặc biệt khi đang SSH vào server mà không có browser extension.

Cách tốt nhất: Tips và những điều cần biết trước khi dùng CRIU

Nên dùng CRIU khi:

  • Container đang giữ in-memory state quan trọng không thể recover từ disk
  • Đang chạy long-running batch job và không muốn restart từ đầu
  • Cần migrate khi nâng cấp phần cứng cho CPU/RAM intensive workloads
  • Debug production issues: checkpoint để snapshot state tại thời điểm xảy ra lỗi, analyze offline

Không nên dùng CRIU khi:

  • Container có active TCP connections — restore thường fail hoặc connections bị drop
  • Dùng GPU workloads — CRIU không hỗ trợ CUDA/GPU state
  • Container dùng user namespaces phức tạp có thể conflict khi restore
  • Production với strict SLA — CRIU vẫn còn experimental trong Docker, không nên làm lần đầu trên live system

Script checkpoint định kỳ

Một pattern mình thấy hiệu quả là checkpoint định kỳ thay vì chờ đến lúc cần migrate mới làm:

#!/bin/bash
# Chạy cron mỗi giờ: 0 * * * * /opt/scripts/checkpoint.sh

CONTAINER_NAME="myapp"
CHECKPOINT_NAME="auto-$(date +%Y%m%d-%H%M)"

# Xóa checkpoint cũ, giữ 3 checkpoint gần nhất
OLD=$(docker checkpoint ls $CONTAINER_NAME | tail -n +2 | head -n -3 | awk '{print $1}')
for cp in $OLD; do
  docker checkpoint rm $CONTAINER_NAME $cp 2>/dev/null
done

# Tạo checkpoint mới, không pause container
docker checkpoint create --leave-running $CONTAINER_NAME $CHECKPOINT_NAME
echo "[$(date)] Checkpoint created: $CHECKPOINT_NAME"

Những bài học từ thực tế

  • Kernel version phải tương đồng giữa server nguồn và đích — CRIU checkpoint gắn với kernel ABI, kernel quá khác nhau sẽ fail restore
  • Test ở dev environment trước — đừng thử lần đầu trên production khi đang có vấn đề
  • Thời gian dump phụ thuộc vào memory — container dùng 8GB RAM có thể mất vài phút để checkpoint
  • Docker Swarm và Kubernetes chưa native support CRIU migration, cần tool bên thứ ba hoặc dùng CRAC (Coordinated Restore at Checkpoint) cho JVM workloads

CRIU là công cụ mạnh nhưng đòi hỏi hiểu rõ giới hạn của nó. Với stateless service, blue-green vẫn là lựa chọn an toàn hơn nhiều. Nhưng với những container giữ state phức tạp mà không thể chỉ đơn giản là restart, CRIU là cái duy nhất làm được.

Share: