コンテナ集約基盤を作ってみた~全体設計編~

この記事の目次
はじめに
皆さん、こんにちは!
クラウドエンジニアリング部のC.Mです。
部署名の通りクラウドインフラ(主にAWS)の要件定義から設計/構築/運用まで一通り担当しています。
マイナビ内では、多数のサービスが立ち上がっています。
しかし、サービスの増加に伴い、インフラの構築面や運用面で様々な課題が出てきました。
それらの問題を解決するために「マルチテナントのコンテナ集約基盤」を構築しました。
今回の記事では、コンテナ集約基盤の全体設計について紹介します。
※各コンポーネントの詳細は、それぞれを担当したメンバーが3-4月にかけて別記事を公開予定です。
コンテナ集約基盤の構想背景
今までの進め方
これまでは以下のような流れで、内製開発を進めてきました。

インフラ構築の基本方針
これまでは以下の方針でインフラ構築をしてきました。
・プロジェクトごとに、IaC用のGitHubリポジトリを作成
・IaCはAWS CDKを利用
・汎用CDKコードをもとに、プロジェクト用のCDKコードを作成
・汎用CDKコード: AWSの提供しているBLEA(Baseline Environment on AWS)をマイナビの内製開発向けにカスタマイズしたもの
・コンテナ動作環境はECS(Fargate起動タイプ)
今までの進め方における課題点
しかし、これまでの進め方には、以下の課題がありました。
1.最低でも1ヵ月程度のリードタイムが発生
・汎用CDKコードをもとにしているとはいえ、案件ごとにCDK開発が必要なため
2.アプリチームにインフラ環境を提供した後の手戻り
・ローカル開発環境では動いたコンテナが、クラウド環境上で動かないことがある。
3.運用の属人化
・プロジェクトが多いため、1プロジェクトにアサイン可能なインフラ担当者は数名
・結果、運用の属人化が発生
課題解消のアプローチ
課題を解決するためにインフラチーム内で相談し、「マルチテナントのコンテナ集約基盤」を構築する方針になりました。
1.数日のリードタイムで提供したい。
・マルチテナントのコンテナ集約基盤を事前に用意しておく。
・プロジェクトごとの専有リソースのデプロイは可能な限り自動化する。
2.手戻りを減らしたい。
・1でリードタイムが減れば、その分クラウド環境での動作確認が前倒しで可能になる。
3.属人化を解消したい。
・サービスを1つの基盤に集約し、インフラチーム全員で運用する。
全体設計の概要
ここから先は、実際の設計について説明します。
アウトラインは以下になります。
・AWSアカウント戦略
・AWSアカウントの分割単位
・prod/stg/devに分ける。
・異なるプロジェクトのサービスも同一アカウント内に配置
・アプリチームの権限は、サービス単位で作成するIAMロールで制御
・AWSアカウントはスケールアウト可能とする。
・1アカウントだけでは、サービスの増加とともにAWSクォータの上限に達してしまう。
・最初からスケールアウトを前提とした設計とする。
・サービス単位のリソース分割方法
・共有リソースと専有リソースで分割する。
・専有リソースはさらに、サービス単位で分割する。
・専有リソースのデプロイ自動化
・フォームへの申請から、CDKデプロイまでを自動化する。
・コンテナのデプロイ方法は以下2パターン
・ローリングデプロイ
・Blue/Greenデプロイ
・DB
・TiDB Cloud Serverless(NewSQL)を採用
・既存プロジェクトではAurora(MySQL互換)の採用が多かったが、いくつか課題があったため
・オブザービリティツール
・New Relicを採用
AWSアカウント戦略
各環境の定義
集約基盤では、各環境を以下のように分割しています。
・prod
・本番環境
・stg
・検証環境(アプリチーム向け)
・dev
・検証環境(集約基盤の管理者向け)
・集約基盤自体の検証に使用
例) 集約基盤全体への機能追加
・アプリチームはこの環境にはアクセス不可
本番環境と検証環境を分けるのは当然ですが、集約基盤ではさらに検証環境を「アプリチーム向け」と「集約基盤の管理者向け」に分けています。
stg環境ではアプリチームが常に開発を行うため、「集約基盤自体の検証環境」は、アプリチームが使用する環境とは分離しています。
AWSアカウントの分割方法
概要
「AWSアカウント」は、用途別に以下の3種類に分けています。
・本体用アカウント
・Route53用アカウント
・アプリチーム管理用アカウント
本体用アカウント
コンテナなど主要なリソースはこのアカウントに配置します。
・主なリソース
・VPC
・ALB
・ECS
・ECR
・コンテナのパイプライン
・初期リリース時点では、各環境ごとに1アカウント
・サービスの増加とともに、クォータを考慮してスケールアウト予定
Route53用アカウント
Route53はアカウントスケールアウトが不要なため、本体用とはアカウントを分けています。
・主なリソース
・ALBのドメイン用ホストゾーン
・CloudFront(または他CDN)のオリジンとして設定するドメイン
・上記ドメインのACM認証用CNAMEレコードとALB用Aliasレコード
・用途
・マイナビの全ドメインは、インフラチームの1つのAWSアカウント内のRoute53で集中管理している。
・しかし、ALBのドメインは社外ユーザーが直接アクセスしないので、リードタイム短縮のため、集約基盤専用のサブドメインをこのアカウントに移譲する。
・本体用から分ける理由
・クォータを考慮すると、ホストゾーンやDNSレコードのスケールアウトは不要
アプリチーム管理用アカウント
プロジェクトごとの要件に柔軟に対処するため、一部リソースはアプリチームが管理するアカウントに配置します。
・本体用/Route53と異なり、プロジェクトごとに作成する。
・アプリチームに管理を移譲するリソースを作成する。
・CloudFront
・WAF(CloudFront用)
・その他の一部サービス
・権限移譲理由
・CloudFront
・キャッシュ設定やパス設定がアプリケーションごとに大きく異なる。
・後述する専有リソースのパラメータファイルのみによる管理が難しい。
・WAF
・誤検知があった場合、対象ルールの除外をアプリチームだけで迅速に行えるようにするため。
・その他の一部サービス
・クォータの制限が厳しく、本体用アカウントで管理するのにそぐわないもの
本体用アカウントのスケールアウト
集約基盤では将来的に多数のサービスが稼働することを想定しています。
そのため最初から、AWSアカウントをスケールアウトする前提で設計しています。
・本体用アカウント上にデプロイされるサービスの増加に伴い、AWSアカウントのクォータに達してしまう。
・クォータ抵触前に、スケールアウトしていく。
・スケールアウトは本体アカウントのみ
・Route53アカウントはスケールアウト不要
・クォーターの抵触検知方法
・AWS公式の以下ソリューションを参考にして、クォータ監視を行う。
・AWSでのクォータモニタを試してみた
各環境(prod/stg/dev)がスケールアウトする想定のため、本体用アカウントの環境名は、末尾に数字を付与します。
・prod01,prod02,...
・stg01,stg02,...
・dev01,dev02,...
全体構成図
ここでおおまかな全体の構成図を載せます。
サービス単位で作成するリソースは淡い赤色にしています。

サービス単位のリソース分割方法
共有リソースと専有リソース
定義
本体用AWSアカウントには、VPCなどの「複数のサービスで共有するリソース」と、ECSなどの「サービス単位で作成するリソース」があります。
それらをそれぞれ「共有リソース」「専有リソース」と定義しています。
・共有リソース: 環境単位で作成
・専有リソース: 各環境にサービス単位で作成

共有リソース
主な共有リソースは以下になります。
・VPC
・サブネット
・NAT Gateway
・VPCエンドポイント
・セキュリティグループ
・VPCエンドポイント用のみ
・ALBやECS用は専有リソースとして作成
専有リソース
主な専有リソースは以下になります。
・ALB
・ALBにアタッチするACMやWAFも同様
・ECS
・EFS
・サービス単位で作成有無を選択
・ElastiCache
・サービス単位で作成有無を選択
・上記リソースのセキュリティグループ
デフォルトでは同一サービス内のみでの通信を許可
サービス間連携が必要な場合のみ、個別に許可
CDKコードの分割方法/デプロイ単位
要件と実現方法
ここまでは「AWSアカウント戦略」「サービス単位のリソース分割方法」について説明してきました。
これらをCDKで実装するための具体的な要件と実現方法について整理しましょう。
・以下のリソースはそれぞれ個別に独立してデプロイ可能である。
・Route53用アカウント
・Route53ホストゾーン
・本体用AWSアカウント
・共有リソース
・専有リソース ※共有リソースのデプロイが前提
各リソースのデプロイ単位は以下になります。
・Route53ホストゾーン -> 環境単位
・共有リソース -> 環境単位
・専有リソース -> 環境+サービス単位
そのため、 cdk deploy
コマンドの実行時に「環境名」や「サービスID」を指定できるようにします。
Route53ホストゾーン、共有リソース
cdk deploy --all -c environment=<環境名>
専有リソース
cdk deploy --all -c environment=<環境名> -c service=<サービスID>
さらに、CDKコードの保守性を高めるため、各環境/サービスの差分はパラメータファイルのみで管理し、stack/constructファイルは共通とします。
・Route53ホストゾーン
・stack/constructファイルは、全環境共通
・環境単位でパラメータファイルを作成
・ホストゾーンIDなどは、パラメータファイルで専有リソースに連携
・クロスアカウントになるので、SSMパラメータストア連携は不向きであるため
・共有リソース
・stack/constructファイルは、全環境共通
・環境単位でパラメータファイルを作成
・VPC IDなどは、SSMパラメータストアで専有リソースに連携
・専有リソース
・stack/constructファイルは、全サービス共通
・サービス単位でパラメータファイルを作成
・サービスの差分はすべてパラメータファイルで管理
各サービスの差分はパラメータファイル管理とすることで、新規サービス作成や既存サービス更新は以下のように独立して行うことが可能になります。
・新規サービス作成
・新規パラメータファイルを作成
・cdk ls
で作成されるスタック一覧を確認
・cdk deploy
で新規サービスの専有リソースを作成
・既存サービス更新
・対象サービスのパラメータファイルを変更
・cdk diff
で更新差分を確認
・cdk deploy
で更新を反映
CDK用リポジトリのディレクトリ構成
上記の要件を踏まえた結果、リポジトリ構成は以下のようになりました。
共有リソース(common)と専有リソース(services)は本体用アカウントにあり、各環境(prod/stg/dev)がスケールアウトする想定のため、「prod01,prod02,...」のような環境名となっています。
usecases
|ーhostedzones/
| |ーbin/
| |ーlib/
| |ーconstruct/
| |ーstack/
| |ーparams/
| |ーdev.ts
| |ーstg.ts
| |ーprod.ts
|ーcommon/
| |ーbin/
| |ーlib/
| |ーconstruct/
| |ーstack/
| |ーparams/
| |ーdev01.ts
| |ーstg01.ts
| |ーstg02.ts
| |ーprod01.ts
| |ーprod02.ts
|ーservices
| |ーbin/
| |ーlib/
| |ーconstruct/
| |ーstack/
| |ーparams/
| |ーdev01/
| ...
| |ーstg01/
| |ーservice-ab.ts
| |ーservice-xy.ts
| ...
| |ーstg02/
| ...
| |ーprod01/
| ...
| |ーprod02/
| ...
専有リソースのデプロイ自動化
申請からCDKデプロイまでのおおまかな流れは以下になります。

「自動生成」の部分について補足します。
・フォームの項目例
・環境(prod or stg)
・サービスID
・既存サービス更新の場合のみ指定
・新規サービス作成時には、サービスIDが自動発番される。
・アプリケーションのGitHubリポジトリ名
・ECSタスクのポート番号
・EFSは作成必要or不要
・ElastiCacheは作成必要or不要
・フォームの入力内容をもとに、CDKパラメータファイルは機械的に生成される。
・自動生成スクリプトは、Google Apps ScriptやStep Functions上で動作
・集約基盤管理者がパラメータファイルとcdk diff結果をチェックし、問題なければ承認する。
・承認トリガーでcdk deploy
コンテナのデプロイ方法
コンテナのデプロイ方法は以下の2パターンを用意します。
1.ecspressoを使用してデプロイ
・ecspressoを使用してデプロイ
参照:ecspresso公式ドキュメント
・サービスの設定更新がアプリ側で対応可能
2.CodeDeployを使用したBlue/Greenデプロイ
・CodeDeployを使用してデプロイ
・CDKでECSサービス/タスクを作成
1は既存プロジェクトの多くで採用しているため、集約基盤初期リリース時点での実装が必須です。
一方、2は既存プロジェクトでの採用は少ないので、初期リリース後に追加実装予定としました。
サービス選定
TiDB Cloud Serverless
以下の理由から、TiDBを選定しました。
・MySQLと高い互換性がある。
・リードだけでなくライトのスケールアウトも可能
・Auroraでは、ライタのスケールアウトは難しい。
・アプリチームへの権限移譲が容易に可能
・1つのTiDB組織に複数のプロジェクトを作成し、プロジェクト単位で権限を分離できる。
・集約基盤での運用想定
・サービス単位でプロジェクトを作成する。
・DBクラスターは、各プロジェクト内に作成する。
・アプリチームにプロジェクトのオーナー権限を移譲する。
・Serverlessの上限変更をアプリチームだけで管理できるようにするため。
・参考: アイデンティティアクセス管理
New Relic
集約基盤の運用方針を考慮すると、必須/任意要件は以下になりました。
・必須要件
・ログやCloudWatchメトリクスがツール上から確認可能であること
・各サービス単位でログ/メトリクス閲覧、ダッシュボード作成が可能であること
・集約基盤上のサービスはそれぞれ担当するアプリチームが異なるため、権限分離が必須
・任意要件
・アプリケーション性能ボトルネックの可視化が行えること
・ビジネス的な指標(KPI/KGI)の達成状況を可視化できること
任意要件まで満たせる見込みがあったため、オブザービリティツールはNew Relicを選定しました。
おわりに
長文となりましたが、ここまで読んでくださりありがとうございます。
今回の記事では各項目の詳細については記載しきれませんでしたが、3-4月頃にそれぞれの開発をメインで担当したメンバーが個別記事を作成予定です。
開発時のナレッジや苦労話も公開予定ですので、お楽しみに!
※本記事は2025年02月時点の情報です。