Triển khai Feature Flags với Unleash: ‘Cái phanh’ an toàn cho hệ thống mà không cần redeploy code

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Cơn ác mộng lúc 2 giờ sáng và cái giá của việc ‘Rollback bằng cơm’

Màn hình Slack đỏ rực thông báo từ Sentry. Dashboard Grafana thì các đường chart CPU nhảy dựng đứng như cột điện. Đó là kịch bản kinh hoàng mà mình trải qua tuần trước ngay sau khi nhấn nút “Merge” tính năng thanh toán mới.

Đồng hồ điểm 2 giờ sáng. Một lỗi logic trong xử lý khuyến mãi khiến hệ thống treo cứng. Cách duy nhất để cứu vãn là Rollback. Khổ nỗi, quy trình CI/CD của công ty mất tận 15 phút để build image, chạy test và đẩy lên Kubernetes. 15 phút hệ thống “chết lâm sàng” đồng nghĩa với việc hơn 2.000 đơn hàng bị hủy bỏ và uy tín sụt giảm nghiêm trọng.

Cảm giác bất lực nhìn server lỗi mà không thể làm gì ngoài việc đợi thanh progress bar của Jenkins chạy thực sự rất ám ảnh.

Gốc rễ vấn đề: Khi Deployment bị nhầm lẫn với Release

Sai lầm cốt lõi của chúng ta thường là để Logic nghiệp vụ và Quy trình triển khai (Deployment) dính chặt vào nhau.

Muốn bật tính năng, bạn deploy code. Muốn tắt khi có bug, bạn lại phải rollback code. Cách làm truyền thống này tiềm ẩn ba rủi ro lớn:

  • Phản ứng chậm chạp: Đợi CI/CD chạy là cực hình khi Production đang cháy.
  • Được ăn cả, ngã về không: Bạn buộc phải bung tính năng cho 100% người dùng thay vì test thử trên một nhóm nhỏ 5%.
  • Code rác nảy sinh: Những đoạn if (env === 'production') mọc lên như nấm, khiến codebase trở nên hỗn loạn.

Những giải pháp ‘chữa cháy’ thông thường có hiệu quả?

Nhiều anh em sẽ nghĩ ngay đến việc dùng Environment Variables. Sửa biến môi trường rồi restart pod. Cách này nhanh hơn deploy code nhưng vẫn bắt server khởi động lại, gây gián đoạn kết nối người dùng trong vài giây.

Số khác chọn lưu config trong Database. Hệ thống sẽ check DB mỗi khi user request. Cách này linh hoạt hơn nhưng lại vô tình tạo áp lực lên Database và làm tăng latency của hệ thống nếu thiếu cơ chế cache.

Unleash – Giải pháp tách biệt Deploy và Release

Rút kinh nghiệm từ “vết xe đổ” đó, mình quyết định setup hệ thống Feature Flags bài bản. Sau khi cân nhắc LaunchDarkly (chi phí khá chát, khoảng $75/tháng cho team nhỏ) và Flagsmith, mình chọn Unleash bản Open Source.

Unleash cho phép bạn đẩy code lên production bất cứ lúc nào, nhưng tính năng mới sẽ nằm im lìm sau một cái “công tắc”. Khi bạn tự tin, chỉ cần lên UI của Unleash gạt switch là xong. Mọi thay đổi có hiệu lực ngay lập tức mà không cần chạm vào một dòng code nào.

Tự dựng Unleash Server với Docker trong 5 phút

Để toàn quyền kiểm soát dữ liệu và tiết kiệm chi phí, mình chọn cách tự host bằng Docker Compose. Đây là phương án tối ưu nhất cho các team kỹ thuật.

# docker-compose.yml
version: "3.9"
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: unleash
      POSTGRES_PASSWORD: password
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres", "-d", "unleash"]
      interval: 2s
      timeout: 5s
      retries: 10

  unleash:
    image: unleashorg/unleash-server
    ports:
      - "4242:4242"
    environment:
      DATABASE_URL: postgres://postgres:password@db/unleash
      DATABASE_SSL: "false"
      DATABASE_MAX_CONNECTIONS: 20
      UNLEASH_SECRET: your-secure-secret
    depends_on:
      db:
        condition: service_healthy

Chạy lệnh docker-compose up -d, sau đó truy cập localhost:4242. Đăng nhập với user admin và pass unleash42 để bắt đầu quản lý flag.

Kết nối ứng dụng Node.js: Latency gần như bằng 0

Thay vì viết code cứng, mình bọc logic xử lý trong Unleash Client. Thư viện này cực kỳ thông minh vì nó fetch flag về và cache lại trong bộ nhớ ứng dụng.

Cài đặt thư viện rất đơn giản:

npm install unleash-client

Cấu hình trong code thực tế:

const { initialize, isEnabled } = require('unleash-client');

const unleash = initialize({
  url: 'http://your-unleash-api/api/',
  appName: 'payment-service',
  customHeaders: { Authorization: 'YOUR_API_TOKEN' },
});

async function processPayment(order) {
  // Check flag 'new-payment-gateway' trong memory
  if (isEnabled('new-payment-gateway')) {
    return useNewGateway(order);
  }
  return useOldGateway(order);
}

Vì check flag trực tiếp từ cache nên latency chỉ mất khoảng < 1ms. Nếu server Unleash gặp sự cố, client sẽ tự động dùng giá trị cache cuối cùng hoặc fallback về giá trị mặc định, đảm bảo hệ thống không bao giờ bị gián đoạn.

Kinh nghiệm thực chiến để tránh ‘Nợ kỹ thuật’

Feature Flags là công cụ mạnh mẽ nhưng nếu lạm dụng sẽ biến code thành một đống hỗn độn.

1. Quy tắc ‘Dọn dẹp sau tiệc’

Khi tính năng đã chạy ổn định 100% cho toàn bộ user, hãy xóa flag đó ngay. Đừng để code của bạn chứa đầy những đoạn if-else từ các tính năng đã ra mắt từ đời nào.

2. Chiến lược Rollout theo tỷ lệ (Gradual Rollout)

Thay vì bật cho tất cả, mình thường thử nghiệm cho 10% user dựa trên userId. Nếu metric ổn định, mình mới tăng dần lên 25%, 50% rồi 100%.

Mẹo nhỏ: Khi cần test nhanh các cấu hình JSON strategy phức tạp, mình hay dùng toolcraft.app/vi/tools/developer/json-formatter để format và kiểm tra dữ liệu trả về từ API Unleash cho chuẩn xác.

3. Đặt tên Flag theo mục đích

Hãy quên những cái tên vô nghĩa như test-flag-1. Hãy dùng format: [action]-[feature]-[version], ví dụ: enable-stripe-v3-migration để bất kỳ ai trong team nhìn vào cũng hiểu mục đích của nó.

Lời kết

Từ ngày áp dụng Unleash, mình đã có thể ngủ ngon hơn. Nếu có sự cố, mình chỉ cần mở điện thoại, truy cập dashboard và gạt nút OFF. Trong chưa đầy 1 giây, toàn bộ người dùng được đưa về phiên bản an toàn.

Nếu dự án của bạn đang mở rộng và tần suất deploy ngày càng dày, hãy triển khai Feature Flags ngay hôm nay. Đừng đợi đến lúc phải ngồi trực server giữa đêm mới thấy hối hận.

Share: