Ngừng ‘vật lộn’ với YAML: Xây dựng Pipeline linh hoạt với Dagger

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

Nỗi ám ảnh mang tên ‘Địa ngục YAML’

Tôi đã từng dành cả cuối tuần để chuyển đổi một microservice quy mô vừa từ GitHub Actions sang GitLab CI. Đáng lẽ nó phải đơn giản. Nhưng thay vào đó, tôi đã mất 72 giờ chỉ để dịch các cú pháp riêng biệt, sửa các lỗi biến môi trường và chờ đợi các runner thất bại. Đó là vòng lặp ‘commit, push, wait, fail’ kinh điển mà mọi kỹ sư DevOps đều ghét.

Các công cụ CI/CD truyền thống coi pipeline của bạn là cấu hình tĩnh. Bạn viết những chuỗi dài trong một file YAML mà chỉ cloud runner mới hiểu được. Việc kiểm thử logic đó cục bộ gần như là không thể nếu không có các công cụ như act, vốn thường không mô phỏng chính xác môi trường production. Dagger khắc phục điều này bằng cách chuyển logic từ cấu hình sang mã nguồn (code).

Dagger là một engine có khả năng lập trình, cho phép bạn viết pipeline bằng các ngôn ngữ bạn đã biết như Python, Go hoặc TypeScript. Nó coi hạ tầng CI của bạn như phần mềm thực thụ. Điều này có nghĩa là bạn có thể chạy chính xác cùng một pipeline trên MacBook Pro giống như cách bạn làm trên một môi trường đám mây hiệu năng cao.

Bắt đầu nhanh: Pipeline Dagger đầu tiên của bạn trong 5 phút

Hãy cùng xây dựng một pipeline hoàn chỉnh. Chúng ta sẽ sử dụng Python vì tính dễ đọc và phổ biến trong ngành. Trước khi bắt đầu, hãy đảm bảo bạn đã cài đặt Docker, vì Dagger sử dụng nó để thực thi các container.

1. Cài đặt Dagger CLI

# Cho người dùng macOS sử dụng Homebrew
brew install dagger/tap/dagger

# Hoặc sử dụng script cài đặt
curl -L https://dl.dagger.io/dagger/install.sh | sh
sudo mv ./bin/dagger /usr/local/bin/dagger

# Xác nhận cài đặt thành công
dagger version

2. Thiết lập môi trường

Tạo một thư mục mới và khởi tạo một môi trường ảo (virtual environment) để giữ cho các dependency của bạn gọn gàng.

mkdir dagger-demo && cd dagger-demo
python3 -m venv .venv
source .venv/bin/activate
pip install dagger-io

3. Viết logic cho Pipeline

Tạo một file có tên main.py. Script này định nghĩa một container để tải Node.js, cài đặt các dependency và thực thi test.

import sys
import anyio
import dagger

async def main():
    async with dagger.connection(dagger.Config(log_output=sys.stderr)) as client:
        # Tải mã nguồn cục bộ
        src = client.host().directory(".")

        # Xây dựng môi trường thực thi
        container = (
            client.container()
            .from_("node:18-alpine")
            .with_directory("/src", src)
            .with_workdir("/src")
            .with_exec(["npm", "install"])
            .with_exec(["npm", "test"])
        )

        # Chạy pipeline và lấy kết quả đầu ra
        result = await container.stdout()
        print(result)

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

4. Thực thi cục bộ

dagger run python main.py

Dagger sẽ tải image và chạy các bước của bạn. Nếu các bài test vượt qua ở đây, chúng cũng sẽ vượt qua trong GitHub Actions. Không còn phải đoán mò nữa.

Nguyên lý hoạt động của Dagger

Dagger không chỉ chạy các script theo thứ tự. Nó xây dựng một Đồ thị có hướng không chu trình (Directed Acyclic Graph – DAG) cho các hoạt động của bạn. Khi bạn định nghĩa một bước với with_exec, Dagger sẽ thêm nó vào đồ thị thay vì chạy ngay lập tức. Việc thực thi chỉ diễn ra khi bạn yêu cầu kết quả, chẳng hạn như stdout() hoặc publish().

Sức mạnh của Dagger Engine

Một engine bền bỉ—thường là một Docker container—sẽ xử lý các tác vụ nặng. Nó quản lý việc thực thi và bộ nhớ đệm (caching) thông minh. Không giống như các runner tiêu chuẩn thường bắt đầu từ con số không, Dagger cực kỳ hiệu quả. Trong một dự án React gần đây, khả năng layer caching của Dagger đã giảm thời gian chạy npm install của chúng tôi từ 90 giây xuống còn chỉ 3 giây trong các lần chạy sau.

Container dạng module

Hãy coi pipeline của bạn như một tập hợp các hàm. Bạn có thể truyền container giữa các hàm để tạo ra các quy trình làm việc (workflow) dạng module. Một hàm có thể xử lý việc kiểm tra lỗi bảo mật (security linting), trong khi hàm khác xử lý việc build production.

def run_lint(container: dagger.Container):
    return container.with_exec(["npm", "run", "lint"])

def build_binary(container: dagger.Container):
    return container.with_exec(["npm", "run", "build"])

Một Pipeline, mọi nhà cung cấp CI

Dagger biến cấu hình GitHub hoặc GitLab của bạn thành một lớp đệm (shim) đơn giản. Các file YAML của bạn có thể giảm kích thước tới 80% hoặc hơn vì chúng chỉ cần kích hoạt script Dagger.

Ví dụ: GitHub Actions

File .github/workflows/ci.yml của bạn trở thành một trigger mẫu:

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

Ví dụ: GitLab CI

File .gitlab-ci.yml trông gần như tương tự về mặt logic:

run-dagger:
  image: docker:latest
  services: [docker:dind]
  script:
    - curl -L https://dl.dagger.io/dagger/install.sh | sh
    - ./bin/dagger run python main.py

Nếu bạn quyết định chuyển sang CircleCI hoặc Jenkins vào tháng tới, bạn sẽ không phải viết lại các bài test của mình. Bạn chỉ cần gọi cùng một script Python đó.

Chiến lược để có Pipeline tốt hơn

1. Xử lý Secret an toàn

Tuyệt đối tránh việc ghi cứng (hardcoding) thông tin xác thực. Dagger tích hợp sẵn tính năng quản lý secret giúp ngăn chặn dữ liệu nhạy cảm bị rò rỉ vào log build hoặc các layer của image.

gh_token = client.set_secret("github-token", os.environ["GITHUB_TOKEN"])
container = container.with_secret_variable("GH_TOKEN", gh_token)

2. Sử dụng Cache Volume bền bỉ

Tốc độ là tất cả trong CI. Sử dụng cache volume của Dagger để duy trì các thư mục như node_modules, .m2, hoặc ~/.cache/pip qua các lần chạy khác nhau. Điều này có thể tiết kiệm hàng phút cho mỗi lần build.

cache = client.cache_volume("node-deps")
container = container.with_mounted_cache("/src/node_modules", cache)

3. Coi mã nguồn CI như mã nguồn Production

Vì bạn đang sử dụng Python, hãy tận dụng tối đa sức mạnh của nó. Tổ chức pipeline của bạn bằng các class, chia nhỏ logic thành các module và thậm chí viết unit test cho chính logic CI của bạn. Tôi đã thấy những file YAML phình to thành những ‘con quái vật’ 2.000 dòng không thể đọc nổi. Dagger cho phép bạn giữ mọi thứ sạch sẽ bằng cách sử dụng các design pattern tiêu chuẩn.

Chuyển sang Dagger đòi hỏi một sự thay đổi về tư duy, đặc biệt nếu bạn đã dành nhiều năm để viết YAML. Tuy nhiên, khả năng debug lỗi CI cục bộ trong vài giây là một thắng lợi lớn về năng suất. Cuối cùng, bạn có thể ngừng việc sử dụng lịch sử git như một nơi để thử nghiệm cú pháp CI.

Share: