Lean Baseball

No Engineering, No Baseball.

TerraformとGitHub Actionsで複数のCloud RunをまとめてDevOpsした結果, 開発者体験がいい感じになった話.

ざっくり言うと「TerraformとGitHub ActionsでGoogle Cloudなマイクロサービスを丸っとDeployする」という話です.

  • Infrastructure as Code(IaC)は個人開発(趣味開発)でもやっておけ
  • 開発〜テスト〜デプロイまで一貫性を持たせるCI/CDを設計しよう
  • 個人開発(もしくは小規模システム)でどこまでIaCとCI/CDを作り込むかはあなた次第

なお, それなりに長いブログです&専門用語やクラウドサービスの解説は必要最小限なのでそこはご了承ください.

あらすじ

突然ですが, 皆さんはどのリポジトリパターンが好きですか?

  1. 「ポリレポ(Polyrepo)」パターン - マイクロサービスを構成するアプリケーションやインフラ資材を意味がある単位*1で分割してリポジトリ化する.
  2. 「モノレポ(Monorepo)」パターン - アプリケーションもインフラもその他ものも, まとめて一つのリポジトリにぶち込んで管理する.

個人的には仕事上どっちも経験あって, どっちの良さも悪さも学んだので好き嫌いは無いのですが, 苦労話はモノレポの方が多い*2ような気がします.

私は個人開発で作ってるアプリケーションが大体の場合マイクロサービス化されることが多いので比較的ポリレポ派だったりしますが,

  • アプリケーション(複数ある)のテストとデプロイ
  • クラウド(インフラ)構成の管理とデプロイ
  • 一つのサービス(を構成する複数のアプリとクラウド)をまとめて管理したい

という開発と運用を色々考えて試行錯誤した結果,

独立した2つのシステム(アプリとデータ基盤)毎にリポジトリを分割, TerraformとGitHub Actionsで管理して程よい感じになりました.

アプリ(SoI)とデータ基盤(SoR)で分けていい感じに

概念的には上の絵のイメージです.

もはや個人開発レベルじゃない気もするんですが笑, 前回のブログおよび先日の登壇で良い感触を得たので改めてこのブログで言語化しようと思います.

なおこのブログは前回ブログの続編かつ, 「Jagu'e'r Cloud Native #13 ハイブリッド Meetup」でお話をしたこちらの補足でもあります.

shinyorke.hatenablog.com

speakerdeck.com

ちょっと難易度高めのブログかもしれませんがご了承を🙏

TL;DR

アプリからインフラ(クラウド)まで, まとめてCI/CD(DevOps)しておくと開発者体験はグッと上がります, 個人でも(ただしやり過ぎ注意).

今回作ったシステム

個人開発サービスとして2022年から構築・運用している「メジャーリーグのデータ可視化アプリとデータ基盤」が対象のシステムとなります.

野球データ基盤2024年版blueprint

データ可視化アプリがSoI(System of Insight)な役割をする分析アプリとなります, こんな感じでデータ可視化をします.

MLBデータ分析アプリ(開発中)

既に2024年のデータにはすでに対応してるのでこのブログでも野球ネタでちょいちょい出せるレベルには到達しています⚾*3

このデータは毎日配信されるMLBの公式データ(Baseball Savant)に依存しています. このデータを毎日チェックしてDWH(BigQuery)に保存・管理するためのデータ基盤(SoR, いわゆるSystem of Records)がシステムとして存在します.

データ基盤(SoR)のワークフロー

データ基盤でデータマネジメントしつつ, データ可視化アプリで気づきを得てネタを量産するのがこのシステムの全体像となります.

役割毎のモノレポ

blueprintからもお察しですが, SoI(データ可視化アプリ)とSoR(データ基盤)という明確に役割が違う2者を分けて開発しています*4.

【再掲】blueprintとリポジトリの単位

この2つに分けた理由ですが,

  • そもそも別々に開発していた為, リポジトリが分かれている
  • デプロイの単位が異なる. 具体的にはデータ可視化アプリ側の方が開発頻度, デプロイ回数共に増える想定*5
  • データ基盤のデータは他の目的でも使うので, データ可視化アプリ側とは疎結合にしたかった*6

だいたいこの辺が理由となります.

TerraformとGitHub Actions

前提を説明したところで本題に入ります.

このシステムの中でTerraformとGitHub Actionsを使った箇所ですが,

  • Terraform
    • Cloud Runをはじめとしたクラウドリソースの管理(全てではない*7
  • GitHub Actions(CIタスク)
    • 各アプリケーションのテスト
    • Artifact(=各アプリのDocker Image)のビルド
    • Terraformの記述・構成チェック(validate, plan)
  • GitHub Actions(CDタスク)
    • Terraformの実行を通したデプロイ(apply)

上記の目的で使いました.

TerraformでIaC

言いたいことを先にいうと,

Infrastructure as Code(IaC)は個人開発(趣味開発)でもやっておけ, Terraformじゃなくてもいいから.

これに限ります.

shinyorke.hatenablog.com

前回ブログと少しだけ内容被りますが一応書いておきます.

IaC化する前に発生した問題

昨年末から今年の2月くらいまで, データ基盤部分を開発していたときに問題がおこりました.

ワイ「あれ?管理対象のクラウドサービス多すぎでは😇」

実際問題, 確認したら多かったのです.

データ基盤で使ったクラウドサービス

  • Cloud Runのアプリが3つ
  • データ保存先となるCloud Storageのbucket
  • Queueベースで動かすアプリ(2つ)のためのCloud Pub/Subの設定(Queueおよびメッセージのスキーマ)
  • 毎日のバッチ起動を管理するCloud Scheduler
  • Loggingの設定
  • これらをいい感じに動かすためのService Account
  • GitHub ActionsでCI/CDやるための設定, 具体的にはWorkload Identity(WID)周りの設定

マイクロサービス指向で設計・実装したため使うサービスが増えるのは想定の範囲内だった...とはいえ, 思っていたより3.34倍🐯*8くらいのボリュームになって焦りました.

詳細の説明は避けますが, データ可視化アプリ側も同じような課題がありました*9.

前回ブログで,

自分が携わっているシステム・プロダクトが複雑かつデプロイ回数増えそうだったらIaC入れておけ, 個人開発(趣味)だったとしても.

そんな事を書きましたが, まさにこの条件に当てはまりました.

自分としては, 「mainブランチへのmerge一発でアプリもインフラもデプロイしたい!」という非常に大切な目標(とある種の技術的な実験と体験*10)もあったので, 「IaC化する意味がある箇所はやっておこう」という意思決定をしました.

Terraformを選択した理由

Terraformを使った理由「みんなも私もよく使っているから」です, 以上.

本当にこれに尽きると思います, 個人的な事情で言えば仕事でも日常的に触れているからというのもあります.

前職時代にほんの少し触って好感触を得た「Serverless Framework」も選択肢とありましたが,

  • 将来含めて考えると使うものすべてがサーバレスとは限らない
  • システム本体だけでなく, WIDなどCI/CDに必要な周辺リソースもIaC化したい
  • そもそもServerless Frameworkが久しぶりすぎてまた手を付けるつもりが(ry

という事情でTerraformにしました, ちなみにAnsibleやChefなどよく知れている物は最初から選択肢にありませんでした*11.

GitHub ActionsでDevOps

DevOps, 具体的にはCI/CD環境をどうするかという問に対して,

  • GitHub上で管理しているのでGitHub Actionsでいい感じにする ※今回採用したアプローチ
  • Terraformの実行を含めたGoogle Cloud周りをCloud Buildで程よくGitOpsする, これは公式のサイトにもあるベスプラ

この二択でしたが, GitHub Actionsにしました, Cloud BuildでのGitOpsを選ばなかった理由は...時間がなかったらからです*12(いずれやりたい).

Workflowを考える

「どの順番でテストをしてビルドしてデプロイするか?」みたいなWorkflowを思いつきで作ると危険と思い, 設計をしました.

まず結論からお見せします.

データ可視化アプリのDevOpsフロー

データ基盤のDevOpsフロー

両方とも, 最後のDeploy(Terraform apply)から逆算して, 「Pull Requestですべてのレビューができる想定」で組んでしまえ!という一貫性をもたせる設計にしました.

  • すべてのpushで必ずテストを回す
    • Unit Test(と言うより, アプリ単位でのRegression Test)
    • Terraform fmt/validate
  • Pull Request作ったらDeployに必要な資材を揃える&レビュー用の証跡を残す
    • Cloud Buildでアプリをビルド -> Artifact Registryに保存
    • Terraform planして結果をPull Requestに乗せる
  • mainへのmergeでDeploy
    • Terraform Apply

このルールに乗っかる様に設計・実装しました.

なお個人開発なので明確なIntegration Test/システム全体のRegression Testは組んでいません*13.

Job毎の依存関係も上記の絵に寄せて実装しました.

Workflowを実装

Workflowの実装ですが, 雰囲気だけお見せします(データ可視化アプリケーションより抜粋).

めちゃくちゃ長いので読みたい方だけどうぞ.

この実装のポイントですが,

  • Workflowは処理単位(可視化アプリ, バックエンドAPI, Terraformという単位)で分けず, すべて1個で実装(理由は後ほど)
  • Jobの依存はneedで管理, needs: [terraform-test, api-build, dashboard-build] ←こんな感じ
  • Google Cloudとの連携はすべてWorkload Identity(WID)で管理. credential jsonは使わない(使うべきではない*14

これでやりました, お陰様でDeploy前のPull Requestでチェックもいい感じにできるようになりました.

Pull Requestにしっかり乗ってますよの絵

こちらはこの記事が大変良くできていて参考になりました, ありがとうございます🙏

この構成でメジャーリーグが本国開幕する前に無事にアプリケーションが運用できるようになりました, やったね.

やってみた結果

この構成に至るまで, 2つのリポジトリで狂ったようにTerraformとGitHub Actionsを実装・テストしました*15.

個人開発なのに頑張り過ぎでは...とも思いましたが, 「よかったこと」「微妙だったこと」の学びがありました.

よかったこと

一言でいうと「野球を見ながらだろうが酒を飲みながらだろうが安心して開発・Deployできる程度に仕組みが整ったこと」でしょうか.

  • 開発からデプロイまでの一貫性
  • ほぼ100%の自動化を達成
  • 大胆な構成変更ができる

IaCとGitHub Actionsでの自動化でこれらの喜びを手に入れました.

開発からデプロイまでの一貫性

必ずテストをパスしないとデプロイができない, 壊れたまま本番に出ることが無い仕組みになった.

mainへの直pushをしようがタグ打ちをしようが, 最後のterraform planが通過しないと本番に発射されることは無いので,

  • 野球や競馬を見ながら気楽に開発
  • 酒を飲みながら(ry

でもシステムが壊れる事は無くなりました, 自分がズルをしない限り*16.

勿論仕事ではこんなふざけた事はやらないのですが, 自分の個人開発(趣味開発)ではこれぐらいの事はしたい*17ので仕組みとして入れて正解だったと思います.

勿論, 仕事などで真面目にやる時も(いや真面目な理由でやる時こそ)この一貫性は非常に重要です.

仕組み的にも, デプロイしたバージョンが微妙だったらRevertして戻せるので本当に楽な仕組みになったと思います.

ほぼ100%の自動化を達成

毎回のリリースに必要な作業・営みをTerraform/GitHub Actionsで一貫して自動化した結果, 無駄な手作業から開放された

大雑把に言うと, 「手元でコード書いててテストしてgit pushしたらよしなに進む仕組みができた」といったところでしょうか.

  • アプリケーションのテスト, ビルド, デプロイ
  • 実行環境(今回は全てCloud Run)に紐づくクラウドリソースのデプロイ

これらのCI/CDを自動化したので本当に楽でした.

なお,

  • Workload Identity関連の設定. Terraformで実装したものの, 実行と管理はローカルで実施.
  • Terraform関連の設定(backend定義など). Workload Identityと同様の管理.
  • BigQueryの定義. こちらはshellなどで実装, 実行もローカル.

上記に関しては諸事情*18で管理から外しましたが, いずれも毎回変わるものではないので支障にはなっていません.

大胆な構成変更ができる

将来的な構成変更(アプリが増える, 実行環境を変更するetc...)も大胆に行えるようになった.

プログラムのコードは勿論, インフラ(Cloud)設定もコード化(IaC)したので,

  • コードを読めば構成がわかる
  • インフラレベルの話も追加・削除がTerraformでいい感じに

という世界観ができました.

データ可視化アプリ側のバックログとして,

  • AIチャットを実装する
  • 認証・認可の方式を変更する(IAPを使いたい)

などなど, 色々あるのですがいずれもTerraformの定義を増やしたり変えたりで行けそうなので気持ちが楽になりました.

微妙だったこと

一言で言うと,

開発者体験を手に入れた代償として複雑な仕組みの管理が必要となってしまった.

ことです.

巨大なWorkflowが辛い

約300行近い巨大なGitHub Actions Workflowと向き合って開発しないといけない, しかもこれが2つある.

本当はリソース(アプリケーション, インフラ)の単位でWorkflowを分割, Workflow同士の依存関係で解決したかった(Workflowの実装を軽くしたかった)のですが,

  • GitHub Actionsの「Events that trigger workflows」の内容に従い, 試したけどうまくいかず.
  • 調べると, 「あるWorkflowが終わってから実施」みたいな密結合な依存はできなさそう, と解釈.
  • 「全てのアプリのビルドとterraformのチェックが終わってからterraform plan」みたいな事がやりたい場合はWorkflowじゃなくてjobの依存で解決するのが筋.

と判明したため, Workflowの分割は断念しました, 解決したかったらポリレポにするしか無いなとも思ったのですがその手は取りたくなかったので*19.

ビルドの回数が多い

Pull Requestとmainへのmerge(もしくはpush)毎に毎回Artifact Buildが走るのがもったいない.

今のところCloud Buildで課金されるほど使ってないので問題にはなってませんが, 余計に使っちゃってるのはもったいない認識です.

本当はPull Requestの時点でBuildしたやつをそのまま使えるのがベストなのですが...タグ打ちなどを工夫して切り抜けることも手もありますが, それはそれで複雑になりそうな予感がしたので断念しました.

余談ですが, システム全体のコストは非常にお安く済んでいますので今はコスト的な問題はありません(月額10ドルも行っていません).

なんやかんやで複雑

結局のところ, 複雑な仕組みになってしまった.

私一人で開発するものなので今は問題ないですが, 来年の今頃メンテする時に忘れていそうとかそのような恐怖はあります.

と言ってもほぼコードとして書かれているのと別途blueprintやDesign docは用意しているのでなんとか回避はできそうと思っています.

結び

というわけで, 個人開発(と自分の満足と実験)の為に行った, 「個人開発マイクロサービスのIaCとDevOps化」の話でした.

結構な大作ブログになってしまいましたが, 気になるところや「もうちょっとここ聞かせろや!」みたいな話があればぜひコメント下さい, なるべくリクエストに従って情報を出したいと思います.

最後に一つだけ言っておくと,

個人開発でIaCするのは重要だけど, このブログの例のような複雑な仕組みにする必要性は各自で考えて欲しい!

ですね, 今回やってること, シンプルにやるなら「データを見せるアプリ」「毎日収集するバッチ」「データベース」この3つあれば終わる話でもあるので*20.

とはいえ, IaCとかDevOpsによる開発者体験は本当に良い*21のでぜひ皆さんも考えてやってみるとよいのかなと思います.

最後までお読みいただきありがとうございました.

【Appendix】参考文献

本文内で出てきた参考文献となります.

cloud.google.com

cloud.google.com

qiita.com

docs.github.com

*1:大抵の場合「単独でテストができてデプロイもできる(他リソースに依存しない)単位」だと思いますがこのブログの文脈とはあまり関係ないので割愛.

*2:ポリレポの方は最近の概念(モノレポと比べたら)なのでそりゃそうよねって気もします.

*3:そもそもこんなアプリを何故作ってるかというと, 自分が見たい・確認したいデータをシュッと確認するのとブログネタを量産するためです. まさにSoIな目的です.

*4:開発をスタートした2022年から元々別の目的で作ってたモノでした.

*5:例えば新しいグラフがほしいとか, AIチャットなどの新機能を作るなどetc...

*6:データ基盤のデータはアプリではなく, Baseball Savant(元データ)の構造に依存するようにしたかったので.

*7:具体的にはDNSやAPI Keyの管理は敢えて外しました.

*8:なんでや!...ってマジで思いました.

*9:こっちはPub/Subは無かったものの, API Gateway(含むOpen API管理)という難題があった為, 同じようにIaC化しました.

*10:設計・実装パターンとして有効かどうかのお試しだったり, このブログのようなアウトプットのネタだったりといった実験と体験です.

*11:選択肢にない理由は「手続き型IaC」ではなくて, 「宣言型IaC」が欲しかったからです.

*12:諸々の登壇やブログの為に割ける時間を考えて泣く泣く断念, この方式はいずれ挑戦するつもりです.

*13:このレベルは全て手動で良いと判断

*14:JSONが漏れたら...というセキュリティ的な事情です. WIDを使うのはデフォですね.

*15:Terraformにお詳しい方ならお察しかもですが, それなりの規模のコードベースにはなりました笑, 約半月間, 土日はずっとTerraformと向き合っていた程度に.

*16:管理者は自分で開発者も自分だけのため

*17:これは別テーマでブログにしたいのですが, 自学自習や何かの為の技術の勉強・キャッチアップ「辛い」「義務」とか思わず, 趣味や生活の延長線でやれればいいのになー, という提案はしたいです(私がそういう人なので).

*18:ほぼ初回のみの作業であること, WIDの権限云々という問題がありGitHub Actionsでの管理から外しました.

*19:各アプリとterraform用リポジトリを用意して分割することでWorkflowそのものは小さくできますが, システム単位でのデプロイを志向してモノレポにしたのでその精神に反するなと思い断念しました.

*20:私の場合は設計や実装の実験的な試みだったり, 発表ネタにできないか?と思いながら作ってるのでここまで凝っています. が, 普通に使うものが欲しいのであればDjangoでアプリを一個作って済ませると思います.

*21:様々な理由で仕事でこれをできていない方は個人開発で試すのは本当に良いことなので強くおすすめします.