はじめに
hacomono VP of Platform Engineering のやじ(@srv)です。
大人になったらレーシングドライバーになるのが最近の夢です。
hacomono ではアプリケーションを支えるアーキテクチャの刷新を行っておりました。
私のブログではしばらく、このアーキテクチャ変更について紹介させて頂ければと思います。
TL;DR
- hacomono の元々のアーキテクチャはシングルテナント方式だった。
- シングルテナントで多数のテナントを抱えるのは辛い。
- マルチテナントアーキテクチャへ移行した。
- 今回の記事はお触りだけで、詳細は続編へ続く。
hacomono のアーキテクチャ
2021 年当時、hacomono では 150 社ほどのお客様へサービスを提供していました。
当時のアーキテクチャはお客様毎に環境を提供しており、Provisioning は Jenkins 経由で Terraform と Ansible を実行することにより実現していました。
しかしながら上記のアーキテクチャにより、以下の述べる様な問題に日々悩まされていました。
1. 安定しないデプロイ
Jenkins を利用した CD を採用しておりますが、以下の様なトラブルによりデプロイが安定していませんでした。
- メモリー不足でデプロイに失敗するインスタンスが発生する
- デプロイ中にインスタンスのストレージがパンクする
- Jenkins のストレージがパンクする
- 新しい対策を入れた事により CD が動かなくなる
トラブルが発生する都度対策を講じて来てはいるものの、なかなか安定しない状況が続いておりました。
この様にデプロイ方式の抜本的な変更が必要な時期が差し迫っていました。
2. 構築時期の違いによる環境差分とこれを起因としたトラブル
構築時期によりインフラ構成が異なる状況が発生しており、これを起因として一分環境でのデプロイや実行中に問題を発生する場合がありました。
- 異なるバージョンの OS
- 異なるバージョンのライブラリ、ミドルウェア
- 異なるバージョンのランタイム(Ruby、Rails、Node.js)
問題が発覚する都度対策を講じる状況で、抜本的な対策が実施できずにおりました。
この様な問題を解決するため、Provisioning に関するアーキテクチャ変更が必要であると考えました。
3. 増え続けるインスタンス数
お客様が順調に増えている状況で、その結果インスタンス数もうなぎのぼりで増えていく状況でした。当初 100 インスタンスほどで提供していた状況が 300 インスタンスへ、そして 600 インスタンスへとどんどん増えていく状況となってしまっていました。
インスタンス数が増えるとインスタンス障害が発生する確率も増えて行きますので、毎月どこかのお客様の環境が停止してしまう状況を抱えておりました。
この様な状況を駆逐打開するため、アーキテクチャの刷新を決意しました。
最初のアーキテクチャ(お蔵入り)
当時の EC2 をベースとしたアーキテクチャを極力維持した形で上記の問題を解決するために以下のアーキテクチャを考え、検証を進めました。
- CI 内で packer によりベースとなる AMI を構築
- EC2 インスタンスは ASG 経由で管理する
- EC2 UserData を使った環境差分の取り込み、サービス起動
この様なアーキテクチャによるサービス提供を進めましたが以下の点より適応を断念いたしました。
- インフラコストの上昇(ALB のコストが乗る)
- 起動までの時間(EC2 の起動に 20 分ほどかかる)
- 何も変わらないインスタンス数
ただ Immutable Infrastructure という考え方は重要で、サービス安定化には必須の考え方と考えております。
そこで Immutable Infrastructure を実現しつつ、問題点の解消を行うため次の様なアーキテクチャを適用することを計画しました。
hacomono マルチテナントアーキテクチャ
現在運用開始しているマルチテナントアーキテクチャは以下の様な要素で構成されています。
- ECS Fargate
- Amazon Aurora Cluster
- StepFunctions
- Lambda
この様な構成にするためには何点か問題を抱えており、これらを一つずつ解決してきた事により今回のマルチテナントアーキテクチャが完成いたしました。
※マルチテナントアーキテクチャの詳細については後日の投稿で詳しく解説させていただこうと思います。
1. 設定ファイルと起動スクリプト
hacomono はコンテナでの運用を考慮されていなかったため設定情報がローカルファイルに書き出されており、Provisionig 時に環境固有の情報が書き出されている様になっておりました。
コンテナ化のため設定情報を環境変数から取り出せる様に Rails、Nuxt.js、nginx それぞれに対して改善を行いました。
2. 静的コンテンツ
hacomono では静的コンテンツ内に顧客毎の情報が含まれる様になっており、EC2 の Provisionig 時にこれらが生成される様になっておりました。
このようなコンテンツをすべて SSR、もしくは SPA 内で動的に取得する様に書き換えていきました。
※ この対応は CTO に夜なべしてもらって地道に対応いたしました。
3. マルチテナント化
マルチテナント化には apartment を利用しております。現行アーキテクチャとの併存、並びにマイグレーションを想定した場合に最短で対応可能であると判断したためこちらを採用いたしました。
そのため Rails は一定数のテナント毎にサービスを分断しており、それらの Rails サービスへ適切にルーティングするための API Gateway の様な仕組みを Go で作りました。
※ こちらの仕組みの詳細については後日の投稿で解説させて頂く予定です。
4. バッチ
EC2 上の cron を使って実現されていたバッチ処理をマルチテナントへ対応させるため StepFunctions と ECS Task を使って実現いたしました。
※ こちらの仕組みについても後日の投稿で解説させて頂く予定です。
この様な対応を行うことにより Immutable Infrastructure の実現を目的としたコンテナ化、並びにマルチテナント化を実現出来ました。
まとめ
今回は「マルチテナントへの道 序章」と題して、hacomono のマルチテナントアーキテクチャへの道のりの一端を紹介させていただきました。
今後マルチテナントアーキテクチャの詳細や CI/CD、プロビジョニングを行うための壮大な負債についてなど今後の投稿の中で紹介させて頂き、最後に hacomono の次のアーキテクチャの計画についても触れさせて頂こうと予定しております。