マルチサイト運用
1 つの運用主体が複数の KUKAN サイト(例: 複数自治体のデータカタログ)を運用する場合、 サイトごとにフル構成を複製するとコストがサイト数に比例して増えます。 マルチサイト構成では、時間課金される「箱」(データベースクラスタ、OpenSearch、VPC、 ECS クラスタ、ロードバランサー)を共有し、データと名前空間を持つ論理リソース(データベース (ジョブキューを含む)、検索インデックス、ストレージバケット、web/worker サービス)をサイト別に持ちます。
- サイト間の分離: サイトごとの専用 DB ロール(他サイト DB へは接続不可)、 検索インデックス prefix、バケット(ジョブキューはサイトの DB の中にあるため DB と同じく分離)
- サイトごとに独自ドメイン・ブランド(
brand、外観カスタマイズ)・サイズ調整が可能 - 同一運用主体のサイト群での利用を前提とした構成です(後述のセキュリティ境界を参照)
AWS(CDK)
Section titled “AWS(CDK)”infra/config/environments.ts の環境エントリに sites を宣言すると、
その環境が SharedStack(共有の箱)+ SiteStack × N に分割されます。
prd: { account: '...', scale: 'medium', githubRepo: '...', deployBranch: 'main', sites: [ { name: 'citya', // 英小文字+数字、2〜16 文字 domainName: 'catalog.city-a.example.jp', hostedZoneId: 'Z...', hostedZoneName: 'city-a.example.jp', certificateArn: 'arn:aws:acm:us-east-1:...', // pipeline モードでは必須 webAclArn: 'arn:aws:wafv2:us-east-1:...', // 複数サイトで共有可 }, { name: 'cityb', enableWaf: false }, ],},sites宣言が標準の書き方です。新規構築はサイト 1 件でもsitesで 始めてください(例:sites: [{ name: 'main' }])。sitesの無い シングルサイト形状(サイト固有の設定を env 直下に書く)は過去互換として 従来どおり動作しますが、新規構築では使用しません- 稼働中のシングルサイト形状の環境に
sitesを後付けすることはできません (後述の「シングルサイト形状からの移行」を参照) - サイト固有の設定(ドメイン・証明書・WAF・アクセス制限・バケット名など)は
各サイトエントリに書きます。
sitesを宣言した環境の env 直下に書くと synth 時にエラーになります(シングルサイト形状との混在はできません) - ALB は環境で 1 本を全サイトで共有します。各サイトは共有 ALB にターゲットグループと
リスナールールを追加し、そのサイトの CloudFront が付ける
X-Kukan-Siteヘッダーで 振り分けられます。ルールの優先度はサイト名から自動採番されるため通常は何も書く 必要がなく、synth 時に衝突が報告された場合だけ、追加したサイトにalbPriority(1〜999)を指定します。1 環境に載せられるサイトは 50 が上限です(CloudFront の VPC origin あたりの Distribution 数の制限。超える場合は環境を分けます) - サイトドメインの証明書と WAF はシングルサイトと同じ規則です: standalone デプロイ
では不足分が us-east-1 に自動作成され(ドメインには
hostedZoneId/hostedZoneNameが必要)、pipeline モードでは事前作成した ARN を貼り付けます - デプロイ順は自動制御されます: 共有スタック → 先頭サイト(カナリア)→ 残りサイトを
deployConcurrencyサイトずつ並列(既定 2、1で直列)。同時に更新されるサイトの ぶんだけ DB 接続数の余裕が必要で、足りなければ synth 時にエラーになります。 本体コードの変更は全サイトに展開され、特定サイトだけの変更は他サイトに影響しません - AWS Backup はマルチサイトでもそのまま使えます(データベースは共有スタックで 1 回だけ、バケットはサイトごとにバックアップされます)
- サイズや接続プールはサイトごとに
overridesで調整できます (web/worker/dbPool/backupのみ。backupは S3 の設定のみ、workerのhealthPortは不可)
設定の妥当性は cdk synth 時に検査されます(サイト名、証明書 ARN、
共有データベースの接続数バジェット超過など)。全パラメータの一覧は
環境設定リファレンスを、
詳細な運用手順(サイト削除時の残存リソースとパージ、接続数の考え方)は
リポジトリの docs/specs/jp/phase4-deploy.md を参照してください。
サイト数と DB サイズの目安
Section titled “サイト数と DB サイズの目安”synth 時の接続数チェック(警告なし = 推定 max_connections の 70% 以下)で
求めた最小構成。K は deployConcurrency(カナリア後に同時デプロイするサイト数、
既定 2)。デプロイ時は希望数が minSize に戻る(テンプレートが固定)ため、同時更新
1 サイトあたりの追加は新タスク minSize 台分の接続だけで、K を上げるコストは小さい。
ACU の変更は「先に DB だけ変えて再起動 → in-sync 確認 → サイト追加」の二段階
(max_connections は静的パラメータ)。
接続数は各プールの上限で決まり、worker のジョブの
並行数(WORKER_CONCURRENCY)には依存しない。並行数を上げても、足りない分のジョブは接続を待つだけで
接続は増えない。
K の上限は「(推定 max_connections × 0.7 − 平常時の最大) ÷ 同時更新 1 サイトあたりの追加」で、
超えると synth の警告・エラーが収まる最大の K を示す。その範囲でどこまで上げるかは、1 つの波で
同時に止まりうるサイト数との兼ね合いになる(カナリアは常に 1 サイトだけ)。
small(RDS、1 サイト最大 20 接続 + 更新中 10) — overrides: { db: { instanceClass } }
| サイト数 | K=1 | K=2 | K=4 | K=8 |
|---|---|---|---|---|
| 1 | db.t4g.micro | db.t4g.micro | db.t4g.micro | db.t4g.micro |
| 2 | db.t4g.micro | db.t4g.micro | db.t4g.micro | db.t4g.micro |
| 3 | db.t4g.micro | db.t4g.small | db.t4g.small | db.t4g.small |
| 4 | db.t4g.small | db.t4g.small | db.t4g.small | db.t4g.small |
| 5 | db.t4g.small | db.t4g.small | db.t4g.small | db.t4g.small |
| 6 | db.t4g.small | db.t4g.small | db.t4g.medium | db.t4g.medium |
| 8 | db.t4g.medium | db.t4g.medium | db.t4g.medium | db.t4g.medium |
| 10 | db.t4g.medium | db.t4g.medium | db.t4g.medium | db.t4g.medium |
| 15 | db.t4g.medium | db.t4g.large | db.t4g.large | db.t4g.large |
| 20 | db.t4g.large | db.t4g.large | db.t4g.large | db.t4g.large |
medium(Aurora、1 サイト最大 60 接続 + 更新中 15) — overrides: { db: { minAcu, maxAcu } }
| サイト数 | K=1 | K=2 | K=4 | K=8 |
|---|---|---|---|---|
| 1 | 0.5–2 ACU | 0.5–2 ACU | 0.5–2 ACU | 0.5–2 ACU |
| 2 | 0.5–2 ACU | 0.5–2 ACU | 0.5–2 ACU | 0.5–2 ACU |
| 3 | 0.5–2 ACU | 0.5–2 ACU | 0.5–2 ACU | 0.5–2 ACU |
| 4 | 0.5–2 ACU | 0.5–2 ACU | 0.5–4 ACU | 0.5–4 ACU |
| 5 | 0.5–4 ACU | 0.5–4 ACU | 0.5–4 ACU | 0.5–4 ACU |
| 6 | 0.5–4 ACU | 0.5–4 ACU | 0.5–4 ACU | 0.5–4 ACU |
| 8 | 0.5–4 ACU | 0.5–4 ACU | 0.5–4 ACU | 0.5–8 ACU |
| 10 | 0.5–8 ACU | 0.5–8 ACU | 0.5–8 ACU | 0.5–8 ACU |
| 15 | 0.5–8 ACU | 0.5–8 ACU | 0.5–8 ACU | 0.5–8 ACU |
| 20 | 0.5–16 ACU | 0.5–16 ACU | 0.5–16 ACU | 0.5–16 ACU |
large(Aurora、1 サイト最大 250 接続 + 更新中 60)
| サイト数 | K=1 | K=2 | K=4 | K=8 |
|---|---|---|---|---|
| 1 | 0.5–4 ACU | 0.5–4 ACU | 0.5–4 ACU | 0.5–4 ACU |
| 2 | 0.5–4 ACU | 0.5–4 ACU | 0.5–4 ACU | 0.5–4 ACU |
| 3 | 0.5–8 ACU | 0.5–8 ACU | 0.5–8 ACU | 0.5–8 ACU |
| 4 | 0.5–8 ACU | 0.5–8 ACU | 0.5–16 ACU | 0.5–16 ACU |
| 5 | 0.5–16 ACU | 0.5–16 ACU | 1–16 ACU | 1–16 ACU |
| 6 | 1–16 ACU | 1–16 ACU | 1–16 ACU | 1–16 ACU |
| 8 | 1–16 ACU | 1–16 ACU | 1–16 ACU | 1–32 ACU |
| 10 | 1–32 ACU | 1–32 ACU | 1–32 ACU | 1–32 ACU |
| 15 | 環境分割 | 環境分割 | 環境分割 | 環境分割 |
| 20 | 環境分割 | 環境分割 | 環境分割 | 環境分割 |
minAcu 1 は、minACU 0/0.5 では max_connections が 2,000 に固定される制約による
(maxAcu を上げても効かない)。「環境分割」は Aurora の上限 5,000 に対して 70% を
超えるため、共有クラスタ(環境)を分ける。
シングルサイト形状からの移行
Section titled “シングルサイト形状からの移行”稼働中のシングルサイト形状の環境に sites を後付けすることはできません。
スタックの分割とリソースの物理名変更を伴うため、CloudFormation 上は全リソースの
置き換え(=データ喪失)になります。移行は、新しい環境を並行構築して切り替える
ブルーグリーン方式で行います:
-
sitesを宣言した新しい環境エントリを追加してデプロイする(旧環境はそのまま残す) -
データベースを移行する(旧環境で
pg_dump→ 新環境のサイトデータベースへリストア) -
ストレージのファイルを新環境のサイトバケットへコピーする(
aws s3 sync) -
新環境の管理画面「リソースの処理状況」で「全リソースの再処理」を実行し、検索インデックスを 本文ごと作り直す(索引が空のままなら worker も同じ処理を自動で積みます)
-
動作確認のうえ、カスタムドメインを付け替える: 旧環境エントリからドメイン設定を 外してデプロイ → 新環境のサイトエントリに追加してデプロイ → DNS を新環境へ切替 (同一ドメインは 2 つの CloudFront に同時に割り当てられないため、この順で行う)
-
旧環境を破棄する: 先に
cdk destroyを実行し、そのあとでenvironments.tsの 旧エントリを削除する(environments.tsからエントリを消しても、デプロイ済みの スタックは自動削除されません。またエントリを先に消すと環境を合成できなくなり、 destroy 自体がやりにくくなります)。本体スタックの destroy コマンドは環境の 管理方法で異なります:- standalone 管理:
npx cdk destroy -c env=prd 'Prd/**' - pipeline 管理(掲載例のように
githubRepoを持つ環境): 修飾パスでnpx cdk destroy 'KukanPipeline/Prd/**'(-c envは付けない)。destroy 後は すみやかに旧エントリを削除して push し、旧環境のパイプラインを消します (旧パイプラインが残ったままdeployBranchへ push すると、破棄した環境が 再作成されてしまいます)
us-east-1 の GlobalStack は別途削除が必要です。
needsGlobalStack=falseに なった状態(pipeline モードで ARN を貼った、手順 5 でドメインを外しenableWaf: falseにした等)では GlobalStack は CDK の合成対象から消えるため、 上記のcdk destroyでは拾えず、物理スタックだけが残ります。管理方法に よらず、CloudFormation で物理スタック名を直接指定して削除するのが確実です:aws cloudformation delete-stack --stack-name Prd-KukanGlobalStack --region us-east-1(証明書 / WebACL は RETAIN のため、このスタック削除後も残ります)。destroy 後も次のリソースは残るため、確認のうえ手動でパージします:
- S3 バケット — 空にしてから削除
- アクセスログのバケット — ログは保持日数で消えるので、空になってから削除
- データベースの最終スナップショット — 保管課金が続くため不要なら削除
- AWS Backup の vault(有効にしていた場合)
- ACM 証明書と WAF WebACL(自動作成していた場合。どちらも RETAIN のため GlobalStack を destroy しても残り、WebACL は残っている間課金が継続します)。 必ず GlobalStack の destroy が済んでから手動削除してください — スタックを 残したまま証明書/WebACL だけ消すと CloudFormation スタックが drift します
- standalone 管理:
パラメータの書き換えは「サイトスコープの設定を env 直下から sites[] の
エントリへ移す」だけです。移行前:
prd: { account: '000000000000', scale: 'medium', githubRepo: 'owner/repo', deployBranch: 'main', domainName: 'catalog.example.jp', hostedZoneId: 'Z0123456789', hostedZoneName: 'example.jp', certificateArn: 'arn:aws:acm:us-east-1:...', enableWaf: false, // WAF を使っている場合は webAclArn を設定},移行用に追加する新環境(旧環境と併存させるため別名にする):
prd2: { account: '000000000000', // 共有側の設定(account/scale/CI 等)はそのまま scale: 'medium', githubRepo: 'owner/repo', deployBranch: 'main', sites: [ { name: 'main', // ドメイン関連は手順 5 の付け替えまでコメントアウトしておく。 // 証明書 ARN は旧環境と同じものを使い回せる // domainName: 'catalog.example.jp', // hostedZoneId: 'Z0123456789', hostedZoneName: 'example.jp', // certificateArn: 'arn:aws:acm:us-east-1:...', enableWaf: false, // 付け替えまでの検証用(旧環境の WAF/アクセス制限設定に合わせて調整) }, ],},ドメイン付け替えまでの新環境は CloudFront の既定ドメイン(デプロイ出力の
CloudFrontDomainName)で検証できます。手順 5 の付け替え前までは旧環境が
無傷のまま動いているため、問題があれば移行を中断するだけで済みます。
付け替え後に切り戻す場合は、ドメインを逆向きに付け替え直す必要があります: 新環境のサイトエントリからドメイン設定を外してデプロイ → 旧環境エントリに 再設定してデプロイ → DNS を旧環境へ戻す。ドメインと証明書を旧 CloudFront に 付け直す前に DNS だけ戻しても、証明書不一致のエラーになる点に注意してください。
Docker Compose(オンプレミス)
Section titled “Docker Compose(オンプレミス)”同じ「箱共有・論理個別」モデルを Docker Compose で構成する opt-in テンプレートを
docker/multi-site/ に用意しています。共有スタック(postgres / minio /
opensearch / ollama / caddy)を 1 回起動し、サイトごとに web/worker のペアを
共有ネットワークに接続します。手順は同ディレクトリの README を参照してください。
通常のシングルサイト運用はリポジトリルートの compose.yml をそのまま使います。
セキュリティ境界
Section titled “セキュリティ境界”マルチサイト構成はサイト間のハードなマルチテナント境界ではありません。
- 資格情報レベルで分離されるのはデータベース(その中のジョブキューを含む)のみです。
worker への通知(
/wake)はサイトごとの秘密から導いたトークンで認証します。 検索インデックス・バケットの分離は命名規約と(AWS では)IAM・セキュリティグループによるもので、 特にオンプレミス構成では共有サービス側の認証がありません - 信頼できない第三者にサイトを提供する用途には使わず、その場合はサイトごとの 完全分離(環境の複製)を選択してください
- 共有インフラの障害・メンテナンスは全サイトに同時に影響します。SLA 要求が 近いサイト同士だけを同居させてください
キャパシティの目安
Section titled “キャパシティの目安”- データベース接続数: 各サイトの接続プール上限 × 最大タスク数の合計が
共有クラスタの
max_connectionsに収まる必要があります。AWS では synth 時に 自動検査され、超過時はエラー、70% 超で警告になります - OpenSearch: 全サイトのインデックスが 1 ドメイン(オンプレでは 1 JVM)を
共有します。AWS では medium(m6g.large.search)以上を推奨
(2 サイト以上を小型の burstable インスタンスに載せると synth 時に警告が
出ます)、オンプレではヒープサイズ(
OPENSEARCH_JAVA_OPTS)をサイト数に 応じて拡張してください