Product Management
Đăng nhập
ESC

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

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

Bài 50 — Pipeline Cost Optimization

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

Có một sự thật mà nhiều đội DevOps chỉ nhận ra khi đã quá muộn: pipeline CI/CD không hề miễn phí. Mỗi lần bạn git push, mỗi lần một Pull Request được mở, mỗi lần một job chạy test song song trên mười runner — tất cả đều được quy ra tiền. Ở giai đoạn đầu, khi đội nhỏ và số commit ít, hóa đơn chỉ vài chục đô mỗi tháng nên không ai để ý. Nhưng khi công ty tăng trưởng, số lượng developer nhân đôi, số microservice nhân ba, tần suất deploy nhân năm, thì con số đó có thể phình lên hàng nghìn, thậm chí hàng chục nghìn đô mỗi tháng — âm thầm, đều đặn, và gần như không ai chịu trách nhiệm.

Tôi từng chứng kiến một startup fintech ở TP.HCM giật mình khi nhận hóa đơn GitHub Actions 4.200 USD trong một tháng, gấp bốn lần bình thường, chỉ vì một developer vô tình cấu hình workflow chạy full test suite trên macos runner (đắt gấp 10 lần Linux) cho mỗi lần push nhánh feature. Không ai cố tình phá, cũng chẳng có tấn công nào — chỉ là không ai nhìn vào chi phí.

Bài học này dạy bạn cách nhìn pipeline dưới lăng kính tài chính: xác định đâu là chỗ "đốt tiền", đo lường được chi phí, và áp dụng các kỹ thuật tối ưu mà không hy sinh tốc độ hay chất lượng. Đây là một kỹ năng ngày càng được săn đón, vì nó nằm ở giao điểm của kỹ thuật và kinh doanh — thứ mà một kỹ sư DevOps giỏi cần có để nói chuyện với cả team lead lẫn CFO.

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

Để tối ưu chi phí, trước hết phải hiểu tiền chảy đi đâu. Chi phí của một pipeline CI/CD thường đến từ ba nhóm chính, mà tôi gọi vui là "ba cái vòi nước": compute (giờ chạy), storage (lưu trữ) và egress (băng thông ra).

Cost driver 1 — CI minute (giờ compute)

Đây thường là khoản lớn nhất. Các nền tảng tính tiền theo phút chạy của runner. Với GitHub Actions, giá niêm yết cho runner Linux x64 tiêu chuẩn (2 vCPU) là 0,008 USD/phút — tức khoảng 0,48 USD/giờ. Nghe rẻ, nhưng hãy nhân lên: một job build mất 8 phút, chạy 200 lần/ngày, tức 1.600 phút/ngày, khoảng 12,8 USD/ngày chỉ cho một job. Nhân với 30 ngày và hàng chục job khác, con số leo thang rất nhanh.

Điều quan trọng cần nhớ là hệ số nhân theo loại runner. GitHub tính:

  • Linux x64: hệ số 1 (0,008 USD/phút)
  • Windows: hệ số 2 (đắt gấp đôi)
  • macOS: hệ số 10 (đắt gấp mười — vì Apple silicon/hardware đắt)
Ngoài ra runner càng nhiều core (4, 8, 16, 32 vCPU) thì đơn giá càng cao theo tỷ lệ. Một runner 16-core Linux có thể đắt gấp 8 lần runner 2-core. GitLab và các nền tảng khác có cơ chế tương tự với đơn vị "compute minutes" nhân hệ số.

Cost driver 2 — Storage (lưu trữ)

Ba loại dữ liệu ngốn storage: artifacts (file build đầu ra như file .jar, .zip, binary), cache (dependency như node_modules, .m2, layer Docker) và logs (log của từng job). GitHub cho một hạn mức miễn phí rồi tính tiền phần vượt (khoảng 0,25 USD/GB/tháng cho artifact/storage). Vấn đề lớn nhất là artifact không có retention hợp lý: mặc định GitHub giữ artifact 90 ngày. Một pipeline tạo artifact 500 MB mỗi lần build, chạy 100 lần/ngày, giữ 90 ngày, sẽ tích lũy hàng terabyte rác.

Cost driver 3 — Egress (băng thông ra)

Egress là dữ liệu đi ra khỏi mạng nhà cung cấp cloud. Khi runner kéo image Docker khổng lồ từ registry ở vùng khác, đẩy artifact sang S3 ở region khác, hay deploy build ra CDN, bạn trả tiền băng thông. AWS tính egress khoảng 0,09 USD/GB — nghe nhỏ nhưng một image 2 GB kéo 500 lần/ngày là 1 TB/ngày, tức 90 USD/ngày chỉ riêng băng thông.

Nguyên tắc nền tảng: bạn không thể tối ưu thứ bạn không đo được

Trước khi cắt giảm bất cứ thứ gì, phải có visibility — khả năng nhìn thấy chi phí theo từng chiều: theo repo, theo workflow, theo team, theo loại runner. GitHub cung cấp báo cáo usage dạng CSV; các công ty lớn thường đẩy dữ liệu này vào một dashboard (Grafana, Metabase) để phân bổ chi phí. Không đo được thì mọi nỗ lực tối ưu chỉ là đoán mò.

Tình huống thực tế

Ví dụ 1 — Startup fintech TP.HCM và cái bẫy macOS runner

Một startup fintech khoảng 25 kỹ sư ở Quận 1 phát triển ứng dụng ví điện tử, có cả backend (Node.js) lẫn app React Native. Hóa đơn GitHub Actions của họ bỗng nhảy từ ~1.000 USD lên 4.200 USD/tháng.

Điều tra ra: đội mobile cần build iOS nên dùng macos-latest runner (hệ số 10). Ban đầu chỉ chạy khi merge vào main. Nhưng một kỹ sư "tiện tay" đổi trigger thành chạy trên mọi push của mọi nhánh, kèm cả job chạy full test suite trên macOS (dù test đó chạy được trên Linux). Kết quả: 6.000 phút macOS/tháng × 0,08 USD = 480 USD chỉ cho phần test lẽ ra nên nằm trên Linux (48 USD).

Bài học: Chỉ dùng macOS cho những gì bắt buộc phải chạy trên macOS (build/sign iOS). Mọi thứ khác — lint, unit test, backend build — đẩy về Linux. Và không bao giờ để runner đắt tiền chạy trên mọi nhánh; giới hạn bằng if condition hoặc paths filter. Sau khi tách, hóa đơn của họ về lại 1.100 USD.

Ví dụ 2 — Công ty e-commerce và núi artifact 8 TB

Một sàn thương mại điện tử tầm trung ở Hà Nội (khoảng 60 kỹ sư, kiến trúc microservices với ~40 service) phát hiện khoản storage GitHub tăng đều đặn mỗi tháng, cuối cùng chạm 8 TB, tốn khoảng 2.000 USD/tháng chỉ cho lưu trữ.

Nguyên nhân: mỗi service tạo Docker image tarball ~700 MB làm artifact để "dễ debug", giữ mặc định 90 ngày, và không ai từng xóa. Ngoài ra họ upload cả thư mục coverage/ và dist/ như artifact cho mọi PR — dữ liệu gần như không bao giờ được tải về lần thứ hai.

Bài học: Đặt retention-days ngắn (1–7 ngày cho artifact debug, 30 ngày cho release thật sự). Đừng dùng CI artifact để lưu Docker image — đã có container registry chuyên dụng cho việc đó. Sau khi dọn dẹp và đặt retention 3 ngày cho artifact tạm, storage của họ tụt xuống dưới 400 GB, tiết kiệm hơn 1.900 USD/tháng.

Ví dụ 3 — Đội SaaS và cache kéo tụt thời gian build một nửa

Một công ty SaaS làm phần mềm quản lý nhân sự (khoảng 15 kỹ sư, Đông Nam Á) có monorepo frontend TypeScript. Mỗi CI job đều chạy npm install từ đầu, mất trung bình 4 phút chỉ để tải dependency. Với 300 lần chạy/ngày, đó là 1.200 phút/ngày × 0,008 = 9,6 USD/ngày chỉ để... tải lại thứ y hệt.

Họ thêm actions/cache cho ~/.npm và node_modules, khóa cache theo hash của package-lock.json. Thời gian cài dependency tụt từ 4 phút xuống 40 giây. Tổng thời gian mỗi job giảm gần một nửa. Vừa tiết kiệm ~4 USD/ngày (~120 USD/tháng), vừa rút ngắn feedback loop cho developer — một mũi tên trúng hai đích.

Bài học: Caching không chỉ để build nhanh hơn; nó trực tiếp cắt giảm số phút compute phải trả tiền. (Chi tiết kỹ thuật caching đã được học ở Bài 11; ở đây ta nhìn nó dưới góc độ chi phí.)

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

Dưới đây là quy trình tối ưu chi phí pipeline mà tôi khuyên bạn áp dụng theo thứ tự — vì bước 1 (đo lường) là nền tảng cho mọi quyết định sau.

Bước 1 — Đo lường và phân bổ chi phí. Trước khi cắt bất cứ thứ gì, hãy lấy báo cáo usage. Trên GitHub, vào Settings → Billing → Usage, xuất CSV. Xác định top 5 workflow tốn phút nhất, top 5 repo tốn storage nhất. Quy tắc 80/20 gần như luôn đúng: 20% workflow ngốn 80% chi phí. Tập trung vào đó.

Bước 2 — Sửa runner cho đúng. Rà soát mọi job dùng macos hoặc windows runner. Hỏi: job này có thật sự cần OS đó không? Chuyển tất cả những gì có thể về Linux. Rà soát runner nhiều core: một job lint đơn giản không cần 16 vCPU.

Bước 3 — Ngừng chạy những gì không cần. Đây là khoản tiết kiệm lớn nhất mà nhiều người bỏ qua. Dùng paths filter để job chỉ chạy khi file liên quan thay đổi — không build lại toàn bộ backend khi chỉ sửa một file README:

on:
  pull_request:
    paths:
      - 'src/backend/**'
      - 'package.json'

Thêm concurrency để hủy các lần chạy cũ khi có push mới trên cùng nhánh, tránh chạy song song vô ích:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Bước 4 — Bật caching cho dependency. Cache node_modules, .m2, ~/.gradle, layer Docker (dùng cache-from/cache-to với BuildKit). Khóa cache theo hash của lockfile để đảm bảo đúng.

Bước 5 — Đặt retention cho artifact và log. Với artifact tạm, đặt retention-days: 3. Với release chính thức, 30 ngày là đủ. Đừng dùng artifact để lưu Docker image — hãy đẩy vào registry.

Bước 6 — Giảm egress. Đặt registry, runner và storage trong cùng một region/cloud để dữ liệu di chuyển nội bộ (thường miễn phí). Cân nhắc pull-through cache cho image cơ sở. Với self-hosted runner (Bài 25), egress trong cùng VPC gần như bằng 0.

Bước 7 — Cân nhắc self-hosted runner cho khối lượng lớn. Khi số phút CI vượt một ngưỡng nhất định (thường vài trăm nghìn phút/tháng), runner tự host trên máy ảo đã có sẵn hoặc spot instance có thể rẻ hơn nhiều so với giá niêm yết. Nhưng nhớ tính cả chi phí vận hành và bảo mật.

Bước 8 — Thiết lập giám sát và cảnh báo. Đặt budget alert: khi chi phí vượt X% so với dự kiến, hệ thống gửi thông báo (Slack, email). Điều này giúp bắt sự cố kiểu "macOS runner trên mọi nhánh" trong ngày thay vì cuối tháng.

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

Lỗi 1 — Tối ưu quá đà, hy sinh developer experience. Cắt giảm mù quáng để tiết kiệm 50 USD nhưng khiến 30 developer chờ CI thêm 5 phút mỗi lần là lỗ nặng. Một giờ của developer đắt hơn nhiều giờ compute. Luôn cân bằng chi phí máy với chi phí người.

Lỗi 2 — Quên hệ số nhân của runner. Rất nhiều người tưởng "10 phút macOS cũng như 10 phút Linux". Không — nó đắt gấp 10. Luôn kiểm tra hệ số trước khi chọn runner.

Lỗi 3 — Dùng CI artifact như kho lưu trữ vĩnh viễn. Artifact CI dành cho dữ liệu tạm giữa các job, không phải để lưu build history. Dùng registry (cho image) hoặc object storage rẻ hơn (S3, R2) cho lưu trữ dài hạn.

Lỗi 4 — Không có concurrency control. Khi developer push liên tục 5 commit trong 2 phút, không có cancel-in-progress nghĩa là bạn trả tiền cho 5 lần chạy dù chỉ lần cuối có ý nghĩa.

Mẹo hay:

  • Fail fast: Đặt job nhanh và rẻ (lint, format check) chạy trước. Nếu lint fail, không cần chạy full test suite tốn kém.
  • Matrix có chọn lọc: Test trên 8 phiên bản Node × 3 OS = 24 job. Hỏi xem bạn thật sự cần bao nhiêu tổ hợp. Có thể chạy full matrix chỉ khi merge vào main, còn PR chỉ chạy tổ hợp phổ biến nhất.
  • Egress miễn phí trong cùng cloud: Cloudflare R2 không tính egress — một lựa chọn tốt để lưu artifact/cache nếu bạn kéo lại nhiều.
  • Spot/preemptible instance cho self-hosted runner: rẻ hơn 60–90% cho các job không quan trọng về độ trễ.

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

  • Audit chi phí. Lấy báo cáo usage GitHub Actions (hoặc GitLab) của một dự án bạn đang làm (hoặc một repo mẫu). Xác định: workflow nào tốn nhiều phút nhất? Có job nào chạy trên runner đắt tiền không cần thiết không? Viết ra 3 khoản tiết kiệm tiềm năng kèm ước tính con số.
  • Thêm paths filter và concurrency. Lấy một workflow bất kỳ, thêm paths filter để nó chỉ chạy khi code liên quan thay đổi, và thêm block concurrency với cancel-in-progress: true. Push vài commit liên tiếp và quan sát các lần chạy cũ bị hủy.
  • Bật caching và đo. Thêm actions/cache cho dependency của dự án. Ghi lại thời gian job trước và sau khi có cache. Tính ra số phút tiết kiệm mỗi tháng nếu job chạy 100 lần/ngày, rồi quy ra tiền theo giá 0,008 USD/phút.
  • Tính toán so sánh. Giả sử pipeline của bạn tiêu 400.000 phút Linux/tháng. Tính chi phí ở mức giá niêm yết. Sau đó ước tính chi phí nếu chuyển 60% khối lượng đó sang self-hosted runner trên spot instance với giá tương đương ~0,003 USD/phút (đã gồm vận hành). Khoản chênh lệch có đủ lớn để đáng công không?

Tóm tắt

Pipeline CI/CD tiêu tiền theo ba vòi chính: compute (CI minute, nhớ hệ số nhân — Linux ×1, Windows ×2, macOS ×10), storage (artifact, cache, log — kiểm soát bằng retention) và egress (băng thông ra — giảm bằng cách giữ mọi thứ trong cùng region/cloud). Nguyên tắc gốc là không thể tối ưu thứ không đo được, nên bước đầu tiên luôn là lấy báo cáo usage và phân bổ chi phí theo 80/20.

Các đòn bẩy tiết kiệm mạnh nhất, theo thứ tự thường gặp: chọn đúng loại runner, ngừng chạy job không cần thiết (paths filter, concurrency), bật caching cho dependency, đặt retention ngắn cho artifact, và cân nhắc self-hosted runner khi khối lượng đủ lớn. Nhưng đừng bao giờ quên nguyên tắc vàng: giờ của developer đắt hơn giờ của máy — tối ưu chi phí compute không được phép làm chậm feedback loop hay hạ chất lượng. Một kỹ sư DevOps giỏi tối ưu chi phí như một bác sĩ giỏi kê thuốc: đúng chỗ, đúng liều, và luôn đo lại kết quả.

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