Product Management
Đăng nhập
ESC

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

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

Bài 6 — GitHub Actions — Workflow & Action Basics

Đã đọc 0/14 mục · còn khoảng 13 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

Ở những bài trước, bạn đã hiểu CI/CD là gì, phân biệt được CI với CD, và nắm được triết lý "Pipeline as Code" — tức là mọi thứ về quy trình build, test, deploy đều được viết ra dưới dạng file cấu hình, nằm ngay trong repo cùng với code. Bài này là lúc chúng ta chạm tay vào công cụ cụ thể đầu tiên, và cũng là công cụ phổ biến nhất hiện nay: GitHub Actions.

Tại sao lại là GitHub Actions? Hãy nhìn vào thực tế thị trường Việt Nam. Phần lớn các đội startup, các team sản phẩm ở Tiki, MoMo, các công ty outsourcing như FPT Software hay các agency nhỏ đều đang lưu code trên GitHub. Khi CI/CD được tích hợp thẳng vào chính nơi bạn lưu code, bạn không phải cài thêm server riêng, không phải cấu hình webhook lằng nhằng, không phải trả tiền cho một dịch vụ thứ ba như CircleCI hay Travis (những cái tên từng thống trị nhưng nay đã nhường sân). Một anh sinh viên năm cuối cũng có thể bật CI cho dự án đồ án của mình trong 5 phút với hạn mức miễn phí khá rộng rãi.

Nhưng "dễ bắt đầu" không có nghĩa là "dễ làm đúng". Rất nhiều bạn viết được một file YAML chạy được, nhưng không thực sự hiểu Workflow, Job, Step là gì, chúng liên hệ với nhau ra sao, cái gì chạy song song, cái gì chạy tuần tự, và một "action" thực chất là gì. Hậu quả là khi pipeline hỏng, họ chỉ biết copy-paste giải pháp từ Stack Overflow mà không hiểu mình đang sửa gì. Bài này sẽ trang bị cho bạn nền tảng vững chắc về cấu trúc và các khối xây dựng cơ bản của GitHub Actions, để mọi thứ về sau — secrets, environments, caching, matrix — đều đứng trên nền móng chắc chắn.

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

GitHub Actions được xây dựng từ một vài khái niệm lồng vào nhau như những chiếc hộp Nga (matryoshka). Hiểu đúng thứ tự lồng nhau này là chìa khóa để đọc bất kỳ file workflow nào.

Workflow — bản thiết kế của cả quy trình

Workflow là một quy trình tự động hóa, được mô tả bằng một file YAML đặt trong thư mục .github/workflows/ ở gốc repository. Một repo có thể có nhiều workflow — ví dụ một file ci.yml để chạy test mỗi khi có commit, một file deploy.yml để triển khai khi merge vào nhánh main, một file nightly.yml chạy theo lịch mỗi đêm.

Điểm quan trọng: file phải nằm đúng thư mục .github/workflows/. GitHub quét thư mục này để nhận diện workflow. Nếu bạn đặt sai chỗ (ví dụ .github/workflow/ thiếu chữ "s"), GitHub sẽ hoàn toàn phớt lờ nó và bạn sẽ ngồi thắc mắc tại sao chẳng có gì chạy.

Mỗi workflow được kích hoạt bởi một sự kiện (event), khai báo qua từ khóa on. Sự kiện có thể là push (có người đẩy code lên), pull_request (có PR mới hoặc PR được cập nhật), schedule (theo lịch cron), hoặc workflow_dispatch (bấm nút chạy tay). Chi tiết về các mẫu trigger phong phú hơn sẽ được bàn kỹ ở Bài 23; ở đây bạn chỉ cần hiểu rằng on là "cái công tắc" bật workflow.

Job — đơn vị chạy trên một máy

Bên trong một workflow là một hoặc nhiều Job. Mỗi job chạy trên một runner — hiểu nôm na là một máy ảo sạch sẽ được GitHub cấp phát riêng cho lần chạy đó. Bạn khai báo loại máy bằng runs-on, ví dụ ubuntu-latest, windows-latest, hay macos-latest.

Có hai điều cực kỳ quan trọng về job mà người mới hay hiểu nhầm:

  • Mặc định, các job chạy song song (parallel). Nếu bạn khai báo job test và job lint trong cùng workflow mà không nói gì thêm, GitHub sẽ khởi động hai máy ảo riêng biệt và chạy chúng cùng lúc. Điều này giúp pipeline nhanh hơn, nhưng cũng nghĩa là chúng không chia sẻ dữ liệu với nhau một cách tự nhiên.
  • Mỗi job có một môi trường hoàn toàn độc lập. Job build chạy xong, cài đặt thư viện, tạo ra file — thì job deploy chạy trên máy khác sẽ không thấy những file đó, trừ khi bạn chủ động truyền qua bằng artifact (sẽ học ở Bài 12) hoặc dùng needs để tạo phụ thuộc.
Từ khóa needs là cách bạn bắt job này chờ job kia. Ví dụ deploy cần build chạy thành công trước thì viết needs: build. Lúc này quan hệ song song bị phá vỡ có chủ đích, tạo thành một chuỗi tuần tự.

Step — hành động nhỏ nhất trong một job

Bên trong mỗi job là một danh sách Step, chạy tuần tự từ trên xuống dưới trên cùng một runner. Đây là điểm khác biệt then chốt so với job: các step chia sẻ chung một môi trường, nên file mà step 1 tạo ra thì step 2 dùng được ngay.

Mỗi step làm một trong hai việc:

  • Chạy một lệnh shell trực tiếp, qua từ khóa run. Ví dụ run: npm test hay run: ./gradlew build.
  • Gọi một Action có sẵn, qua từ khóa uses. Ví dụ uses: actions/checkout@v4.

Action — mảnh ghép tái sử dụng

Vậy Action (viết hoa, số ít) chính xác là gì? Đây là điểm khiến nhiều người rối. Bản thân cái tên "GitHub Actions" (số nhiều, chỉ cả nền tảng) khác với "một action" (một thành phần cụ thể).

Một Action là một đơn vị code đóng gói sẵn, giải quyết một tác vụ hay lặp lại, để bạn không phải viết lại. Thay vì tự viết cả chục dòng lệnh git để lấy code về máy runner, bạn chỉ cần uses: actions/checkout@v4 — action checkout do GitHub viết sẵn sẽ lo hết. Tương tự, actions/setup-node@v4 sẽ cài đặt Node.js phiên bản bạn muốn.

Cú pháp uses: owner/repo@version cho biết action nằm ở đâu. actions/checkout nghĩa là repo checkout trong tổ chức actions (tổ chức chính thức của GitHub). Phần sau dấu @ là phiên bản — có thể là tag (@v4), một nhánh, hoặc một mã commit SHA cụ thể. Việc ghim phiên bản này cực kỳ quan trọng cho tính ổn định và bảo mật, chúng ta sẽ nói ở phần lỗi thường gặp.

Kho action công khai khổng lồ nằm ở GitHub Marketplace, nơi có hàng nghìn action do cộng đồng đóng góp: gửi thông báo Slack, deploy lên AWS, quét bảo mật, v.v.

Tóm lại cấu trúc lồng nhau: Workflow chứa Job → Job chứa Step → Step chạy lệnh hoặc gọi Action.

Tình huống thực tế

Ví dụ 1 — Startup fintech Sài Gòn và pipeline đầu tiên

Một startup fintech nhỏ ở Quận 1, TP.HCM, khoảng 8 lập trình viên, đang xây một ứng dụng ví điện tử bằng Node.js. Trước đây, quy trình của họ là: dev tự chạy test trên máy mình rồi push. Kết quả là cứ vài ngày lại có một lần "trên máy tôi chạy được mà" — code lên nhánh chung thì gãy vì thiếu test, hoặc quên chạy lint.

Anh tech lead quyết định dựng một workflow CI đơn giản. File .github/workflows/ci.yml có một job tên test, runs-on: ubuntu-latest, gồm bốn step: checkout code, cài Node 20, chạy npm ci, và chạy npm test. Workflow này kích hoạt on: pull_request.

Kết quả sau hai tuần: mọi PR đều có một dấu tick xanh hoặc dấu X đỏ ngay trên GitHub. Số lần "trên máy tôi chạy được" giảm từ khoảng 3-4 lần/tuần xuống gần như 0. Điều đáng nói là toàn bộ chi phí là 0 đồng — họ dùng gói miễn phí 2.000 phút/tháng cho repo private, và mỗi lần chạy chỉ tốn khoảng 2 phút.

Bài học: Một workflow tối giản với đúng một job và vài step đã tạo ra giá trị lớn ngay lập tức. Bạn không cần pipeline phức tạp để bắt đầu gặt hái lợi ích. Sức mạnh nằm ở chỗ "tự động và luôn luôn chạy", chứ không nằm ở độ hoành tráng.

Ví dụ 2 — Team e-commerce và bài học về job độc lập

Một team e-commerce (giả định là "ShopViet", khoảng 20 kỹ sư) có một frontend React và một backend Java Spring. Bạn dev viết một workflow với hai job: job build-frontend và job build-backend. Bạn kỳ vọng job backend sẽ dùng được các file mà job frontend build ra để đóng gói chung. Kết quả: job backend báo lỗi "không tìm thấy thư mục dist/".

Nguyên nhân đúng như đã cảnh báo ở trên: hai job chạy trên hai runner khác nhau, môi trường hoàn toàn tách biệt. Job frontend build ra dist/ trên máy của nó, rồi máy đó bị hủy khi job kết thúc. Job backend nhận một máy trắng tinh, chẳng có dist/ nào cả.

Team đã sửa bằng cách dùng actions/upload-artifact ở cuối job frontend để lưu dist/, rồi actions/download-artifact ở đầu job backend để lấy lại. Đồng thời thêm needs: build-frontend vào job backend để đảm bảo thứ tự.

Bài học: Ranh giới giữa job là ranh giới cứng về môi trường. Step chia sẻ đĩa và biến với nhau; job thì không. Khi cần truyền dữ liệu giữa các job, phải dùng artifact, và khi cần thứ tự, phải dùng needs. Hiểu điều này giúp bạn thiết kế pipeline đúng ngay từ đầu thay vì đập đi xây lại.

Ví dụ 3 — Agency và sự cố phiên bản action bị "trôi"

Một agency ở Đà Nẵng làm web cho khách hàng nước ngoài. Họ viết action trong workflow theo kiểu uses: some-community/deploy-action@main — tức là ghim vào nhánh main của một action cộng đồng. Mọi thứ chạy tốt suốt vài tháng.

Rồi một sáng, pipeline deploy đột nhiên gãy hàng loạt ở tất cả dự án, dù họ chẳng đổi một dòng code nào. Điều tra ra thì tác giả của action cộng đồng kia đã push một breaking change lên nhánh main. Vì agency ghim @main, họ tự động "nuốt" luôn phiên bản mới có lỗi.

Họ khắc phục bằng cách đổi sang ghim tag phiên bản cụ thể như @v2.3.1, và với các action nhạy cảm về bảo mật thì ghim thẳng commit SHA đầy đủ.

Bài học: uses không chỉ là "gọi action" mà còn là "gọi phiên bản nào của action". Ghim @main hay @master biến pipeline của bạn thành con tin của người khác. Luôn ghim phiên bản ổn định.

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

Hãy cùng dựng một workflow CI hoàn chỉnh cho một dự án Node.js từ đầu.

Bước 1 — Tạo đúng thư mục. Ở gốc repo, tạo thư mục .github/workflows/. Chú ý dấu chấm đầu và chữ "s" ở cuối. Đây là nơi GitHub tìm workflow.

Bước 2 — Tạo file YAML. Tạo file .github/workflows/ci.yml. Tên file tự do, nhưng nên đặt gợi nhớ.

Bước 3 — Đặt tên và khai báo trigger.

name: CI
on:
  pull_request:
  push:
    branches: [ main ]

Workflow này chạy khi có PR, và khi có push trực tiếp vào main.

Bước 4 — Khai báo job và runner.

jobs:
  test:
    runs-on: ubuntu-latest

Ta có một job tên test chạy trên máy Ubuntu mới nhất.

Bước 5 — Viết các step.

    steps:
      - name: Lấy code về
        uses: actions/checkout@v4

      - name: Cài đặt Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Cài dependencies
        run: npm ci

      - name: Chạy test
        run: npm test

Để ý hai kiểu step: uses gọi action có sẵn (checkout, setup-node), còn run chạy lệnh shell trực tiếp (npm ci, npm test). Từ khóa with là cách truyền tham số đầu vào cho một action — ở đây ta bảo setup-node cài phiên bản 20.

Bước 6 — Commit và push. Đẩy file lên GitHub. Vào tab Actions trên giao diện repo, bạn sẽ thấy workflow bắt đầu chạy. Bấm vào để xem log từng step theo thời gian thực.

Bước 7 — Đọc kết quả. Mỗi step hiện dấu tick xanh nếu thành công, X đỏ nếu lỗi. Bấm vào step lỗi để xem log chi tiết. Nếu npm test fail, log sẽ chỉ rõ test nào không qua.

Đó là toàn bộ vòng lặp cơ bản: viết YAML → push → xem kết quả ở tab Actions → sửa nếu cần.

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

Sai thư mục. Lỗi số một của người mới: đặt file ở .github/workflow/ (thiếu "s") hoặc ngay ở gốc repo. GitHub sẽ im lặng phớt lờ. Nếu tab Actions không thấy workflow nào, hãy kiểm tra đường dẫn đầu tiên.

Lỗi thụt lề (indentation) trong YAML. YAML cực kỳ nhạy với khoảng trắng và không cho phép dùng tab, chỉ dùng dấu cách. Một step thụt sai hai dấu cách là cả workflow báo lỗi cú pháp. Mẹo: bật hiển thị khoảng trắng trong editor, và dùng extension YAML để bắt lỗi ngay khi gõ.

Quên bước checkout. Rất nhiều bạn viết ngay run: npm ci mà quên actions/checkout@v4 ở đầu. Runner khởi động là một máy trắng, chưa có code của bạn trên đó. Không checkout thì không có package.json, và lệnh sẽ fail với lỗi khó hiểu. Hãy nhớ: checkout gần như luôn là step đầu tiên.

Nhầm lẫn run với uses. Không thể vừa run vừa uses trong cùng một step — mỗi step chỉ làm một trong hai. Nếu cần cả gọi action lẫn chạy lệnh, tách thành hai step.

Ghim phiên bản action lỏng lẻo. Như ví dụ 3, tránh @main. Dùng tag cụ thể @v4, hoặc SHA cho action bên thứ ba. Với action chính thức của actions/* thì @v4 là chấp nhận được.

Mẹo — đặt tên step rõ ràng. Dùng name: cho mỗi step. Khi pipeline gãy, một cái tên như "Chạy test" dễ tìm hơn nhiều so với dòng lệnh thô hiện mặc định.

Mẹo — dùng workflow_dispatch để test tay. Thêm workflow_dispatch: vào phần on: để có nút "Run workflow" bấm chạy thủ công, rất tiện khi debug mà không muốn tạo commit giả.

Mẹo — bắt đầu nhỏ. Đừng cố viết workflow 5 job ngay lần đầu. Một job, vài step, chạy được đã, rồi mở rộng dần.

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

  • Dựng workflow CI đầu tiên. Lấy một dự án bất kỳ của bạn (Node, Python, hay Java đều được). Tạo .github/workflows/ci.yml với một job chạy checkout, cài môi trường ngôn ngữ tương ứng, cài dependencies và chạy test. Push lên và xác nhận nó chạy ở tab Actions.
  • Phân biệt song song và tuần tự. Thêm một job thứ hai tên lint. Quan sát ở giao diện Actions rằng test và lint chạy song song. Sau đó thêm needs: test vào job lint và quan sát chúng chuyển thành tuần tự. Ghi lại thời gian chạy tổng của hai trường hợp để thấy sự khác biệt.
  • Chứng minh job độc lập. Ở job test, tạo một file bằng run: echo "hello" > note.txt. Ở một job khác không có needs, thử run: cat note.txt. Quan sát nó fail. Đây là cách bạn tự tay kiểm chứng rằng các job không chia sẻ đĩa.
  • Thử một action từ Marketplace. Tìm một action đơn giản trên GitHub Marketplace (ví dụ action in ra thông tin môi trường) và thêm vào workflow với đúng cú pháp uses: owner/repo@version. Chú ý ghim phiên bản cụ thể.

Tóm tắt

GitHub Actions được xây từ bốn khối lồng nhau: Workflow là file YAML trong .github/workflows/, kích hoạt bởi sự kiện qua on. Bên trong là các Job, mỗi job chạy trên một runner độc lập qua runs-on, và mặc định các job chạy song song với môi trường tách biệt — muốn tuần tự thì dùng needs, muốn truyền dữ liệu thì dùng artifact. Bên trong mỗi job là các Step chạy tuần tự, chia sẻ chung môi trường; mỗi step hoặc chạy lệnh shell qua run, hoặc gọi một Action tái sử dụng qua uses: owner/repo@version.

Nắm chắc bốn khái niệm này và mối quan hệ lồng nhau giữa chúng, bạn đã có nền tảng để đọc và viết bất kỳ workflow nào. Ba nguyên tắc vàng cần khắc cốt: đặt file đúng thư mục, checkout gần như luôn là step đầu tiên, và luôn ghim phiên bản action. Ở bài tiếp theo, chúng ta sẽ nâng cấp workflow này để xử lý Secrets và Environments — cách đưa mật khẩu, token, và cấu hình nhạy cảm vào pipeline một cách an toàn.

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