Dừng ngay tình trạng tràn lan YAML: Tập trung hóa GitHub Actions với Reusable Workflows và Composite Actions

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

Cái giá của việc “Copy-Paste” đối với tốc độ kỹ thuật

Mở rộng từ một repository lên năm mươi không chỉ là vấn đề thêm code; đó là bài kiểm tra khả năng chịu tải của hạ tầng. Một kẻ tiêu diệt năng suất thầm lặng thường xuất hiện: trùng lặp YAML. Bạn xây dựng một pipeline CI/CD hoàn hảo cho Repo A. Sau đó Repo B cần logic tương tự, vì vậy bạn copy file .github/workflows/main.yml. Đến khi bạn chạm tới Repo J, bạn đang phải trông nom mười file giống hệt nhau.

Hãy tưởng tượng bạn cần cập nhật một trình quét bảo mật hoặc nâng cấp Node.js từ v18 lên v20 trên toàn bộ tổ chức. Bạn sẽ phải mở hai mươi pull request, chờ hai mươi build, và cầu nguyện rằng mình không bỏ sót một dòng nào trong số đó. Kiểu làm việc thủ công nhàm chán này chính là thứ mà DevOps hướng tới việc loại bỏ.

Trong một vai trò trước đây, tôi đã dành 34 giờ trong tuần đầu tiên chỉ để cập nhật thủ công URL của Docker registry trên 42 repository riêng biệt. Đó là một công việc cực kỳ nản lòng mà lẽ ra có thể tránh được nếu có một nguồn chân lý (source of truth) duy nhất. Để chuyển mình từ một “kỹ sư YAML” thành một kiến trúc sư DevOps thực thụ, bạn phải làm chủ nghệ thuật trừu tượng hóa.

GitHub Actions cung cấp hai công cụ chính cho việc này: Composite ActionsReusable Workflows. Chúng trông có vẻ giống nhau, nhưng đóng vai trò khác nhau trong hành trình xây dựng pipeline theo nguyên tắc DRY (Don’t Repeat Yourself).

Bộ công cụ: Những viên gạch và Bản thiết kế

Chọn đúng công cụ ngay từ đầu sẽ giúp tránh được những rắc rối về kiến trúc sau này. Bạn không muốn xây nhà từ từng hạt cát riêng lẻ khi bạn có thể sử dụng các tấm bê tông đúc sẵn.

Composite Actions: Những viên gạch Lego tùy chỉnh của bạn

Một Composite Action đóng gói nhiều bước (step) của workflow thành một action duy nhất có thể gọi được. Hãy coi nó như một hàm private nằm bên trong một job. Sử dụng nó cho các lệnh shell lặp đi lặp lại luôn diễn ra cùng nhau trong cùng một môi trường.

  • Phạm vi: Hoạt động trong một job duy nhất.
  • Cấu trúc: Được định nghĩa trong file action.yml.
  • Yêu cầu chính: Mọi bước run phải khai báo rõ ràng shell (ví dụ: shell: bash).
  • Lý tưởng cho: Cài đặt các công cụ CLI cụ thể, thiết lập thông tin xác thực đám mây hoặc chạy các script dọn dẹp tiêu chuẩn.

Reusable Workflows: Dây chuyền lắp ráp tiêu chuẩn hóa

Một Reusable Workflow là toàn bộ một file YAML mà một workflow khác có thể kích hoạt. Không giống như các action, đây là những bản thiết kế hoàn chỉnh bao gồm các job, runner và logic. Chúng đóng vai trò là “Con đường chuẩn” (Golden Path) cho toàn bộ quy trình CI/CD của bạn.

  • Phạm vi: Được gọi ở cấp độ job.
  • Kích hoạt: Được kích hoạt bởi sự kiện on: workflow_call.
  • Kiểm soát: Bạn có thể định nghĩa nghiêm ngặt các input và secret nào được yêu cầu từ phía người gọi.
  • Lý tưởng cho: Tiêu chuẩn hóa toàn bộ vòng đời build-test-deploy cho hơn 100 microservice.

Xây dựng Nguồn chân lý duy nhất

Hãy đi vào thực tế. Chúng ta sẽ tạo một repository trung tâm tên là devops-assets. Đây sẽ là nơi chứa logic dùng chung mà chúng ta có thể gọi từ bất kỳ repository dự án nào trong tổ chức.

Bước 1: Tạo Composite Action để thiết lập môi trường

Bên trong devops-assets, tạo đường dẫn: .github/actions/setup-node-cache/action.yml. File này xử lý việc cài đặt Node.js và cache dependency—một tác vụ lặp đi lặp lại thường tốn 10-15 dòng YAML cho mỗi repo.

# devops-assets/.github/actions/setup-node-cache/action.yml
name: 'Thiết lập Node và Cache'
description: 'Cài đặt Node.js và thiết lập cache npm'
inputs:
  node-version:
    description: 'Phiên bản Node.js cần dùng'
    required: true
    default: '20'

runs:
  using: "composite"
  steps:
    - name: Thiết lập Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}

    - name: Cache các dependency
      uses: actions/cache@v4
      with:
        path: ~/.npm
        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

    - name: Cài đặt các dependency
      shell: bash
      run: npm ci

Dòng shell: bash là bắt buộc ở đây. Nó đảm bảo các script của bạn chạy ổn định bất kể shell mặc định của hệ điều hành trên runner là gì.

Bước 2: Thiết kế Reusable Workflow để Testing

Tiếp theo, chúng ta tạo một bản thiết kế bộ test hoàn chỉnh trong .github/workflows/standard-ci.yml. Workflow này sử dụng composite action mà chúng ta vừa xây dựng.

# devops-assets/.github/workflows/standard-ci.yml
name: Standard CI

on:
  workflow_call:
    inputs:
      node-version:
        required: false
        type: string
        default: '20'
    secrets:
      SONAR_TOKEN:
        required: false

jobs:
  test-and-lint:
    runs-on: ubuntu-latest
    steps:
      - name: Lấy code (Checkout)
        uses: actions/checkout@v4

      - name: Sử dụng composite action của chúng ta
        uses: your-org/devops-assets/.github/actions/setup-node-cache@v1
        with:
          node-version: ${{ inputs.node-version }}

      - name: Chạy Test
        run: npm test

Trigger workflow_call chính là cánh cổng. Nó cho phép file này được coi như một hàm thư viện có thể tái sử dụng bởi bất kỳ repository nào khác.

Bước 3: Workflow người gọi tinh gọn

Bây giờ, hãy xem repository ứng dụng của bạn trở nên sạch sẽ như thế nào. Trong my-web-app/.github/workflows/ci.yml, bạn chỉ cần thế này:

# my-web-app/.github/workflows/ci.yml
name: CI

on:
  push:
    branches: [ main ]

jobs:
  call-standard-ci:
    uses: your-org/devops-assets/.github/workflows/standard-ci.yml@v1
    with:
      node-version: '22'
    secrets: inherit

Bạn vừa giảm một workflow từ 100 dòng xuống còn 10 dòng. Nếu ngày mai bạn cần thêm bước quét bảo mật cho mọi dự án, bạn chỉ cần cập nhật một file trong devops-assets, và thay đổi sẽ lan tỏa khắp mọi nơi ngay lập tức.

Quy tắc sinh tồn cho các Pipeline tập trung

Tập trung hóa giúp đơn giản hóa việc quản lý, nhưng nó cũng tạo ra một điểm lỗi duy nhất (single point of failure). Nếu bạn làm hỏng workflow dùng chung, bạn sẽ làm hỏng khả năng triển khai của toàn bộ công ty.

1. Cố định phiên bản của bạn

Đừng bao giờ sử dụng @main. Nếu bạn push một thay đổi gây lỗi (breaking change) lên branch main, mọi dự án sẽ thất bại cùng lúc. Luôn sử dụng tag (ví dụ: @v1.2.0) hoặc commit SHA. Hãy test các thay đổi trên một branch, gắn tag cho nó, và sau đó nâng cấp các dự án của bạn một cách dần dần.

2. Cách truyền Secret

Các Secret không tự động đi theo người gọi. Bạn phải truyền chúng một cách rõ ràng hoặc sử dụng secrets: inherit. Mặc dù inherit tiện lợi cho các công cụ nội bộ, nhưng việc định nghĩa rõ ràng các secret trong khối workflow_call sẽ tốt hơn cho việc kiểm tra bảo mật và nguyên tắc đặc quyền tối thiểu.

3. Hiểu rõ ranh giới

Nếu bạn cần điều phối nhiều job—ví dụ như một job build phải hoàn thành trước khi job deploy bắt đầu—thì Reusable Workflow là lựa chọn duy nhất của bạn. Nếu bạn chỉ muốn tránh việc phải gõ npm installaws configure lần thứ một nghìn, hãy dùng Composite Action.

Bước nhảy vọt về độ trưởng thành DevOps

Chuyển sang kiến trúc tập trung là một cột mốc quan trọng. Nó chuyển trọng tâm của bạn từ việc “làm cho nó chạy được” sang “làm cho nó mở rộng được”. Bằng cách trừu tượng hóa hạ tầng vào các Reusable Workflow, bạn trao quyền cho các lập trình viên tập trung vào tính năng thay vì cú pháp YAML. Hãy bắt đầu nhỏ: xác định một tác vụ thiết lập lặp đi lặp lại và biến nó thành một Composite Action ngay hôm nay. Bản thân bạn trong tương lai—và đội ngũ trực on-call—sẽ cảm ơn bạn vì đã giảm bớt gánh nặng bảo trì.

Share: