くりにっき

フルスタックキュアエンジニアです

Herokuにあった個人アプリを軒並み対応した

背景

Herokuの無料プラン終了のため10個以上あった個人アプリを1ヶ月くらいかけて色々対応してました

blog.heroku.com

対応したと言ってもHerokuから移行しないという判断を下した対応もあるのでその辺含めて書いていきます。

やったこと1. 廃止

これが一番簡単

それなりに利用されてそうだけど諸般の事情*1で移行しないと決めたアプリに関しては下記のように「NO LONGER MAINTAINED !」ってお知らせをだして、GitHubにもお知らせを書いてリポジトリアーカイブしました。

https://github.com/sue445/app-stat-api#no-longer-maintained

具体的には下記が該当

github.com

github.com

github.com

github.com

github.com

実は一番最後のやつに関しては途中までGoogle App Engine(以降、AppEngineと呼称)に移行してたんですが *2、移行作業中にこれを動かしてるTwitterボットのアカウントが凍結してることを知って一気にやる気が消えて移行断念しました...

ボットが凍結されていたらツイート投稿時にエラーになってるはずなんだけどRollbar入れてたのに検知できていなかったっぽくて本当に謎。

やったこと2. GCPに移行

完全に自分の慣れの問題だけど手持ちのHerokuアプリはほぼ全部GCPに移行しました。

僕自身はAWSGCPを両方とも業務で使い倒しているからAWSにも全然移行できたと思います。

この手のプラットフォーム選定は誰しも一家言持っているはずなのでみんなも自分が好きなやつに移行すればいいと思います。

GCPといっても色々ありますが、僕は下記のような選定基準で使い分けました。

ユースケース

URLベースで見たユースケース

  • URLベースだとこんな感じ
    • 今まで作ってきたツールが割とウェブアプリとかwebhookとかが多かったので自分の中ではURLを起点に考えるのが自然でした
  • SlackやGitHubに設定するwebhookであればランダムな文字列が入っていても問題ないと思うのでCloud FunctionsやCloud Runでよい
    • ちなみにGoogle App Engineだと一度起動したインスタンスはリクエストの処理時間が一瞬だったとしても最低15分間の費用が発生するため *3、webhook用途だとあまり向かないというのもあります
    • Cloud Functions *4 やCloud Run *5だといずれも100ミリ秒単位の課金
  • 普通にブラウザからアクセスさせたい場合はそういうランダムな文字列が入っていると見栄えがよろしくないのでGoogle App Engineを使うかカスタムドメインを使うしかなさそう
  • 安いTLDを使えばドメインはそんなに費用かからないしGCPのマネージドSSL証明書も無料なのでカスタムドメインでもええやん説はあるんですが、SSL証明書を紐づけるためのロードバランサーが個人アプリだと高くつくんですよね...

実行環境で見たユースケース

  • 図中の「使いたいDockerイメージがGCPにある」とは、DockerイメージがContainer Registry(gcr.io)かArtifact Registry(pkg.dev)にホスティングされていること
    • Cloud RunやGoogle App Engine Flexible EditionはこれらのDockerイメージしか対応していないので実は重要ポイント
    • Cloud Runで動かしたいためだけにいくつかの個人ツールはArtifact RegistryにDockerイメージをホスティングしました
  • DockerHubやGitHubのContainer Registry (ghcr.io)のイメージを使いたい場合はGKE一択
    • GCEインスタンスdocker-compose up もできなくはないけどマジでサーバの管理やりたくなさすぎる...(2回目)

実際にGCPに移行したアプリ達

Cloud Run

HerokuからCloud Runに移行したのは下記

github.com

github.com

github.com

採用理由としては

  • アプリケーションをDeploy to Herokuで配布してたので、脱Heroku後はDockeriseしてDockerイメージとして配布したかった
  • リリース用のバージョンtagとは別に、mainブランチの内容を常にデプロイして動作確認として使うための環境を考えたらCloud RunかAppEngine(Flexible Edition)が適切だった
  • emoy_webhookは会社アカウントのHerokuでも動かしているのでそっちのHeroku対応必要だったのだが、OSSとして配布してるDockerイメージを会社のGCPプロジェクトのCloud Runで動かすのが一番楽

って感じだったと思います。

個人アプリを配布したい場合はアプリケーションをDockeriseしてArtifact RegistryでDockerイメージを配布し、それをCloud Runとかで動かせるようにするのが便利ですね

ちなみにCloud Run移行後に Cloud Run Button の存在を思い出したんですが、DockeriseとしとけばCloud Run以外のDocker環境でも動かせるのでこれはこれで対応としてはありだったと思ってます。

Cloud Functions

HerokuからCloud Functionsに移行したのは下記

github.com

Cloud Run移行したアプリと違ってこれはDockerイメージとして配布する必要がなかったのでCloud Functiinsに移行しました。

ちなみにこのアプリは素朴なSinatraアプリなのですが、https://github.com/GoogleCloudPlatform/functions-framework-ruby を使うと下記のような感じでSinatraアプリを手軽にCloud Functions対応できてむっちゃ便利でした。

FunctionsFramework.http("regional-rb-calendar") do |request|
  App.call(request.env)
end

アプリケーションコードだとfunctions-framewor依存はこの3行だけなので、 Sinatra::Base を継承した App に関してはいつも通りにSinatraアプリとしてテストを書けるのが嬉しい。

AppEngine

OSSじゃなくて恐縮ですがサザエ実況用の個人アプリをHerokuからAppEngineに移行してました。

デプロイ時にdb:migrateするために https://github.com/GoogleCloudPlatform/appengine-ruby を使っているのですが、Ruby 3.0だと動かなかったのでパッチを投げています。もし困っている人がいたらこのパッチを使ってください。 *6

github.com

GCP移行した全てに共通してること

下記のような理由で1つのアプリにつきGCPプロジェクト(とそれに対応するTerraformリポジトリ)を1つずつ作ってます

  • 予算アラートを仕込む時はGCPプロジェクト単位でやるのが慣れている
  • 各アプリは完全に独立してるのでGCPプロジェクトも独立してるのが自然
  • AppEngineのアプリやFirestoreのスキーマみたいに1つのGCPプロジェクトで1つしか作れないものがたまにある
    • Firestoreに関してはkeyのprefixを工夫すれば複数アプリ共存させることもできるが、keyのパターンやcollection単位でIAMの権限を制御できないのでGCPプロジェクトを分けるのが確実

正直Terraformリポジトリを量産するのは大変のように思えるかもしれないですが、 https://github.com/sue445/terraform-gcp-template のおかげで1つ辺り10分前後で初期セットアップ(リポジトリ作ってから、git pushしたらGitHub Actionsでapplyされるところまで)できているのでそんなに大変ではなかったです。(自分のいつもやる構成をテンプレートリポジトリにしといて本当に良かった...)

terraform-gcp-templateに関しては下記を参照

sue445.hatenablog.com

やったこと3. CircleCIに移行

下記はCircleCIに移行しました

github.com

  • 雑なボットだとCloud Functionsでもいいんだけど、GCPプロジェクトの管理が手間なのも一理ある
  • ボット実行時の状態管理(通知したかどうかのフラグなど)が不要な場合はCIで動かすのが一番お手軽

というモチベーションでCircleCIに移行しました。

こいつは元々Herokuでボットを動かすついでにちょっとした画面も作っていたんですが、画面はなくてもいいのでこの機会に画面をなくしてCircleCIで動かせるようにしました。

ちなみにみんな大好きGitHub Actionsは https://docs.github.com/ja/site-policy/github-terms/github-terms-for-additional-products-and-features#actions

GitHub でホストされるランナーを使用している場合、GitHub Actions が使用されているリポジトリに関連付けられているソフトウェア プロジェクトの運用、テスト、デプロイ、または公開 とは無関係のその他のアクティビティ。

に該当しそうなので、ボット実行の環境として使うのは危ない予感がして避けました。

  • アプリの定期CIのためにスケジューラを使うのはいいけど、スケジューラ実行が主になるとダメだという認識
  • GitHub Actions が使用されているリポジトリに関連付けられているソフトウェア プロジェクト( = ボットスクリプト)の運用」で解釈すればセーフなんだが、サポートに問い合わせるとやぶ蛇になりそうなので聞きづらい...

CircleCIはこの手の記述はなかったので問題ないはず

付録A. 道のり

付録B. 調査メモ(移行時に参考にしたドキュメントやサービスなど)

無料プラットフォームがまとまってるドキュメント

移行作業中何回も読んだ。

blog.unasuke.com

ElephantSQL (PostgreSQL)

www.elephantsql.com

20MBまでなら無料

www.elephantsql.com

  • 前述のサザエ実況アプリではこれを採用しました
    • 毎週日曜のサザエ実況ツイート数をDBに記録してるんだけど、約10年分のデータで8.6MBしか使ってなかったのであと10年は無料プランで戦えるw
  • AWS, GCP, Azureが選択可能
  • 無料だと東京リージョンが使えるのはAWSだけですが、東京リージョンがあるだけでもかなり嬉しい

PlanetScale (MySQL)

planetscale.com

無料プランだと1 organization辺り1データベースが無料で使える

planetscale.com

  • 1データベースとはいえストレージ5GB使えるので小さいアプリなら1つのデータベースに複数相乗りは可能そう
  • Railsだとschema_migrationsが有るので複数相乗りは厳しい気がする

Redis Enterprise Cloud

redis.com

30MBまで無料

Redis Enterprise Cloud Pricing | Redis

  • クラウド(AWS, Azure, GCP)のリージョンが使える
  • 今回の移行よりも前に別アプリで使ってた

付録C. Redisを雑にFirestoreに置き換えたらクラウド破産しかけた

  • OSSにしてない個人アプリでHerokuとRedisを使っていて、GCP移行時にHerokuのRedisをFirestoreに差し替えた
  • RedisのノリでFirestoreでもgetしまくったらRead Opsがヤバいことになった
    • FirestoreはReadやWriteやDeleteの回数に対して課金が発生する
  • 一番多いときでRead Opsが1日1,600万回(1日9ドル)
  • 色々チューニングして今は1日辺りの無料枠に収まる範囲で運用できるようになっている
  • クラウド破産」は正直釣りタイトルだったと反省してる

2022/09/22 20:45ブコメレス

id:tengo1985

ドメインSSL証明書はcloudflare被せちゃダメなの?とは思った。アプリの用途や利用具合なんかはわかってないからなんとも言えないんだけど。

なるほどー!普段そんなに使ってないから最初から選択肢になかったです。(仕事ではたまに使うけど個人では契約していない)

*1:主に自分のモチベーションの問題

*2:余談ですが「以降」と「移行」をかけたダジャレではありません

*3:https://cloud.google.com/appengine/pricing?hl=ja

*4:https://cloud.google.com/functions/pricing

*5:https://cloud.google.com/run/pricing

*6:appengine:execの処理の実装はserverless-exec-rubyにあるのでこっちをなおす必要がある