🏋️

お家ラズパイk8sクラスタで動かしてたアプリをGCPに移行した話

2021/12/20に公開

この記事はGoogle Cloud Platform Advent Calendar 2021の19日目の記事です。

https://qiita.com/advent-calendar/2021/gcp

この記事では、ラズパイk8sクラスタで運用してたアプリをGCPに移行した話を書きます。
なるべく利用料金を抑えるための構成を頑張ってみたので、そのポイントを書いていこうかと思います。
最終的には 4円/日 程度で運用できてます。
(もっと安くできるぞ!って部分あれば是非コメント等で教えていただけると嬉しいです)

記事としては

  1. 元々自宅のk8sで動かしていた時の構成
  2. GCP移行しようとした経緯
  3. GCP移行時に金額を抑えるために変更すべき箇所
  4. 最初のアーキテクチャ案(没案)
  5. 第2のアーキテクチャ案
  6. さらに料金を安くするために

の流れで書こうと思います。

元々自宅のk8sで動かしていた時の構成

はてブから情報引っ張ってきてSlackへ通知するアプリをk8sで運用してました。
別のアドベントカレンダー記事でアプリ概要について書いてるので、よかったら先に読んでもらえるとこの後のGCP移行の話も理解しやすいかもです。

https://zenn.dev/esaka/articles/4eb358988bc987

ざっくり下記のような構成で動かしてました。

k8s上で3つのアプリ(scraper, observer, notifier)とそれぞれのアプリ間のデータ受け渡しようにredisをDeploymentリソースで動かしてました(これらの経緯は上の記事で書いてます)
ざっくりそれぞれのアプリの役割を描くと

  • scraper: はてブのマイページを5分に1回スクレイピングして、自分がブックマークした情報を取得する
  • observer: scraperが取得したブックマークページを5分に1回APIに投げて、新しいコメントがついてないかを確認する
  • notifier: observerが取得した新規コメントをslackへ通知する

というものになります。scraper, observerは5分に1回動作してあとはsleepしてるバッチ処理的なもの、notifierは新しいコメントが来たときに動作するイベント駆動的なアプリになります。(次以降のGCP移行時の構成はこれを意識しました)

GCP移行しようとした経緯

GKE Autopilotができたこと

一番大きかったのは今年GKE Autopilotができたことです。

https://cloud.google.com/blog/ja/products/containers-kubernetes/introducing-gke-autopilot

知ってる方が多いと思いますが、簡単に説明しますと
GKE Autopilotを利用しますと、GCEのノードを持たずにPodの作成要求が入るたびに動的に実行環境が用意されます。課金体系もPodが要求したコンピューティングリソース(CPU, Memory)の料金だけとなります。
これにより今回移行しようとしているようなバッチ処理やイベント駆動的な短時間の処理だけで済むようなアプリでは非常に安価で利用することができるようになります(待ち時間はPodを落とせば無料になる)
AWSとかよく触る人ならEKS Fargageが近いイメージだと思います。

1つのゾーンクラスタ or AutoPilotクラスタなら永久無料枠で使える

お恥ずかしながら、最近まで知りませんでした。
2018,19年当たりにEKSなどと同じようにGKEもクラスタ管理費用がかかるようになったという情報からアップデートをしてませんでした。。

https://cloud.google.com/kubernetes-engine/pricing/

GKE の無料枠では、ゾーンクラスタと Autopilot クラスタに適用される月額 $74.40 のクレジットが、請求先アカウントごとに提供されます。単一のゾーンクラスタまたは Autopilot クラスタのみを使用する場合は、このクレジットで最低限そのクラスタの月額の費用がすべてカバーされます。

これにより単一のAutopilotクラスタなら、1日数分程度のコンピューティング費用とネットワーク利用料金だけで運用できるのではないか?と思いGCPに移行を決めました。

GCP移行時に金額を抑えるために変更すべき箇所

上の理由からGCPに移行しようと思いましたが、今の構成をそのまま移行すると
常時稼働のアプリケーションとRedisをautopilotで動かすだけで結構金がかかってしまいます。

大きく3つのポイントがあります。

アプリケーション内でsleepさせない

元々の構成だと全てのアプリが無限ループ内で処理とsleepを交互にやってる感じのコードにして、k8sのDeploymentリソースとして運用してました(お家k8sクラスターなので)
これだとAutopilotの説明でも書いたようにsleep時間も課金されてしまうので、sleepをアプリの外でやらせる必要があります。幸いk8sにはcronjobというリソースがあるので、これを利用して"*/5 * * * *"で5分に1回起動させて、処理終わったらPod終了させることで処理時間だけの課金に済ませることができます。

Redisをコンピューティング費用掛からず、ストレージ料金/オペレーション料金しかかからないものに変更する

元々の構成だとRedisをk8sクラスタ上でDeploymentリソースとして運用してました(ほんとはstatefulsetとかにしてredisもストレージにデータ定期的に吐き出すか、そもそもRedisじゃなくてちゃんとストレージ保存するストア使うべきですが、個人のたかだか通知アプリなので手抜きしてました)。これも同じくそのままにすると無駄にコンピューティングリソースなどかかるので、どうにかしたいです。

KVSとして使えて、かつインスタンス費用など掛からずストレージ料金/オペレーション料金しか掛からないAWSでいうDynamoDB的なものをGCPで探したところ、Datastore/Firestoreが該当するのかと思い、今回はFirestoreを利用することにしました
(ここあまり詳しくないのですが、ドキュメントを見る感じFirestoreに統一しようとしてるんですかね?最近)

イベント駆動はKnative/Cloud Runを利用しよう

アプリのうちnotifierだけはsleepが5分に1回でなく、slackのAPIレート制限(1秒に1回しか呼び出せない)に準拠して2秒sleep(一応余裕持って+1秒にしてる)となります。
これをcronjobでやるのは無理なので、イベント駆動で行いたいです。

k8sでイベント駆動だとKnativeが思い浮かびますが、GKEでKnativeやるならCloud Runでいいでしょうというのが自然かと..

最初のアーキテクチャ案(没案)

変更ポイントを考慮しつつ、下記のような構成を最初は考えました。

observer, scraperのバッチ処理的なアプリはGKEでcronjobで動かして、データのやり取りにはFirestoreを利用します。
Observerは投稿してほしいコメントをPub/Subに突っ込み、Push SubscriptionでCloud Runで動作させるnotifierを実行させるという構成を考えました。

が、これは先ほど述べたSlackのAPI Rate Limitに対応できず没にしました。

Slackの制約

先ほども述べたようにSlackではチャットのメッセージ投稿用のAPI(chat.postMessage)のレート制限があり
1秒に1回までとなっています。
単純にpub/subのPush Subscriptionでポンポン投げてたら余裕で引っかかってしまいます。

GCPで1秒sleepさせるには?

Pub/SubのPush Subscriptionだとデベロッパー側でレート制限できなさそう

https://cloud.google.com/pubsub/docs/push#delivery_rate

Pub/SubのPush Subscriptionで配信レートの設定がありますが、これは自分で設定できずにGCP側で処理成功件数を見てレートを調整する仕組みになっています。

https://stackoverflow.com/questions/45087540/how-to-rate-limit-google-cloud-pub-sub-queue

stackoverflowで似た質問をしてる人いましたが、GCPで管理は無理で自分で管理しないとダメのように見えました。

Push Subscriptionでなく、workflowsで制御する(無料枠に収まらない)

なら自分で管理するということで、Sleepにコンピューティング費用をかけずにできるものとしてworkflowsがあるのかと思いました。

https://cloud.google.com/workflows

ただ、workflowsの無料枠は月当たり最初の5000ステップまででsleepにもステップが割かれることや定期的にPub/Subに新しいコメント入ってないか確認するstepも入れたら余裕で超えそうだと思い、これも諦めました。

第2のアーキテクチャ案(妥協案)

結局notifierもGKEでautopilotで動かすことにしました。イベント駆動でなく他のアプリ同様5分に一回起動するCronjobとして動かし、firestoreからコメント情報を全て取得し、Slackに投稿したらfirestoreからそのコメントdocumentを削除し、2秒スリープして再度slackへ投稿、削除を繰り返します。

2秒✖️コメント数分の無駄なコンピューティング費用はかかりますが、東京リージョンの場合1vCPU, 1GBメモリで大体0.06$/1h(3600s)程度なので、妥協してこの案にしました。(もしかしたらworkflowsの方が安いかもしれない)

(実際は0.25vCPU, 512MBメモリ(AutoPilotで指定できるリソースの最低値)で動作させてるので料金はさらに1/4になります)

これで運用したところ、大体1日当たり全てのGCP費用で7円程度になりました
(AutoPilotのvCPU費用で大体6円、その他のネットワーク料金やmemory, storage費用で1円くらいの内訳)

上で書いた4円じゃなかったのか?と思うかもですが、さらに安くした話を次の章で書きます。

さらに料金を安くするために

Spot Pods for GKE Autopilotでさらに安く

https://cloud.google.com/blog/ja/products/containers-kubernetes/announcing-spot-pods-for-gke-autopilot

今年の11月にSpot Pods for GKE Autopilotがリリースされました。

Spot Pod でワークロードを実行する場合、通常料金の Pod の 60~91% の割引が受けられます

Spotインスタンスなどと同様に、急遽停止する可能性がある代わりに破格の割引を受けれるものになります。これまで説明してきたように、今回動いてるアプリはかなり短時間で動くバッチアプリかつ、もし仮に落ちてもただのslackの通知アプリでさほど問題ないので早速導入してみました。

これによって利用料金が7円/日から4円/日に減りました。

12/1-12/17までが普通のAutopilot Pod, 12/18からSpot Podsに移行

アプリのDockerイメージをasia.gcr.ioに配置する

Autopilotでは、普通のノードのようにイメージキャッシュ機能が働かずPod起動のたびに毎回Image pullを行うようです。
そのため外部のdocker hubでなく同じGCP内のコンテナレジストリ使った方がいいかなと考え
gcr.ioにイメージを置いていたら、転送費用で1日44円もかかってました。。。

gcr.ioはusリージョンに配置されるらしく、東京リージョンで使おうとすると転送費用が発生するそうです。
asia.gcr.ioに置くことで、転送費用を無料にできます

参考にさせてもらった記事

最後に

普段は仕事でAWS使う機会が多くあまりGCPは触ってこなかったのですが、GCPでは小さい利用用途に限り1年過ぎても使える永久無料枠などあるの多くて、個人開発アプリとか運用するのにはとてもいいなと思いました。
今後もGCPの情報を追っていきたいと思います。

Discussion