Product Management
Đăng nhập
ESC

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

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

Bài 20 — Feature Flags trong CI/CD

Đã đọc 0/15 mục · còn khoảng 12 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 là kỹ sư ở một sàn thương mại điện tử. Đội của bạn đã mất ba tuần để xây tính năng "thanh toán trả góp". Code đã xong, đã review, đã merge. Nhưng sếp yêu cầu: "Tính năng này chỉ được bật đúng 10 giờ sáng ngày khai trương chiến dịch, và ban đầu chỉ mở cho khách ở Hà Nội và TP.HCM để xem hệ thống chịu tải ra sao."

Nếu không có công cụ phù hợp, bạn sẽ kẹt. Bạn không thể merge code vì sợ nó "lộ" ra sớm. Bạn phải giữ một nhánh (branch) sống dai dẳng, mỗi ngày lại đi giải quyết conflict. Đến ngày khai trương, bạn phải chạy deploy vào đúng 10 giờ sáng — trong lúc cả team hồi hộp nhìn màn hình, và nếu có lỗi thì phải rollback cả bản build.

Feature Flags (cờ tính năng) sinh ra để giải quyết chính xác vấn đề này. Nó là kỹ thuật cho phép bạn tách rời việc deploy code khỏi việc release tính năng cho người dùng. Code có thể nằm sẵn trên production nhưng "tắt", và bạn chỉ cần lật một công tắc để bật nó lên — không cần deploy lại, không cần rebuild.

Trong toàn bộ khóa học này, đây là bài học về một trong những kỹ thuật quan trọng nhất giúp CI/CD hiện đại trở nên an toàn và linh hoạt. Nắm được nó, bạn sẽ hiểu vì sao các công ty như Facebook, GrabPay hay Shopee có thể "deploy nhiều lần mỗi ngày" mà không làm sập trải nghiệm người dùng.

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

Feature Flag là gì?

Về bản chất, một feature flag chỉ là một câu lệnh điều kiện trong code:

if (featureFlags.isEnabled('checkout_installment', user)) {
  renderInstallmentOption();
} else {
  renderNormalCheckout();
}

Điểm mấu chốt không nằm ở câu if — nó nằm ở chỗ giá trị của flag được quyết định ở bên ngoài code, thường là qua một dashboard hoặc một dịch vụ cấu hình. Bạn có thể thay đổi hành vi của ứng dụng đang chạy trên production mà không cần chạm vào code hay deploy lại.

Decouple deploy khỏi release — trái tim của kỹ thuật này

Đây là ý tưởng quan trọng nhất bạn cần khắc cốt ghi tâm. Trong mô hình truyền thống, "deploy" và "release" là một: khi bạn đẩy code lên production, người dùng lập tức thấy tính năng mới. Điều này gộp hai rủi ro rất khác nhau vào cùng một thời điểm:

  • Rủi ro kỹ thuật của việc deploy: build có chạy được không, migration có lỗi không, service có khởi động được không.
  • Rủi ro nghiệp vụ của việc release: người dùng có thích tính năng không, nó có gây tải bất thường không, thời điểm ra mắt đã đúng chưa.
Feature flags cho phép tách hai rủi ro này ra. Bạn deploy code (đã tắt) vào bất kỳ lúc nào an toàn, ví dụ 2 giờ chiều thứ Tư khi mọi người còn tỉnh táo. Sau đó bạn release (bật flag) theo lịch nghiệp vụ, có thể là 10 giờ sáng ngày khai trương. Nếu có sự cố, bạn tắt flag ngay lập tức — nhanh hơn nhiều so với rollback cả bản build.

Feature flags và Trunk-based Development

Feature flags là "người bạn song sinh" của trunk-based development (một chiến lược branching mà bài khác trong khóa sẽ đi sâu). Ý tưởng là: thay vì giữ một nhánh feature sống hàng tuần, bạn merge code chưa hoàn thiện vào nhánh chính hàng ngày — nhưng bọc nó trong một flag đang tắt.

Nhờ đó, code luôn được tích hợp liên tục (đúng tinh thần chữ "CI"), conflict được giải quyết sớm khi còn nhỏ, và bạn không bao giờ rơi vào cảnh "merge hell". Không có feature flags, trunk-based development gần như bất khả thi với các tính năng lớn.

Các loại feature flag

Không phải flag nào cũng giống nhau. Phân biệt được chúng giúp bạn quản lý vòng đời tốt hơn:

  • Release flags (cờ ra mắt): dùng để bật/tắt tính năng mới. Sống ngắn — bật xong, ổn định rồi thì gỡ đi.
  • Ops flags (cờ vận hành): dùng như "kill switch" để tắt nhanh một tính năng nặng khi hệ thống quá tải. Sống lâu hơn.
  • Experiment flags (cờ thử nghiệm): dùng cho A/B testing — chia người dùng thành nhóm để so sánh.
  • Permission flags (cờ phân quyền): mở tính năng cho một nhóm khách nhất định, ví dụ khách hàng VIP hay khách beta.

Targeting — bật cho ai, khi nào

Điều làm feature flags mạnh mẽ là khả năng nhắm mục tiêu (targeting). Bạn không chỉ bật/tắt cho tất cả, mà có thể bật theo phần trăm người dùng (10%, rồi 50%, rồi 100%), theo vùng địa lý, theo loại tài khoản, theo phiên bản app... Đây chính là nền tảng cho canary deployment và progressive delivery mà các bài sau sẽ khai thác sâu hơn.

Tình huống thực tế

Ví dụ 1 — Sàn TMĐT Việt Nam ra mắt "ví trả sau"

Một sàn thương mại điện tử tại TP.HCM (gọi là ShopViet) muốn ra mắt tính năng "ví trả sau" (buy-now-pay-later). Đây là tính năng nhạy cảm về tài chính, đội ngũ rất lo lắng về rủi ro.

Họ dùng feature flag với chiến lược mở dần:

  • Tuần 1: Bật flag chỉ cho 50 nhân viên nội bộ (targeting theo email công ty). Team QA test trên chính production thật.
  • Tuần 2: Bật cho 1% người dùng thật ở TP.HCM. Con số nhỏ giúp nếu có lỗi tính lãi suất thì chỉ ảnh hưởng vài trăm người.
  • Tuần 3: Vào thứ Sáu, một bug được phát hiện: hệ thống tính sai hạn mức cho khách có nhiều đơn hàng đang treo. Thay vì họp khẩn lúc nửa đêm để rollback, một kỹ sư trực chỉ cần vào dashboard và tắt flag trong 15 giây. Không một khách hàng mới nào bị ảnh hưởng thêm.
  • Tuần 4: Sau khi sửa bug và deploy bản mới (flag vẫn tắt), họ bật lại và tăng dần lên 10%, 50%, rồi 100% toàn quốc.
Bài học rút ra: Feature flag biến một sự cố có thể thành khủng hoảng thành một cú "tắt công tắc" bình thản. Bán kính ảnh hưởng (blast radius) được kiểm soát ở mọi bước.

Ví dụ 2 — Startup fintech và kill switch cứu ngày Black Friday

Một startup fintech ở Đông Nam Á cung cấp cổng thanh toán. Trong ngày cao điểm mua sắm (giả định lưu lượng gấp 8 lần ngày thường), họ có một tính năng mới: "gợi ý phương thức thanh toán bằng AI", vốn gọi tới một service machine learning khá nặng.

Khi lưu lượng tăng vọt lúc 12 giờ trưa, service ML này bắt đầu chậm và kéo theo cả luồng thanh toán chậm theo, thời gian phản hồi tăng từ 200ms lên 3 giây. Đội SRE lập tức kích hoạt ops flag để tắt tính năng gợi ý AI. Luồng thanh toán ngay lập tức trở về mượt mà, vì code fallback (không có AI) đã nằm sẵn trong nhánh else của flag.

Điểm hay: họ đã chuẩn bị flag này như một kill switch ngay từ đầu, dù tính năng chạy tốt trong lúc test. Đây là tư duy "thiết kế để có thể tắt".

Bài học rút ra: Với những tính năng phụ thuộc tài nguyên nặng hoặc dịch vụ bên ngoài, hãy luôn bọc chúng trong một ops flag để có thể tắt khi hệ thống nguy cấp. Flag không chỉ để ra mắt — nó còn là công cụ vận hành.

Ví dụ 3 — Team sản phẩm dùng flag để A/B test nút "Đặt hàng"

Một công ty giao đồ ăn muốn biết: đổi màu nút "Đặt hàng" từ cam sang xanh lá có làm tăng tỷ lệ chuyển đổi không? Thay vì tranh cãi trong phòng họp, họ dùng experiment flag chia người dùng thành hai nhóm 50/50.

Sau hai tuần, dữ liệu cho thấy nhóm nút xanh có tỷ lệ hoàn tất đơn cao hơn 3,2%. Họ quyết định bật flag xanh cho 100% người dùng. Toàn bộ quá trình không cần một lần deploy nào giữa các thay đổi cấu hình — cả hai phiên bản UI đã nằm sẵn trong code.

Bài học rút ra: Feature flag không chỉ là công cụ kỹ thuật, mà còn là công cụ ra quyết định dựa trên dữ liệu. Nó cho phép "thử rồi mới quyết", giảm rủi ro của những phán đoán cảm tính.

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

Dưới đây là quy trình đưa feature flag vào pipeline CI/CD của bạn.

Bước 1 — Chọn công cụ quản lý flag. Với dự án nhỏ, bạn có thể bắt đầu bằng một file cấu hình JSON hay biến môi trường. Khi trưởng thành hơn, hãy dùng dịch vụ chuyên dụng như LaunchDarkly, Unleash (mã nguồn mở, tự host được — hợp với công ty muốn kiểm soát dữ liệu), Flagsmith hay ConfigCat. Ưu điểm của dịch vụ chuyên dụng: dashboard trực quan, targeting mạnh, ghi log audit ai bật/tắt flag lúc nào.

Bước 2 — Bọc code mới trong flag. Khi phát triển tính năng, viết code trong nhánh if (flag). Đảm bảo nhánh else (hành vi cũ) vẫn hoạt động đầy đủ — đây là "đường lui" của bạn.

if flags.is_enabled("new_search_ranking", context=user):
    results = new_ranking_algorithm(query)
else:
    results = legacy_ranking(query)

Bước 3 — Merge sớm và thường xuyên. Vì flag đang tắt mặc định, bạn có thể merge code chưa hoàn thiện vào nhánh chính hàng ngày. Pipeline CI chạy test cho cả hai nhánh của flag.

Bước 4 — Deploy với flag tắt. Code lên production nhưng người dùng chưa thấy gì. Đây là deploy "im lặng", rủi ro thấp.

Bước 5 — Bật dần (progressive rollout). Mở flag cho nội bộ trước, rồi 1%, 10%, 50%, 100%. Ở mỗi mốc, theo dõi các chỉ số (metrics) như tỷ lệ lỗi, độ trễ, tỷ lệ chuyển đổi. Nếu xấu, tắt ngay.

Bước 6 — Dọn dẹp flag (cleanup). Đây là bước hay bị bỏ quên nhất. Khi tính năng đã ổn định 100% và bạn chắc chắn không quay lại phiên bản cũ, hãy gỡ flag và nhánh code chết. Đặt một task trong sprint để làm việc này, coi nó như một phần bắt buộc của vòng đời flag.

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

Lỗi 1 — Flag "sống mãi không chết" (flag debt). Đây là cạm bẫy phổ biến nhất. Team bật flag lên 100% rồi quên gỡ. Sau một năm, codebase đầy những if vô nghĩa, mỗi flag nhân đôi số đường đi của code, khiến test và bảo trì thành ác mộng. Mẹo: Đặt ngày hết hạn cho mỗi flag ngay khi tạo. Nhiều công cụ có tính năng cảnh báo flag quá hạn. Một số team còn viết test tự động fail nếu flag tồn tại quá 90 ngày.

Lỗi 2 — Kết hợp flag chồng chéo không kiểm soát. Nếu bạn có 10 flag, về lý thuyết có tới 2^10 = 1024 tổ hợp trạng thái. Không ai test hết được. Mẹo: Giữ số flag đang hoạt động ở mức tối thiểu, tránh để nhiều flag phụ thuộc lẫn nhau. Ưu tiên flag độc lập.

Lỗi 3 — Không có hành vi mặc định an toàn. Nếu dịch vụ flag bị sập hoặc mạng lỗi, ứng dụng của bạn phải làm gì? Mẹo: Luôn định nghĩa giá trị mặc định (thường là "tắt", tức hành vi cũ đã được kiểm chứng) khi không gọi được dịch vụ flag. Đừng để cả ứng dụng chết chỉ vì không đọc được một flag.

Lỗi 4 — Đưa logic nghiệp vụ phức tạp vào trong flag. Flag nên trả lời câu hỏi "bật hay tắt", không nên chứa cả một cây quyết định phức tạp. Mẹo: Giữ điều kiện flag đơn giản, dễ đọc, dễ xóa.

Lỗi 5 — Quên rằng flag cũng là một điểm rủi ro bảo mật. Một permission flag bị cấu hình sai có thể mở tính năng trả phí cho người không trả tiền. Mẹo: Kiểm tra targeting kỹ, ghi log mọi thay đổi flag, và giới hạn ai có quyền bật/tắt flag trên production.

Mẹo tổng quát: Đặt tên flag rõ ràng và có quy ước, ví dụ release_checkout_installment_2026q1. Tên tốt giúp người sau biết flag này là gì, thuộc loại nào, và khi nào nên gỡ.

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

  • Cài đặt cơ bản: Dùng Unleash (bản Docker mã nguồn mở) hoặc một file JSON cấu hình đơn giản, tạo một flag tên show_new_banner. Viết một ứng dụng web nhỏ hiển thị banner khác nhau tùy trạng thái flag. Thử bật/tắt mà không restart ứng dụng.
  • Targeting theo phần trăm: Mở rộng flag ở bài 1 để chỉ bật cho 25% người dùng dựa trên user ID. Kiểm chứng bằng cách chạy thử 100 user ID giả và đếm xem có khoảng 25 người thấy banner mới không.
  • Thiết kế kill switch: Chọn một tính năng gọi API bên ngoài trong dự án của bạn. Bọc nó trong một ops flag với nhánh else là hành vi fallback an toàn. Viết ra kịch bản: "Khi nào tôi sẽ tắt flag này?"
  • Lập kế hoạch cleanup: Liệt kê các flag (thật hoặc giả định) trong một dự án. Với mỗi flag, ghi rõ: loại flag, ngày dự kiến gỡ, và người chịu trách nhiệm. Đây là bài tập tư duy quan trọng để tránh flag debt.

Tóm tắt

Feature flags là kỹ thuật cho phép tách rời deploy khỏi release: code có thể nằm sẵn trên production nhưng tắt, và bạn bật nó lên khi sẵn sàng — không cần deploy lại. Đây là nền tảng giúp trunk-based development khả thi, vì bạn có thể merge code chưa hoàn thiện hàng ngày mà không sợ nó lộ ra sớm.

Có nhiều loại flag: release, ops (kill switch), experiment và permission — mỗi loại có vòng đời và mục đích riêng. Sức mạnh thực sự nằm ở khả năng targeting: bật dần theo phần trăm, theo vùng, theo nhóm người dùng, giúp kiểm soát bán kính ảnh hưởng.

Qua các tình huống thực tế, bạn thấy flag không chỉ để ra mắt tính năng an toàn (ShopViet mở dần ví trả sau), mà còn là công cụ vận hành cứu nguy khi quá tải (kill switch ngày Black Friday) và công cụ ra quyết định dựa trên dữ liệu (A/B test nút đặt hàng).

Cạm bẫy lớn nhất là flag debt — flag không được dọn dẹp. Hãy luôn đặt ngày hết hạn, giữ số flag tối thiểu, định nghĩa hành vi mặc định an toàn, và coi việc gỡ flag là một phần bắt buộc của vòng đời. Làm được vậy, feature flags sẽ là một trong những công cụ mạnh nhất giúp bạn deploy tự tin và release có kiểm soá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