Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 23 — Pipeline Trigger Patterns

Đã đọc 0/15 mục · còn khoảng 11 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn vừa gia nhập một startup fintech ở TP.HCM. Ngày đầu tiên, bạn push một dòng sửa lỗi chính tả vào file README, và... toàn bộ pipeline chạy: build lại image Docker nặng 1.2 GB, chạy 800 test unit, deploy lên staging. Mất 22 phút. Chỉ để sửa một dấu phẩy. Đội của bạn tiêu tốn hàng nghìn phút CI mỗi tháng cho những lần chạy vô nghĩa như vậy, và hóa đơn GitHub Actions cứ leo thang.

Ở chiều ngược lại, một đội khác lại gặp vấn đề trái ngược: developer merge code vào nhánh main nhưng pipeline không tự chạy, phải vào bấm nút thủ công, và nửa số lần họ quên bấm. Bug lọt lên production vì "chưa ai chạy test".

Cả hai vấn đề đều bắt nguồn từ cùng một chỗ: trigger — cơ chế quyết định khi nào và tại sao một pipeline bắt đầu chạy. Trigger là "cửa vào" của toàn bộ hệ thống CI/CD. Cấu hình trigger đúng nghĩa là pipeline chạy đúng lúc cần, không chạy khi không cần, và mỗi lần chạy đều có mục đích rõ ràng. Cấu hình sai thì hoặc bạn lãng phí tài nguyên và thời gian chờ, hoặc bạn bỏ sót những lần kiểm thử quan trọng.

Trong bài này, chúng ta sẽ đi sâu vào các pattern (mẫu hình) trigger phổ biến — từ push, pull request, schedule cho tới các kiểu trigger nâng cao như manual dispatch và path filter. Mục tiêu là để bạn nhìn vào bất kỳ dự án nào cũng biết ngay: "Pipeline này nên được kích hoạt bằng cách nào để vừa nhanh, vừa an toàn, vừa tiết kiệm?"

Khái niệm cốt lõi

Trigger, hiểu đơn giản, là sự kiện (event) khiến CI/CD engine bắt đầu thực thi một workflow. Mỗi nền tảng gọi tên hơi khác nhau — GitHub Actions dùng khóa on:, GitLab dùng rules: với biến $CI_PIPELINE_SOURCE, Jenkins dùng triggers trong Jenkinsfile — nhưng bản chất giống nhau: có một sự kiện xảy ra, hệ thống lắng nghe, rồi khởi chạy pipeline tương ứng.

1. Push — kích hoạt khi có commit mới

Đây là trigger cơ bản nhất. Khi bạn push commit lên một nhánh, pipeline chạy. Push trigger thường được dùng cho các nhánh "quan trọng" như main, develop, hoặc các nhánh release. Điểm mấu chốt là bạn hầu như luôn muốn lọc theo nhánh (branch filter), chứ không phải push nhánh nào cũng chạy toàn bộ.

Ví dụ với GitHub Actions:

on:
  push:
    branches:
      - main
      - 'release/**'
    paths-ignore:
      - '**.md'
      - 'docs/**'

Đoạn trên nói: chỉ chạy khi push vào main hoặc bất kỳ nhánh nào bắt đầu bằng release/, VÀ bỏ qua nếu chỉ thay đổi file Markdown hoặc thư mục docs. Chính paths-ignore này sẽ giải quyết vấn đề "sửa dấu phẩy mà chạy 22 phút" ở phần mở đầu.

2. Pull Request — kích hoạt khi mở/cập nhật PR

Đây là trigger quan trọng nhất cho chất lượng code. Khi ai đó mở một Pull Request (hoặc Merge Request trong GitLab), pipeline chạy để kiểm tra code trước khi nó được merge. Đây là hàng rào bảo vệ nhánh chính.

on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [main]

Ba types cần nhớ: opened (PR vừa mở), synchronize (có commit mới push vào PR đang mở), reopened (PR bị đóng rồi mở lại). Nếu bạn quên synchronize, pipeline sẽ chỉ chạy lần đầu mở PR và không chạy lại khi developer sửa code theo review — một lỗi rất phổ biến khiến bug lọt qua.

Một chi tiết an toàn: với repo công khai, GitHub có hai event là pull_request và pull_request_target. Cái sau chạy trong ngữ cảnh nhánh đích và có quyền truy cập secrets — nếu dùng bừa với PR từ người lạ (fork), bạn có thể bị đánh cắp secret. Nguyên tắc: PR từ fork thì dùng pull_request (không có secret), chỉ dùng pull_request_target khi thực sự hiểu rủi ro.

3. Schedule (cron) — kích hoạt theo lịch

Có những công việc không cần chạy theo mỗi commit mà nên chạy định kỳ: quét bảo mật ban đêm, chạy bộ test tích hợp nặng, refresh dependency, hay build "nightly" để phát hiện những lỗi tích lũy.

on:
  schedule:
    - cron: '0 18 * * *'  # 18:00 UTC = 01:00 sáng giờ VN

Cú pháp cron gồm 5 trường: phút giờ ngày-tháng tháng thứ-trong-tuần. Lưu ý cực kỳ quan trọng với người Việt: GitHub Actions và hầu hết CI đều dùng UTC. Việt Nam là UTC+7, nên 0 18 * thực chất là 1 giờ sáng hôm sau theo giờ Việt Nam. Rất nhiều đội đặt "nightly build" nhưng nó lại chạy vào giữa trưa vì tính nhầm múi giờ.

4. Manual / Workflow Dispatch — kích hoạt thủ công

Không phải mọi thứ nên tự động. Deploy lên production, chạy migration database, hay rollback — đây là những hành động cần con người bấm nút có chủ đích. GitHub gọi là workflow_dispatch, GitLab có "manual jobs", Jenkins có nút "Build with Parameters".

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Môi trường deploy'
        type: choice
        options: [staging, production]
        default: staging

Trigger thủ công còn cho phép truyền tham số (input), rất hữu ích cho các thao tác vận hành cần lựa chọn.

5. Các trigger khác đáng biết

  • Tag / Release: chạy khi tạo tag v1.2.3 — thường dùng để build và publish bản release chính thức.
  • API / webhook / repository_dispatch: kích hoạt từ hệ thống bên ngoài, dùng khi bạn muốn một dịch vụ khác châm ngòi pipeline.
  • workflow_run / pipeline phụ thuộc: một pipeline chạy sau khi pipeline khác hoàn tất — nền tảng cho các quy trình nhiều tầng.

Tình huống thực tế

Ví dụ 1 — Tiki và bài toán hóa đơn CI phình to

Giả định một đội platform tại một sàn thương mại điện tử lớn của Việt Nam (lấy bối cảnh giống Tiki) có monorepo với 40 service. Ban đầu họ cấu hình đơn giản: mọi push vào bất kỳ nhánh nào cũng chạy toàn bộ pipeline test cho cả 40 service. Với khoảng 60 developer, mỗi ngày có tới 300–400 lần push, mỗi lần chạy ngốn ~35 phút runner. Hóa đơn CI lên tới hơn 120 triệu đồng/tháng.

Đội tái cấu trúc trigger theo ba hướng: (1) thêm paths filter để chỉ chạy test của service nào thực sự bị thay đổi; (2) chỉ chạy pipeline đầy đủ trên pull_request vào main, còn push nhánh feature chỉ chạy lint + unit test nhanh; (3) chuyển bộ integration test nặng sang schedule chạy 1 giờ sáng. Kết quả: số phút CI giảm ~65%, hóa đơn còn khoảng 42 triệu đồng/tháng, và thời gian chờ trung bình của developer khi mở PR giảm từ 35 phút xuống 8 phút.

Bài học: Không phải mọi thay đổi đều xứng đáng với mọi bước kiểm thử. Path filter và việc phân tách "trigger nhanh cho feature branch, trigger đầy đủ cho PR" là đòn bẩy tiết kiệm lớn nhất.

Ví dụ 2 — Startup fintech và cú deploy production không kiểm soát

Một startup fintech ở Hà Nội cấu hình pipeline deploy production tự động chạy mỗi khi push vào main. Nghe có vẻ hiện đại và "continuous deployment". Nhưng một chiều thứ Sáu, một developer merge nhầm một PR chưa hoàn thiện vào main, và 90 giây sau, code lỗi đã lên production. Cổng thanh toán gián đoạn 40 phút trong giờ cao điểm.

Sau sự cố, họ tách trigger làm hai tầng. Push vào main chỉ tự động deploy lên staging. Deploy production chuyển sang workflow_dispatch — phải có người bấm nút, chọn version, và pipeline yêu cầu phê duyệt (environment protection rule) từ một người thứ hai. Continuous Delivery (sẵn sàng deploy bất kỳ lúc nào) vẫn giữ được, nhưng bước "bấm cò" ra production nằm trong tay con người.

Bài học: Tự động hóa đến staging, nhưng để một trigger thủ công có kiểm soát ở cửa production. Trigger không chỉ là chuyện kỹ thuật — nó là ranh giới an toàn vận hành.

Ví dụ 3 — Đội mobile và nightly build lệch múi giờ

Một đội phát triển app giao đồ ăn ở Đông Nam Á đặt nightly build để chạy bộ UI test dài 50 phút, cấu hình cron: '0 22 *' với ý định chạy lúc 10 giờ tối. Nhưng vài tuần sau QA phàn nàn rằng cứ 5 giờ sáng máy build lại nóng ran và kết quả test sáng ra mới có — trong khi họ tưởng có sẵn từ tối. Nguyên nhân: 22:00 UTC = 05:00 sáng giờ Việt Nam (UTC+7).

Họ sửa thành cron: '0 15 *' (15:00 UTC = 22:00 giờ VN), và thêm một workflow_dispatch để QA có thể tự châm ngòi build bất cứ lúc nào cần bản mới. Ngoài ra, họ thêm ghi chú múi giờ ngay cạnh dòng cron trong file để người sau không mắc lại.

Bài học: Luôn quy đổi cron về UTC và ghi chú rõ ràng. Múi giờ là cái bẫy kinh điển với các đội ở Việt Nam.

Hướng dẫn từng bước

Đây là quy trình để thiết kế trigger cho một dự án mới:

  • Liệt kê các loại pipeline bạn cần. Thông thường có: (a) pipeline kiểm tra nhanh cho feature branch, (b) pipeline đầy đủ cho PR, (c) pipeline deploy staging, (d) pipeline deploy production, (e) job định kỳ. Mỗi loại sẽ có trigger riêng.
  • Gán trigger phù hợp cho từng loại. Feature branch → push (có branch filter). PR → pull_request với types: [opened, synchronize, reopened]. Staging → push vào main. Production → workflow_dispatch. Định kỳ → schedule.
  • Thêm branch filter. Đừng bao giờ để trigger "chạy mọi nhánh" cho pipeline nặng. Chỉ định rõ branches: cho những nhánh thực sự cần.
  • Thêm path filter. Với paths và paths-ignore, đảm bảo thay đổi tài liệu không kích hoạt build code, và trong monorepo thì chỉ service bị đổi mới chạy.
  • Quy đổi múi giờ cho cron. Viết lịch theo UTC, tính ngược từ giờ Việt Nam (trừ đi 7 tiếng), và ghi chú giờ địa phương ngay cạnh dòng cron.
  • Bảo vệ trigger nhạy cảm. Với deploy production, kết hợp workflow_dispatch cùng environment protection rule (yêu cầu approval). Với repo công khai, cẩn thận với pull_request_target và secrets.
  • Kiểm soát chạy đồng thời (concurrency). Thêm nhóm concurrency để khi push liên tiếp, các lần chạy cũ bị hủy, tránh lãng phí:
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
  • Test và quan sát. Push thử, mở PR thử, chạy manual thử. Xem tab Actions để xác nhận đúng trigger chạy đúng lúc, và không có lần chạy thừa nào.

