Khắc Phục Docker Pull Timeout: Ép Hệ Thống Phải "Lì Đòn" Trước Mạng Chậm
Ở bài trước chúng ta đã "đóng gói" thành công ứng dụng vào Docker. Mọi thứ tuyệt vời, cho đến khi bạn mang cái file docker-compose.yml đó quăng lên con Server giá rẻ (VPS) vừa mới thuê để chạy thử.
Bạn gõ docker-compose up -d. Terminal bắt đầu hiện ra những thanh tiến trình (progress bar) tải các layer (lớp) của Image.
Được 90%... 95%... 99%... và đột nhiên màn hình khựng lại.
Một dòng chữ đỏ chót đập vào mắt:
ERROR: context deadline exceeded hoặc net/http: TLS handshake timeout hoặc Client.Timeout exceeded while awaiting headers.
Docker của bạn vừa bị Timeout (Quá thời gian chờ) khi kéo (pull) Image từ Docker Hub về! Bạn chửi thề, gõ chạy lại, và nó lại chết ở đúng 99%.
Đây là một nỗi đau cực kỳ phổ biến khi làm việc với Docker trên các đường truyền mạng không ổn định (đặc biệt là đứt cáp quang) hoặc khi server của bạn kéo những cái Image nặng tới vài Gigabyte.
Hôm nay, chúng ta sẽ mổ xẻ tận gốc nguyên nhân và đưa ra 3 cấp độ giải pháp từ ngọn đến gốc để ép Docker phải "lì đòn" hơn.
PHẦN 1: TẠI SAO DOCKER LẠI TIMEOUT?
Trước khi copy-paste lệnh để sửa lỗi, một Vibe Coder phải hiểu bản chất vật lý của vấn đề.
Khi bạn gõ lệnh docker pull node:18, Docker không tải 1 file nguyên khối. Docker Image được cấu tạo từ nhiều Layer (Lớp) xếp chồng lên nhau. Mặc định, Docker Engine sẽ cố gắng tải 3 Layer cùng một lúc (Concurrent Downloads) để tiết kiệm thời gian.
Vấn đề nảy sinh ở 2 chỗ:
- Giới hạn thời gian của Client: Docker Compose mặc định chỉ cho phép kết nối mạng nín thở chờ đợi trong 60 giây. Nếu sau 60 giây mà một layer nặng 500MB chưa tải xong (do đứt cáp, mạng chậm), nó sẽ tự động ngắt kết nối và báo lỗi
Timeout. - Nghẽn cổ chai băng thông: Nếu đường truyền của Server chỉ có 10Mbps, việc Docker cố gắng tải 3 layer khổng lồ cùng lúc sẽ làm chia nhỏ băng thông (mỗi layer chỉ được ~3Mbps). Kết quả là CẢ 3 layer đều lết chậm rì rì và cùng nhau chạm mốc 60 giây Timeout, chết chùm cả đám!
Hãy đi vào từng cấp độ giải quyết, từ dễ đến khó.
PHẦN 2: CẤP ĐỘ 1 - NỚI LỎNG THỜI GIAN CHỜ (DÀNH CHO DOCKER COMPOSE)
Đây là cách nhanh nhất và hay dùng nhất. Nếu 60 giây là không đủ, chúng ta sẽ gia hạn cho nó lên thành 5 phút (300 giây) hoặc lâu hơn.
Docker CLI và Docker Compose sử dụng các biến môi trường (Environment Variables) để quản lý thời gian timeout của API. Bạn chỉ cần khai báo 2 biến này trước khi gõ lệnh.
Cách 1: Gõ trực tiếp trên Terminal (Tạm thời)
Thay vì gõ docker-compose up -d, bạn hãy gõ:
COMPOSE_HTTP_TIMEOUT=300 DOCKER_CLIENT_TIMEOUT=300 docker-compose up -d
Lệnh này báo cho Docker biết: "Ê, cứ bình tĩnh mà tải, tao cho mày 300 giây (5 phút) cho mỗi request đấy, đừng có ngắt ngang!"
Cách 2: Cấu hình vĩnh viễn trong file .env (Khuyên dùng)
Nếu lười gõ lệnh dài, hãy mở file .env nằm cùng thư mục với docker-compose.yml và thêm 2 dòng này vào:
COMPOSE_HTTP_TIMEOUT=300
DOCKER_CLIENT_TIMEOUT=300
Từ nay về sau, bạn chỉ cần gõ docker-compose up -d như bình thường, nó sẽ tự động đọc file .env và áp dụng thời gian chờ mới.
PHẦN 3: CẤP ĐỘ 2 - ĐIỀU TIẾT LUỒNG TẢI TRONG DOCKER DAEMON
Như đã phân tích ở Phần 1, việc tải 3 layer cùng lúc trên một mạng yếu là tự sát. Để giải quyết, chúng ta sẽ can thiệp sâu vào "trái tim" của Docker là Docker Daemon để ép nó chỉ tải 1 layer tại một thời điểm. Thu hẹp luồng xả để tăng áp lực nước!
3.1. Trải nghiệm trực quan: Băng thông vs Luồng tải
Để hiểu rõ tại sao việc "Giảm số luồng tải (Concurrent)" lại giúp tránh Timeout trên mạng chậm, mời bạn xem bộ mô phỏng dưới đây:


