大規模レガシーテストを 倒すための CI基盤の作り方 / #CICD2023
by
KONDO Uchio
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
Uchio Kondo @ Mirrativ, Inc. 大規模レガシーテストを 倒すための CI基盤の作り方 CI/CD Conference 2023 @ Shinbashi
Slide 2
Slide 2 text
株式会社ミラティブ インフラ・ストリーミングチーム 2022/5 〜株式会社ミラティブ インフラ、ミドルウェア、社内ツールなどの 開発と運用をしている Ruby育ち、現在はGopher クラウドネイティブ技術の傍ら tcpdumpとstraceも好き Twitter/GitHub @udzura 近藤宇智朗
Slide 3
Slide 3 text
ミラティブについて ● 「わかりあう願いをつなごう」をミッションに、 ゲーム配信プラットフォーム「Mirrativ(ミラティブ)」を開発・運営 ● スマホ一つで簡単ゲーム配信+アバター(エモモ)作成ができる ● ライブゲーム開発に積極投資中! ライブゲーミングについて: https://prtimes.jp/main/html/rd/p/000000074.000033025.html
Slide 4
Slide 4 text
ミラティブの技術スタック ● ライブ配信+アバター(Unity)+ゲームという感じで、 スタートアップの中でもかなり変わったことができるかも
Slide 5
Slide 5 text
https://speakerdeck.com/hr_team/engineers-handbook 大事なお知らせ ● インフラ/ ストリーミング/ バックエンド基盤/ ライブゲーム開発 ● Go、Google Cloudユー ザーに近い開発に 興味のある方歓迎!
Slide 6
Slide 6 text
アジェンダ 今日お話しすること ● ミラティブにおける CI ● Google Cloud + Cloud Buildを選んだ理由 ● Cloud Build を使う上での課題と対応実装 ○ GitHub との連携 ○ skip ciの罠の回避 ○ 大規模なテスト移行に際し挑戦したこと、他 ● まとめ ○ CI基盤を作った上での学び ○ ここでも推測するな、計測せよ
Slide 7
Slide 7 text
ミラティブにおける CI
Slide 8
Slide 8 text
前提: ミラティブの歴史 意外とコードに歴史がある ・開発のスタートは2015年 ・開発の当初は Perl をベースに構築 ・元々あった別の配信サービスを土台になるべく高速にリリースした ・その後: サービスや人員の拡大とともに、Goの割合を増やす
Slide 9
Slide 9 text
ミラティブにおけるCI 今回のスコープ=アプリケーションサーバのチームのCI 定期実行するもの: ・☑ Go のテスト ・☑ E2E テスト ・☑ Perl のテスト(prove) ・☑ Lint (Go, perlcritic, YAML諸々) ・☑ フロントエンド系 (assetsのビルド) リリース時に実行するもの: ・☑ イメージ作成+GitHub Release
Slide 10
Slide 10 text
CI/CDに求められていること ・色々な言語/目的があるがいい感じにCIしたい ・ある程度汎用的かつ、カスタマイズできて欲しい ・たくさんあるテストケースをいい感じに回したい ・特にPerlのテスト(prove)がたくさんある ・Goなどもテストは多いが、proveは以下の特徴があった 1) 並列実行が簡単でない 2) e2e 相当のテストも多く複雑
Slide 11
Slide 11 text
Cloud Build を選んだ背景
Slide 12
Slide 12 text
ここまでの話に加えての課題 プラットフォームをまとめたい話 ・ミラティブでは色々な経緯で様々なSaaS、プラットフォームを利用 ・無論有用なものはそのまま使うが、そうは言っても統一できるものはしたい ・統一の目的については後述 ・今回の場合、インフラ全般を動かしているところ(Google Cloud)と CIで利用しているサービスが異なっていた そのCIサービスのコスト面+ ミラティブの事情で移行の話に
Slide 13
Slide 13 text
プラットフォームを統一したい 綺麗にまとめたい ・利用をある程度集約させるメリット ・各所にアカウントが分散する管理コストを下げる ・一定以上のボリュームを出したい ・相互連携やレイテンシ、通信費の問題を解決しやすくなる ・特に、ストレージの通信量問題 ・e.g. Google Cloudの場合、Container Registory(GCS)と サービス実行リージョンを両方US multi regionにすると 通信が無料になる
Slide 14
Slide 14 text
そもそも、Cloud Buildとは? 意外と知られていない...ような ・Google Cloudのマネージドなビルド環境 ・基本機能 ・GitHub/Gitlabなどソースコードリポジトリとの連携 ・pushやP/Rに応じたビルド ・Pub/Sub を受け取ってのビルド ・イメージやアーティファクトのpush ・Cloud Function/Runのデプロイの内部(Buildpack)も実はこれ
Slide 15
Slide 15 text
CI基盤にCloud Buildを選んだ理由 CIもサーバレスでいこう ・例えば Jenkins on Compute、Tekton on GKE のような選択肢もあるが... ・今回はCloud Buildを軸に構築した: ・実行基盤の管理はマネージドにしたい ・細かい機能(トリガー、GitHub checksの更新等)を任せる ・従量課金で実行したい
Slide 16
Slide 16 text
Cloud Build、内部的には... Google Cloudは組み合わせ ・内部的には、どうやら ・Compute Engineで特定のサイズのインスタンスを立ち上げ ・内部でDocker、その他デーモンをいい感じに立ち上げ ・step定義に応じてジョブをコンテナとして実行 ・となっている。したがってdockerでホストのnamespaceに アタッチすると色々見えたりする。 ・実質Compute Engineの時間貸し(?)
Slide 17
Slide 17 text
具体的な課題と実装の話
Slide 18
Slide 18 text
1) GitHubとの連携周り
Slide 19
Slide 19 text
GitHub との連携上の課題 ・前提として、開発はGitHub (Enterprise Cloud) ・CBはGitHub Actionのように最初からトークンを 裏で発行してくれるわけではない ・(特にGo)複数のプライベートモジュールにアクセスできる権限が トークンに欲しい ・上記のような要件を解決するには...
Slide 20
Slide 20 text
A. GitHub App を使う https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app
Slide 21
Slide 21 text
GitHub App でのトークン発行の流れ JWTを 作ってサイン JWTで APIにアクセス Permissionなど 指定可能
Slide 22
Slide 22 text
Cloud Functionを最初に叩いて発行 ・Cloud Functionを経由してトークンを発行する 🤖 Function 起動 Secret Key->JWT -> Token API Token 発行 出力 保存して その後の stepで利用 Cloud Build Cloud Functions GitHub App
Slide 23
Slide 23 text
GitHub App でのトークン発行後 ・Goのプロジェクトの場合、以下の感じで url.*insteadOf を定義すると そのまま `go get/go mod download` ができて便利 ・チェックアウトはもちろん、Permissionsを適切に設定して 例えばreviewdogのトークンにも使える
Slide 24
Slide 24 text
2) “skip ci” の扱い
Slide 25
Slide 25 text
CDを動かし始めて出てきた課題 ・アプリケーションサーバのイメージビルド+pushのジョブを 別のプラットフォームからCloud Buildに移行した ・イメージビルドは特定の tag を push したら動くようになっていた ・「なんか動いてない時があるみたいなんですけど」 ・そんな... 事実を受け止め調査してみる スムーズに動き始めているように見えたが...
Slide 26
Slide 26 text
原因: skip ci の際の挙動に問題が ・コミットなどに `skip ci` の文字列が含まれていると、 Cloud Buildのトリガーが走らない... > [skip ci] または [ci skip] を commit メッセージに追加すると、ビルドは呼び出されません。 ・運用上あえてskip ciにしてるコミットがあった(マスタだけ更新等) →そのコミットがデプロイ対象の場合、tag pushしても動かない 普通は問題ないけどたまに困るやつ https://cloud.google.com/build/docs/automating-builds/create-manage-triggers?hl=ja#skipping_a_build_trigger
Slide 27
Slide 27 text
回避策: Cloud BuildのPub/Subトリガー ・これを使うと、コミットの内容に関係なくトリガーを走らせることができる 💻 リリース作成 = Tag Push Webhook で Push eventを 送る Cloud Function で 受け取って Pub/Subに投げる Cloud Build TriggerでPub/Subを 選択 Cloud Pub/Subは本当に便利
Slide 28
Slide 28 text
checks を上書きすることもできる ・GitHub Appに checks: write のPermissionを付与する ・GitHub PR → Webhook → Cloud Function → Checks API でできる Webhook で PRのeventを 送る 1) GitHub Appから トークン発行 2) Checks API経由で更 新
Slide 29
Slide 29 text
3) 大規模テストの課題向き合い
Slide 30
Slide 30 text
課題: Perl のテスト (prove) 移行 ・最初に説明した通り、創業当初からあるコード+テスト ・むしろテストはしっかり書いている。しかしどうしても: ・古くなる ・多くなる ← 特にこれが大変 ・だが、テストがある程度サービスを守ってくれたことも事実。 どうにかして現実的に運用し続ける方法を考えたい ただレガシーと片付けるのではなく向き合う
Slide 31
Slide 31 text
課題を一歩一歩進める ・そもそもたくさんあるテストが現実的な時間で終わるのか? → どうにかしてたくさんのテストを実行する仕組みを考える ・まずは一通り走る状態にして、チューニングしようと考えた → 後述するが、ややアバウトすぎるアプローチではあったかも
Slide 32
Slide 32 text
たくさんのテスト実行の方法 ・単純にトリガーを分ける?(それで対応したものもある) → 今回はテストケースが多すぎるので難しい... checks が溢れかえるのは開発者体験が... ・こういう作戦 ・トリガーからPub/Sub Topicをワーカ数発行する ・ワーカでテスト実行 ・ワーカの実行結果もPub/Subで取得、集計して表示/成功失敗
Slide 33
Slide 33 text
具体的な流れ PRの 作成 PRトリガー の起動 ソースコードを 一度GCSに固める テスト対象ファイルを Payloadに含めて Topic Publish Topicの数だけ テストジョブが起動 コード、実行イメージを GCSから落とす ……
Slide 34
Slide 34 text
具体的な流れ(テスト終了後) check 更新 テスト結果のTopicを Subscribeして 結果を待つ→ 集計、成功失敗判定 テストの結果は 勝手にPub/Subに 流れる (cloud-builds) ✅ ✅ ❌
Slide 35
Slide 35 text
ちなみに: 通知もPub/Subで ・Cloud Buildのジョブのキュー、開始、終了など、全てのイベントはcloud-builds というト ピックにpublishされる ・Cloud Functionでsubscribeして、中身を検査して通知したい時だけ Slackに送る、ということをしている
Slide 36
Slide 36 text
ワーカー作戦のまとめ ・ワーカーをたくさん作る→一回のテストは短くなる→全体は短くなる たくさんあるテストをなんとか回すことはできるようになった けれど... 新たな課題が ・並行しすぎることによる様々なオーバーヘッドが発生した =不要にBuild時間を使っているようだ ・実際、何度か走らせてみるとコストがかかりすぎる →ちゃんと必要十分な計算リソースを割り当てたい なんとか全件走るようになったが
Slide 37
Slide 37 text
どれくらいのコストが適切か見積もる ・元のプラットフォームのテストの所要時間を把握、分析する ・Cloud Buildで同等のテストを実行できるようにする ・それらを比べる ・パフォーマンス劣化をしていないか ・劣化している場合何がボトルネックか ・現実的なコストで収められるか ・その上で、ボトルネックは解消するか、他に縮める方法がないかを調べる
Slide 38
Slide 38 text
1) インスタンス、並行数を決定 ・何も考えずにたくさん詰め込むと遅くなる ・所要時間もだが、LAなどリソースのメトリックも見て決定 1分程度の劣化で収まり、 LA1が5を恒久的に超えない(8コアで) 1 step 2 steps 3 steps 4 steps 5 steps 6 steps 所要時間 4m42s 5m10s 5m53s 6m06s 6m59s タイムアウト 増分 +28s +43s +13s +53s LA1 3 ~ 4 4 ~ 5.5 4 ~ 5 5 ~ 6 6 ~ 7.5 メモリ不足 ※ n1-highcpu-8 で同じテストケースで計測
Slide 39
Slide 39 text
2) CPUの割り当てを最適化 ・テスト実行には当然MySQL、memcachedなどいろいろなサービスが サイドカーで立ち上がる ・なのでperlのプロセスだけでなく プラスアルファでそこそこの数のプロセスが立ち上がってくる ・色々試すと、そもそも実行時間自体が安定しない ・なるべく少ないCPUでたくさんのプロセスを回そうとする ・そうするといい感じにCPUが割り当たっていない場合がある? この辺からクラウドを使い倒してる感じに
Slide 40
Slide 40 text
CPUの割り当て、どう最適化? ・dockerのcpuスケジューリング周りのオプションを調整した ・値をいくつか変えり、オプションを比べた感じ → MySQLに優先してCPUが割り当たるようにすると時間が改善 コンテナが使うCPUを固定する(pinning) コンテナのCPU割り当てのweightを上げる (デフォルト = 1024)
Slide 41
Slide 41 text
CPU周りを調整した比較 設定なし CPU cpuset CPU shares #1 7m56s 7m33s 7m21s #2 9m14s 7m24s 7m36s #3 7m24s 7m08s 7m37s AVG. 8m11s 7m21s 7m31s ※ n1-highcpu-8 で同じテストケース/3 stepsで計測 ※ cpuset = MySQL に3コア、 perl テストに 5コア ※ shares = MySQL は shares=4096、perlは shares=2048
Slide 42
Slide 42 text
3) ホストネットワークの利用 ・cloud buildはデフォルト、 cloudbuildという内部ネットワークにアタッチしてstepを起動 ・MySQLなど追加サービスを利用する場合は、docker composeなどで cloudbuildにアタッチさせて起動 ・vethという仮想ネットワークを作るので 少しだけオーバーヘッドがある → --network=host で比べてみる ・普通は気にならないが MySQLのボトルネックを無くしたら 少し差が出た ※ n1-highcpu-8 で同じテストケースで計測 net=host net=cloudbuild #1 7m21s 7m52s #2 7m36s 7m37s #3 7m37s 8m28s AVG. 7m31s 7m59s
Slide 43
Slide 43 text
現在の進捗 定期実行するもの: ・✅ Go のテスト ・✅ E2E テスト ・🚧 Perl のテスト(prove) ・✅ Lint (Go, perlcritic, YAML諸々) ・✅ フロントエンド系 (assetsのビルド) リリース時に実行するもの: ・✅ イメージ作成+GitHub Release Perl はもう少し開発基盤チームと チューニングしてから移行予定
Slide 44
Slide 44 text
まとめ+学びなど
Slide 45
Slide 45 text
Cloud Build 導入でやったこと ・GitHub といい感じに連携できるようにする GitHub App と Cloud Functions を使い倒す ・skip ciの際の挙動の罠を上手に回避する ・数の多いテストを回す仕組みを作る ・色々とチューニングをして、コストを最適化する もちろんテスト時間の短縮は開発生産性にダイレクトに効く 今日お話ししたことのまとめ
Slide 46
Slide 46 text
学び: Cloud Build の扱い方 CIのための一種のフレームワーク ・Cloud Buildには、ある程度最低限のものはあるが、One-Size-Fits-All に 全て揃っているとは考えるべきではない ・逆に、周辺のサービスを小さく組み合わせることで色々なことができる ・Cloud Functions ・Cloud Pub/Sub ・Cloud Run, GCS... ・アプリケーション基盤を含めた大きな観点で組み合わせる
Slide 47
Slide 47 text
学び2: 推測するな、計測せよ 基本だが難しい ・初め、とにかく並行化して速くすればいいと考えて、ジョブを大量に実行するような アーキテクチャにしていた ・結果的にコストが余計にかかりすぎることに→何のための移行? ・現状を把握する+現在のボトルネックを見つけ出す ・それがシステムの改善にとって一番重要 ・そして、考えられるチューニングポイントは一通り試すべき ・クラウドネイティブだからこそ計算機と仲良く
Slide 48
Slide 48 text
https://speakerdeck.com/hr_team/engineers-handbook 大事なお知らせ(2) ● インフラ/ ストリーミング/ バックエンド基盤/ ライブゲーム開発 ● Go、Google Cloudユー ザーに近い開発に 興味のある方歓迎!
Slide 49
Slide 49 text
https://tech.mirrativ.stream/ テックブログも見てください
Slide 50
Slide 50 text
ご清聴ありがとうございました