Product Management
Đăng nhập
ESC

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

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

Bài 8 — GitLab CI/CD — Pipeline Structure

Đã đọc 0/15 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

Ở các bài trước, bạn đã làm quen với GitHub Actions — nơi mọi thứ xoay quanh khái niệm "workflow" và các "action" nhỏ ghép lại. Nhưng thế giới CI/CD không chỉ có GitHub. Rất nhiều công ty tại Việt Nam và Đông Nam Á — đặc biệt là các ngân hàng, fintech, doanh nghiệp nhà nước và startup muốn tự chủ hạ tầng — lại chọn GitLab. Lý do rất thực tế: GitLab có thể self-host (cài trên server riêng của công ty), tích hợp sẵn Git repository, CI/CD, container registry, quản lý issue và security scanning trong cùng một nền tảng. Với những tổ chức bị ràng buộc về dữ liệu không được đưa ra nước ngoài (như ngân hàng theo quy định của Ngân hàng Nhà nước), GitLab self-managed gần như là lựa chọn mặc định.

Điều quan trọng bạn cần nắm ở bài này không phải là "GitLab tốt hơn hay GitHub tốt hơn", mà là cấu trúc của một pipeline trong GitLab CI/CD. Một khi bạn hiểu được ba trụ cột: file .gitlab-ci.yml, khái niệm stages, và khái niệm jobs, cùng cách chúng phối hợp với nhau, bạn sẽ đọc và viết được bất kỳ pipeline GitLab nào. Đây là kiến thức nền tảng mà mọi bài sau (caching, artifact, deployment strategies) đều dựa vào. Nếu bạn nắm chắc cấu trúc, phần còn lại chỉ là ghép thêm tính năng.

Hãy hình dung thế này: nếu GitHub Actions giống như việc bạn thuê nhiều dịch vụ nhỏ bên ngoài rồi lắp ráp lại, thì GitLab CI/CD giống như một nhà máy có dây chuyền sản xuất rõ ràng — nguyên liệu đi qua từng công đoạn (stage), mỗi công đoạn có nhiều thợ (job) làm việc song song. Bài học hôm nay sẽ dạy bạn cách thiết kế dây chuyền đó.

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

Một file duy nhất: .gitlab-ci.yml

Khác với một số hệ thống CI cần cấu hình rải rác nhiều nơi, GitLab CI/CD quy ước rất đơn giản: toàn bộ pipeline được định nghĩa trong một file YAML duy nhất tên là .gitlab-ci.yml, đặt ở thư mục gốc (root) của repository. Khi bạn push code lên, GitLab tự động đọc file này, phân tích và chạy pipeline tương ứng. Không cần bấm nút, không cần cài plugin — chỉ cần có file là pipeline khởi động.

File này được viết bằng YAML, nên bạn phải cực kỳ cẩn thận với thụt lề (indentation). YAML dùng khoảng trắng (space) chứ không dùng tab, và sai một dấu cách có thể làm hỏng cả pipeline. Đây là lỗi phổ biến nhất của người mới.

Pipeline = Stages + Jobs

Đây là công thức bạn cần khắc cốt ghi tâm:

  • Pipeline là toàn bộ quy trình tự động chạy khi có sự kiện (push code, tạo merge request...).
  • Stage (giai đoạn) là một nhóm công việc chạy tuần tự. Các stage chạy theo thứ tự: stage sau chỉ bắt đầu khi stage trước đã hoàn tất thành công.
  • Job (công việc) là đơn vị thực thi nhỏ nhất — một tập hợp lệnh chạy trong một môi trường. Các job trong cùng một stage sẽ chạy song song với nhau.
Nói cách khác: stage xếp theo chiều dọc (tuần tự), job trong mỗi stage xếp theo chiều ngang (song song). Đây là điểm mấu chốt giúp pipeline vừa nhanh (song song) vừa an toàn (tuần tự có kiểm soát).

Ví dụ một pipeline điển hình:

stages:
  - lint
  - test
  - build
  - deploy

lint-code:
  stage: lint
  script:
    - echo "Đang kiểm tra chất lượng code..."
    - npm run lint

unit-test:
  stage: test
  script:
    - npm run test:unit

integration-test:
  stage: test
  script:
    - npm run test:integration

build-app:
  stage: build
  script:
    - npm run build

deploy-staging:
  stage: deploy
  script:
    - echo "Triển khai lên môi trường staging..."
    - ./deploy.sh staging