3.2. Code Thực Chiến: Sửa daemon.json
Để áp dụng chiến thuật trên, bạn cần truy cập vào Server (Linux/Ubuntu) và cấu hình lại Docker Daemon.
Bước 1: Mở file cấu hình của Docker (Nếu chưa có, câu lệnh này sẽ tự tạo mới).
sudo nano /etc/docker/daemon.json
Bước 2: Dán cấu hình sau vào file:
{
"max-concurrent-downloads": 1,
"max-concurrent-uploads": 1
}
(Lưu ý: Nếu file đã có sẵn nội dung JSON, bạn chỉ cần thêm 2 dòng trên vào, nhớ phẩy (,) cho đúng chuẩn JSON).
Bước 3: Khởi động lại dịch vụ Docker để nhận cấu hình mới.
sudo systemctl restart docker
Giờ thì thử gõ docker pull ubuntu hoặc docker-compose up -d. Bạn sẽ thấy tốc độ tải khác biệt một trời một vực, lao vun vút bất chấp cáp quang biển đang bảo trì!
LỜI KẾT & TƯ DUY PHÒNG BỆNH HƠN CHỮA BỆNH
Tăng Timeout, giảm luồng tải, hay dùng Mirror suy cho cùng cũng chỉ là giải pháp "Chữa cháy" ở tầng Hạ tầng (Infrastructure).
Là một Software Engineer, bạn phải tự hỏi lại bản thân: "Tại sao cái Docker Image của mình lại to đến mức nghẽn cả mạng?"
Rất nhiều anh em mới làm quen Docker có thói quen bê nguyên Hệ điều hành (như FROM ubuntu nặng 500MB) hoặc bê nguyên thư mục node_modules (nặng 1GB) đúc thẳng vào Image. Một Image nặng 1.5GB đẩy lên Server tốn tiền băng thông, tốn tiền RAM, tải thì Timeout, mà lúc chạy thì ì ạch.
Chủ đề tiếp theo: Kỹ Thuật Multi-stage Build - Ép Cân Docker Image Từ 1GB Xuống 50MB
Đã đến lúc chúng ta giải quyết tận gốc vấn đề dung lượng. Ở bài viết tới, mình sẽ bật mí một "Tuyệt kỹ luyện đan" trong Dockerfile mang tên Multi-stage Builds (Build nhiều giai đoạn).
Chúng ta sẽ tạo ra một kịch bản: Dùng một cái Container "to béo" chứa đủ mọi đồ nghề (trình biên dịch C++, Python, devDependencies) để build code. Sau khi build ra mã máy xong, chúng ta chỉ nhặt đúng cục Code thành phẩm đó, vứt sang một cái Container "siêu mẫu" (Alpine) chỉ nặng đúng 5MB để mang lên Production chạy!
Kết quả? App của bạn bảo mật hơn gấp 10 lần, kéo Image mất đúng 2 giây và không bao giờ biết Timeout là gì. Anh em chuẩn bị sẵn sàng để ép cân cho App nhé!
All Rights Reserved