- 背景
- やったこと1. 廃止
- やったこと2. GCPに移行
- やったこと3. CircleCIに移行
- 付録A. 道のり
- 付録B. 調査メモ(移行時に参考にしたドキュメントやサービスなど)
- 付録C. Redisを雑にFirestoreに置き換えたらクラウド破産しかけた
- 2022/09/22 20:45ブコメレス
背景
Herokuの無料プラン終了のため10個以上あった個人アプリを1ヶ月くらいかけて色々対応してました
対応したと言ってもHerokuから移行しないという判断を下した対応もあるのでその辺含めて書いていきます。
やったこと1. 廃止
これが一番簡単
それなりに利用されてそうだけど諸般の事情*1で移行しないと決めたアプリに関しては下記のように「NO LONGER MAINTAINED !」ってお知らせをだして、GitHubにもお知らせを書いてリポジトリをアーカイブしました。
https://github.com/sue445/app-stat-api#no-longer-maintained
具体的には下記が該当
実は一番最後のやつに関しては途中までGoogle App Engine(以降、AppEngineと呼称)に移行してたんですが *2、移行作業中にこれを動かしてるTwitterボットのアカウントが凍結してることを知って一気にやる気が消えて移行断念しました...
ボットが凍結されていたらツイート投稿時にエラーになってるはずなんだけどRollbar入れてたのに検知できていなかったっぽくて本当に謎。
やったこと2. GCPに移行
完全に自分の慣れの問題だけど手持ちのHerokuアプリはほぼ全部GCPに移行しました。
僕自身はAWSとGCPを両方とも業務で使い倒しているからAWSにも全然移行できたと思います。
この手のプラットフォーム選定は誰しも一家言持っているはずなのでみんなも自分が好きなやつに移行すればいいと思います。
GCPといっても色々ありますが、僕は下記のような選定基準で使い分けました。
ユースケース図
URLベースで見たユースケース図
- URLベースだとこんな感じ
- 今まで作ってきたツールが割とウェブアプリとかwebhookとかが多かったので自分の中ではURLを起点に考えるのが自然でした
- SlackやGitHubに設定するwebhookであればランダムな文字列が入っていても問題ないと思うのでCloud FunctionsやCloud Runでよい
- 普通にブラウザからアクセスさせたい場合はそういうランダムな文字列が入っていると見栄えがよろしくないので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回目)
- GCEインスタンスに
実際にGCPに移行したアプリ達
Cloud Run
HerokuからCloud Runに移行したのは下記
採用理由としては
- アプリケーションを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に移行したのは下記
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に移行してました。
8年くらいHerokuで動かしてたオレオレRailsアプリを1週間くらいかけてAppEngineに移行できた。(移行が必要なアプリは残り1つ)https://t.co/aVr6SJeOV9
— sue445 (@sue445) 2022年9月18日
ちなみにアプリだけだとやったことはこんな感じですね。(これ以外だとAppEngineのGCPプロジェクトを管理するためのTerraformリポジトリも新規で作ってる) pic.twitter.com/C0rQ2aXCmO
— sue445 (@sue445) 2022年9月18日
デプロイ時にdb:migrateするために https://github.com/GoogleCloudPlatform/appengine-ruby を使っているのですが、Ruby 3.0だと動かなかったのでパッチを投げています。もし困っている人がいたらこのパッチを使ってください。 *6
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に関しては下記を参照
やったこと3. CircleCIに移行
下記はCircleCIに移行しました
- 雑なボットだと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. 道のり
ISUCONの本選ライブ中継を見ながらHerokuのFree app対応をしてる。(10アプリくらい管理しててようやく1つ終わったところ)
— sue445 (@sue445) 2022年8月27日
自分がHerokuで管理してる個人アプリでHerokuからの移行を諦めたやつにNO LONGER MAINTAINEDを追加する修正をしていた。(つらい)
— sue445 (@sue445) 2022年9月9日
Heroku移行進捗はこんな感じ
— sue445 (@sue445) 2022年9月9日
移行完了:4
サポート終了:4(リポジトリアーカイブしてHerokuがアプリを消すまでは動かし続ける)
未着手:4(頑張れば移行できる)
10個以上あったHerokuの個人アプリ、あらかた対応して残り2個になった。どっちもDB含めて移行が必要なので大変そう...
— sue445 (@sue445) 2022年9月11日
8年くらいHerokuで動かしてたオレオレRailsアプリを1週間くらいかけてAppEngineに移行できた。(移行が必要なアプリは残り1つ)https://t.co/aVr6SJeOV9
— sue445 (@sue445) 2022年9月18日
付録B. 調査メモ(移行時に参考にしたドキュメントやサービスなど)
無料プラットフォームがまとまってるドキュメント
移行作業中何回も読んだ。
ElephantSQL (PostgreSQL)
20MBまでなら無料
- 前述のサザエ実況アプリではこれを採用しました
- 毎週日曜のサザエ実況ツイート数をDBに記録してるんだけど、約10年分のデータで8.6MBしか使ってなかったのであと10年は無料プランで戦えるw
- AWS, GCP, Azureが選択可能
- 無料だと東京リージョンが使えるのはAWSだけですが、東京リージョンがあるだけでもかなり嬉しい
PlanetScale (MySQL)
無料プランだと1 organization辺り1データベースが無料で使える
- 1データベースとはいえストレージ5GB使えるので小さいアプリなら1つのデータベースに複数相乗りは可能そう
- Railsだとschema_migrationsが有るので複数相乗りは厳しい気がする
Redis Enterprise Cloud
30MBまで無料
Redis Enterprise Cloud Pricing | Redis
付録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ブコメレス
ドメインと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にあるのでこっちをなおす必要がある