Trong ví dụ này, thứ tự chạy sẽ là: đầu tiên lint-code chạy. Nếu qua, hai job unit-test và integration-test chạy song song (vì cùng stage test). Nếu cả hai qua, build-app chạy. Cuối cùng deploy-staging chạy. Nếu bất kỳ job nào thất bại, các stage sau sẽ không chạy — pipeline dừng lại, bảo vệ bạn khỏi việc deploy code lỗi.

Cấu trúc của một job

Mỗi job có một số thành phần quan trọng:

  • stage: job này thuộc giai đoạn nào. Nếu không khai báo, mặc định job thuộc stage test.
  • script: danh sách các lệnh shell sẽ chạy. Đây là phần bắt buộc — không có script, job không hợp lệ.
  • image: chỉ định Docker image làm môi trường chạy. Ví dụ image: node:20 để chạy trên Node.js 20.
  • before_script / after_script: lệnh chạy trước/sau script (thường dùng để cài dependency hoặc dọn dẹp).
  • rules / only / except: điều kiện để job chạy (ví dụ chỉ chạy trên nhánh main).
  • artifacts: khai báo file sinh ra cần giữ lại để truyền sang stage sau.
  • tags: chọn runner phù hợp để chạy job.

GitLab Runner — người thực sự làm việc

Một điều nhiều người mới nhầm lẫn: GitLab bản thân nó không chạy pipeline. Nó chỉ điều phối. Người thực sự thực thi các lệnh là GitLab Runner — một tiến trình được cài trên máy nào đó (có thể là máy chủ của GitLab.com, hoặc server riêng của công ty bạn). Runner nhận job, tải code về, dựng môi trường (thường là một Docker container), chạy các lệnh trong script, rồi báo kết quả về. Ở bài về Self-hosted Runners chúng ta sẽ đào sâu, còn hiện tại bạn chỉ cần hiểu: runner là đôi tay thực thi của pipeline.

Biến môi trường và variables

GitLab cho phép định nghĩa biến ở cấp global hoặc cấp job qua từ khóa variables, và cả biến bí mật (secret) cấu hình trong giao diện Settings > CI/CD > Variables. Đây là cách bạn truyền thông tin như URL API, tên môi trường, hoặc mật khẩu deploy mà không viết cứng vào file.

Tình huống thực tế

Tình huống 1: Fintech Việt Nam chuyển từ deploy thủ công sang pipeline có stage

Một công ty fintech tại TP.HCM (giả định tên PayViet) có đội 8 lập trình viên, ban đầu deploy bằng cách SSH vào server rồi git pull thủ công mỗi tối. Hậu quả: trung bình mỗi tháng có 2-3 lần deploy nhầm code chưa test, gây lỗi thanh toán, có lần khiến khoảng 400 giao dịch bị treo trong 30 phút.

Họ chuyển sang GitLab self-hosted (do quy định dữ liệu tài chính không được ra khỏi Việt Nam) và thiết kế pipeline 4 stage: lint → test → build → deploy. Điểm mấu chốt là họ đặt job deploy vào stage cuối cùng, và cấu hình để stage deploy chỉ chạy khi ba stage trước đều xanh. Kết quả sau 3 tháng: số lần deploy code lỗi giảm về gần 0, vì code chưa qua test đơn giản là không thể tới được bước deploy.

Bài học rút ra: Sức mạnh lớn nhất của việc chia stage không phải là "tự động hóa", mà là tạo ra rào chắn (gate). Thứ tự tuần tự của stage biến thành một chuỗi kiểm soát chất lượng mà con người không thể "lách" được như khi deploy thủ công.

Tình huống 2: Startup e-commerce tăng tốc pipeline bằng job song song

Một startup thương mại điện tử ở Hà Nội (giả định tên ChoNhanh) có pipeline test chạy tới 18 phút, khiến lập trình viên phải chờ lâu mỗi lần push code. Khi phân tích, họ phát hiện họ nhét toàn bộ test (unit, integration, e2e) vào một job duy nhất trong stage test, chạy tuần tự lệnh này sau lệnh kia.

Giải pháp: họ tách thành ba job riêng — test-unit, test-integration, test-e2e — cùng đặt trong stage test. Vì các job cùng stage chạy song song, và họ có 3 runner rảnh, thời gian stage test giảm từ 18 phút xuống còn khoảng 7 phút (bằng job chậm nhất là e2e). Tổng thời gian pipeline giảm gần 40%.

Bài học rút ra: Chia job hợp lý trong cùng một stage là "đòn bẩy tốc độ" gần như miễn phí, miễn là bạn có đủ runner. Đừng gom tất cả vào một job khổng lồ — hãy tách theo loại công việc để tận dụng song song hóa.

Tình huống 3: Đội outsource nhầm giữa stage và job dẫn tới race condition

Một công ty gia công phần mềm ở Đà Nẵng nhận dự án cho khách Singapore. Một bạn dev mới đặt cả job build và job deploy vào cùng một stage tên release, nghĩ rằng "chúng liên quan nhau nên để chung". Kết quả: vì cùng stage nên hai job chạy song song — job deploy bắt đầu trước khi job build tạo xong file artifact. Deploy lên môi trường staging là bản cũ, gây nhầm lẫn suốt một buổi chiều khi tester báo "tính năng mới không thấy đâu".

Họ sửa bằng cách tách build và deploy thành hai stage riêng biệt, để deploy chỉ chạy sau khi build hoàn tất, đồng thời dùng artifacts để truyền file build sang stage deploy.

Bài học rút ra: Nếu hai công việc có quan hệ phụ thuộc (cái này cần kết quả của cái kia), chúng phải ở hai stage khác nhau. Cùng stage nghĩa là "chạy song song, không phụ thuộc nhau". Nhầm lẫn điểm này gây ra lỗi race condition rất khó lần.

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

Hãy cùng xây một pipeline GitLab cơ bản từ đầu cho một dự án Node.js.

Bước 1 — Tạo file .gitlab-ci.yml ở thư mục gốc. Đây là điều kiện tiên quyết. GitLab chỉ nhận diện pipeline khi file nằm đúng ở root, đúng tên (có dấu chấm ở đầu).

Bước 2 — Khai báo danh sách stage theo thứ tự mong muốn.

stages:
  - lint
  - test
  - build
  - deploy

Thứ tự bạn liệt kê ở đây chính là thứ tự thực thi. Hãy sắp xếp từ "rẻ và nhanh" đến "đắt và chậm" — kiểm tra lint trước (vài giây), test sau, build tốn tài nguyên hơn, deploy cuối cùng. Nguyên tắc "fail fast": nếu code sai chính tả cú pháp, hãy để nó fail ngay ở lint thay vì tốn 10 phút build rồi mới biết.

Bước 3 — Định nghĩa image chung. Đặt image mặc định để mọi job có môi trường nhất quán:

default:
  image: node:20

Bước 4 — Viết job đầu tiên cho stage lint.

lint:
  stage: lint
  before_script:
    - npm ci
  script:
    - npm run lint

npm ci cài dependency sạch từ package-lock.json, đảm bảo môi trường tái lập được.

Bước 5 — Thêm job test, tách nhỏ để song song.

test-unit:
  stage: test
  before_script:
    - npm ci
  script:
    - npm run test:unit

test-integration:
  stage: test
  before_script:
    - npm ci
  script:
    - npm run test:integration

Bước 6 — Job build và truyền artifact.

build:
  stage: build
  before_script:
    - npm ci
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week

Từ khóa artifacts bảo GitLab giữ lại thư mục dist/ để stage sau dùng. Không có bước này, mỗi job chạy trong container mới toanh và không thấy file của job trước.

Bước 7 — Job deploy với điều kiện chỉ chạy trên nhánh main.

deploy-production:
  stage: deploy
  script:
    - echo "Đang triển khai production..."
    - ./deploy.sh
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

rules giúp bạn kiểm soát khi nào job chạy. Ở đây, deploy production chỉ xảy ra khi commit nằm trên nhánh main, tránh việc mọi nhánh feature cũng deploy lên production.

Bước 8 — Commit và push. Ngay khi push, vào GitLab, mở mục CI/CD > Pipelines, bạn sẽ thấy pipeline đang chạy với biểu đồ trực quan: các cột là stage, các ô trong cột là job. Màu xanh là thành công, đỏ là thất bại, vàng là đang chạy.

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

Lỗi thụt lề YAML. Đây là "kẻ thù số một". YAML không chấp nhận tab, chỉ dùng space, và mọi cấp con phải thụt đều nhau. Mẹo: cấu hình editor (VS Code) hiển thị khoảng trắng và tự chuyển tab thành 2 space. GitLab có sẵn công cụ CI Lint (trong mục CI/CD > Editor hoặc Pipelines) để kiểm tra cú pháp file trước khi push — hãy dùng nó thường xuyên.

