ツナワタリマイライフ

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

【WSA 研】Site Reliability Engineering における重要領域とパフォーマンス指標の提案

ずいぶん公開に時間が経ってしまった。6/4 か。。。特に出せなかった理由はない。より「ちゃんとした」形で出したいと思っていたが、そんな日はこないので当時のまま公開する。

WSA 研に初参加し、はじめて自分の身の回りの仕事、SRE の関心対象に対して計測を行なった。当時は本当に手探りであったが、この時泥臭くデータを取り、考察したことが2ヶ月後の今に確実に繋がっている。貴重な機会をくれた WSA研のメンバーに感謝したい。

当日は参加者からたくさんフィードバックをもらえた。その時いただいた意見は今に活きており、より実務に生かすことができている。

修士卒業以来久しぶりに「研究」っぽいことをしたが新鮮で楽しかった。ビジネスと研究、行ったり来たりするのいいかもな、と思った。


本資料は第8回WebSystemArchitecture研究会の予稿です。

以下が当日使ったスライドです。

背景

Site Reliability Engineering(以下、SRE)はインフラ・サービス運用をソフトウェアで制御することで、スケーラビリティや信頼性を担保する役割である。また、SRE は DevOps を実装しているという見方もあり*1、サービスのデリバリーを高速に繰り返すことにも関係が深い。加えて、システムの信頼性のためにはクラウドを含めたインフラの管理や設計にも関わるなど、その業務範囲は多岐に広がる。そしてこれらはビジネス・組織の規模や性質、組織内の分担によっても大きく異なる。そのため、多くの企業で SRE をどう採用し、どのように活かすのかの解答を探すのが難しい。*2

加えて、ソフトウェア・プロダクトを開発して利益を得る企業のプロダクト開発組織は、通常ビジネス KPI を定め、その改善のために開発を行うが、SRE の場合そういった指標を持って改善を行う"プロダクト開発的なアプローチ"はまだ一般的ではない。ただし、DevOps 領域においては書籍 「Lean と DevOps」の科学」*3でのキーメトリクスとして「デプロイの頻度」「変更のリードタイム」「MTTR」「変更失敗率」が紹介されており、エリート企業はこのどれもが優れていることが報告されている。

本発表では、前半で、筆者が所属する企業*4での経験を元に、SRE の業務領域のうち、重要な領域とその領域ごとの関係性について考察する。これによって、同様な規模の組織における SRE の活動方針のモデルを定義する。後半では前半で定義した領域のうち、2つに着目し、「Lean と DevOps の科学」の紹介内容を拡張しながら、プロダクト開発的なアプローチを SRE 業務全体に適用できるパフォーマンス指標とその測定方法を提案する。

SRE が扱う5つの領域

SRE が扱う領域は企業によって異なる。本研究では、筆者が所属する Quipper Ltd. が提供するサービス*5のように、クラウド上での Web サービスを提供する企業の SRE が関わる領域を考察していく。

クラウド上でサービスを構築していることから、今回、AWS Well-Architected*6 Framework を参考にした。この資料によると、Five Pillars として「Operational Excellence」「Security」「Reliability」「Performance Efficiency」「Cost Optimization」があげられている。AWS Well-Architected はクラウドアーキテクトが、スケーラブルで信頼性のあるクラウドインフラを構築するためのガイドである。

「Lean と DevOps の科学」と「AWS Well-Architected」の両者を参考にしつつ、筆者が所属する環境では以下の5つを重要領域だと捉えた。

  • Reliability
  • Developer Productivity
  • Platform
  • Security
  • Cost

1つずつ内容を説明する。

Reliability

その名の通り、Site(Service)の信頼性である。AWS Well-Architected でいう「Reliability」「Performance Efficiency」が関連する。

Developer Productivity

開発者生産性である。DevOps が関連する。

Platform

Kubernetes などに代表される、Application Platform を開発者に提供している。

Security

AWS Well-Architected にも存在する。また、インフラだけに閉じず、Security Incident は一度起こすと一気にビジネスが破綻しかねない重要な要素である。

Cost

AWS Well-Architected にも存在する。クラウドに限らず、コンピューティングリソースを使うために Cost は発生する。

領域の関係性

これら5つの領域はそれぞれが密接に関わっている。

以下、その関係性について説明する。

Platform の特性

Developer のための Application Platform は、それ自体がその上のレイヤーの他の全ての分野を支える関係性にある。

Trade-off

  • Reliability(可用性)をあげようとするとコストがあがる。99.9% という数字で表される Availability の桁を1つあげるには非常に大きな労力がかかる。*7
  • Security を高めようとすると、利便性, Developer Productivity を損ねる可能性がある

提案指標と測定方法

今回は5つの領域のうち、「Lean と DevOps の科学」に関連が深い"Developer Productivity" と、SRE の本丸である"Reliability" の2つの領域に絞って指標を提案する。

Reliability

Site "Reliability" Engineering を考える上で、サービスの安定稼働は第一のミッションであり、いかに他の指標を満たそうともサービスの信頼性を落としては意味がない。

しかし、サービスの可用性を指標にはしない。サービスレベルはビジネス要求によって異なる。また、サービスレベルを高めるとコストも高まる。サービスレベル目標(Service Level Objectives) として、信頼性と機能開発のアジリティのバランスをとるためのツールでしかないので、この数値自体を目標値にするのは本質的ではない。

そこで今回は、「Lean と DevOps の科学」でも紹介されていた、"MTTR / Mean time to recovery" を採用した。これを小さくするために、障害を局所化したり、すぐに復旧できるようにする必要がある。*8

MTTR 測定の自動化は非常に難しい。"障害"の判定基準は企業により異なるからである。仮に可用性(Uptime)を採用したとしても、それだけでは全てのシステム障害をカバーすることはできない。そのため、障害と判定されたインシデントの報告フローに MTTR を項目として組み込んで手動で計測する。

なお、MTTR を指標とすべきかどうかは懐疑的な面がある。その一番の理由は障害の発生数が非常に少なく、指標として追いかけるには母数が少なく、ばらつきが大きくなってしまうことが予想されるからである。*9しかし、重要指標であることには変わりなく、数が少ないことは手動でのトラッキングも可能であることから採用した。

Developer Productivity

以下の4つを指標としてあげた。それぞれの指標は開発環境と本番環境両方で区別して計測する。

  • デプロイ回数
  • デプロイ時間
  • CI 安定性
  • 変更失敗率

CI 安定性以外は「Lean と DevOps の科学」であげられた基準と同じあるいは似た指標である。また、「デプロイ時間」は該当ブランチにマージしてから、実際にコードが環境に適用されるまでの時間である。「Lean と DevOps の科学」における「リードタイム」は「コードのコミットから本番稼働までの所要時間」であるが、レビューの時間を含むと変数が多くなってしまうため、マージから本番稼働までの所要時間、とした。

CI 安定性は「デプロイ時間」に内包される(CI が失敗すれば Rerun を行う必要があるので、デプロイ時間は長くなる)が、より分析をしやすくするために CI 安定性(CI 成功率)も指標に掲げた。

これらの指標は基本的に CI サービスから取得する。CI サービスがそのような metrics を提供していればそれを利用すれば良い。もし提供していない場合でも、CI の Job / Workflow で時間を計測し、何らかの時系列ストレージに情報を送ることで取得できる。Monitoring SaaS*10では Custom Metrics がサポートされているので、こちらをストレージ兼可視化システムとして採用することができる。

変更失敗率は、コード適用後、何かしらの"不具合"が発生した件数である。これは"不具合"の定義に依存してしまうため、計測が難しい。そこで、原則不具合があった場合は即 Revert をして、その後 HOTFIX を適用する運用とし、本番環境に対応するブランチにおける Revert commit の数を計測する。

指標のまとめ

領域ごとの指標と測定方法をまとめた。

領域 指標 測定方法
Reliability MTTR 障害発生時に、障害報告フロー内の必須項目として手動で計測
Developer Productivity デプロイ回数 CI サービスの metrics
Developer Productivity デプロイ時間 CI サービスの metrics
Developer Productivity CI 安定性 CI サービスの metrics
Developer Productivity 変更失敗率 本番環境デプロイに対応するブランチの Revert commit の数

計測結果

MTTR

以下、筆者が所属する企業で計測した MTTR を複数の観点で図にした。

f:id:take_she12:20210604042715p:plain

これは TTR の散布図である。2019年から2020年前半にかけて、1000分以上停止しているインシデントが3件存在している。それ以降はそのようなインシデントはなく、減少傾向にある。

f:id:take_she12:20210604042720p:plain

これは半年ごとの MTTR の平均である。2019年後半に MTTR は増大し、その後減少傾向にある。

f:id:take_she12:20210604042723p:plain

これは横軸を TTR としたヒストグラムである。Incident Response in SRE Figure2 で紹介されているグラフと近い形を示しており、少数の長時間解決しないインシデントと、多数の短時間で復旧したインシデントがあることがわかる。

デプロイ回数

以下、筆者が所属する企業における、Production に Deploy した回数である。

f:id:take_she12:20210604044419p:plain

なお、筆者が所属する企業特有の事情があるので説明する。筆者が所属する企業は monorepo*11 を採用しており、かつ 1つの Database を複数アプリケーションで共有するDistributed Monolithとなっている。この monorepo で master branch に merge された場合、この Distributed Monolith を構成する全サービスがデプロイされる。今回プロットしたデプロイ回数は Distibuted Monolith へのデプロイ回数である。Microservice 化は徐々に進んでおり、その場合別のブランチへのマージを契機に microservice へのデプロイが行われる。

デプロイ回数が減少傾向にあるのは、microservice 化が進んでいるからだと考えられる。

デプロイ時間

f:id:take_she12:20210616035334p:plain

これは master branch の CI Workflow の median である。(Duration Period は 7 Days)なお測定開始は2021年2月である。

CI 安定性

f:id:take_she12:20210604050634p:plain

これは master branch の CI Workflow の成功率である。(Duration Period は 7 Days)なお測定開始は2021年2月である。

変更失敗率

f:id:take_she12:20210604051018p:plain

これは master branch を base branch とする、すべての merge 済み PR の数と、"Revert" という Title が含まれている PR の数である。

f:id:take_she12:20210604051022p:plain

これは master branch を base branch とする、すべての merge 済み PR の数に対する "Revert" という Title が含まれている PR の比率である。

まず、2020年 1st half に merge 済み PR が増えている理由は、本番環境に対する Kubernetes manifest の変更が行われる場合、develop branch を経由する必然性がないため、HOTFIXとして直接 PR 作成されたことがある。

次に、2021年 1st half に Revert PR が増えている理由として、Argo Rollouts*12による Canary Release を採用している過程で、Canary Strategy を1度有効にして Promotion し、終わったあとそれを Revert する動きが多くあることがあげられる。これは Canary Strategy は通常有効にしておらず、Kubernetes Deployment と同様に Rolling Update を行うが、重要なリリースのみ Canary を行っているためである。

このような事情より、Revert PR は必ずしも変更失敗を示していないため、別の方法での変更失敗率の計測が必要である。

まとめと今後の展望

今回、SRE が関わる領域において分類し、Reliability と Developer Productivity 領域において、「Lean と DevOps の科学」を参考にしながらパフォーマンス指標を提案し、計測を行なった。

MTTR に関しては自動的に計測するツーリングが必要だと考える。今回はデータソースとして Postmortem を用いたが、Postmortem を書く基準が定まっていなかったり、Incident によっては発生時間、検知時間、復旧時間が記載されていないものもあった。MTTR 自体が有効な指標かどうかはまだ判断が難しいが、計測することによって、Incident Response の型化が期待できる。

Developer Productivity 領域の指標は、いずれも monorepo の master branch のみを対象であった。そのため、原則 Weekly Release であり、それに対してどれだけ HOTFIX が行われたか、というものを示すに止まった。今後は microservice 群にも範囲を拡大し、より広い範囲でのデプロイ回数の計測を行いたい。変更失敗率に関しては、何らかのルールとプロセスの追加によって計測可能にするアプローチが必要だと考える。(例えば PR に label をつけるなど)

今後、このようにあらゆるものを計測して可視化するアプローチが、SRE だけに止まらず、様々な領域で有効になっていくと考えられる。そのため、計測して可視化する一般的なアーキテクチャやデザインパターンについても探求したい。*13

また、今回提案した領域は飽くまで筆者が所属する規模の組織での話であることから、組織やビジネスの規模に対して SRE Practice をどのような順番で適用すべきかの Trail map のようなものを定義できれば、今後新規に SRE を採用する企業にとって大きな貢献になるだろう。

謝辞

本発表で話した内容は以前複数の会社の SRE Member と会話したことが元になっている。その場に参加いただいた @katainaka0503 , @kazoooto , @taisho6339 に心から感謝する。

筆者が所属する Quipper Ltd. の SRE Team およびすべての関係者に心から感謝する。

*1:https://www.ahsan.io/2019-05-26-sre-implements-devops/#:~:text=Site%20Reliability%20Engineering%20(SRE)%20and,team%20building%20and%20running%20software.

*2:そのような理由で、各社の取り組みを共有するSRE Loungeというコミュニティも存在する

*3:https://book.impress.co.jp/books/1118101029

*4:プロダクト2つ、開発者数100名程度

*5:https://studysapuri.jp

*6:https://aws.amazon.com/architecture/well-architected/?wa-lens-whitepapers.sort-by=item.additionalFields.sortDate&wa-lens-whitepapers.sort-order=desc

*7:そのバランスを取るツールが Service Level Objectives である

*8:なお、応用として障害発生時の MTTR に Number of the affected user をかけることも指標になりうるが、今回は運用が複雑になるため採用を見送った。

*9:Incident Metrics in SREでも MTTR を指標として採用すべきではない "these statistics(MTTR or MTTM) are poorly suited for decision making or trend analysis in the context of production incidents." とされている

*10:Datadog, Mackerel 等

*11:https://en.wikipedia.org/wiki/Monorepo

*12:https://argoproj.github.io/argo-rollouts/

*13:筆者が Cloud Native Days Spring 2021 Online で発表した Metric-Driven Decision Making with Custom Prometheus Exporter はそのパターンの1つと言えるだろう

ソフトウェアエンジニアリングと認知

職業ソフトウェアエンジニアを7年+ やってきて、いわゆる新卒、ジュニアからキャリアをスタートし、今は Site Reliability Engineering 領域で Lead をしている。

こうした中で、自分自身のことを振り返ると、ソフトウェアエンジニアリングとは自己認知との戦いではないか、と思う。

自分自身、自分の認知の歪みでパフォーマンスが著しく出なかった経験が何度もあった。そして認知の矯正によってパフォーマンスが急に出るようになった経験、その両方がある。

昨日たまたま、エンジニアリングと認知について話す機会があったのでついでに記録しておく。

ソフトウェアエンジニアリングに対する認知

ソフトウェアエンジニアリング、システムプログラミング、なんでもいいが、こういったものを取り扱ってビジネス価値を発揮している我々にとって、機械の上で動く、なんらかの入力を通じて動く何某は当然目に見えないし、ソースコードを丹念に追わないと動作原理は理解できない。

そういった「理解できない」こと。「わからないこと」と呼ぼう。あるいは「問題」と呼ぼう。新規開発だろうが既存プロダクトの保守だろうが、大きく我々は「問題」を解決している。その問題解決のためにはこれら「わからないこと」と正しく向き合う必要がある。

「わからないことと正しく向き合う」ということは、対象に対して正しく認知することに他ならない。

問題を正しく認知するとはどういうことか。そのために、どのようなアプローチが有効か。

  • エラーメッセージを正しく読みとり、その指示に従う
  • 自分がした行動と結果を記録し、その因果を捉える
  • 問題の再現させ、発生条件を狭める
  • 内部の仕組みを理解するために、該当するソースコードを読む

正しく認知できてないとはどういうことか。誤ったアプローチとはどういうことか。

  • google で検索し、検索結果に出る対象コマンドをコピペで打ってみる
  • 対象のソフトウェアの参考書を引っ張り出して0から読み直す
  • 同僚に「うまくいきません!」と助けを求める

上記は別に 100% 間違っているわけではない。たまたまそれで解決することもある。しかし、その行動は問題の対象に正しく向き合っておらず、有効性の低いアプローチだと思うだろう。

文字にすればびっくりするが、ここまで極端でないにせよ、後者のアプローチのようなことを取っていたことが僕にはあった。

人間は何らかの外的要因、例えば精神的な安定性であったり、周囲に対する心理的安全性であったりで驚くほど簡単に認知能力が下がり、パフォーマンスが出なくなる。

ここで言いたいのは、

  • 認知能力がソフトウェアエンジニアリングにもっとも重要であり、
  • その認知能力は外的要因で簡単に狂ってしまうものであり、
  • 自身の認知能力を認知する、メタ認知こそがジュニアソフトウェアエンジニアにとって最初に超えるべき壁である

ということだ。ある程度経験を積んできた僕たちは、こういったことを伝え続けるとともに、認知能力を下げない環境づくりに力を割かないといけないと思う。「経験を積むしかない」ことはある意味事実であることは認めるが、それ以上にできることがあると思う。

チームメンバーに対する認知

ジュニアソフトウェアエンジニアの育成、という文脈からもわかるように、(おそらく圧倒的多数を占める)チームでのソフトウェア開発について取り上げる。複数人で協業してソフトウェアを扱う仕事をするにあたって、前述したソフトウェアだけでなく、人間というチームメンバーとも向き合う必要がある。

"正しさ"は環境によって異なる。チームが醸成する価値観によって異なる。ある問題に対してどうアプローチを取るかに対して、絶対的な正解があるとはいえない。だからこそ、もっとも正しいらしいものを、時にはチームで議論をして合意を得ながら、時には個人の責任感を持って、意思決定していく。

チームメンバーはいろんなコミュニケーションの方法を使ってメンバーと協業していくだろう。その代表的な方法として我々は言葉を使う。「こうしたほうがいい」「この方法だとこの面でデメリットがある」「ここの考慮事項を考えておいたほうがいい」様々だ。

しかし、「こうしたほうがいい」という言葉1つにとっても、これを「正しく認知する」ことが求められる。コードレビューの文脈でもよくあるが、コードへの指摘が個人攻撃だと捉えることは十分にありえる。誰もが正しく認知できるわけではないのだ。こうしたほうがいい -> お前は間違っている -> お前のコードは最悪だ -> お前はソフトウェアエンジニアの適性がない あえて極端な例を書いたが、こういう状況は起きうる。これが良い悪いではない、起きうるということを言っている。

ここで言いたいのは、

  • チーム開発が必要な場面において、チームメンバーとの協業の過程でメンバーの言動を正しく認知することが重要だが、
  • その認知能力は外的要因で簡単に狂ってしまうものであり、
  • 自身の認知能力を認知する、メタ認知こそがジュニアソフトウェアエンジニアにとって最初に超えるべき壁である

後ろ2つをあえて同じことを書いた。対エンジニアリングだけでなく、対人間に対しても同じことが言える。

だからこそ、正しく他者の言動を正しく認知するためには相手の言動のその背景を理解する必要があるし、自分が誤解してそうだと思えばその意図を確認する必要がある。

Tech Lead, Senior / Stuff Engineer 相当の Role の人間に高いドキュメンテーション能力や、言語化能力、コミュニケーション能力が求められるのは、大きなインパクトを、多くの人間が関わる中で、より正しく遂行する必要があるからだと思う。

自分にとってのブレイクスルー: 分からないことを受け入れること

自分自身、自分の認知の歪みでパフォーマンスが著しく出なかった経験が何度もあった。そして認知の矯正によってパフォーマンスが急に出るようになった経験、その両方がある。

7年も職業 Software Engineer をやっていると、それなりに挫折も経験したし、良い意味でのブレイクスルーも何度も経験した。

一番直近のブレイクスルーは、直属のマネージャからかけられた「(あの Senior Software Engineer だって)わからないんです。」という言葉だった。

この頃、得体のしれない「技術力」が足りない、という漠然とした課題感があった。しかし、何がどう足りなくて、どういった状態が足りていて、そのために何をすればいいのか、そのすべてが分からない状態だった。

1on1 で、技術力があると思うひとは誰だと思いますか?という問いにいくつかの人をあげた。それに対して、直近あった、難易度が高い問題に対して、そういったクラスの人たちが集まって取り組んでいた事例をあげながら、「この人だって最初から分かってるわけではないんです。この人でも分からないことはあるんです」と言ったことを言ってくれた。

それと同時に、自分がこれまでしてきたことに対して「これまでこういうことをしてきましたよね。だから、もうできるんです。分かってるんですよ。」とも言ってくれた。

これを受けて、分からなくてもいいんだと、分からないことは普通で、当たり前で、そこからどうするかが大事ということと、 自分も分かっていることだってあって、それは肯定して良い、ということを感じることができた。

今思えば、この時、無意識下にあった「分からないことに対する漠然とした不安」が自分の認知能力を奪っていたんだと思う。

この後、テクニカルに難易度が高く、インパクトの大きい仕事をいくつか完遂し、自信を持つことができ、Software Enginering のスキルがあがることで、もともと得意だったコミュニケーションスキルを活かして Team / Project Lead をやれている。周囲から見てもきっとそうだと思うが、自分から見ても、パフォーマンスは大きく変わり、成長できたと思う。

おわりに

当時の自分に、あるいはこれから業界に入ってくるエンジニアに対して、自分ができることは、こういった認知能力の大切さを伝え続けることと、それを阻害しない環境づくりをしつづけることだと思う。

こういうことが書けるようになり、ここまで成長するまで見守ってくれた、手を差し伸べてくれた、前職と現職の全ての方に、心から感謝します。特に、現職の SRE Team にいた・いる上司、同僚が、挑戦と失敗を辛抱強く見守ってくれたおかげです。

業界への恩返しとして、こういうことをやっているので、よかったらどうぞ。

Roomba で掃除が完了したら IFTTT 経由で Pixe.la で草を生やす

かくかくしかじか部屋を綺麗にする意識が高まり、Roomba e5 を買いました。

で、かくかくしかじかそういう話を社内 LT でして、この時健康やお金の話も一緒にしていて、定量的に評価して目標値を決めるっていう SRE 的なアプローチを、部屋の綺麗さでやれたらいいよねって話題があって

f:id:take_she12:20210322185938p:plain:w300

まぁ結局 Clean map 機能は e5 では使えなかったのでどのみち面積はわからないんですが、床には物を置かないという前提で、スケジュール実行していれば、毎日実行はしてくれるので、その結果をとりあえず可視化してみようと思い、pixe.la を試してみました。

pixe.la

ご存知の方も多いと思います。GitHub の草を API 経由ではやしてくれるサービスです。

たくさん事例があって楽しいですね。

github.com

Roomba が掃除完了したら IFTTT 経由で草を生やす

さて本題。とても簡単かつこちらの記事で紹介されているものとほぼ同じです。

t.co

アカウント作成

README にある通りですが

curl -X POST https://pixe.la/v1/users -d '{"token":"secret", "username":"chaspy", "agreeTermsOfService":"yes", "notMinor":"yes"}'
{"message":"Success. Let's visit https://pixe.la/@chaspy , it is your profile page!","isSuccess":true}

Graph 作成

curl -X POST https://pixe.la/v1/users/chaspy/graphs -H 'X-USER-TOKEN:secret' -d '{"id":"roomba","name":"roomba","unit":"commit","type":"int","color":"shibafu"}'
{"message":"Success.","isSuccess":true}

Webhook 作成

curl -X POST https://pixe.la/v1/users/chaspy/webhooks -H 'X-USER-TOKEN:secret' -d '{"graphID":"roomba","type":"increment"}'
{"message":"Success.","isSuccess":true,"webhookHash":"1234567890123456789012345678901234567890"}

草を生やす

上で返ってきた webhookHash を使います。

curl -X POST https://pixe.la/v1/users/chaspy/webhooks/1234567890123456789012345678901234567890
{"message":"Success.","isSuccess":true}

これまでで草を生やす準備は完了。

IFTTT で iRobot 連携する

iRobot アカウントでログインします。

ifttt.com

IFTTT で掃除が完了したら Webhook を発行する

実際に作ったのはこれなんですが、

ifttt.com

If は iRobot で JobComplete Then で上記の webhook の設定をします。

f:id:take_she12:20210322191708p:plain:w300

これで、実際に roomba をリモートで動かしてみて、完了したら IFTTT が実行されました。

f:id:take_she12:20210322192128p:plain:w300

ちゃんと草も生えた!

pixe.la

今後

  • 生やした草を表示する Static site をホストする
  • Withings の体重計とも連動できそうなので、体重増減を草生やす
  • Withings の体重増減のグラフもパブリックにしたいので調べる
  • ジムに行ったら、運動したら草生やす

などなど、Pixe.la 使い倒していこうと思います!最高のサービスを a-know さんありがとうございました!

Cloud Native 読書会第5回 NGINX Ingress Controller に参加した

した。

zenn.dev

コードを読むことは重要である。これはここ数年思っていることである。そしてある程度読めるようになった。コード、読めば読めるし、書けば書けるな、というところまではなってきた。しかし、解決したい課題がない状態でコードを読むということはあまり経験がない。しかし優秀なソフトウェアエンジニアはみんなきっとそうしているのであろう。そういうものが体験できるかな、と思い参加した。あと Nginx Ingress Controller は(あんまり関わってないけど)弊社でも動いているので意味は大きい。

主催の Wantedly 南さんや田中さんとはもう何度も話している間なので、安心して参加できた。白金台のオフィスに遊びにいくみたいな気持ちで。自己紹介を軽くして、entry point からさっそく読んでいく。そしてさすが南さん、これが早い。このスピードで読んでいくのね。もちろん手元に Clone はしていたが、自分でコードジャンプや grep をしながら全体の話についていくのは無理だと早々に判断したので、手を止めて聞きに徹した。

そうすると面白くて、他人がコードを読むときの思考をトレースできて貴重な体験だった。コメントをちゃんと読んでるなあ、とか、メインの処理のあたりをつけて、それ以外は読み飛ばしているなあ、とか、ここがこうしているという仮説を立てて、その検証をするために別のコードを追っているなあ、とか。これは実はあまりできない経験なのではなかろうか。

今回参加者のほぼ全員(多分)が Nginx Ingress は使っていて、動作概要は知っていたようで、この前提がないと厳しいだろうなと感じたし、逆に前提がみんなあるからこそこのスピードで読み終えられたなと思った。エントリポイントで config を読み込んで nginx を起動している部分や、config の変更を検知して再起動をしている部分、そしてコアの部分には lua のコードが多く出てきて、nginx ingress を深く読み特には lua を読めないといけないのね、と(おそらく参加者全員が)驚いた。

みんなの感想を聞いてて思ったのは、基本挙動は知っているメンバーが揃っているとはいえ、Ingressyaml がだいたいこんなんだとして、Nginx Ingress Controller が Ingress を読んで、nginx.conf を生成して、nginx として動いているってのが基本的な動作ですよね。だいたいこういう流れになっていると(いったん)想像するとして、読んでいきましょう、でコードの該当部分をマッピングしていく、みたいにできると便利そうだと思った。どうにもテキストベースの GitHubパーマリンクと図をあらわすツールみたいなのをいい感じにあわせるのが難しいが。

あと今回は南さんの画面共有だったけど、VSCode LiveShare とかで見ていくとどんな感じかなーとも気になった。今日は〜の何行目!みたいなことを言ってたけど、そういうのも共有楽になったりするかもしれない。あとかめねこさんが言ってたブックマーク のエクステンションも使えばなおさら便利そう。一方 GitHub もコードジャンプできるし十分使えるので一長一短かも。自分は Go は Vim か Goland で読んでジャンプできるようにしています。

次回は ArgoCD。弊社でもバリバリ使っているし、使ってる人も多いので参加者も増えるんじゃないかな、と期待。次回は少し予習をしてきて、よりみんなをワクワクさせられるような場になるように貢献できたらいいなと思う。

南さん、田中さん、参加されたみなさんありがとうございました!

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 をそのまま扱ったので勉強になった。

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

おまけ

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

AWS Config の Compliance の Capped Count を Prometheus 形式で Export する OSS 作った

作った。

github.com

AWS の Prometheus Exporter では4作目、Prometheus Exporter としては6作目。

blog.chaspy.me

blog.chaspy.me

blog.chaspy.me

blog.chaspy.me

blog.chaspy.me

なぜ作ったか

もともとのきっかけは AWS IAM で 2FA が有効になっていないユーザがいて、たまたま気づいたが定期的に検知、通知ができてないことから。

AWS Config と SeucrityHub は有効化していたので、mfa の rule は有効になっていた。

ちょっとまだこの違反時の Slack 通知はまだできていないんだが、ついでなのでこの数も送っちゃおう、となった。

使ってる API

describe compliance by config rule

CLI

docs.aws.amazon.com

go sdk

https://docs.aws.amazon.com/sdk-for-go/api/service/configservice/#ConfigService.DescribeComplianceByConfigRule

今回は特にシンプルで、この API 1つから帰ってくる Compliance by Config Rule を metric として送っていて、Value としては Capped Cound を出している。

https://docs.aws.amazon.com/sdk-for-go/api/service/configservice/#ComplianceByConfigRule

type ComplianceByConfigRule struct {

    // Indicates whether the AWS Config rule is compliant.
    Compliance *Compliance `type:"structure"`

    // The name of the AWS Config rule.
    ConfigRuleName *string `min:"1" type:"string"`
    // contains filtered or unexported fields
}
type Compliance struct {

    // The number of AWS resources or AWS Config rules that cause a result of NON_COMPLIANT,
    // up to a maximum number.
    ComplianceContributorCount *ComplianceContributorCount `type:"structure"`

    // Indicates whether an AWS resource or AWS Config rule is compliant.
    //
    // A resource is compliant if it complies with all of the AWS Config rules that
    // evaluate it. A resource is noncompliant if it does not comply with one or
    // more of these rules.
    //
    // A rule is compliant if all of the resources that the rule evaluates comply
    // with it. A rule is noncompliant if any of these resources do not comply.
    //
    // AWS Config returns the INSUFFICIENT_DATA value when no evaluation results
    // are available for the AWS resource or AWS Config rule.
    //
    // For the Compliance data type, AWS Config supports only COMPLIANT, NON_COMPLIANT,
    // and INSUFFICIENT_DATA values. AWS Config does not support the NOT_APPLICABLE
    // value for the Compliance data type.
    ComplianceType *string `type:"string" enum:"ComplianceType"`
    // contains filtered or unexported fields
}
type ComplianceContributorCount struct {

    // Indicates whether the maximum count is reached.
    CapExceeded *bool `type:"boolean"`

    // The number of AWS resources or AWS Config rules responsible for the current
    // compliance of the item.
    CappedCount *int64 `type:"integer"`
    // contains filtered or unexported fields
}

struct はこんな感じの階層構造になってる。

ComplianceByConfigRule
|__Compliance
   |__ComplianceContributorCount
     |__CapExceeded
     |__CappedCount
   |__ComplianceType
|__ConfigRuleName

ConfigRuleName が rule 名、ComplianceType が COMPLIANCE か NONCOMPLIANCE か。CapExceeded が、よくわかんないけどルール違反が 25 をこえたかどうか。Capped Count が違反してる数で、25以上の場合は25になる。

課題

Capped Count を Value として送っているので、そもそも ComplianceType が COMPLIANCE - ちゃんと遵守している場合、value が 0 となってしまい、どの rule が守られているのかという情報は送信されない。考えたがどうにもしょうがないような気もする。あと気にすべきは NONCOMPLIANCE なものの数なのでやむなしかなと。

COMPLIANCE の rule が全部で何個、NONCOMPLIANCE の rule が全部で何個かを出すなら別の API と別の metrics として export すれば良いと思うが、まぁいったんこれは別になくてもいいかなと思った。見たい気もするけどなくても良い。

結果

こんな感じ。

f:id:take_she12:20210213033141p:plain

AWS MFA の数だけ注目してみるとこんな感じ。7人もいるのでどげんかせんといかん。

f:id:take_she12:20210213033146p:plain

まぁルールを完璧にいきなりするのは難しいので少しずつ、できるところから。

次回

AWS 編は Cost が気になっているのだけれど、ちょっとおやすみして、CircleCI Insight に挑戦する予定。

AWS RDS の Max Connections を Prometheus 形式で Export する OSS を作って current connections / max connections 比率でアラートを出す

作った。

github.com

これは何か

RDS の設定値、max_connections は PostgreSQLMySQL ともにある。接続数がこの数値を超えるとエラーとなるため、この数値を超えないように監視できることが望ましい。

しかしこの max_connections、RDS の parameter group の value の1つであり、cloudwatch metrc に存在しない。そのため Datadog Integration でも取ることができない。

というわけでそれを取って Prometheus 形式で Export することにした。

なお、現在の接続数 database_connections は取れる。

docs.datadoghq.com

これとの比で、例えば9割になったら Alert みたいなことができるようになって便利。

また、新規サービスリリース前に Production Readiness Checklist でも DB の接続数大丈夫?みたいな項目があるが、それを減らすことができる。

結果

こんな感じ。まぁ Maxcon を可視化しても全然面白くはない。そうそう変わるもんではないしね。Datadog 上に metric として値が欲しかっただけである。

f:id:take_she12:20210209040027p:plain

アラート

ここで気づくが、aws.rds.database_connections はよしなにタグに値をつけてくれるが、この Exporter でもつけている、DB Instance Identifier と DB Instance Class の Key 名を合わせないと Alert が作れない。

雑に GitHub で調べると、付与されるタグはこのように決まっていることがわかる。

github.com

RDS auto_minor_version_upgrade、dbinstanceclass、dbclusteridentifier、dbinstanceidentifier、dbname、engine、engineversion、hostname、name、publicly_accessible、secondary_availability-zone

snake case かと思ったらそうでもなかったりしてポリシーがよくわからないが、とにかく同じ tag として扱いたければ dbinstanceclass dbclusteridentifier に変える必要があった。

タグさえ一致させればこんな風に欲しい値が手に入った。

f:id:take_she12:20210209044016p:plain

こんな感じで monitor を作る。

f:id:take_she12:20210209044531p:plain

いったん低めの8割で作った。

課題

Issue にあげているが、、、

Max Connection の値の確からし

一応 autora-postgresql の場合、デフォルトは以下のような計算式で max connections は決まる。

Aurora PostgreSQL: "LEAST({DBInstanceClassMemory/9531392},5000)"

ただ、この計算式がイマイチ実際の default と違うような予感がしているので、要検証だと思っている。

ちなみにこの default 値の場合の instance class に対する maxcon はこのように map を持っている。

github.com

   auroraPostgresMaxcon := map[string]int{
        "db.r4.large":    1600, // Memory  15.25 GB
        "db.r4.xlarge":   3200, // Memory  30.5  GB
        "db.r4.2xlarge":  5000, // Memory  61    GB
        "db.r4.4xlarge":  5000, // Memory 122    GB
        "db.r4.8xlarge":  5000, // Memory 244    GB
        "db.r4.16xlarge": 5000, // Memory 488    GB
        "db.r5.large":    1800, // Memory  16    GB
        "db.r5.xlarge":   3600, // Memory  32    GB
        "db.r5.2xlarge":  5000, // Memory  64    GB
        "db.r5.4xlarge":  5000, // Memory 128    GB
        "db.r5.8xlarge":  5000, // Memory 256    GB
        "db.r5.12xlarge": 5000, // Memory 384    GB
        "db.r5.16xlarge": 5000, // Memory 384    GB
        "db.r5.24xlarge": 5000, // Memory 768    GB
        "db.m4.large":    900,  // Memory   8    GB
        "db.m4.xlarge":   1800, // Memory  16    GB
        "db.m4.2xlarge":  3600, // Memory  32    GB
        "db.m4.4xlarge":  5000, // Memory  64    GB
        "db.m4.10xlarge": 5000, // Memory 160    GB
        "db.m4.16xlarge": 5000, // Memory 256    GB
        "db.m5.large":    900,  // Memory   8    GB
        "db.m5.xlarge":   1800, // Memory  16    GB
        "db.m5.2xlarge":  3600, // Memory  32    GB
        "db.m5.4xlarge":  5000, // Memory  64    GB
        "db.m5.8xlarge":  5000, // Memory 128    GB
        "db.m5.12xlarge": 5000, // Memory 192    GB
        "db.m5.16xlarge": 5000, // Memory 256    GB
        "db.m5.24xlarge": 5000, // Memory 384    GB
        "db.t2.micro":    125,  // Memory   1    GB
        "db.t2.small":    250,  // Memory   2    GB
        "db.t2.medium":   450,  // Memory   4    GB
        "db.t2.large":    900,  // Memory   8    GB
        "db.t2.xlarge":   1800, // Memory  16    GB
        "db.t2.2xlarge":  3600, // Memory  32    GB
        "db.t3.micro":    125,  // Memory   1    GB
        "db.t3.small":    250,  // Memory   2    GB
        "db.t3.medium":   450,  // Memory   4    GB
        "db.t3.large":    900,  // Memory   8    GB
        "db.t3.xlarge":   1800, // Memory  16    GB
        "db.t3.2xlarge":  3600, // Memory  32    GB
    }

これが多分ちょっとあやしいので確認したい。

RDS MySQL / Aurora MySQL サポート

現状していない。

やればできると思うが、こいつらの Default もまぁ結構キモい。特に Aurora MySQL。どうしてそうなったんだろう。

Aurora MySQL: "GREATEST({log(DBInstanceClassMemory/805306368)*45},{log(DBInstanceClassMemory/8187281408)*1000})"
RDS MySQL: {DBInstanceClassMemory/12582880}

正規表現マッチやってやれないことはないけど、log とかなんとか言ってるしマジ。まぁ結局 default の場合結果の値は static に持つのだけれども。

驚いたこと

max_connections は cluster parameter group と db parameter group 両方で設定できる

max connections は実は cluster parameter group と db parameter group 両方で設定できるのだが

あとに設定したほうが有効になるということ。知らんわ。

今回作った Exporter は DB Instance の Parameter Group を describe しているので、cluster parameter group にだけ設定しているパターンだと正しい値が取れないです。

Default はインスタンスクラスによって決まるが、それを静的な値で上書きできる

なんか全部 max はこれで決まるかと思ったけど、fixed value を入れることもできる。

そうしている場合はそちらが値に入ってるので、素直にそれを使うようになっている。

感想

Database Instance を for でぶんまわしたあとインスタンスごとに db parameter group を describe するので、loop の頻度によっては結構すぐに rate limit に達してしまう。DB Instance、いま動かしている環境だと 90 なんだけど、1分おきでたかだか3周とかで rate limit に達してたので、270 / 3min でアウトは厳しすぎない?あと rate limit 各サービスによって違うので結局調べても正しい値にたどり着けなかった。

コード的には parameter group value が default だと LEAST({DBInstanceClassMemory/9531392},5000) こういう式が、それ以外だと static に fixed value もとりうるというところがだいぶキモかった。計算式はわかったんだけど default は素直に計算された値を parameter group の value にいれといて、、、と思った。そうすれば今回書いたような map を持つ必要はなかった。

おわりに

これで aws.rds.database_connections の anormary のアラートで、接続数が少ない DB で false positive が起きてたのをアラート消せる。めでたしめでたし。

次回

AWS Config の結果を export するやつの予定。

docs.aws.amazon.com

なんかいろいろ考えてたけど忘れた。このへんか。