ツナワタリマイライフ

日常ネタから技術ネタ、音楽ネタまで何でも書きます。

CircleCI の Insights API の結果を Prometheus 形式で Export する OSS 作った

作った。

github.com

シリーズものです。Prometheus Exporter としては7作目。

経緯

解決したい課題

所属している会社では monorepo を採用しており、大量の Build と Test と Deploy とその他いろいろの Job が超巨大ワークフローとして走る。

しかしまぁこれが遅い。改善を続けてきているがまだ遅い。そしてそれがどう遅い、何が遅いということが見えやすい状況とはいえない。

あと Flaky Test がぼちぼちあって、それによって脳死 Rerun が状態化している。よくない。

テストやビルドなんかは基本的にサービスを開発している Developer が詳しく、SRE が直接改善していくことはできないではないが、効果的とはいえない。

SRE としてはサービスの Observability と同じ構図で、Insights を得られる指標を提供し、その活用方法を組織にインストールして、それを Developer に活用してもらうということを CI でも取りたい。

CircleCI Insights では不足か

最初は多分うちの monorepo が裏のデータが大きすぎて GUI がうんともすんとも開かないみたいな状態で使い物にならなかったが、最近は十分活用できるレベルになっている。

API ではここで表示している値を取ってきてるだけなので、単に取り回しの問題で、Datadog に送ったほうがよりカスタマイズ性があがるという理由である。

サマリを把握する分には CircleCI Insights の GUI で十分である。

これまでやってきたこと

これまでも Insights API が出る前から Job や Job 内の Step ごとにかかった時間を Push で Datadog に送るなどしていた。

しかし活用が難しい。なぜか。それは前述したように、Sparse metrics になるから。

Push として metric を送る場合、それは Event 的なデータとなる。継続的な"状態"を示すデータではない。これは解析しづらい。データ量が不足するということと、データ量自体が得られるかどうかの頻度が metric によるからである。適当な間隔で Aggregation / Rollup したりなんたりの処理が必要で面倒である。

そういうわけで値自体はとれていたが活用には至っていなかった。

差分検知に関する問題

これは今回の件と関係するようで関係しないんだが、弊社では Build / Test の時間を短縮するために独自の差分検知の仕組みを使っている。

これは monorepo 内の各サービス(ディレクトリがトップレベルで切られている)の sha1Kubernetes の configmap に保存しており(ここに置くのが良いのかというのはある)ここと差があれば差分ありと判定されビルドやテストが走る。差分なしと判定されればビルトやテストはスキップされる。

...んだが、CircleCI の Workflow は Dynamic に構成することはできない。条件によってこのジョブを走る、走らせない、みたいなことができない。そのため、ジョブ自体は走るがすぐに halt するということをしている。

これによって何が問題かというと、前述する metric がスキップしたものとそうでないものが区別できない点にある。これは Insights の情報も同じである。

その点で GitHub Actions の Path filter は優位にあり、CircleCI も現状開発中のようだ。

どうするのか

大きく2点の対応が必要。

  1. ビルドやテストなど、傾向を追いたいものは Nightly で定期的に実行し、統計情報を安定的に取得する
  2. よりコントローラブルにするために Insights API の結果を Prometheus 形式で Export して Datadog に送る

今回作った OSS は 2 を解決するものであり、1 は別途行う必要がある。

CircleCI Insights は結構イケていて、統計情報を提供してくれる点が大きい。これまで自前でやっていた"Duration Second" という事実を Event 的に送っても活用は難しい。CircleCI Insights に統計情報の計算はお任せして、その推移を追って活用することができる。

統計情報というのは API のページを見てもらえばわかるが一定の Time Window における Job / Workflow の Success Rate や Duration Second の P95, median, min, max などのことである。

circleci.com

結果

いい感じ。

  • Workflow ごとの Success Rate

f:id:take_she12:20210217064247p:plain

  • Workflow ごとの Duration metrics, median

f:id:take_she12:20210217064253p:plain

  • Job ごとの Success Rate

f:id:take_she12:20210217064300p:plain

  • Job ごとの Duration metrics, median

f:id:take_she12:20210217064306p:plain

「やだ、、、私のテスト/ビルド、遅すぎ?」みたいなことがすぐわかって便利ですね。

まだこれらは差分検知の影響もあって確からしい値じゃないものもあるのですが、それでもそれなりに事実に近い結果は出ていそうです。

わかっていない点

CircleCI API の Rate Limit

rate exceeded のエラーをよくもらう。(後述するが API call 数が多いので)

これ結局いくつが上限なのかわかってない。

実際に動かすときは30分に1度実行するようにしていて、それだと大丈夫そう。

CircleCI Insights の計測間隔

metric を見る限り、last-7-days の time window にすると、24時間は同じ値を返し続けているように見える。その瞬間から24時間前を常に出しているわけではなく、1日に1度バッチ的にリフレッシュしているようだ。

last-90-days にした場合、GUI 上はグラフが週に一度しか変わってないように見えるので、もしかして API でも 7 days に一度しかこの値はリフレッシュされないのか?という点を気にしている。

f:id:take_she12:20210217064947p:plain

毎回瞬間ごとに計算する必要はないと思うが、せめて 24時間に一度は Time window が伸びても計算しなおしてほしい。表示が重くなるから GUI 上だけそうなっているのかどうかはわかっていない。

改善したい点

OSS のほうの話です。

Repository / Branch の持たせ方

現状はそれぞれ環境変数で持たせていて怒りのループぶんまわしを決めているので無駄がすごく多い。

例えば repo が "chaspy/hoge,chaspy/huga" で、branch が"develop,release,master" だとして、chaspy/hoge はこの3つの branch が存在するとして、chaspy/huga に release branch がなかったとしても API call してしまう。

この辺は yaml みたいな構造データで設定をいれるべきなんだろうなあと思っている。

repo: 
  - name: chaspy/hoge
    - branch
      - develop
      - master
  - name: chaspy/huga
    - branch
      - develop
      - release
      - master

Go の CircleCI SDK がない

これがあるがメンテされておらず、Ingiths API はもちろんサポートされてない。v1 まで。

github.com

v2 用のライブラリ書いてもいいかなーと思ったけどいったんパスした。このリポジトリ内でまず外部利用可能な感じにできたらしようと思う。

いまはなんかパッケの名前がそれっぽいがこれそうじゃなくて API call をラップした一連の処理群で切っている。

github.com

学んだこと

JSON-to-Go 便利

API Response 格納する構造体定義するのばりめんどいやんと思ったけどレスポンスペーストするだけでできてめっちゃ便利。

json.Unmarshal 便利

response を構造体につっこむの楽で便利。

               err = json.Unmarshal(body, &wfJobsInsight)
                if err != nil {
                    return []WorkflowJobsInsightWithRepo{}, fmt.Errorf("failed to parse response body. body %v, err %w", string(body), err)
                }

wfJobsInsight がその構造体。body は response body。

おわりに

AWS は go-sdk を使うだけって感じだったけど http response をそのまま扱ったので勉強になった。

うまく改善につなげられますように。

おまけ

いろいろ書いてデータ眺めて観察してわかった知見をまとめて話そうと思うよ。