個人開発のサービスをVPSからVercelとCloud Runに移行した話
最近以下のような記事で個人開発のコストの話をよく見かけて、ちょうど自分も個人サービスをコストカットのためにVPSからほぼ無料なスタックに移行していたので構成とかを書いてみる。
前提としてはこんな感じ。
- 仲間内で使ってるだけのWebアプリケーション。月イチくらいしか使わない
- 技術スタックは技術的な実験とか学習を兼ねているので多少オーバースペックになるのはいい
- お金はなるべくかけたくない
移行前のスタック
- フロントエンドはNuxt.js、Netlify
- バックエンドはRailsでgRPC、envoyを噛ませてフロントエンドからはgRPC-Webで呼んでる
- VPS上にバックエンドのアプリケーションとDB(postgres)を動かしてる
バックエンドは普通のRailsアプリにしてHerokuにするのが一番楽でお金もかからないんだけど、gRPC-Webを試してみたくて、そうするとproxyが必要になってHerokuとかでは運用できないのでVPSに乗せていた。ついでにDBもVPS上で動かしていた。
まあgRPC-Webは技術的な実験としてはおもしろかったけどあんまり流行る気配もないし、使ってみた感じけっこう微妙な面も見えてきてので一回やめることにした。
移行後のスタック
- フロントエンドはNext.jsにしてVercel
- バックエンドはRailsでGraphQLにしてCloud Run
- DBはPlanetScale
フロントエンド
SSRとかする必要はないのでNetlifyのままでもよかったけど、NuxtのアプリケーションはもうメンテしたくないのでNext.jsに移行するついでにVercelにした。
Next.jsに移行するのはサクっとできたけど、誤算だったのはVercelの無料プランだとOrganizationのリポジトリはGitHub連携できないということ。個人リポジトリに移してもよかったけど、仲間内で使ってるアプリケーションなのでできればOrganizationのままにしておきたくて、GitHub連携を諦めてGitHub Actionsでデプロイすることにした。
amondnet/vercel-actionというのを使ってこんな感じ。わりとサクっとできた。
name: Deploy To Vercel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: yarn
- run: yarn install
- uses: amondnet/vercel-action@v20
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-args: '--prod'
vercel-org-id: ${{ secrets.ORG_ID}}
vercel-project-id: ${{ secrets.PROJECT_ID}}
working-directory: ./
PR時にプレビュー環境作ってくれるみたいのはないけど、まあそんなに頻繁に開発するものでもないのでこれで妥協した。
バックエンド
gRPC-WebをやめてGraphQLにするというのは最初に決めたものの、Railsをやめるかどうかはけっこう迷ったけど、結局サーバーサイドのロジック書き直すのめんどくさかったのでRailsのままいくことにした。ちなみに他の候補としてはGoとかNext.jsのAPI Routesあたり。
デプロイ先はHerokuにしてもよかったけど、Cloud Runが気になっていたので勉強も兼ねてCloud Runにした。Cloud Runで動かすの自体はサクっとできたけど、デプロイのジョブをCloud Buildで作るところがちょっと大変だった。
Cloud Buildにしたのは特に強い意志はなくてCloud ConsoleでポチポチしてたらGitHubにpushしたらデプロイまでやってくれるジョブが生えたのでそのまま使ったんだけど、そのジョブにDBのmigrationを足したりするのに苦労した。やり始めたら楽しくなってきたのでCloud Buildの結果をpubsubからCloud Function経由でSlackに通知するというところまで作り込んでしまった。便利。
月イチくらいしか使わないサービスなので使うときにCloud Runのコールドスタートがやや気になるかなと思い(実際は待てばいいだけだが)、Railsにこんな感じのエンドポイントを生やしておき
get "/ping", to: ->(env) { [200, {}, ["pong"]] }
最初のリクエストのときにprefetchを飛ばすことでCloud Runを起こすということをやってみたりした。
<link rel="preload" href="https://api-server/ping" as="fetch">
アプリケーションの特性上、最初のリクエスト時はAPIサーバーからデータをfetchすることがないのでこれでいいんだけど、ほとんどのアプリは最初のリクエスト時にfetchが走ると思うので特殊なケースだけ可能なワークアラウンドである。
DB
にもあるように、個人開発のコストはDBが難しいんだけど、今回はデータもアクセスも少なくてDBaaS無料プランで十分そうだったので今回はPlanetScaleを試してみた。
ブランチの機能とか便利そうだけどそこまで本格的に運用するわけではないけど、普通に無料のMySQLサーバーとしてだけ使っている。特にハマりどころもなくて便利だった。東京リージョンがあるのもいい。
まとめ
VPSを発破解体してほぼ無料スタックにできたんで満足。まあでもやっぱこれぐらいだったらHerokuでいいね。
Discussion