Hướng dẫn sử dụng Chisel trên Ubuntu: Tạo Container Image siêu tối giản để tăng cường bảo mật

Ubuntu tutorial - IT technology blog
Ubuntu tutorial - IT technology blog

Container image “phình to” — vấn đề quen thuộc nhưng ít ai để ý

Có một pattern mình thấy rất phổ biến: dev viết xong app, build Docker image từ ubuntu:22.04, và image đó nặng 400–500MB chỉ để chạy một binary Python 50KB. Khi mình còn mới tiếp xúc với container, điều đó không thấy là vấn đề — “chạy được là được” mà. Nhưng sau khi CVE scanner báo đỏ liên tục vì đống package không dùng đến nằm trong image, mình mới hiểu tại sao “slim” hay “distroless” lại được nhắc nhiều đến vậy.

Vấn đề cốt lõi là: khi bạn cài một package như libssl3, bạn không chỉ nhận được thư viện SSL — bạn nhận cả man pages, docs, locale files, headers. Những thứ đó chiếm dung lượng và quan trọng hơn, chúng là attack surface tiềm năng khi có lỗ hổng bảo mật xuất hiện.

Chisel là gì và tại sao nó khác với Alpine hay Distroless?

Chisel là công cụ do Canonical phát triển, cho phép bạn “cắt lát” (slice) package Ubuntu — chỉ lấy đúng những file cần thiết cho ứng dụng, bỏ qua toàn bộ phần còn lại. Khái niệm gốc gọi là package slicing.

Khác với Alpine (dùng musl libc, đôi khi gây incompatibility với app compiled cho glibc) hay Distroless (cố định, khó customize), Chisel cho bạn:

  • Dùng đúng Ubuntu packages — không lo incompatibility với glibc
  • Chọn chính xác slice nào cần: chỉ runtime files, không docs, không headers
  • Kết hợp với Docker multi-stage build để ra image siêu nhỏ

Mỗi package trong Chisel được định nghĩa bằng “slices” — các nhóm file con trong package. Ví dụ package libssl3 có thể có slice libssl3_libs (chỉ chứa .so files) và libssl3_dev (chứa headers cho development). Cú pháp slice là package_slicename.

Cài đặt Chisel trên Ubuntu

Chisel là binary Go, cài nhanh không cần dependency phức tạp:

# Download binary mới nhất
CHISEL_VERSION=$(curl -s https://api.github.com/repos/canonical/chisel/releases/latest | grep tag_name | cut -d'"' -f4)
curl -sSL "https://github.com/canonical/chisel/releases/download/${CHISEL_VERSION}/chisel_${CHISEL_VERSION}_linux_amd64.tar.gz" | tar xz
sudo mv chisel /usr/local/bin/

# Kiểm tra
chisel --version

Hoặc cài qua snap nếu đang dùng Ubuntu desktop/server:

sudo snap install chisel --channel=latest/stable

Để xem các slice có sẵn của một package:

# Xem slice của libssl3
chisel info --release ubuntu-22.04 libssl3

# Xem slice của python3.10
chisel info --release ubuntu-22.04 python3.10

Thực hành: Tạo minimal image cho ứng dụng Python

Mình lấy ví dụ thực tế — một Flask API đơn giản. So sánh trực tiếp 2 cách build.

Cách thông thường (ubuntu:22.04 base)

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3 python3-pip && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip3 install -r requirements.txt
COPY app.py .
CMD ["python3", "app.py"]

Kết quả: image ~250MB, kèm theo hàng trăm package không liên quan đến Flask.

Cách dùng Chisel với multi-stage build

Chisel hoạt động tốt nhất khi kết hợp với Docker multi-stage — stage đầu để slice, stage cuối chỉ copy những gì cần:

# Stage 1: Tạo rootfs tối giản bằng Chisel
FROM ubuntu:22.04 AS chisel-stage
RUN apt-get update && apt-get install -y curl
RUN CHISEL_VERSION=$(curl -s https://api.github.com/repos/canonical/chisel/releases/latest | grep tag_name | cut -d'"' -f4) && \
    curl -sSL "https://github.com/canonical/chisel/releases/download/${CHISEL_VERSION}/chisel_${CHISEL_VERSION}_linux_amd64.tar.gz" | tar xz -C /usr/local/bin/

# Cắt chính xác những slice cần thiết cho Python runtime
RUN chisel cut --release ubuntu-22.04 --root /rootfs \
    base-files_base \
    base-passwd_data \
    libc6_libs \
    libssl3_libs \
    python3.10_minimal \
    python3-minimal_minimal

# Stage 2: Install Python packages vào thư mục riêng
FROM ubuntu:22.04 AS pip-stage
RUN apt-get update && apt-get install -y python3 python3-pip
WORKDIR /app
COPY requirements.txt .
RUN pip3 install --target=/app/packages -r requirements.txt

# Stage 3: Final image - từ scratch, chỉ có đúng những gì cần
FROM scratch
COPY --from=chisel-stage /rootfs /
COPY --from=pip-stage /app/packages /app/packages
COPY app.py /app/
ENV PYTHONPATH=/app/packages
CMD ["/usr/bin/python3", "/app/app.py"]

Image size sau khi dùng Chisel: khoảng 45–60MB, giảm 75–80% so với base ubuntu image.

Ví dụ thực tế: Minimal image cho Go binary

Với ứng dụng Go compiled tĩnh, image còn nhỏ hơn nữa vì không cần Python runtime:

# Stage 1: Build Go binary
FROM golang:1.22 AS build-stage
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .

# Stage 2: Chisel - chỉ cần ca-certificates và timezone data
FROM ubuntu:22.04 AS chisel-stage
RUN apt-get update && apt-get install -y curl
RUN CHISEL_VERSION=$(curl -s https://api.github.com/repos/canonical/chisel/releases/latest | grep tag_name | cut -d'"' -f4) && \
    curl -sSL "https://github.com/canonical/chisel/releases/download/${CHISEL_VERSION}/chisel_${CHISEL_VERSION}_linux_amd64.tar.gz" | tar xz -C /usr/local/bin/

RUN chisel cut --release ubuntu-22.04 --root /rootfs \
    base-files_base \
    ca-certificates_data \
    tzdata_zoneinfo

# Stage 3: Final image
FROM scratch
COPY --from=chisel-stage /rootfs /
COPY --from=build-stage /app/server /server
USER 65534:65534
ENTRYPOINT ["/server"]

Go binary tĩnh + Chisel rootfs: image chỉ còn 10–15MB. Và vì base là FROM scratch, không có shell, không có package manager — attack surface gần như bằng không.

Số liệu thực tế: CVE trước và sau khi dùng Chisel

Khi mới chuyển từ CentOS sang Ubuntu, mình mất khoảng 1 tuần để quen với hệ thống package management. Lúc đó mình hay dùng ubuntu:22.04 làm base image cho mọi thứ — tiện, quen tay. Nhưng sau khi scan Trivy báo 47 CVE trong một image “tưởng sạch”, mình bắt đầu nghiêm túc nhìn lại.

# Scan CVE với Trivy - so sánh trước/sau
trivy image my-app:ubuntu-full
# Total: 47 (LOW: 21, MEDIUM: 18, HIGH: 7, CRITICAL: 1)

trivy image my-app:chisel-minimal
# Total: 2 (LOW: 2, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

Số CVE giảm từ 47 xuống còn 2 — chủ yếu vì bạn đã loại bỏ hoàn toàn những package không liên quan đến ứng dụng. Không có package nào thừa thì cũng không có lỗ hổng nào thừa.

Một vài lưu ý thực tế khi dùng Chisel

  • Không phải mọi package đều có slice: Repository chisel-releases đang phát triển. Nếu package bạn cần chưa có slice definition, bạn phải tự viết hoặc dùng giải pháp khác.
  • Debug khó hơn: Image không có shell, không có ls, cat. Khi cần debug, dùng docker export để inspect filesystem, hoặc tạm thời thêm busybox_musl slice vào build debug.
  • FROM scratch cần /etc/passwd: Nếu app cần resolve UID/GID, thêm slice base-passwd_data vào danh sách Chisel.
  • Dependencies tự động resolve: Nếu slice A phụ thuộc slice B, Chisel tự kéo B vào — không cần liệt kê thủ công.

Kết luận

Chisel không phải giải pháp cho tất cả mọi trường hợp — nếu app cần nhiều package phức tạp chưa có slice definition, bạn sẽ phải bỏ thêm công. Nhưng với những service production cần bảo mật cao, đây là tool đáng đầu tư.

Pattern mình hay dùng bây giờ: ubuntu full image để build → Chisel slice để tạo runtime rootfs → FROM scratch để copy vào. Ba bước đó giữ nguyên toàn bộ ecosystem Ubuntu (không lo glibc incompatibility như Alpine) trong khi vẫn đạt được image size và security profile gần với Distroless.

Nếu bạn đang dùng Ubuntu làm base image cho container và chưa xem xét Chisel, 2 tiếng thử nghiệm thực tế và chạy Trivy scan sẽ cho bạn thấy sự khác biệt rõ ràng hơn bất kỳ bài viết nào.

Share: