0

Tối ưu CI/CD cho Hệ thống Microservices - Bài 4 - Tối ưu Docker Image: Build siêu tốc, chạy siêu nhẹ

I. Nỗi ám ảnh của những "Image béo phì"

Trong kiến trúc microservices, việc containerize (đóng gói) ứng dụng là tiêu chuẩn. Tuy nhiên, một sai lầm phổ biến là sử dụng các base image đầy đủ (như node:18 hay golang:1.21) cho môi trường production.

Điều này sinh ra các image có dung lượng lên tới 1GB chỉ cho một API service đơn giản. Hậu quả là:

  • CD chậm chạp: Quá trình push image lên Registry và pull về server mất quá nhiều băng thông và thời gian, làm chậm tiến độ deploy.

  • Scale kém linh hoạt: Khi hệ thống chịu tải cao và cần scale thêm container mới (Auto Scaling), việc kéo một image khổng lồ sẽ làm mất "thời điểm vàng" để hứng traffic.

  • Lỗ hổng bảo mật tồi tệ (Attack Surface): Một image chứa đầy đủ các công cụ build như compiler, package manager (npm, git, curl) hay bash shell sẽ là "mỏ vàng" cho hacker nếu service bị khai thác.

II. Multi-stage Build - Kỹ thuật "Vắt chanh bỏ vỏ"

Đây là "vũ khí tối thượng" để giảm dung lượng image từ hàng GB xuống chỉ còn vài chục MB. Tư duy của Multi-stage Build là chia Dockerfile thành nhiều giai đoạn (stages):

  1. Stage 1 - Builder (Người thợ xây): Sử dụng các base image "hạng nặng" (có sẵn Node.js SDK, Go compiler, Git...). Ở stage này, chúng ta copy mã nguồn, chạy npm install hoặc go build để sinh ra file thực thi (binary) hoặc thư mục build cuối cùng.

  2. Stage 2 - Runner (Môi trường chạy): Sử dụng một base image cực kỳ tinh gọn (như alpine hoặc distroless). Thay vì copy toàn bộ mã nguồn, ta chỉ lấy đúng file thực thi (binary của Go) hoặc thư mục chứa code đã compile (kèm node_modules production) từ Stage 1 sang.

Mọi thứ thừa thãi ở Stage 1 (mã nguồn gốc, các thư viện devDependencies, compiler) sẽ bị vứt bỏ hoàn toàn. Một service Go sau khi áp dụng Multi-stage có thể giảm từ 800MB xuống chỉ còn 15MB.

III. Distroless Image: Đỉnh cao của sự tối giản và bảo mật

Nếu bạn muốn đưa tính bảo mật và tối ưu lên mức cao nhất, hãy từ bỏ các hệ điều hành như Ubuntu hay Alpine trong image cuối cùng. Google đã tạo ra khái niệm Distroless Images. Đây là những image chỉ chứa duy nhất ứng dụng của bạn và các thư viện runtime (như Node.js runtime). Chúng hoàn toàn không có shell (/bin/sh), không có package manager, không có cả ls hay grep. Kẻ tấn công dù có tìm ra lỗ hổng Remote Code Execution (RCE) cũng không thể mở shell để phá hoại hệ thống. Đối với các service viết bằng Go đã compile thành file binary độc lập, bạn thậm chí có thể dùng image scratch (image trống hoàn toàn, dung lượng 0MB).

IV. Đừng quên .dockerignore

Tương tự như .gitignore, file .dockerignore ngăn chặn các file rác (thư mục .git, node_modules ở máy local, file biến môi trường .env, log) bị đưa vào trong Docker build context. Nếu quên file này, Docker daemon sẽ phải tải hàng trăm MB dữ liệu rác trước khi bắt đầu build, vừa làm chậm quá trình vừa dễ phá hỏng cấu trúc Layer Caching mà ta đã bàn ở Bài 3.

V. Tổng kết bài học

Sự kết hợp giữa Layer Caching (Bài 3) và Multi-stage Build / Distroless (Bài 4) tạo ra một quy trình CI chuẩn mực: Build trong tíc tắc, đóng gói cực nhẹ và an toàn tuyệt đối khi đưa lên môi trường Production. Khi image đã hoàn hảo, chúng ta đã sẵn sàng để tự động hóa khâu đưa nó đến tay người dùng.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí