DockerでUnleashをデプロイ:ゼロダウンタイムの機能制御とA/BテストのためのセルフホストFeature Flags

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

クイックスタート:5分でUnleashを起動する

夜中の2時、チームメンバーが壊れた決済機能を本番環境にプッシュした。ホットフィックスのテストと再デプロイには45分かかる。Feature Flagsもなかった。キルスイッチもなかった。3,000人のユーザーが壊れたチェックアウトページにアクセスし、Slackチャンネルは阿鼻叫喚の状態だった。

その夜、週が終わる前にUnleashを設定することを自分に誓った。ここでは、実際に設定した内容と、6時間前から動かしておきたかったものを詳しく紹介する。

Unleashはオープンソースのfeature flagプラットフォームだ。Dockerへのセルフホストはわずか5分で完了する。フル機能のUI、REST API、主要な全言語向けSDKが揃っており—すべて無料で、自分のインフラ上で動かせる。

まずDockerとDocker Composeを準備して、以下をdocker-compose.ymlに書き込む:

version: '3.8'

services:
  unleash_db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: unleash
      POSTGRES_USER: unleash
      POSTGRES_PASSWORD: secret123
    volumes:
      - unleash_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U unleash"]
      interval: 10s
      timeout: 5s
      retries: 5

  unleash:
    image: unleashorg/unleash-server:latest
    ports:
      - "4242:4242"
    environment:
      DATABASE_URL: postgres://unleash:secret123@unleash_db/unleash
      INIT_FRONTEND_API_TOKENS: default:development.unleash-insecure-frontend-api-token
      INIT_CLIENT_API_TOKENS: default:development.unleash-insecure-client-api-token
    depends_on:
      unleash_db:
        condition: service_healthy

volumes:
  unleash_data:

起動する:

docker compose up -d

http://localhost:4242にアクセスする。デフォルト認証情報:admin / unleash4all。これでfeature flagシステムが動いている。クイックスタートはここまで。

詳細解説:Unleashの実際の仕組みを理解する

アーキテクチャをわかりやすく解説する

システムは3つのコンポーネントで構成されている:管理UI(ブラウザで見るもの)、REST API(アプリが通信するもの)、PostgreSQLデータベース(フラグ定義が保存される場所)だ。アプリケーションはリクエストのたびにUnleashサーバーを呼び出すわけではない—それはレイテンシの悪夢になる。代わりに、SDKが15秒ごとにAPIをポーリングし、フラグの状態をローカルにキャッシュする。フラグの評価はプロセス内のメモリ上で行われる。リクエストごとのネットワーク往復はゼロだ。

これはほとんどのチュートリアルが省略する詳細だ。最初にこれを設定したとき、フラグのチェックがすべてネットワーク呼び出しだと思っていた。ローカルキャッシュモデルを理解すると、信頼性についての考え方が変わる—Unleashがダウンしても、アプリは最後に知られたフラグの状態で動き続ける。

最初のFeature Flagを作成する

Unleash UIで、Feature Toggles → New feature toggleに移動する。new-checkout-flowという名前をつけ、タイプにReleaseを選び、defaultプロジェクトに割り当てる。development環境で有効にする。

では、アプリに統合してみよう。公式SDKを使ったPythonの例:

pip install UnleashClient
from UnleashClient import UnleashClient

client = UnleashClient(
    url="http://localhost:4242/api",
    app_name="my-app",
    custom_headers={"Authorization": "default:development.unleash-insecure-client-api-token"}
)

client.initialize_client()

if client.is_enabled("new-checkout-flow"):
    # 新しいチェックアウト体験を提供する
    run_new_checkout()
else:
    # 旧フローにフォールバック
    run_legacy_checkout()

ExpressやNext.jsバックエンドを使っている人向けのNode.jsバージョン:

npm install unleash-client
const { initialize } = require('unleash-client');

const unleash = initialize({
  url: 'http://localhost:4242/api',
  appName: 'my-app',
  customHeaders: {
    Authorization: 'default:development.unleash-insecure-client-api-token',
  },
});

unleash.on('synchronized', () => {
  if (unleash.isEnabled('new-checkout-flow')) {
    console.log('新しいチェックアウトが有効です');
  }
});

アクティベーションストラテジー:真の力

オン/オフは単なる入口だ。本当のレバレッジはアクティベーションストラテジーにある。機能を以下のユーザーにロールアウトできる:

  • 段階的ロールアウト — X%のユーザーに有効にする(userIdごとに固定)
  • ユーザーID — 特定のユーザーアカウントのみに有効にする(内部テストに最適)
  • IP — 特定のIP範囲からのみ有効にする
  • カスタム制約 — 上記のいずれかをAND/ORロジックで組み合わせる

段階的ロールアウトでは、フラグをチェックする際にユーザーコンテキストを渡す:

from UnleashClient import UnleashClient
from UnleashClient.strategies import Strategy

client = UnleashClient(
    url="http://localhost:4242/api",
    app_name="my-app",
    custom_headers={"Authorization": "default:development.unleash-insecure-client-api-token"}
)
client.initialize_client()

# 段階的ロールアウトが正しく機能するようにユーザーコンテキストを渡す
context = {"userId": "user-12345", "sessionId": "session-abc"}

if client.is_enabled("new-checkout-flow", context):
    run_new_checkout()

コンテキストがない場合、段階的ロールアウトはランダムにフォールバックする—同じユーザーが、あるリクエストでは新しいチェックアウトを見て、次のリクエストでは古いものを見る可能性がある。常にuserIdを渡すこと。

応用編:サードパーティサービスなしのA/Bテスト

実際のA/Bテストを実行する

Variant機能は、任意のフラグをA/Bテストに変換する。ブール値の代わりに、フラグはバリアント名とオプションのペイロードを返す。カスタムの重みで複数のバリアントにトラフィックを分割できる。

UIでフラグを編集し → Variantsに移動 → バリアントを追加する:control(重み500)とnew-design(重み500)。これは50/50の分割だ。

variant = client.get_variant("checkout-ab-test", context)

if variant["name"] == "new-design" and variant["enabled"]:
    show_new_design()
else:
    show_control()

# バリアントをアナリティクスシステムに記録する
analytics.track("checkout_shown", {
    "userId": context["userId"],
    "variant": variant["name"]
})

スティッキネスは自動的だ—リクエスト1で「new-design」にいたユーザーは、同じuserIdを渡す限り、リクエスト100でも「new-design」にいる。

本番環境での運用:セキュリティとスケーリング

クイックスタートのcomposeファイルにあるAPIトークンは意図的にセキュアでない—localhostには問題ないが、それ以外の場所には適さない。本番環境では、すぐにローテーションすること:

# Unleashコンテナにアクセスする
docker compose exec unleash sh

# またはUIからトークンを生成する:
# Settings → API access → New API token
# タイプを設定: CLIENT、環境: production

本番環境向けにcomposeファイルを更新する—セキュアでないトークン環境変数を削除し、UIのみでトークンを管理する。また、リソース制限を追加する:

unleash:
  image: unleashorg/unleash-server:latest
  deploy:
    resources:
      limits:
        cpus: '1.0'
        memory: 512M
  environment:
    DATABASE_URL: postgres://unleash:${DB_PASSWORD}@unleash_db/unleash
    AUTH_TYPE: open-source
  restart: unless-stopped

TLSを使ったリバースプロキシ(NginxまたはCaddy)の背後にUnleashを置くこと。アプリサーバーはHTTPSでUnleashにアクセスすべきで、本番環境では平文のHTTPは絶対に使わない。

現場から学んだ実践的なヒント

長く使われることを意識してフラグに名前をつける

悪い例:feature1test-thingjohns-experiment

良い例:checkout-new-payment-flowhomepage-hero-ab-test-2024q3

フラグ名は、複数のサービスのコードに組み込まれると名前を変えるのが難しい。flag_v2flag_v2_newflag_v2_finalという名前の40個のフラグが含まれたコードベースを引き継いだことがある。説明的な名前に30秒使うだけでいい。

使われなくなったフラグは積極的にアーカイブする

Feature Flagsは、機能が完全にリリースされた瞬間から技術的負債になる。ロールアウトが成功したら、コードからフラグのチェックを削除し、Unleashでフラグをアーカイブする。ロールアウトの2週間後にクリーンアップのカレンダーリマインダーを設定する。3ヶ月以上経過した明確なオーナーのないフラグは、すべてクリーンアップ候補として扱っている。

環境を適切に使い分ける

環境サポートは組み込まれている:development、staging、productionだ。環境ごとに別々のAPIトークンを使うこと。developmentで有効になっているフラグが自動的にproductionで有効になるべきではない—それらの状態は独立したまま保つ。これは当たり前に聞こえるが、チームが素早く動いているときは常に省略される。

データベースをバックアップする

# シンプルなバックアップスクリプト — cronに追加する
docker compose exec unleash_db pg_dump -U unleash unleash > unleash_backup_$(date +%Y%m%d).sql

フラグの定義はビジネスロジックだ。失うと、どの機能がどのユーザーに対して有効になっているかを再現するために奔走することになる。毎日バックアップし、オフサイトに保存すること。

ヘルスチェックエンドポイント

/healthエンドポイントはUnleashに付属している—監視システムに組み込もう:

curl http://localhost:4242/health
# {"health":"GOOD"}

これをUptimeRobot、Grafana、または既に使っているものに組み込む。アプリが古いフラグを使い始める前に、Unleashがダウンしていることを知る必要がある。

冒頭で説明した夜中の2時のインシデント?Unleashが既に動いていれば、スマートフォンから10秒でフラグを無効にするだけで済んだはずだ—45分のホットフィックス対応ではなく。セットアップには数分かかるだけだ。難しいのは、Slackチャンネルが荒れた後ではなく、リスクのあるコードをプッシュする前にフラグを使うという習慣を身につけることだ。

上記のDocker Composeファイルから始めよう。今週のうちにdev環境でフラグの切り替えを試してみる。本番環境の準備ができる頃には、このパターンが自然に感じられるようになっているはずだ。

Share: