環境抽象化 & Feature Flag 駆動切替¶
対象フェーズ: Closed Beta 〜 GA 全体 作成日: 2026-04-19 最終更新: 2026-04-19 ステータス: 承認待ち 前提ドキュメント: デプロイメント戦略
ポリシー準拠
本ドキュメントは最新インフラポリシーに準拠しています。Beta=OSS セルフホスト(XServer VPS + CoreServerV2 CORE+X、Garage、Redis+BullMQ、Postfix+Dovecot+Rspamd)、本番=OCI ファースト。AWS は Cognito のみ。環境変数 aws-s3 / aws-sqs / aws-ses のオプションは削除しています。
1. 目的¶
Recerdo のコードベースを 単一リポジトリ(single codebase, many deploys) として維持し、以下の切替操作をすべて コード変更なし で実現する。
- Beta(XServer VPS + CoreServerV2 CORE+X、全て OSS) → 本番(OCI ファースト)への移行
- メッセージキュー実装の切替(Redis+BullMQ → OCI Queue Service(AMQP 1.0))
- オブジェクトストレージの切替(Garage → OCI Object Storage)
- 認証プロバイダの多重化(Cognito 単独 → Cognito + 別 IdP の併用を将来検討)
- 特定機能の段階的ロールアウト(%ベース/組織ベース/IP ベース)
- 障害時の即時機能停止(Kill Switch)
2. 設計原則¶
2.1 三層の "差し替え可能性"¶
Recerdo では、環境差異を以下の 3 層 に分解して管理する。いずれもコード変更を伴わない。
| 層 | 手段 | 切替の粒度 | 反映速度 | 主な用途 |
|---|---|---|---|---|
| レイヤー 1: 環境変数(12-factor Config) | OS 環境変数 / .env / Secret Manager | デプロイ単位 | 再起動が必要 | DB 接続先・外部サービス URL・APIキー |
| レイヤー 2: Feature Flag(Flipt) | Feature Flag Svc の評価 API | ユーザー / 組織 / IP 単位 | リアルタイム(数秒) | 機能 ON/OFF・段階的ロールアウト・A/B |
| レイヤー 3: アダプタ選択(DI コンテナ) | 起動時に Interface → Implementation を注入 | プロセス単位 | 再起動が必要 | Queue / Storage / Mail / Transcoder の実装切替 |
2.2 Twelve-Factor Config への準拠¶
12-factor config に準拠し、以下を遵守する:
- ✅ 環境変数で設定を渡す(設定ファイルではなく)
- ✅ コードと設定を厳密に分離(コードを即 OSS 公開してもシークレットが漏れない)
- ✅ "環境"(dev/beta/prod)をグループ化しない(個々の環境変数は独立管理)
- ❌
if env == "production"のような分岐禁止(値の差ではなく動作の差で判断する) - ❌ クラウド事業者名をドメイン層にハードコードしない("oci" / "aws" / "garage" を書かない)
禁止パターン
2.3 Feature Flag の評価ポリシー¶
- 全サービスは Feature Flag Svc(recerdo-feature-flag) に
EvaluateFlagで問い合わせる - 評価結果は ローカルキャッシュ TTL 30 秒(障害時のフォールバックは "最後の既知値" を維持)
- すべての評価は FlagEvaluation テーブルに記録(§4.3 参照)
3. 環境変数カタログ¶
3.1 命名規約¶
UPPER_SNAKE_CASE- ドメイン別プレフィックス:
QUEUE_*、STORAGE_*、MAIL_*、AUTH_*、DB_*、MEDIA_* - シークレット接尾辞:
_SECRET、_TOKEN、_KEY(ログから自動マスク対象)
3.2 主要な環境変数(抜粋)¶
| 変数名 | Beta 既定値 | 本番既定値 | 説明 |
|---|---|---|---|
APP_ENV | beta | production | ログ識別のみに使用(動作分岐禁止) |
QUEUE_PROVIDER | redis-bullmq | oci-queue | Queue 実装選択(aws-sqs は採用しない) |
QUEUE_URL | redis://vps-redis:6379/0 | https://cell-1.queue.ap-tokyo-1.oci.oraclecloud.com/... | 接続先 |
STORAGE_PROVIDER | garage | oci-oss | オブジェクトストレージ実装:Garage (Beta, S3 互換 OSS) → OCI Object Storage (本番, S3 互換 API)。aws-s3 / minio は採用しない |
STORAGE_ENDPOINT | https://garage.coreserver.example.com | https://objectstorage.ap-tokyo-1.oraclecloud.com | S3 互換 API エンドポイント |
STORAGE_BUCKET_ALBUM | recerdo-album-beta | recerdo-album-prod-apnortheast | バケット名 |
STORAGE_BUCKET_HLS | recerdo-hls-beta | recerdo-hls-prod | HLS マスタ・レンディション・セグメント用 |
MAIL_PROVIDER | postfix-smtp | postfix-smtp | Beta/本番ともに CoreServerV2 の Postfix+Dovecot+Rspamd を SMTP 参照 |
MAIL_SMTP_HOST | mail.coreserver.example.com | mail.coreserver.example.com | SMTP エンドポイント |
MAIL_SMTP_PORT | 587 | 587 | Submission ポート(STARTTLS) |
MEDIA_TRANSCODER | ffmpeg-hls | ffmpeg-hls | 動画→HLS 変換実装 |
MEDIA_HEIC_CONVERTER | libheif | libheif | HEIC→JPEG/WebP 変換 |
MEDIA_HLS_VARIANTS | 360p,720p,1080p | 360p,720p,1080p | HLS レンディションのカンマ区切り |
MEDIA_HLS_SEGMENT_SEC | 6 | 6 | HLS セグメント長(秒) |
DATABASE_URL | mysql://...@vps-db:3306/recerdo | mysql://...@mds-tokyo.oraclecloud.com:3306/... | DB 接続文字列(スキーマは MariaDB 互換 に保つ) |
DATABASE_COMPAT_MODE | mariadb-10.6 | mariadb-10.6 | MariaDB 互換チェックに使用する比較基準 |
REDIS_URL | redis://vps-redis:6379/1 | rediss://ocicache.ap-tokyo-1.oraclecloud.com:6379 | Redis 接続 |
AUTH_COGNITO_POOL_ID | ap-northeast-1_xxxxxxxxx | ap-northeast-1_yyyyyyyyy | Cognito User Pool(AWS 利用はここのみ) |
FEATURE_FLAG_URL | http://flipt:8080 | http://flipt-internal.vcn:8080 | Flipt エンドポイント |
OBSERVABILITY_BACKEND | loki | oci-logging or loki | ログ集約先 |
CDN_BASE_URL | https://cdn-beta.recerdo.app | https://cdn.recerdo.app | CDN 公開 URL(Cloudflare 無料〜Pro) |
AUDIT_ARCHIVE_BUCKET | recerdo-audit-beta | recerdo-audit-archive-prod | 監査ログ冷蔵バケット |
削除済みの環境変数オプション
以下のオプションはポリシーにより 削除 されました。コードや設定にこれらが残っている場合は除去してください: - STORAGE_PROVIDER=aws-s3 / STORAGE_PROVIDER=minio - QUEUE_PROVIDER=aws-sqs - MAIL_PROVIDER=aws-ses / MAIL_PROVIDER=sns - AWS SNS / DynamoDB / RDS / Aurora / ElastiCache / CloudFront / Lambda / CloudWatch / ECS / Fargate / EC2 関連の全ての *_PROVIDER 値
アダプタ選択ポリシー
アダプタ選択には *_PROVIDER 環境変数を用いるが、AWS 系アダプタ(SNS/SQS/SES/DynamoDB/RDS/Aurora/ElastiCache/CloudFront/Lambda/CloudWatch/ECS/Fargate/EC2)は採用しないため未実装。AWS の利用は Cognito のみ。
3.3 シークレット管理¶
- Beta: XServer VPS の
.env.local(ファイルパーミッション 600)+sops + age暗号化で Git 管理 - 本番: OCI Vault から起動時に環境変数へ注入
- CI/CD: GitHub Actions の Environment Secrets
4. Feature Flag カタログ(インフラ系)¶
機能系フラグは 機能仕様:Feature Flag 管理 を参照。本節では インフラ・アーキテクチャ切替 専用のフラグを記載する。
4.1 インフラ切替フラグ¶
| Flag Key | 型 | 用途 | Beta 既定値 | 本番既定値 |
|---|---|---|---|---|
infra.queue.provider | VARIANT | Queue 実装選択 | redis-bullmq | oci-queue |
infra.storage.provider | VARIANT | Storage 実装選択 | garage | oci-oss |
infra.mail.provider | VARIANT | Mail 送信実装 | postfix-smtp | postfix-smtp |
infra.media.transcoder | VARIANT | メディア変換実装 | ffmpeg-hls | ffmpeg-hls |
infra.dualWrite.enabled | BOOLEAN | Beta/本番両系への二重書き込み | false | true(移行中のみ) |
infra.readFrom | VARIANT | 読み取り先指定 | beta | both → prod |
infra.queue.killswitch | BOOLEAN | 全キュー処理を一時停止 | false | false |
infra.cdn.aggressive-cache | BOOLEAN | CDN で全レスポンスをキャッシュ | false | true |
infra.observability.sample-rate | VARIANT | トレーシングサンプリング率 | 0.1 | 0.01 |
4.2 段階的ロールアウトの例¶
本番切替時は、Percentage Rollout を使う:
# Flipt config (例)
flag: infra.storage.provider
rules:
- priority: 1
segment: internal-team # 社内テスター
variant: oci-oss
- priority: 2
rollout:
percentage: 10 # 一般ユーザー 10%
variant: oci-oss
- priority: 3
variant: garage # 残り 90% は Beta 継続
ダッシュボードで 10 → 30 → 50 → 100 % と段階的に増やし、エラー率悪化を検知したら即ロールバック。
4.3 評価ログとコンプライアンス¶
すべての Feature Flag 評価は FlagEvaluation テーブルに記録し、以下を満たす:
- 7 日間のホットストレージ保持(問題調査用)
- 30 日経過後は Audit 冷蔵バケットへ移送
- Kill Switch 発動は Audit Svc へ同期通知(監査要件)
5. アダプタパターン実装(Hexagonal Architecture)¶
5.1 ポートとアダプタ¶
ドメイン層は "Port"(インタフェース)のみを知り、"Adapter"(具象実装)は起動時に DI で差し込む。クラウド事業者名やプロダクト名はドメイン層に出現しない。
flowchart LR
subgraph Domain["ドメイン層 (インフラ非依存)"]
UC["UseCase: EnqueueEvent"]
Port["Port: QueuePort"]
end
subgraph Adapters["アダプタ層 (差し替え可能)"]
A1["BullMQ Adapter (Redis)"]
A2["asynq Adapter (Redis)"]
A3["OCI Queue Adapter (AMQP 1.0)"]
end
subgraph Config["起動時DIコンテナ"]
DI["infra.queue.provider を見て Adapter を選択"]
end
UC -->|呼び出し| Port
DI -->|注入| Port
Port -.実装.-> A1
Port -.実装.-> A2
Port -.実装.-> A3 5.2 Go での実装サンプル¶
// ドメイン層 (domain/port/queue.go)
type QueuePort interface {
Enqueue(ctx context.Context, job Job) error
Dequeue(ctx context.Context) (Job, error)
}
// UseCase (application/enqueue_event.go)
type EnqueueEvent struct {
queue QueuePort // <- ドメインは Port のみ知る
}
func (u *EnqueueEvent) Execute(ctx context.Context, ev Event) error {
return u.queue.Enqueue(ctx, Job{Type: "event", Payload: ev})
}
// アダプタ (adapter/queue/bullmq_adapter.go)
type BullMQAdapter struct { /* Redis 接続 */ }
func (a *BullMQAdapter) Enqueue(...) error { /* BullMQ API */ }
// アダプタ (adapter/queue/oci_queue_adapter.go)
type OCIQueueAdapter struct { /* AMQP 1.0 接続 */ }
func (a *OCIQueueAdapter) Enqueue(...) error { /* OCI Queue SendMessage */ }
// DIコンテナ (cmd/main.go)
func buildQueue(cfg Config, ff FeatureFlagClient) QueuePort {
provider := ff.StringVariant("infra.queue.provider", cfg.QueueProvider)
switch provider {
case "redis-bullmq": return NewBullMQAdapter(cfg)
case "redis-asynq": return NewAsynqAdapter(cfg)
case "oci-queue": return NewOCIQueueAdapter(cfg)
default: log.Fatalf("unknown provider: %s", provider)
}
return nil
}
5.3 参考実装¶
- omniqueue-rs(Rust): Redis / RabbitMQ を統一インタフェースで抽象化した OSS
- CloudEvents SDK(CNCF): クラウド間で互換のあるイベントフォーマット
- OpenFeature SDK(CNCF): Feature Flag プロバイダの統一インタフェース
6. 変更の影響範囲テスト¶
アダプタ差替えの正しさは、ドメイン層の単体テストは差し替えても同じく PASS することで検証する。
6.1 テストマトリクス(CI 実行)¶
| テスト種別 | 対象 | 実行条件 |
|---|---|---|
| ユニット(Port モック) | ドメイン・UseCase | 全 PR |
| 統合(BullMQ Adapter) | Redis docker-compose | 全 PR |
| 統合(Garage Adapter) | Garage docker-compose | 全 PR |
| 統合(Postfix Adapter) | MailHog or ローカル Postfix | 全 PR |
| 統合(ffmpeg HLS) | ffmpeg container + サンプル MP4 | 全 PR |
| 統合(OCI Queue Adapter) | 夜間ジョブ(本物の OCI テナント) | nightly |
| 統合(OCI Object Storage) | 夜間ジョブ(本物の OCI テナント) | nightly |
| MariaDB 互換性チェック | MariaDB 10.6 コンテナに同一 SQL | 全 PR |
| 負荷テスト | 本番相当クラウド | リリース前 |
6.2 契約テスト(Contract Test)¶
各 Adapter は 同一の Port 契約 を満たすことを保証する:
// adapter/queue/contract_test.go
func TestQueuePortContract(t *testing.T) {
providers := []QueuePort{
NewBullMQAdapter(testCfg),
NewAsynqAdapter(testCfg),
NewOCIQueueAdapter(testCfg),
}
for _, p := range providers {
t.Run(fmt.Sprintf("%T", p), func(t *testing.T) {
// Enqueue / Dequeue / 冪等性 / DLQ 挙動を検証
assertQueueContract(t, p)
})
}
}
7. セキュリティ考慮¶
7.1 シークレットのローテーション¶
- 目標: 四半期ごとに全シークレットをローテーション
- 実装: OCI Vault のローテーション機能を使用、アプリは起動時再読み込み
- Feature Flag 認証: Flipt は**読み取り専用トークン**を各 Svc に配布し、書き込みは Admin Console からのみ
7.2 Feature Flag の改ざん対策¶
- Flipt への書き込みは Admin Console Svc 経由 に限定
- すべての変更は Permission Svc の RBAC と Audit Svc への同期記録を伴う
- 本番環境のフラグ変更は 二段階承認(申請者 + 承認者)を Admin Console に実装
7.3 Kill Switch 発動の監査¶
- エラー率閾値超過で自動発動した場合、Slack + Email(Postfix 経由)で即時通知
- 手動発動は承認ワークフローを経由、発動者を監査ログに記録
8. ロールアウト/ロールバック手順¶
8.1 標準ロールアウト¶
1. 新 Adapter 実装を PR でマージ
2. staging 環境で `infra.*.provider` を新値に切替 → 統合テスト
3. 本番で Percentage Rollout 5% → 監視 → 20% → 50% → 100%
4. 旧 Adapter の環境変数を削除、次リリースでコード削除
8.2 緊急ロールバック¶
1. Flipt ダッシュボードで Kill Switch `infra.*.killswitch=true` 発動
2. アプリは即座に旧 Adapter へフォールバック(30秒以内)
3. インシデント調査と原因修正
4. 修正後、Kill Switch を解除して段階再開
9. 参考文献¶
- The Twelve-Factor App — Config
- OpenFeature — CNCF 標準の Feature Flag 仕様
- Flipt — セルフホスト OSS Feature Flag
- Hexagonal Architecture — AWS Prescriptive Guidance
- omniqueue-rs — Queue 抽象化実装例
- Garage Documentation
- MariaDB vs MySQL Compatibility
10. 関連ドキュメント¶
最終更新: 2026-04-19 ポリシー適用