スタートアップスタジオのゼロイチ開発を効率化させる技術選定 (インフラ編)
はじめに
はじめまして、株式会社ispecのVPoEで、SRE Teamのリーダーをやっている石川です。
今回は第一弾のバックエンド編に引き続き、第二弾のインフラ編です。
前回でもインフラについては軽く触れられていますが、今回はより深掘りして事例と共に紹介します。
概要
ispecではスタートアップスタジオ事業を展開しており、今までに数多くのスタートアップ企業のプロダクトを開発してきたためゼロイチ開発におけるインフラ構築を得意としています。
アプリケーションのインフラは基本的にAWSで構築しています。
AWSはクラウドインフラとしてデファクトスタンダードであり、スタートアップの開発においてカバーできない範囲はほとんどありません。今後もAWSを基本的に採用し続ける予定です。
IaCのツールとしてTerraformのラッパーのTerragruntを採用しています。
後ほど詳しく紹介しますが、TerragruntはTerraform運用時に発生してくる課題を解決してくれるツールとして重宝しています。
また、よく利用するAWSリソースをTerraform moduleとして切り出すことで再利用性を高め、開発速度を引き上げています。
CI/CDの選定についてと付加試験ツールについても触れようと思います。
詳しい紹介
Terragrunt
現在ispec内全てのプロジェクトでTerragruntが採用されています。
ispecのTerraform運用の変遷とともにTerragruntの紹介をします。
Terraform導入
ispecでは創業期からTerraformを採用しています。
Terraform経験者がいなかったため0からの導入で手取り足取りなんとか完成させていたという状況でした。
ディレクトリ構造は以下のように環境ごとに分けていました。(簡単のために細部省略)
$ tree .
.
├── prod
│ ├── alb.tf
│ ├── ecs.tf
│ ├── rds.tf
│ └── vpc.tf
└── stg
├── alb.tf
├── ecs.tf
├── rds.tf
└── vpc.tf
シンプルなファイル構成で、直感的に扱えるというメリットはありましたが、環境ごとに同じコードを書く必要がありDRYではない実装となっていました。
tfstate分割
環境毎にディレクトリを分ける設計だとtfstateが環境ごとに一つの巨大なファイルになります。
tfstateが大きいとTerraformコマンドの実行が遅くなったり、意図せぬ変更が生まれてしまうリスクがあったりしました。
それを解消するために以下のようにディレクトリ分割を採用しました。(簡単のために細部省略)
$ tree .
.
├── module
│ ├── alb
│ │ ├── main.tf
│ │ └── vars.tf
│ ├── ecs
│ │ ├── main.tf
│ │ └── vars.tf
│ └── rds
│ ├── main.tf
│ └── vars.tf
├── prod
│ ├── alb
│ │ ├── main.tf
│ │ └── vars.tf
│ ├── ecs
│ │ ├── main.tf
│ │ └── vars.tf
│ ├── rds
│ │ ├── main.tf
│ │ └── vars.tf
│ └── vpc
│ ├── main.tf
│ └── vars.tf
└── stg
├── alb
│ ├── main.tf
│ └── vars.tf
├── ecs
│ ├── main.tf
│ └── vars.tf
├── rds
│ ├── main.tf
│ └── vars.tf
└── vpc
├── main.tf
└── vars.tf
vpcや他の一部のリソースに関してはTerraform公式のmodulesを使用しています。
上記の課題は解消されましたが、新たにproviderとbackendの扱い方に関する課題が生まれました。
backendに関しては、各ディレクトリにtfstateを置くためのユニークなs3のファイルパスを記述する必要があります。
providerに関しては、全てのディレクトリに同じproviderの情報を記述する必要があります。
bashでTerraformをラップしたスクリプトを書いて解消していましたが、運用していくうちにメンテナンスコストがかかってしまいベストなやり方ではないと感じていました。
Terragrunt導入
TerragruntはTerraformコードとオペレーションをDRYにすることを目指すツールです。
上記の課題を解決するために導入しました。
ディレクトリは以下のようになります。(簡単のために細部省略)
$ tree .
.
├── module
│ ├── alb
│ │ ├── main.tf
│ │ └── vars.tf
│ ├── ecs
│ │ ├── main.tf
│ │ └── vars.tf
│ └── rds
│ ├── main.tf
│ └── vars.tf
├── resource
│ ├── prod
│ │ ├── alb
│ │ │ └── terragrunt.hcl
│ │ ├── ecs
│ │ │ └── terragrunt.hcl
│ │ ├── rds
│ │ │ └── terragrunt.hcl
│ │ └── vpc
│ │ └── terragrunt.hcl
│ └── stg
│ ├── alb
│ │ └── terragrunt.hcl
│ ├── ecs
│ │ └── terragrunt.hcl
│ ├── rds
│ │ └── terragrunt.hcl
│ └── vpc
│ └── terragrunt.hcl
└── terragrunt.hcl
moduleディレクトリにAWSのresourceを記述し、resouceディレクトリのterragrunt.hclから対象のmoduleを呼び出すという設計です。
rootディレクトリにproviderとbackendを記述しておくと、各ディレクトリに自動importされます。
また、依存関係を明示的に記述できたり複数ディレクトリに対してTerraformコマンドを実行できたりと嬉しいことが多く、他にもTerraform運用必要な諸々がカバーされています。
Terragruntをはじめとして、ispecではどうしても必要な場合を除いてツールをスクラッチで開発することは避けています。
SaaSやOSSを活用することでアプリケーションの機能開発に集中できるようにしています。
sqldef
RDBのスキーマのマイグレーションにk0kubun/sqldefを採用しています。sqldefはスキーマ定義を宣言的に管理できるツールで、スキーマ定義と実際のDatabaseのスキーマのdiffを検知して必要なsqlを発行、実行してくれます。
sqldefを採用する以前はRailsエンジニアが多くいたという理由もありthuss/standalone-migrationを採用していました。
RubyとActiveRecordの恩恵を受けられたのはよかったのですが、ActiveRecordとその依存Gemのサイズが大きくbuildに時間がかかってしまっていました。
そこで運用を見直しsqldefに移管しました。実行されるimageはsqldefのバイナリとスキーマファイルだけが乗ったものなので、非常に軽量なのも嬉しいです。
Amazon EKS
Kubernetesはスタートアップのゼロイチ開発ではあまり採用されないかと思いますが、
などの場合にAmazon EKSを採用しています。
基本的にはワーカーノードの管理に開発リソースを割きたくないためFargateモードで運用します。
以下Kubernetesで使っている主要なアプリケーションを紹介していきます。
Helm
HelmはKubernetesのパッケージマネージャーとしてデファクトスタンダートだと思います。
Kubernetesにインストールされる主要なアプリケーションは大体Helm chartsとして公開されているため、大幅な開発リソースの削減ができます。
ネット上に多くの知見が転がっており、開発で詰まっても何かしらの文献があることが多いです。
ArgoCD
ArgoCDはKubernetesでGitOpsのためのツールとしてデファクトスタンダードです。導入も簡単で、GUIでクラスターの様子を直感的に把握できるために重宝しています。
アプリケーションのコードとKubernetesのマニフェストは別リポジトリで管理してします。
GitOpsのフローは以下のようになっています。
- アプリケーションのリポジトリにコードがpush
- CI上でimageをbuild
- ECRにimageをpush
- yqコマンドで対象のマニフェストリポジトリのマニフェストの対象のimageのtagを書き換える
- ghコマンドでPR作成
新しいコードがpushされたことをArgoCDが変更を検知したら、RDSのスキーマのマイグレーションを行うJobが走った後にapplicationコンテナがデプロイされます。ArgoCDのresource_hooksよって制御されています。
external-secret
Kubernetesにアプリケーションをデプロイする際の大きな悩みの一つが機密情報の取り扱いでしょう。
以前はsealed-secretsを採用していました。sealed-secretは開発者がローカルマシンで機密情報を暗号化して、暗号化したyamlをgit管理するという設計です。
しかし、アプリケーション開発者が環境変数を追加するオペレーションをうまく組めず、結局Kubernetes構築者に依頼がきて追加するというオペレーションになってしまっていました。
そこでECSのように直感的にssmを扱えるexternal-secretを導入しました。external-secretsはssmのnameを渡すと対象のvalueを引っ張ってきてsecretを作ってくれます。
ECSでの運用に慣れていることもあり、アプリケーション開発者が自由にsecretを作れるようになりました。
導入にあたりAWS Secrets and Configuration Providerも検討しましたが、EKS Fargateに対応していないという大きな制約があったので見送りました。(公式のブログにあったのでてっきり使えるものだと思ってしまいました...)
Amazon ECS
EKSを使う必要がない場合はECSを採用しています。
現在AWSでコンテナ動かしたかったらECS Fargateが安定じゃないでしょうか?
ホストマシンの管理が不要で、アプリケーションの開発になるべくリソースを割きたいスタートアップにはもってこいです。
オートスケーリングの設定が簡単にできたり、他のAWSリソースとの連携も充実していたりと、AWSを採用する理由になるくらい魅力的なサービスだと思っています。
ispecではPubSubパターンを使ったアーキテクチャを採用することが多く、その際にRedisのPubSubの機能を使っています。
アーキテクチャに合わせてECSでサイドカーを置いたり、ElastiCacheを使ったりしています。
AWS Lambda
AWS LambdaはAWSの象徴的なサービスの一つで、イベント駆動でコンテナをサーバーレスで実行できます。(コンテ以外も実行できます。)
コンピューティングリソースを必要としない単発のジョブの実行に採用しています。
VPC内で実行できるのでRDSのスキーマのマイグレーションのためにsqldefのコンテナの実行を行なっています。
sqldefの実行環境としてECSのスタンドアロンタスクも検討しましたが、Fargateの起動時間がネックとなってしまったため断念しました。
コンピューティングリソースを必要とする場合はスタンドアロンタスクやAWS Batchを採用することもあります。
AWS App Runner
制約がある中でも、最速でデプロイしたい場合などにAWS App Runnerを採用します。
先日VPC Connectorがリリースされ、VPC内のprivateなリソースにアクセスできるようになったのでプロダクションで採用できるようになりました。
サイドカーでコンテナを動かせなかったり、ECSと比べると他のAWSリソースとの連携が弱かったりというデメリットがあります。
例えばssmとの連携がサポートされていないのでssmのsecretの値を使いたい場合には、Terraform上でssmをdataとして取ってきてからapplyする必要があります。
これらの制約の中、満足するアーキテクチャを組めた場合はApp Runnerを採用します。
ECSと比較してマネージドでやってくれることが多く、リソースを抑えることができるのが大きなメリットです。
スタートアップのゼロイチ開発でリリースまで時間がない場合など局所的に活躍できるものだと思っています。
個人的にもっとアップデートされれば、スタートアップのプロジェクトではファーストチョイスになりうるものだと思っているので、今後に期待しています。
開発ロードマップも公開されているので要チェックです!
Github Actions
CI/CDはほとんどGithub Acitionsで完結するようにしています。
現在導入されているものは
- Docker Imageのbuild
- ECRへのpush
- AWS Lambdaのinvoke
- reviewdogの実行
- terragrunt planの実行と実行結果のPRへのコメント
- terragrunt apply
です。
terragruntに関してはssmのparameterを自前にapplyしておく必要があるので、PRへの特定のコメントをトリガーとしてssmだけ先にapplyできるようにしています。
CircleCI
orbsが豊富にあるため、github actionsでやろうとするとスクラッチで開発しないといけなくなった場合などに採用しています。
主にDockerイメージのビルドとECRへのプッシュをcircleci/aws-ecrで、 ECSへのデプロイをcircleci/aws-ecsを利用しています。
少ない記述でワークフローを組めるため重宝しています。
k6
負荷試験ツールとしてk6を採用しています。
k6は、jsでシナリオを記述して負荷試験を行えるものです。
社内にjsをかける人が多いというのも嬉しいポイントです。
マシンスペックがボトルネックにならないように、コンテナ化して社内のKubernetes クラスターで動かしています。
想定されるユーザー数とアプリケーションの性質から同時リクエスト数等を割り出し、負荷試験を実施することで、サーバースペックの見直しや、それに伴う費用の見積もりなどを行っています。
まとめ
以上、主要なスタックをまとめました。
その他のAWSリソースに関してもアーキテクチャに合わせて適宜選定、実装します。
基本的にコンテナサービスに絞っていて、イメージ開発者と実行基盤開発者で責務を分けることでスタートアップに必要なスピード感とスケーラビリティを両立します。
まだまだ監視基盤であったり、SLOダッシュボードであったり、やらなければならないことがたくさん残っています。
採用サイトではSREとして上記の課題を一緒に解決してくれる人を大募集しています。
- 技術へのこだわりやプロダクト愛を活かして働きたい
- フルリモート、フルフレックス下で最大限のバリューを発揮したい
- 心理的安全性の高い環境の中、チームで成果をあげてみたい
そんな思いを持った方がいらっしゃいましたら、ぜひ一度ご応募ください!
Discussion