# Pipeline GitLab đầu tiên ở chỗ khách có thể hỏng dù YAML không sai

> Trên bản self-managed, thủ phạm thường là registry chưa bật hoặc runner không được chạy privileged, nên hãy hỏi trước khi viết.

Bản gốc: https://fdetimes.net/vi/cong-cu/gitlab-ci-cd-cho-fde-pipeline-trong-repo-cua-khach/

Một FDE đến chỗ khách hiếm khi được chọn công cụ CI. Doanh nghiệp đã tự host GitLab thường muốn mọi thứ chạy trong hạ tầng của họ: code, pipeline, image.

Vì thế, kỹ năng đáng giá là đọc nhanh GitLab của khách rồi viết pipeline chạy được ngay trong tuần đầu. Có ba mảnh bạn sẽ đụng vào: file pipeline, runner và registry. Mảnh nào cũng có chỗ hay vướng trên bản self-managed.

## Pipeline là một file, nhưng thứ tự mới là điều cốt lõi

Pipeline được khai báo trong `.gitlab-ci.yml`. Nếu không khai báo `stages`, GitLab dùng bộ mặc định `.pre`, `build`, `test`, `deploy`, `.post`. Thứ tự các mục trong `stages` quyết định job nào chạy trước.

Có hai tầng điều kiện. `workflow:rules` quyết định cả pipeline có được tạo ra không, và khi không rule nào đúng thì pipeline không chạy. Rules ở cấp job thì quyết định từng job có được tạo hay không.

Thử hình dung bạn đang deploy một API RAG cho đội dữ liệu của khách. Bạn chỉ muốn pipeline chạy trên nhánh main, test chạy trước, image được build và đẩy lên registry, cuối cùng mới đến deploy:

```yaml
workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

stages: [test, build, deploy]

unit-test:
  stage: test
  image: python:3.12
  script:
    - pip install -r requirements.txt
    - pytest -q

build-image:
  stage: build
  image: docker:27.3
  services:
    - docker:27.3-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  script:
    - echo "$CI_JOB_TOKEN" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
    - docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
    - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"

deploy:
  stage: deploy
  image: alpine:3.20
  script:
    - ./deploy.sh "$CI_COMMIT_SHORT_SHA"
```

Hãy chạy thử trong đầu. Một commit lên nhánh `feature/chunking` sẽ không tạo pipeline nào, vì rule duy nhất không đúng. Nếu bạn ngồi chờ pipeline xuất hiện mà nó không đến, chỗ đầu tiên cần nhìn là `workflow:rules`, không phải runner.

Job build dùng các biến có sẵn của GitLab để đăng nhập, thay vì gõ cứng một địa chỉ như `gitlab.khach.local:5050`. Cổng 5050 chỉ là mặc định khi registry tự bật nhờ tích hợp Let's Encrypt. Nếu admin của khách tự cấu hình registry, host và cổng rất có thể khác.

Tên image vẫn đi theo mẫu registry/namespace/project, nên nhìn tên là biết image thuộc project nào. Phiên bản image docker được ghim cố định và có bật TLS, đúng như GitLab khuyên khi dùng Docker-in-Docker.

## Runner nào sẽ chạy job của bạn?

File YAML tự nó không chạy được gì. Job được thực thi bởi runner, tức các agent chạy ứng dụng GitLab Runner. Phần mềm này mã nguồn mở, cấp phép MIT, phát triển công khai trong project gitlab-org/gitlab-runner.

Runner có ba phạm vi: instance, group và project. Project runner hợp khi một project cần cấu hình, credentials và lớp kiểm soát bảo mật riêng. Với FDE đang xử lý dữ liệu nhạy cảm của khách, đó thường là loại nên xin. Runner tự quản lý hỗ trợ ba executor: Shell, Docker và Kubernetes.

Docker executor chạy mỗi job trong một container riêng, tách biệt với các job khác. Image được pull theo pull policy khai báo trong `config.toml`, và mặc định là `always`.

Hãy đếm trên pipeline mẫu. Ba job dùng ba image, cộng thêm service `docker:27.3-dind` của job build, tức bốn image. Với policy `always`, mỗi lần push lên main là bốn lượt pull, và con số đó nhân lên nhanh khi cả đội cùng commit. Dependency proxy của GitLab có thể giảm áp lực giới hạn từ Docker Hub.

Khi khối lượng job lớn, Kubernetes executor là lựa chọn đáng cân nhắc, và hai trường hợp công khai cho thấy lý do. CERN chuyển runner từ Docker machine, công cụ đã bị Docker ngừng phát triển, sang Kubernetes và co giãn từ 0 đến 100 pod theo nhu cầu trên hơn 20 node.

SeatGeek thay các runner quy mô cố định chạy trên Nomad bằng Kubernetes executor, mỗi job một pod dùng xong bỏ. Họ chuyển hơn 600 repo, số job chạy đồng thời tăng gấp đôi và thời gian chờ trung bình giảm từ 16 giây xuống 2 giây.

## Ba cái bẫy trên bản self-managed

Bẫy đầu tiên là registry. Trên bản cài Omnibus, registry chỉ tự bật ở cổng 5050 khi dùng tích hợp Let's Encrypt có sẵn. Ngoài trường hợp đó, admin phải tự cấu hình và bật nó lên. Đừng viết job `docker push` trước khi biết registry đã chạy hay chưa.

Bẫy thứ hai là privileged mode. Job `build-image` ở trên dùng Docker-in-Docker, và cách này bắt runner chạy ở chế độ privileged. GitLab cảnh báo chế độ này gần như vô hiệu hóa các cơ chế bảo mật của container, khiến host có nguy cơ bị leo thang đặc quyền.

Ở một ngân hàng hay bệnh viện, câu trả lời rất có thể là không. Khi đó job build image phải được thiết kế lại cùng đội hạ tầng của khách, và bạn nên biết điều này trước khi hứa ngày giao hàng.

Bẫy thứ ba là tính năng phụ thuộc vào gói. Bảng so sánh của GitLab ghi một số tính năng, như Dependency Proxy cho Container Registry, Geo và khôi phục sau thảm họa, chỉ có trên các gói Self-Managed. Hãy hỏi khách đang dùng gói gì trước khi dựa vào chúng.

**Điểm mấu chốt:** Một bản sửa CI tốt không đẩy việc lặp đi lặp lại sang người khác.

Các Solutions Architect của GitLab cảnh báo rằng tối ưu tài nguyên runner cho một đội có thể chuyển việc vặt lặp lại sang lập trình viên. Câu hỏi họ đề xuất rất đáng mang theo: cách giải quyết này có tạo ra việc lặp lại cho ai không?

Với FDE, câu hỏi này còn nặng ký hơn. Sau khi bạn rời đi, khách là người phải sống chung với pipeline đó.

## Nên học gì trước?

Hãy học theo thứ tự bạn sẽ gặp ở chỗ khách. Đầu tiên là cú pháp `stages` và `rules`, vì như ví dụ nhánh `feature/chunking` cho thấy, lỗi "pipeline không chạy" thường bắt đầu từ đây. Sau đó là cách đăng ký một project runner với Docker executor. Cuối cùng là đọc `config.toml` và đặt tên image theo đúng mẫu của registry.

Khi phỏng vấn cho một vị trí FDE, hãy hỏi thẳng khách của công ty thường tự host công cụ đến mức nào. Câu trả lời cho bạn biết mình sẽ làm việc trong hạ tầng của ai, và cũng cho người phỏng vấn thấy bạn hiểu công việc thật.

Trên CV, một dòng cụ thể có sức nặng hơn danh sách công cụ, chẳng hạn: "viết pipeline GitLab ba stage chạy trên project runner riêng, thống nhất với đội bảo mật của khách về privileged mode trước khi build image".

Ở chỗ khách, pipeline đầu tiên còn là bài kiểm tra xem bạn có hiểu hạ tầng của họ hay không. Hãy hỏi kỹ về registry, runner và privileged mode trước khi viết dòng YAML đầu tiên.

**Thử ngay tuần này:**

- Dựng GitLab Runner với Docker executor trên máy cá nhân, đăng ký nó làm project runner rồi chạy pipeline ba stage trong bài
- Mở config.toml của runner, tìm pull policy đang dùng rồi đếm xem một pipeline phải pull bao nhiêu image, tính cả service
- Soạn sẵn năm câu hỏi cho admin GitLab của khách: registry bật chưa, chạy cổng nào, runner thuộc phạm vi nào, executor gì, có cho privileged không

## Nguồn

- [Runners | GitLab Docs](https://docs.gitlab.com/ci/runners/)

- [CI/CD YAML syntax reference | GitLab Docs](https://docs.gitlab.com/ci/yaml/)

- [GitLab container registry administration | GitLab Docs](https://docs.gitlab.com/administration/packages/container_registry/)

- [GitLab container registry | GitLab Docs](https://docs.gitlab.com/user/packages/container_registry/)

- [Docker executor | GitLab Runner docs](https://docs.gitlab.com/runner/executors/docker/)

- [Use Docker-in-Docker | GitLab Docs](https://docs.gitlab.com/ci/docker/docker_in_docker/)

- [GitLab Runner project (gitlab-org/gitlab-runner)](https://gitlab.com/gitlab-org/gitlab-runner)

- [Self-Managed Feature Comparison | GitLab](https://about.gitlab.com/pricing/feature-comparison/)

- [GitLab Runners and Kubernetes: A Powerful Duo for CI/CD](https://kubernetes.web.cern.ch/blog/2023/12/06/gitlab-runners-and-kubernetes-a-powerful-duo-for-ci/cd/)

- [CI Runner Re-architecture (SeatGeek Chairnerd)](https://chairnerd.seatgeek.com/ci-runner-rearchitecture)

- [A story of runner scaling | GitLab Blog](https://about.gitlab.com/blog/a-story-of-runner-scaling/)
