Dùng Dagger dứt điểm ‘YAML Hell’: Xây dựng CI/CD Pipeline bằng Python/Go

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Vấn đề: Nỗi ám ảnh “Works on my machine, but fails in CI”

Hầu như lập trình viên nào cũng từng nếm trải cảm giác này. Bạn push code lên GitHub, đợi 5 phút để CI chạy, rồi nhận gáo nước lạnh vì lỗi lặt vặt. Bạn sửa một dòng YAML, push lại, và cầu nguyện. Vòng lặp này có khi lặp lại đến 20 lần chỉ để cấu hình xong một script đơn giản.

Vấn đề cốt lõi nằm ở công cụ. GitHub Actions, GitLab CI hay Jenkins đều dựa trên YAML — một định dạng dữ liệu, không phải ngôn ngữ lập trình. Khi hệ thống phình to, file YAML trở thành một “mớ bòng bong” cực kỳ khó debug. Nguy hiểm hơn, bạn gần như không thể chạy thử chính xác cái pipeline đó ở máy cá nhân. Mọi thứ phụ thuộc hoàn toàn vào hạ tầng Cloud của nhà cung cấp.

Tại sao YAML CI/CD lại khiến chúng ta mệt mỏi?

  • Thiếu tính trừu tượng: YAML không có hàm, không có kiểu dữ liệu (types). Việc xử lý logic rẽ nhánh phức tạp trong YAML giống như đang viết code bằng tay trái.
  • Môi trường lệch pha: Runner trên Cloud thường dùng OS và thư viện khác hẳn máy local. Kết quả là code chạy mượt ở máy mình nhưng lại lăn đùng ra chết khi deploy.
  • Phản hồi chậm: Mỗi thay đổi nhỏ đều yêu cầu commit và push. Bạn tốn hàng giờ chỉ để chờ đợi runner khởi động.

Dagger: Đưa CI/CD về đúng bản chất là Mã Nguồn

Dagger thay đổi hoàn toàn cuộc chơi. Thay vì viết YAML, bạn viết code bằng chính ngôn ngữ mình thông thạo như Python, Go hay TypeScript. Dagger engine sử dụng Docker (thông qua BuildKit) để thực thi các bước build. Điều này đảm bảo dù bạn chạy ở máy local hay trên Jenkins, kết quả luôn đồng nhất 100%.

Trong một dự án microservices với hơn 30 container tôi từng quản lý, việc chuyển sang Dagger mang lại hiệu quả tức thì. Thời gian build trung bình giảm từ 15 phút xuống còn chưa đầy 5 phút. Cơ chế caching thông minh của BuildKit giúp chúng tôi tiết kiệm tới 40% tài nguyên hệ thống.

Cài đặt Dagger trên máy cá nhân

Trước tiên, hãy đảm bảo máy bạn đã cài Docker. Sau đó, cài đặt Dagger CLI để điều phối việc thực thi code.

# Cài đặt cho Linux/macOS
curl -L https://dl.dagger.io/dagger/install.sh | sh
sudo mv bin/dagger /usr/local/bin/dagger

# Kiểm tra cài đặt thành công
dagger version

Khởi tạo Dagger ngay trong thư mục dự án bằng lệnh:

dagger init

Cấu hình Pipeline bằng Python SDK

Hãy xem cách Dagger thay thế file .github/workflows/ci.yml truyền thống. Thay vì khai báo YAML, chúng ta tạo file ci/main.py để quản lý các bước: Cài đặt -> Unit Test -> Build Image.

import sys
import anyio
import dagger

async def main():
    # Khởi tạo Dagger client
    async with dagger.connection(dagger.Config(log_output=sys.stderr)) as client:
        
        # Lấy thư mục gốc
        src = client.host().directory(".")

        # Định nghĩa môi trường Python 3.11
        python_base = (
            client.container()
            .from_("python:3.11-slim")
            .with_directory("/app", src)
            .with_workdir("/app")
        )

        # Bước 1: Cài đặt dependencies
        runner = (
            python_base
            .with_exec(["pip", "install", "-r", "requirements.txt"])
        )

        # Bước 2: Chạy Unit Test
        print("Đang chạy tests...")
        test_result = await runner.with_exec(["pytest", "tests/"]).stdout()
        print(test_result)

        # Bước 3: Build Image cuối cùng
        image = runner.with_entrypoint(["python", "main.py"])

if __name__ == "__main__":
    anyio.run(main)

Sức mạnh nằm ở cơ chế Caching

Dagger xử lý từng lệnh with_exec trong các container tạm thời. Nếu file requirements.txt không thay đổi, Dagger sẽ tái sử dụng cache ngay lập tức. Những lần chạy sau, bước cài đặt thư viện nặng nề sẽ chỉ tốn vài giây thay vì vài phút như trước.

Debug cục bộ: Không còn phải “đoán mò”

Đây là tính năng đáng giá nhất. Để chạy thử pipeline, bạn chỉ cần gõ lệnh terminal ngay tại máy mình:

python ci/main.py

Toàn bộ log hiển thị trực tiếp. Nếu bước test thất bại, bạn sửa code và chạy lại ngay, không cần git push. Dagger còn hỗ trợ chế độ tương tác (interactive mode). Khi một bước bị lỗi, bạn có thể nhảy thẳng vào bên trong container đó để kiểm tra file hệ thống, giống như đang dùng docker exec.

# Truy cập trực tiếp vào môi trường lỗi để kiểm tra
dagger call test --interactive

Triển khai lên Cloud (GitHub Actions/GitLab CI)

Khi pipeline đã chạy ổn định ở local, việc đưa lên Cloud trở nên cực kỳ nhẹ nhàng. Lúc này, GitHub Actions chỉ đóng vai trò là một máy chạy (runner) đơn thuần. Logic nghiệp vụ đã nằm gọn trong code Python.

name: CI
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run Dagger Pipeline
        uses: dagger/dagger-for-github@v5
        with:
          verb: run
          args: python ci/main.py

Cách tiếp cận này giúp bạn thoát khỏi sự ràng buộc của nhà cung cấp. Nếu mai này team muốn chuyển sang GitLab CI, bạn chỉ cần copy file Python sang là xong. Không cần học lại cú pháp YAML, không cần sửa đổi logic.

Đúc kết kinh nghiệm thực tế

Sau hơn một năm đưa Dagger vào production, tôi rút ra ba quy tắc vàng:

  1. Chia nhỏ để cache: Đừng gộp quá nhiều lệnh vào một with_exec. Càng chia nhỏ, khả năng tái sử dụng cache của Dagger càng hiệu quả.
  2. Quản lý Secret an toàn: Tuyệt đối không hardcode mật khẩu. Hãy tận dụng hàm client.set_secret() để bảo mật thông tin nhạy cảm.
  3. Local First: Luôn đảm bảo pipeline pass ở máy cá nhân trước khi push. Thói quen này giúp lịch sử commit sạch đẹp và tiết kiệm thời gian cho cả team.

Dagger không đơn thuần là một công cụ mới. Nó đại diện cho tư duy: CI/CD is software. Khi coi pipeline là phần mềm, bạn có quyền dùng unit test, linter và trình debug để kiểm soát nó. Hãy giải phóng bản thân khỏi những file YAML khô khan ngay hôm nay.

Share: