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

ご清聴ありがとうございました