Nhầm stage với job. Như tình huống 3 ở trên: nếu bạn cần A chạy trước B, đừng để chung stage. Cùng stage = song song = không đảm bảo thứ tự.

Quên khai báo artifacts giữa các stage. Mỗi job chạy trong môi trường sạch. File job này tạo ra không tự động sang job khác. Muốn truyền, phải dùng artifacts ở job nguồn (khai báo paths) — job đích cùng pipeline sẽ tự động tải về.

Chạy lại npm ci ở mọi job gây chậm. Đây là điểm nhức nhối. Ở bài "Caching Strategies cho CI" bạn sẽ học cách dùng cache để không phải cài lại dependency mỗi lần. Hiện tại chỉ cần biết vấn đề tồn tại và đừng ngạc nhiên khi pipeline hơi chậm.

Job không tìm thấy runner (stuck). Nếu pipeline treo mãi ở trạng thái "pending", thường là không có runner nào phù hợp với tags bạn đặt, hoặc runner chưa được đăng ký. Kiểm tra mục Settings > CI/CD > Runners.

Mẹo dùng needs để phá vỡ thứ tự stage. Mặc định stage chạy tuần tự chặt chẽ. Nếu bạn muốn một job ở stage sau bắt đầu ngay khi một job cụ thể ở stage trước xong (không chờ cả stage), dùng từ khóa needs. Đây là kỹ thuật tối ưu tốc độ nâng cao, nhưng hãy nắm chắc mô hình stage tuần tự cơ bản trước.

Mẹo đặt tên job rõ ràng. Đặt tên job theo mô tả công việc (test-unit, deploy-staging) thay vì tên chung chung (job1, test). Khi pipeline đỏ, tên rõ ràng giúp bạn biết ngay chỗ nào hỏng.

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

  • Đọc hiểu pipeline. Cho danh sách stage - build, - test, - deploy và ba job: compile (stage build), unit và lint (cùng stage test), ship (stage deploy). Hãy viết ra thứ tự thực thi và chỉ rõ hai job nào chạy song song.
  • Viết file từ đầu. Tạo một .gitlab-ci.yml cho dự án Node.js với ba stage install → test → build. Job test phải chạy npm test, job build phải tạo và giữ lại thư mục dist/ bằng artifacts.
  • Sửa lỗi thiết kế. Bạn được giao một pipeline có job build-image và job push-image cùng nằm trong stage release, và push-image báo lỗi "không tìm thấy image". Hãy giải thích nguyên nhân và viết lại cấu trúc stage cho đúng.
  • Thêm điều kiện. Sửa job deploy để nó chỉ chạy trên nhánh main bằng rules, và thêm một job deploy-staging chỉ chạy trên nhánh develop.
  • Nâng cao. Dùng công cụ CI Lint của GitLab để kiểm tra file bạn viết ở bài 2, cố tình thêm một tab sai chỗ và quan sát thông báo lỗi để làm quen với cách GitLab báo lỗi cú pháp.

Tóm tắt

Trong bài này, bạn đã nắm được cấu trúc nền tảng của một pipeline GitLab CI/CD:

  • Mọi pipeline được định nghĩa trong một file duy nhất .gitlab-ci.yml ở thư mục gốc repository, viết bằng YAML — cẩn thận với thụt lề.
  • Công thức cốt lõi: Pipeline = Stages + Jobs. Stage chạy tuần tự (giai đoạn sau chờ giai đoạn trước xong và thành công), còn job trong cùng một stage chạy song song.
  • Mỗi job cần tối thiểu stage và script, thường kèm image, before_script, artifacts, và rules.
  • Việc chia stage tạo ra các rào chắn chất lượng không thể lách được; việc chia job hợp lý trong cùng stage tận dụng song song hóa để tăng tốc.
  • Nhầm lẫn giữa "cần chạy tuần tự" (phải tách stage) và "chạy song song được" (chung stage) là nguồn lỗi kinh điển — hãy luôn tự hỏi: hai công việc này có phụ thuộc nhau không?
Khi đã vững cấu trúc này, các bài tiếp theo về caching, artifact management, và deployment strategies sẽ chỉ là những viên gạch bạn xếp thêm lên nền móng vững chắc đã có. Hãy tự tay viết một .gitlab-ci.yml cho một dự án nhỏ của mình ngay hôm nay — không có cách học nào nhanh hơn việc nhìn pipeline chạy xanh trước mắt.

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