Lỗi thường gặp & mẹo

  • Quên synchronize trong pull_request. Pipeline chỉ chạy lúc mở PR, không chạy lại khi có commit mới. Luôn thêm types: [opened, synchronize, reopened].
  • Tính nhầm múi giờ cron. Như ba ví dụ đã thấy — CI dùng UTC, Việt Nam UTC+7. Luôn trừ 7 tiếng và ghi chú.
  • Trigger trùng lặp (double run). Nếu bạn để cả push và pull_request cho cùng một nhánh, khi push vào nhánh của một PR đang mở, pipeline có thể chạy hai lần. Mẹo: dùng điều kiện if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' để loại bỏ lần chạy dư.
  • Không có concurrency control. Developer push 5 commit liên tiếp, 5 pipeline chạy song song, 4 cái đầu vô nghĩa. cancel-in-progress: true giải quyết ngay.
  • Cron trên nhánh không mặc định. Trigger schedule chỉ chạy trên nhánh mặc định của repo (thường là main). Đặt schedule trên nhánh khác sẽ không bao giờ chạy.
  • Lịch cron GitHub bị trễ. Vào giờ cao điểm (đầu giờ), GitHub có thể trễ lịch cron tới 5–15 phút. Đừng đặt cron vào phút 0 của những giờ chẵn nếu cần độ chính xác cao — chọn phút lẻ như 7 hoặc 23.
  • paths-ignore hiểu sai với PR bắt buộc. Nếu bạn đặt một job là "required check" để merge, mà paths-ignore khiến nó bị bỏ qua, PR có thể kẹt mãi vì check không bao giờ chạy để báo "pass". Cân nhắc dùng job giả (skip job báo success) trong trường hợp này.
  • Mẹo cuối: Đặt tên trigger và workflow rõ ràng theo mục đích ("CI - PR Check", "Deploy - Production Manual"). Khi có sự cố lúc 2 giờ sáng, tên rõ ràng giúp bạn tìm đúng pipeline trong tích tắc.

Bài tập thực hành

Hãy tạo một repo thử nghiệm trên GitHub và cấu hình các workflow sau:

  • CI cho PR: Tạo workflow chạy lint + test khi có pull_request vào main, đủ ba types. Mở một PR, push thêm commit, xác nhận pipeline chạy lại đúng số lần.
  • Fast check cho feature branch: Workflow chạy trên push vào các nhánh feature/*, nhưng bỏ qua khi chỉ đổi file .md. Thử push một thay đổi README và xác nhận nó không* chạy.
  • Nightly job đúng giờ Việt Nam: Tạo một schedule job chạy lúc 2 giờ sáng giờ VN. Tự tính giá trị cron theo UTC (đáp án: 0 19 *) và ghi chú giờ địa phương cạnh dòng đó.
  • Manual deploy có input: Tạo workflow_dispatch với input environment kiểu choice (staging/production). Chạy thử và in ra giá trị input đã chọn trong log.
  • Chống chạy trùng: Thêm khối concurrency với cancel-in-progress: true. Push liên tiếp 3 commit và xác nhận chỉ lần cuối chạy đến cùng.
Thử thách nâng cao: gộp tất cả vào một chiến lược trigger hoàn chỉnh cho một dự án giả định, và viết một đoạn ngắn giải thích vì sao mỗi loại pipeline dùng trigger đó.

Tóm tắt

Trigger là "cửa vào" của mọi pipeline CI/CD, và cấu hình đúng nó là một trong những kỹ năng có sức ảnh hưởng lớn nhất tới tốc độ, chi phí và an toàn của toàn đội. Chúng ta đã đi qua các pattern cốt lõi: push (kèm branch và path filter) cho các nhánh quan trọng, pull request (nhớ synchronize) làm hàng rào chất lượng, schedule/cron cho công việc định kỳ (luôn quy đổi UTC), và manual/workflow_dispatch cho những hành động nhạy cảm cần con người quyết định như deploy production.

Ba bài học vận hành đọng lại: (1) không phải mọi thay đổi đều đáng chạy mọi bước — dùng path filter và tách trigger nhanh/đầy đủ để tiết kiệm; (2) tự động hóa đến staging nhưng giữ trigger có kiểm soát ở cửa production; (3) luôn tỉnh táo với múi giờ khi đặt cron. Nắm vững những pattern này, bạn sẽ biến "cửa vào" pipeline thành một hệ thống chạy đúng lúc, đúng việc, và không lãng phí một phút CI nào.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi