スポットインスタンスで個人開発 Web サーバーを運用する技術

趣味の Web アプリを廉価にデプロイしたい、という話題が最近盛り上がっています。

この記事では、最近の私の個人開発 Web サーバーの運用方法を簡単に説明します。

TL; DR

  • AWS のスポットインスタンスを一台借りて、terminate されたときはいい感じに再起動する
    • データ永続化用の EBS を別途マウントする
  • そこに docker でアプリをたくさんデプロイして相乗りする
    • DB も docker で立てる
  • wildcard 証明書を取って、リバースプロキシに subdomain ベースでルーティング先のコンテナを振り分けさせる
  • 料金は諸々合わせて $13/月 ぐらい

詳解

スポットインスタンス

スポットインスタンス は、AWSから廉価にEC2サーバーを借りる手段ですが、突然サーバーをシャットダウンされる可能性があります。そのため、スポットインスタンス1台だけでサービスを運用するのは、通常は許されない行為です*1

しかし、利用者が少なくお金も取ってないような個人開発アプリケーションという文脈では、多少のダウンタイム(数分/週程度)は十分許容できます。つまり、スポットインスタンスが落とされても、数分以内に新しいインスタンスが立ち上げられれば問題ありません。

私は、個人開発サーバーをスポットインスタンスで運用し、以下のような lambda スクリプトを5分おきに実行するようにしています。

  • 特定のタグがついた EC2 インスタンスが存在するかチェックする
    • 存在しない場合は、適切なインスタンスタイプを判定して新しく作る
  • そのEC2インスタンスに特定の EIP がアタッチされているかチェックする
    • アタッチされていない場合はアタッチする
  • そのEC2インスタンスに特定の EBS がマウントされているかチェックする
    • マウントされていない場合はマウントする
  • そのEC2インスタンスで dockerd が起動しているかチェックする
    • 起動していない場合は起動する

これによって、インスタンスが terminate されても最長10分程で元の状態に戻ります。

ちなみに、大体4ヶ月ぐらい運用していて、実際にこの処理が実行されたのは12回ほどでした。ただ、これには動作確認のため私が手動で terminate したものも含まれます。体感では、 t4g.small を使う限りでは月に一回程度 terminate されるかどうか、といったところです。

また、Heroku と違って東京リージョンにサーバーを立てられるので、日本からアクセスしたときのレイテンシが体感できるレベルで速いです*2*3

docker でアプリをデプロイ

以下の記事を参考にして、docker compose でアプリをデプロイしています。

Docker Contextsを使ってDocker Composeをデプロイする際の注意点 - ぷらすのブログ

たくさんアプリをデプロイしても、母艦となる EC2 インスタンスが汚れないので持続性が高いです。なお、概ね記事通りの構成ですが、いくつか差分があります。

  • Docker Registry を自前で立てて、そこから image を落としてきてコンテナを起動する構成
    • Registry なしの構成はなんかうまく行かなかったので断念
    • Registry も「アプリ」の一つとして運用している
    • ECR は高いので却下
  • 諸々を楽にやるための CLI Tool を作った
    • GitHub - genya0407/carrier

      • `carrier init` で設定ファイルを生成
      • `carrier release` で image を build / push
      • `carrier deploy` でコンテナを起動
    • 自分以外の人が使うことをあんまり想定してない作りです

RDBMS についても、docker コンテナの一つとしてサービスごとに立ち上げています。MySQL はメモリの消費量が多かったので*4PostgreSQLSQLite を使うようにしています。

リバースプロキシ・SSL

前述のブログを参考にして、リバースプロキシには Caddy を使っています。適切に設定するとワイルドカード証明書を勝手に取得してくれるすごいやつです。Caddy も docker コンテナとしてデプロイして、 Dockerの埋め込みDNSサーバを使ったService Discovery を利用して各サービスにルーティングしています。

料金について

月々の料金は大体 $13 ぐらいですが、その内訳は、

  • EBS:$7.5
  • EC2:$4.5
  • Secrets Manager:$0.5
  • Route53:$0.5

という感じで、データの永続化のために契約している EBS の料金が支配的です。

EC2 に関しては、t4g.small というかなり弱めのインスタンスタイプを利用しているということもあり、かなり安く抑えられていると思います。アプリが増えてリソースが足りなくなってきたら、もう少し強いインスタンスに乗り換えても良いかもしれません。

雑感

  • 可用性を犠牲にし、アプリもDBも全部スポットインスタンスに立てるという意思決定が出来たのは良かった
    • RDS とか借り始めるといきなりお金がかかるようになるので厳しい
    • DynamoDB 等を使うという選択肢もあるが、やはり SQL で開発したいという気持ちは強い
  • サーバーのスペックアップ・スペックダウンが自在に行える点は良い
    • 万が一アプリがバズったときに札束で殴る余地がある
    • さくらのVPSとかだと、スペックアップはできてもスペックダウンができないので、一時的なスペックアップという選択肢が取りづらい
  • arm64 のマシンが使えるのが良い
    • ローカルの mac が M1 なので、手元で docker image をビルドする関係上、サーバーも arm64 でないとビルドにかかる時間がえらいことになる
    • 前に使っていたさくらのVPSには arm64 マシンがなかったような気がする(今はあるかも)
  • スポットインスタンス再起動用の lambda をいい感じに作れたのが個人的には気に入っている
    • 冪等なので、インスタンスが存在するときに動かしても問題なく、定期的に実行すれば良いだけなので、考えることが少なくて済んでいる
  • EBS の料金が高いのはちょっと意外だった
  • 本当にアクセス数が少ないアプリしかないので、もう少しコンスタントにアクセスが来るようになったら成立しなくなる可能性は十分ある
    • そのときはそのとき考える
  • k8s で似たようなことができるんじゃないかなと思ってるけど、やり方がよくわからないので勉強中です

以上

*1:実はオンデマンドインスタンスも突然死ぬことはあるので、オンデマンドインスタンス1台で運用するのもやってはいけないのだが...

*2:Heroku はアメリカとヨーロッパにしかサーバーが立てられないので、体感できるレベルでレイテンシがでかい

*3:例えば  Reing あたりにアクセスすると速さを感じられると思う

*4:設定を適切にすれば解決できる気がするが私には無理でした