Ngừng triển khai Docker Image lỗi: Hướng dẫn về Container Structure Tests

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Cuộc gọi Pager lúc 2 giờ sáng: Khi Dockerfile “nói dối”

Vào lúc 2:15 sáng thứ Ba, điện thoại của tôi bắt đầu kêu inh ỏi. Một microservice quan trọng bị kẹt trong trạng thái CrashLoopBackOff trên production. Các bản log đơn giản đến mức phát điên: /usr/bin/python3: not found. Tôi thực sự bối rối. Tôi vừa mới thay thế một image dựa trên Ubuntu nặng 450MB bằng một phiên bản ‘slim’ chỉ 50MB, và nó đã chạy hoàn hảo trên máy tính cá nhân của tôi. Hoặc ít nhất là tôi đã nghĩ vậy.

Sau hai mươi phút đào bới điên cuồng, tôi đã tìm ra thủ phạm. Image gốc (base image) mới đã chuyển binary của Python sang một đường dẫn khác. Script entrypoint của tôi đang trỏ vào một “bóng ma”. Chúng ta thường tin tưởng Dockerfile một cách mù quáng, nhưng hiếm khi xác thực các artifact thực tế cho đến khi chúng được đẩy lên cluster. Việc kiểm tra thủ công các layer cho hàng chục dịch vụ là điều không thể và rất dễ sai sót.

Để giải quyết vấn đề này, bạn cần đến Container Structure Tests (CST). Hãy coi đây là lớp unit testing còn thiếu cho hạ tầng của bạn. Framework này, được Google mã nguồn mở, cho phép bạn chạy các xác nhận (assertions) đối với chính container đó. Bạn có thể kiểm tra các tệp tin cụ thể, đầu ra của lệnh và metadata mà không cần khởi chạy một môi trường tích hợp (integration environment) nặng nề.

Tại sao quy trình Build hiện tại đang “lừa dối” bạn

Hầu hết các lập trình viên đều cho rằng nếu lệnh docker build trả về mã thoát (exit code) là 0 thì image đó đã ổn. Đó là một giả định nguy hiểm. Một bản build thành công chỉ chứng minh rằng cú pháp của bạn hợp lệ. Nó không đảm bảo những điều sau:

  • File nginx.conf bạn đưa vào có đúng quyền 644 hay không.
  • Binary node của bạn thực sự nằm trong $PATH.
  • Các biến môi trường bắt buộc như API_ENDPOINT đã được thiết lập chưa.
  • Image không chạy dưới quyền root, một lỗ hổng bảo mật nghiêm trọng.

CST cho phép bạn mã hóa các yêu cầu này vào một file YAML đơn giản. Nếu image không đáp ứng chính xác các thông số kỹ thuật của bạn, pipeline CI/CD sẽ dừng lại ngay lập tức. Sẽ không còn những bất ngờ lúc 2 giờ sáng nữa.

Cài đặt Framework

Công cụ này là một file binary duy nhất và nhẹ. Nó sẽ không làm nặng runner CI của bạn hoặc yêu cầu một chuỗi phụ thuộc phức tạp. Trên Linux, bạn có thể cài đặt bản binary 15MB bằng một vài lệnh:

# Tải bản binary
curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64

# Cấp quyền thực thi và chuyển vào path của bạn
chmod +x container-structure-test-linux-amd64
sudo mv container-structure-test-linux-amd64 /usr/local/bin/container-structure-test

# Kiểm tra xem nó có hoạt động không
container-structure-test version

Người dùng Mac chỉ cần chạy brew install container-structure-test. Sau khi cài đặt, bạn có thể chuyển từ việc “đoán mò” sang “xác thực”.

Định nghĩa bộ kiểm thử (Test Suite)

CST sử dụng YAML để định nghĩa các mong đợi (expectations). Tôi thường phân loại các bài kiểm tra của mình thành ba khu vực chính: Lệnh (Commands), Tệp tin (Files) và Metadata. Hãy cùng xem cấu hình cho một Python API tiêu chuẩn.

1. Command Tests: Xác thực các file thực thi (Binary)

Đây là loại kiểm tra tôi hay dùng nhất. Nó đảm bảo môi trường hoạt động bình thường. Một file tồn tại thôi là chưa đủ; nó phải thực thi được và trả về kết quả mong đợi.

schemaVersion: "2.0.0"
commandTests:
  - name: "Kiểm tra phiên bản Python"
    command: "python"
    args: ["--version"]
    expectedOutput: ["Python 3.11.*"]
  - name: "Đảm bảo pip có sẵn"
    command: "pip"
    args: ["--version"]
    exitCode: 0

2. Sự tồn tại của file và Quyền truy cập

Điều này lẽ ra đã phát hiện được sự cố lúc 2 giờ sáng của tôi. Tôi cũng sử dụng nó để thực thi các chính sách bảo mật, như đảm bảo các thư mục nhạy cảm không được phép ghi công khai (world-writable).

fileExistenceTests:
  - name: "Kiểm tra entrypoint của ứng dụng"
    path: "/app/main.py"
    shouldExist: true
    permissions: "-rw-r--r--"
  - name: "Kiểm tra bảo mật: Không có SSH key"
    path: "/root/.ssh/id_rsa"
    shouldExist: false

3. Xác thực Metadata

Phần này kiểm tra các “nhãn” (labels) và “môi trường” (environment) của container. Nó hoàn hảo để đảm bảo WORKDIR nhất quán trên tất cả các microservices.

metadataTest:
  envVars:
    - key: "PYTHONUNBUFFERED"
      value: "1"
  exposedPorts: ["8080"]
  workdir: "/app"
  user: "nonroot"

Thực thi: Chạy các bài kiểm tra

Khi config.yaml đã sẵn sàng, hãy chạy kiểm tra đối với image cục bộ của bạn. Tôi thường kích hoạt bước này ngay sau bước build. Chỉ mất chưa đầy 5 giây để chạy hàng chục bài kiểm tra.

container-structure-test test \
  --image my-python-app:v1.0.2 \
  --config tests.yaml

Kết quả trả về rất súc tích. Nếu một bài kiểm tra thất bại, CST sẽ cho bạn biết chính xác lý do—cho dù đó là lỗi không khớp regex trong đầu ra hay lỗi quyền truy cập. Vòng lặp phản hồi này chính là thứ giúp tiết kiệm hàng giờ debug.

Tích hợp vào Pipeline CI/CD

Tự động hóa là nơi bạn nhận được nhiều giá trị nhất. Trong các dự án của tôi, CST đóng vai trò như một cổng kiểm soát chất lượng (quality gate). Nếu bài kiểm tra metadata thất bại vì ai đó quên đặt USER thành non-root, bản build sẽ bị hủy trước khi nó kịp đẩy lên registry.

Dưới đây là một đoạn mã GitHub Actions cho quy trình này:

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Build Docker Image
        run: docker build -t my-app:${{ github.sha }} .

      - name: Run Container Structure Tests
        uses: sudo-bot/[email protected]
        with:
          image: my-app:${{ github.sha }}
          config: tests.yaml

      - name: Push Image
        if: success() # Chỉ đẩy image nếu các bước trước thành công
        run: docker push my-app:${{ github.sha }}

Các lỗi thường gặp và Mẹo chuyên gia

Mặc dù công cụ này khá đơn giản, hãy lưu ý đến môi trường thực thi. Các bài kiểm tra lệnh (command tests) chạy bên trong container. Nếu bạn sử dụng image distroless, bạn sẽ không có shell. Trong những trường hợp đó, bạn sẽ phải dựa hoàn toàn vào fileExistenceTestsmetadataTest.

Đừng cố nhồi nhét mọi thứ vào một file khổng lồ. Đối với các dự án phức tạp, tôi chia nhỏ các bài kiểm tra thành security.yamlruntime.yaml. Bạn có thể truyền nhiều cờ --config cho công cụ, giúp bộ test của bạn dễ bảo trì hơn khi dự án phát triển.

Lời kết

Container Structure Tests không nhằm mục đích kiểm tra logic nghiệp vụ của ứng dụng. Đó là việc của unit tests. Những bài kiểm tra này xác thực chính **phương tiện vận chuyển** (delivery vehicle). Trong một môi trường tốc độ cao, nơi các đội nhóm triển khai hàng chục lần mỗi ngày, chúng ta không thể chấp nhận các lỗi hạ tầng gây ra bởi những lỗi đánh máy đơn giản.

Hãy dành hai mươi phút để thiết lập các bài kiểm tra này ngay hôm nay. Đó là một khoản đầu tư nhỏ nhưng đảm bảo hệ thống tin cậy hơn và quan trọng hơn là giúp bạn có một giấc ngủ ngon trọn vẹn.

Share: