☁️

CI/CD始めたいなら、GitHub ActionsとCloud Functionsでいいんじゃない?

2022/12/23に公開

新人: 「馬庄さん、お疲れ様です!」
先輩: 「お、森本さん。お疲れ。この前は無茶振りしてごめんね。」

新人: 「いえ!良い経験になりました!」
新人: 「(あー、無茶振りの自覚あったのか。。。)」

先輩: 「そう言ってくれてよかったよ。業務はどんな感じ?」
新人: 「新しく入ったプロジェクトでは CI/CD 環境の整備を担当することになりました」
新人: 「ただ、CI/CD って聞いたことはあるんですが、あんまりよく分かってないんですよね」
新人: 「なので、まずは適当にチュートリアルを動かしてみようかなと思ってます。」

先輩: 「なるほど。Git は一通り使ったことあったよね?」
新人: 「はい!基本的なコマンドと Git-flow での開発はやったことがあります」
先輩: 「いいね。んーそうだな。」
先輩: 「CI/CD 始めたいなら、GitHub Actions と Cloud Functions でいいんじゃない?
先輩: 「たしか Google Cloud なら部で検証用のアカウントを持ってたはずだし。」
先輩: 「興味あるなら、簡単な題材でやってみる?」

新人: 「GitHub Actions と Cloud Functions なら聞いたことありますし、やってみたいです!」
先輩: 「いいね。じゃ題材は、誕生日の曜日を当てる Web アプリにしようか」
新人: 「よろしくお願いしまぁぁぁすっ!!」
先輩: 「・・・。どうしたの急に。」
新人: 「すみません。取り乱しました。」

ローカルで Cloud Functions を動く環境を用意しよう

先輩: 「びっくりした」
先輩: 「気を取り直して、まずは ローカルで Cloud Functions を動く環境を用意しよう
先輩: 「Cloud Functions ではFunctions Frameworkに対応してるんだ」
先輩: 「これを使うことでローカルでも Cloud Functions の検証がしやすいよ」

import functions_framework

@functions_framework.http
def main(request) -> str:
    return "Hello World"

新人: 「なるほど。Python で HTTP トリガーの場合だと Flask がベースになってるんですね」
新人: 「アノテーションつけるだけだし、簡単に試せそうですね」
先輩: 「そうそう。これで、日付を入れたら曜日が返る Web アプリを作ってみようか。」

新人: 「わかりました。ちなみにこの前みたいに 3 時間でとか言わないですよね?」
先輩: 「ははは。まさかね。」
新人: 😄
先輩: 「これくらい 30 分もあれば十分でしょ」
新人: 😇
先輩: 「流石に意地悪しすぎかな。じゃヒント。ツェラーの公式を実装するだけで出来るよ」
新人: 「あー。確かにこれなら 30 分で何とかできそうですね」

単体テストを書こう

〜〜 30 分後 〜〜
先輩: 「どう?できた?」
新人: 「はい!できたと思います!」
先輩: 「よし、じゃ次に単体テストを書こうか。」
先輩: 「Python ならpytestで書くことが多いかな」

新人: 「あのー単体テストって書いたことないんですが、書く必要あるんですか?」
先輩: 「某スタンド使いに助走をつけて殴られるぞ」
新人: 「え?」

先輩: 「色々利点はあるけど、やっぱり保守性を上げることかな」
先輩: 「今は実装した直後だし、コード量も多くないから特に問題はないと思う」
先輩: 「ただ数ヶ月後に修正するとしても、何が正しい挙動なのか分からないよね」
新人: 「まぁ、確かにそうですね」

先輩: 「他人が書いたコードを渡されて、リファクタリングを依頼されたら怖くない?」
新人: 「確かに嫌ですね。仕様書見て、仕様が正しいか確認する必要がありますよね。」
先輩: 「数ヶ月後の自分なんて、実装の理解度的には他人同然だからね」
先輩: 「挙句の果てに、仕様書の方が間違ってましたってオチなら発狂もんだよ」

先輩: 「そこに自動テストがあれば、修正してもテストが通せば良いという安心感がある」
先輩: 「経験してみれば分かるけど、この手軽さと安心感が開発者にとって大事だと思うよ」

新人: 「なるほど、でもテストの方が間違っていることもないですか?」
新人: 「それにテストコードを書く工数もかかりますよね」
先輩: 「うん、いい質問だね」

先輩: 「まずテストの方が間違っていることはある。」
先輩: 「そのために、まず失敗するテストを書いて『正しく失敗するか』を確認するね」
先輩: 「あとは実装とテストでダブルチェックできるという見方もできるかな」
先輩: 「正直、テストコードにバグが入る可能性を差し引いても、メリットの方が大きいと思う」
新人: 「なるほど」

先輩: 「テストコードを書く工数もかかる、というのもその通りだと思う」
先輩: 「だから工数調整は必要だと思うけど、テストを書かなくて良い理由にはならないかな」
先輩: 「実装を修正するたびにテストが必要だから、毎回手動だと大変だよね」
先輩: 「早くテストすることで、バグや設計ミスに気づけて修正工数が減ることも考えられる」
先輩: 「実装よりも先にテストを書く開発手法を、テスト駆動開発(TDD)といったりするよ」
新人: 「こう聞くと自動テスト書いた方が良い気がしてきました。」

先輩: 「あとは『テストしにくいコードは、良くないコード』って言われたりするね」
先輩: 「よくあるのは密結合な実装になっていて、テストがしにくいとかね」
先輩: 「詳しくは和田さんのスライドテスト駆動開発を読んどけば間違いない」
新人: 「読んでみます!」
新人: 「(あ、某スタンド使いってこの方か)

テストピラミッドを意識する

新人: 「テストコードを書くメリットはわかりました」
新人: 「今回のテストは localhost に HTTP リクエストする形でしょうか」
先輩: 「最終的にはそうだね。でも単体テストである必要はないかな」

先輩: 「テストは大きく、単体テスト(UT)、結合テスト(IT)、UI テストという分け方ができる」
先輩: 「ざっくり単体テストは関数単位でのテスト、結合テストは複数部品を繋げたテストね」
新人: 「なるほど」
新人: 「今回は誕生日を算出するメソッドは単体テスト、HTTP リクエストは結合テストですね」
先輩: 「まぁそうかな。UI テストでもいいけど、今回の規模感なら厳密に分けなくていいかな」

先輩: 「重要なのはテストピラミッドを意識することだね」
先輩: 「UT -> IT -> UI テストの順で量が減るようにテスト設計することが大事」
先輩: 「小規模のテストを多く、大規模なテストを少なくすることでコスパが良くなるよ」
先輩: 「この辺は書籍だと初めての自動テストとかが参考になるかも」

Test Pyramid
出典: https://www.ministryoftesting.com/dojo/lessons/the-mobile-test-pyramid

新人: 「なるほど、規模の判断ってどうすればいいんですかね?」
先輩: 「2010 年の記事だけど、Google のブログが参考になるかな」
新人: 「外部接続やファイル I/O のある部分は比較的大きなテストになるんですね」
先輩: 「その通り。CI/CD のサイクルを素早く回すためには、テスト時間が短い方が良いからね」
新人: 「わかりました!まずは単体テストを書いてみます」

そもそもなんですけど、CI/CD ってなんですか

先輩: 「テストコードが出来たら CI/CD の設定に入ろうか」
新人: 「はい!」
先輩: 「最初にも言った通り今回は CI/CD サービスとして GitHub Actions を利用する」
先輩: 「他にも GitLab CI/CD や Google Cloud のCloud Buildがあるね」

新人: 「そもそもなんですけど、CI/CD ってなんですか
新人: 「CI は継続的インテグレーション、CD は継続的デプロイってのは分かるんですが。。。」
先輩: 「そうね。これは手を動かして体験した方早いけど、具体例だけあげとこうか。」

先輩: 「『必ずテストを通してから Git リポジトリに push する』というルールを決めたとする」
先輩: 「これくらいならいいかも知れないけど、他にもルールが増えていくことが想定される」
先輩: 「コード整形やテスト結果出力とかね」
新人: 「ルールが増えてくると忘れたり、やるのが面倒ですね。」
先輩: 「そうそう。ざっくり言うとそれを自動化するのが CI と思ってくれたらいいよ。」

先輩: 「CD もほぼ同じで、ソースコードをサーバに配置(デプロイ)に置き換わるだけかな」
先輩: 「開発者としては push すれば、いい感じにやってくれるから開発に集中できるってこと」
先輩: 「あくまで、ざっくりとした理解だから細かいことはやりながら覚えていこう」
新人: 「分かりました!」

ローカルで GitHub Actions を動く環境を用意しよう

先輩: 「じゃ GitHub Actions に触れていこうか」
新人: 「はい!」
先輩: 「といっても正直、公式のクイックスタートやるのが一番早い」
先輩: 「どんな新しい技術を触る時でも、公式のチュートリアルをやるのが重要だね」
新人: 「そうですよね。まず自分で触ってみたいと思います!」

〜〜 30 分後 〜〜
先輩: 「どう?できた?」
新人: 「何となく雰囲気は掴めました。」
新人: 「ただ、毎回 push しないと動作確認できないのが少し面倒ですね」
先輩: 「なるほど。それならローカルで GitHub Actions を動く環境を用意しよう

先輩: 「actというツールを使えばローカルで検証ができるよ」
新人: 「なるほど!これを使えば安全に設定ファイルを作れますね!」
先輩: 「ただし、GitHub Actions と全く同じ挙動ではないから、最後に push して確認してね」
新人: 「わかりました!」

Google Cloud の環境を整えよう

先輩: 「じゃ次にGoogle Cloud の環境を整えようか」
新人: 「アカウントは発行してもらったので、アクセスできるようになってます!」
先輩: 「いいね。」

先輩: 「まず GitHub Actions から Google Cloud のリソースを操作できるようにする必要がある」
新人: 「前に少し触りました。サービスアカウントを作って権限を付与するんですよね」
新人: 「プログラムからアクセスするならサービスアカウントキーを発行すればいいですかね」
先輩: 「サービスアカウントキーを使う方法もあるけど、今回は Workload Identity を使おう」
先輩: 「GitHub Actions でGoogle Cloud 認証の Actionが用意されてるから簡単だよ」

新人: 「どうして Workload Identity の方が良いんですか?」
先輩: 「API キーのような認証情報は流出しないように細心の注意を払う必要がある」
先輩: 「たとえプライベートリポジトリでも、GitHub に push するのは『ダメ、絶対』」
先輩: 「でも流出させない一番の方法は、そもそも認証情報を発行しないことなんだ」
先輩: 「Workload Identity を使えば認証情報ではなく、ID 連携で権限付与ができるんだよ」
新人: 「なるほど。より安全に Google Cloud にアクセスが出来るんですね。」

先輩: 「具体的な設定方法はauthの Readme を参考にしてね」
新人: 「わかりました!」
先輩: 「ただ Cloud Functions を使うために追加手順があるから、後で連携するね」
新人: 「ありがとうございます!」
※この記事の最後に完成版の Git リポジトリへのリンクを貼っておくので、ご参照下さい

GitHub Actions の設定ファイルを記述しよう

新人: 「Google Cloud の設定完了しました」
先輩: 「いいね。じゃ最後にGitHub Actions の設定ファイルを記述しよう
新人: 「はい!」
先輩: 「今回は静的解析、コードフォーマット、テスト、デプロイを自動化していこうか」

新人: 「静的解析って何ですか?」
先輩: 「ざっくり言うとコードを実行する前(静的)に、バグがないかなどを解析することだよ」
先輩: 「この前紹介した flake8 や mypy などのツールを使って行うやつ」
新人: 「なるほど!TypeHint を正しく書いているか、PEP8 に準拠しているかとかですね」
先輩: 「そうそう。ちなみにコードフォーマットはそのままだけど、コードの整形だね」
先輩: 「black や isort などのツールが有名かな」

新人: 「テストは先ほど書いた単体テストの実行ですね」
先輩: 「そう。ローカルでのテストを忘れても、CI が自動でテストしてくれるから楽だよね」
新人: 「属人ではなく、自動化することが大事ですね」
先輩: 「その通り」
先輩: 「バグの原因を人に追求するより、仕組み化して再発防止を図れないかを考えよう」

新人: 「最後のデプロイは、ソースを Cloud Functions に配置して動くようにする工程ですか?」
先輩: 「その通り。具体的にはgcloud functions deployコマンドを実行すれば良いよ」
先輩: 「今回はサードパーティライブラリを使ってるからrequirements.txtの出力も忘れずにね」
新人: 「わかりました!」
先輩: 「じゃ、とりあえず設定ファイル書いてみて。何か詰まったら言ってくれれば良いから。」
新人: 「ありがとうございます!」

〜〜 1 時間後 〜〜
新人: 「完成版をGitHubに push しておきました」
先輩: 「うん。ワークフローも問題なく動いてるし、これで完了だね」
新人: 「色々とサポートして頂き、ありがとうございます!」
先輩: 「これで 開発、テスト、デプロイ の永久機関が完成しちまったなアア〜」
新人: 「・・・。どうしたんですか急に。」
先輩: 「ごめん。取り乱した。じゃ業務でもこれをベースに頑張ってね」
新人: 「はい!ありがとうございました!」

最後に

(今更ながら...)こんにちわ alivelimb です。
少し間が空いてしまいましたが、会話調記事の第三弾です。
最近 Google Cloud の資格を 全資格取得したこともあり、Cloud Functions について書きました。また、業務では AWS や GCP の CI/CD サービスを使うことが多いのですが、「そういえば GitHub Actions をしっかり触ったことないな」と思い、こちらも絡めて記事を書いてみました。
完全に記事駆動開発ですね。

今回作ったものはサンプル例として GitHub にアップロードしているので、適宜参照してください。感想の他、誤りや改善点等あれば是非コメント頂けると幸いです。

振り返り

まず Cloud Functions は Google Cloud が提供する サーバレスなマネージドサービスです。AWS でいうと Lambda 的な立ち位置ですね。サーバレスサービスは従量課金でのため、ちょっとした開発であればコストを低く抑えられます。「早い、安い、使いやすい」というクラウド初心者がクラウドの魅力を享受するには最も適したサービスと言えるのではないでしょうか。

今回の題材として、同じサーバレスサービスでの Cloud Run も考えたのですが、「Python と Web は分かるけど、Docker はまだやったことがない」という若手エンジニアを想定して Cloud Functions を選びました。Docker を使う場合は Cloud Run を検討してみてください。

クラウドに入門しようと思い立ち、「VM や VPC から始めてみたけど、まずネットワークがよく分からなくて、その勉強からだな」という経験をされた方もいるかも知れません。私自身、マスタリング TCP/IPを読んで教科書的な知識は入れたけど、あまり実践はできてなかった頃、クラウドを触りながらネットワークやセキュリティの理解を深めていきました。

それはそれで大事なことだと思っていますが、最初は「まず動くものをサクッと作ってみる」というのも大事な経験だと思います(モチベの維持的にも)。そのサポートに本記事が少しでも役に立っていれば幸いです。

参考にさせて頂いたサイト

Discussion