Chắc anh em làm docker đang hoặc đã từng rất thích hay đó là yêu cầu công việc về tối ưu docker image. Ban đầu research cũng thấy giảm từ 1GB xuống 200MB, từ 200MB xuống 50MB, rồi tiến thêm một bước nữa là distroless để giảm tối đa thành phần trong runtime image. Nhìn thì đúng là thích thật: pull nhanh hơn, registry gọn hơn, security scan cũng ít cảnh báo hơn, cảm giác hệ thống được tối ưu bài bản hơn.

Nhưng tới lúc vào Production, nhất là lúc có sự cố, trước đây mình mới dính một quả và thấy là docker image nhỏ chưa chắc tốt. Có những image nhìn rất ổn ở pipeline, nhưng khi incident xảy ra thì lại làm quá trình chẩn đoán và xử lý chậm đi rõ rệt.
Chắc chắn docker image tối ưu luôn là mục tiêu, mình không nói image nhỏ là sai. Cái mình thấy sai là nhiều team đang tối ưu sai mục tiêu. Họ tối ưu cho số MB trước, trong khi thứ Production thực sự cần lại là khả năng debug, vá lỗi và quan sát hệ thống khi có vấn đề.
Vì sao ai cũng thích image nhỏ?
Lý do thì rất dễ hiểu và đây là điều mình thấy. Một image nhỏ hơn thường kéo theo khá nhiều lợi ích nhìn thấy được ngay:
- Pull nhanh hơn
- Build cache gọn hơn
- Đỡ tốn dung lượng registry
- Ít package hơn nên scanner cũng ít cảnh báo hơn
- Rollout ở môi trường lớn nhìn có vẻ nhẹ nhàng hơn
Vấn đề là từ chỗ nên tối ưu, nhiều team đi luôn sang chỗ phải ép nhỏ bằng mọi giá. Và từ thời điểm đó, bài toán bắt đầu lệch.
Cái đáng nói không nằm ở việc dùng image nhỏ, mà nằm ở chỗ nhiều người chỉ nhìn image như một artifact để build và deploy, trong khi ngoài production, nó còn ảnh hưởng trực tiếp tới khả năng vận hành.
Nhỏ để làm gì, và nhỏ tới mức nào là quá mức?
Theo mình, câu hỏi quan trọng không phải là image này còn cắt được bao nhiêu MB, mà là:
- Nếu production lỗi thì sẽ kiểm tra kiểu gì?
- Nếu TLS có vấn đề thì xác minh ra sao?
- Nếu DNS chập chờn thì nhìn vào đâu?
- Nếu cần vá gấp thì image hiện tại giúp đội vận hành nhanh hơn hay chậm đi?
Nói ngắn gọn, nhiều nơi tối ưu hình thức trước, còn năng lực vận hành lại bị đẩy xuống sau. Mà production không quan tâm image bạn nhỏ cỡ nào nếu lúc có sự cố, đội ngũ không còn đủ công cụ và ngữ cảnh để xử lý nhanh.
Cái giá của image quá tối giản
Cái giá này thường không lộ ra lúc build, cũng không lộ ra lúc demo. Nó chỉ lộ ra khi hệ thống có sự cố.
Ví dụ quen thuộc nhất là service đang timeout, pod vẫn Running, ai đó muốn vào container kiểm tra nhanh một chút. Đến lúc đó mới phát hiện trong image không có shell, không có curl, không có ps, không có ss, không có công cụ kiểm tra DNS hay TLS. Container vẫn chạy, nhưng khả năng kiểm tra trực tiếp trong runtime gần như bị triệt tiêu.
Đây là kiểu tình huống rất dễ bị đánh giá thấp, vì lúc bình thường ai cũng thấy app vẫn chạy. Nhưng tới khi cần xác minh nhanh một thứ gì đó ngay trong runtime, sự tối giản của image bắt đầu trở thành rào cản vận hành.
Những hệ quả kiểu này thường rơi vào vài nhóm rất quen:
- vào được container nhưng không inspect được gì có ích
- thiếu CA certificate nên app gọi HTTPS thất bại
- thiếu timezone data khiến log lệch giờ, khó đối chiếu timeline incident
- DNS có vấn đề nhưng không có công cụ để xác minh
- TLS handshake lỗi mà trong runtime không còn gì để kiểm tra
Mấy lỗi này nhìn bề ngoài rất giống bug ứng dụng, nhưng nguyên nhân gốc lại nằm ở image bị tối giản quá mức. Bình thường ít ai để ý, nhưng đúng lúc hệ thống gặp chuyện thì từng thứ nhỏ một bắt đầu bộc lộ thành vấn đề thật.
Security scan ít cảnh báo hơn, nhưng incident response lại khó hơn
Ít package hơn thì scanner sẽ ra ít CVE hơn, chuyện đó đúng. Nhưng ít CVE hơn không đồng nghĩa với vận hành tốt hơn. Mình từng thấy những image scan lên rất khả quan, gần như chẳng có gì đáng báo động. Nhìn dashboard thì ai cũng thấy yên tâm. Nhưng tới lúc có incident thì:
- Không inspect nổi runtime
- Không kiểm tra nhanh được network hay TLS
- Không xác minh dependency behavior được
- Muốn debug lại phải dựng một image khác
Tức là nhìn trên báo cáo thì image an toàn hơn, nhưng thời gian xử lý sự cố lại dài hơn. Theo mình, đó là một trade-off rất đáng ngại.
Distroless rất hay, nhưng không phải chỗ nào cũng nên dùng
Mình không chê distroless. Ngược lại, mình thấy nó là một hướng rất hay nếu team vận hành cứng.
Nó hợp với những hệ thống mà:
- Runtime đơn giản
- Dependency rõ ràng
- Pipeline build chặt
- Observability tốt
- Đội ngũ đã quen với cách debug không phụ thuộc vào việc vào trong container
Nếu đã có ephemeral container, debug image, host-level tooling hay cách quan sát từ ngoài đủ tốt thì distroless là một lựa chọn hợp lý.
Nhưng vấn đề là nhiều nơi chưa có những thứ đó mà vẫn cố đẩy mọi thứ sang distroless, chỉ vì thấy cộng đồng nói hay hoặc thấy image nhỏ đi rõ rệt. Kết quả thường là:
- Build thì trông rất tối ưu
- Chạy bình thường thì không có vấn đề gì rõ ràng
- Tới lúc incident thì không còn nhiều điểm bám để chẩn đoán
Nói thẳng hơn một chút, team chưa đủ maturity mà tối giản image quá mức thì thường không phải tối ưu, mà là đổi từ runtime image hơi lớn sang incident response phức tạp hơn.
Image nhỏ chưa chắc giúp production nhanh hơn
Nghe qua thì rất hợp lý: image nhỏ hơn thì pull nhanh hơn, deploy nhanh hơn. Điều đó đúng, nhưng chỉ đúng khi pull image thực sự là chỗ nghẽn.
Có những môi trường image nhỏ thực sự đáng giá:
- Autoscaling mạnh
- node mới join thường xuyên
- Registry hoặc network là bottleneck thật
- Image hiện tại đang quá béo vì chứa nhiều thứ không liên quan runtime
Nhưng trong nhiều môi trường khác, rollout chậm không nằm ở chuyện image nặng hơn 50MB hay 100MB. Nó nằm ở chỗ:
- App startup lâu
- Readiness probe chưa hợp lý
- Migration kéo dài
- Cache warm-up chậm
- Kết nối xuống downstream mất thời gian
Tức là có những team đang tối ưu rất hăng một thứ nhìn thấy được, trong khi bottleneck thật lại nằm chỗ khác. Thành ra image nhỏ hơn thật, nhưng production chưa chắc cải thiện tương ứng.
Dấu hiệu cho thấy team đang tối ưu quá tay
Anh em kiên trì đọc đến đây là mình cũng thấy công sức chia sẻ có ý nghĩa lắm rồi. Theo mình thì dấu hiệu đầu tiên là mọi cuộc trao đổi chỉ xoay quanh chuyện image còn nặng bao nhiêu, cắt thêm package nào, bớt thêm layer nào, nhưng gần như không ai hỏi lúc lỗi thì debug ra sao, lúc TLS fail thì xác minh kiểu gì, lúc DNS có vấn đề thì nhìn vào đâu.
Dấu hiệu thứ hai là mỗi lần incident lại phải dựng thêm một image phụ để kiểm tra. Nếu cứ hễ có sự cố là phải build một image khác có curl, bash, dig hay openssl thì rõ ràng image production hiện tại đang bị tối ưu quá mức.
Dấu hiệu cuối cùng, và theo mình là đáng nghĩ nhất, là security scan đẹp lên nhưng MTTR lại tăng:
- Image ít cảnh báo hơn
- Dashboard dễ nhìn hơn
- Nhưng thời gian xử lý sự cố dài hơn
- Số bước workaround nhiều hơn
- Phụ thuộc vào vài người biết mẹo nhiều hơn
Làm sao để vừa tối ưu vừa vận hành được?
Theo mình, hướng thực tế nhất là giữ runtime image tối giản nhưng không đẩy việc tối ưu đi quá xa.
Multi-stage build gần như là mặc định nên có, vì nó giải quyết rất tốt chuyện không mang theo compiler, source code hay cache linh tinh vào image chạy thật.
Bên cạnh đó, đừng cắt những thứ nền tảng một cách vô thức. CA cert, timezone data hay các runtime dependency cơ bản nhiều khi không đáng bao nhiêu MB, nhưng lại rất có giá trị khi hệ thống gặp vấn đề.
Nếu muốn production image thật gọn, thì nên có thêm một lớp hỗ trợ vận hành thay vì cắt sạch rồi hy vọng mọi thứ sẽ ổn. Chẳng hạn:
- Có debug image riêng
- Có ephemeral container nếu chạy Kubernetes
- Có observability bên ngoài đủ mạnh
- Có quy trình incident không phụ thuộc vào việc may ra trong image còn tool
Cắt khả năng quan sát bên trong thì phải bù bằng khả năng quan sát bên ngoài. Không có bù trừ này thì sớm muộn cũng trả giá.
Kết thúc thôi
Docker image nhỏ là tốt, nhưng chỉ tốt khi bạn biết mình đang tối ưu cái gì và đang đánh đổi cái gì.
Nếu tối ưu tới mức image không còn hỗ trợ debug, không còn giúp xác minh runtime, không còn đường vá nóng hay quan sát sự cố, thì đó không còn là tối ưu nữa. Đó là đem sự gọn gàng của pipeline đổi lấy một quy trình vận hành phức tạp hơn khi có incident.
Nếu ai hỏi mình Docker image có nên càng nhỏ càng tốt không, thì câu trả lời của mình vẫn là:
Không. Nó nên nhỏ vừa đủ, tối giản ở mức hợp lý, và quan trọng nhất là vẫn hỗ trợ được vận hành ngoài production.









