Google Cloud Operations Suite で実現する "頑張らないオブザーバビリティ"

SRE チームの市川恭佑です。

先日、CloudNative Days Tokyo 2023 のプロポーザルを提出したのですが、残念ながら採択に至らなかったので、今回は宇宙最速の(?)供養エントリになります。

シェア・投票など、ご応援をくださった皆様にはこの場でお礼を申し上げます。ありがとうございました。

event.cloudnativedays.jp

背景とか、経緯とか

筆者は、カヤックの SRE チームにちょうど2年ほど在籍しています。とは言っても半年ぐらいは学生アルバイトだったので、正社員としては1年半ほどです。カヤックに入る前も、いくつかの会社で IT エンジニアとしてインターンやアルバイトをしていました。

という訳で、何だかんだ仕事で使うプログラムを書き始めてトータル4年半ほどになりますが、そのうち3年半ほどは全て Amazon Web Services(AWS)でホストされる自社 Web サービスに従事していました。

そこから、約1年前に異動した際に、Google Cloud を使った受託モバイルゲーム開発へと環境が変わりました。これに関連して、以前も GoとテストとSDKとGCP という記事を書きましたが、やはり Google Cloud との出会いは重要な発見をもたらし、大きな成長につながりました。

今回発表する予定だったテーマは、オブザーバビリティです。OpenTelemetry と併せてバズワードっぽい感じがあるので少し引いてみられがちだったり、あんまり実践的な知見が転がっていない印象があります。

しかし、筆者の直近の経験から、Google Cloud の Operations Suite さえ活用すればオブザーバビリティの第一歩を踏み出すのは簡単で、それは本質的な価値を生み出すものであることが分かりました。これを皆様にも知ってもらいたい、というのが今回の動機です。

伝えたかったこと

おそらく多くの方が持ち合わせている認識だとは思いますが、「ビジネスの主戦場でない部分に過剰にコストを掛けるのは良くないし、どこから過剰になるかを見極めるのって難しいけど、とりあえず楽に始めるに越したことはないよね」という前提で読んでいただけたらな、と思います。

楽に実現するためのベストプラクティス!

ということで実践的な話をするつもりでした。大事なのは、以下の8項目です。

8項目もあって多い印象を受けますが、頑張らないために頑張るポイントを選んだ結果なので、ご容赦ください。

  1. ログを統一的に吐いてトレースに紐づけよう!
  2. とりあえず外部に通信する箇所のラッパー メソッドでスパンを開始しよう!
  3. パスによるリクエスト分類を Default or Critical の2段階で実装しよう!
  4. Collector の誘惑を振り払って、ParentBased/RatioBasedで我慢しよう!
  5. 夜間バッチは無理せず、ユーザーのリクエストに起因する部分を優先しよう!
  6. Contrib のソースコードは読もう!
  7. みんなで頑張って PromQL を覚えよう!
  8. Operations Suite UI の使い方を、さりげなく継続的にシェアしよう!

8項目って時点で箇条書きでもボリューム感あるのに、詳細について言いたいことメチャクチャありまして。。。 40分の発表枠で喋れるつもりだったので。。。(未練タラタラ)

ただ、一点だけ、6の文脈で伝えたかったことなのですが、GoのHTTPサーバーでotelhttp使おうとしてる人にめっちゃ注意してほしいことが!

絶対に otelhttp.NewHandler をリクエスト スコープで呼び出さないでください!メモリを食い潰します!

あくまで Contrib は Contrib なので、どちらかというと結論は先ほど挙げたように「ちゃんとソースコード読もうね」なんですが、シグニチャ的にミドルウェアにサクッと入りそうで危ないんですよね。ちなみに、しっかり同じ轍を踏んだ人も見つけました。

github.com

実際に筆者のプロジェクトで、OpenCensus => OpenTelemetry 移行のついでにアレコレ改善して "いい感じ" なオブザーバビリティを提供する Pull Request がマージされた後、負荷試験でメモリリークが発覚して Revert PR が作られたときは正直ちょっと焦りました。

みんなも気をつけてね〜〜!ということなのですが、これを適当に対処すると今度は「スパン名が微妙に分かりにくい!」となります。 ということで、Contribハンドラーをお使いになる場合、初期化時に以下のようにオプションを与えるのがオススメです。

神ハンドラー := otelhttp.NewHandler(h, "server",
    otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string {
        return fmt.Sprintf("http.Handle(%s)", r.URL.Path)
    }),
)

ということで具体的な話は一旦ここまで。ちなみに筆者のプロジェクトは、アプリケーション エンジニアの皆様の献身的なご協力により、最近は「なんかおかしい」と思ったらトレースを探してログを辿ってくれる状態になってきました。あとは PromQL を楽しく布教できると、メトリックに対する解像度が上がって「第一原理からのデバッグ」に近づくのかな、と思っています。

それでは、抽象的な話に移ります。

頑張らずに始めることが有効な理由

筆者はオブザーバビリティに関して、とにかく「肩肘張らず、できるところから継続的に取り組むことが大事」という姿勢を一貫しています。なんかそれっぽい話に響くかもしれませんが、マジでコレなんです。ほんまに。

詳しく説明する前に、Manning 出版社『単体テストの考え方/使い方』の第一章二節にて述べられている、以下のような命題について引用させてください。

Good Design  => Easy to Test
Hard to Test => Bad Design

これは単体テストのしやすさと設計の良さに関する言及です*1が、筆者はオブザーバビリティにも同じことが言えると考えます。 つまり、良い設計であればオブザーバビリティが導入しやすく、オブザーバビリティ導入の難しさは悪い設計の証左である、ということです。

そして、これはまさにベストプラクティス5の「夜間バッチは無理しない」にドンピシャな話なんです。 もしピンと来なかったら、適当なバッチ処理でトレースを取ってみてください。何の違和感もなく素直に導入できるのであれば、あなたのプロジェクトは相当に祝福されています。違和感があれば、それは設計の問題です。

これについては、同じく Manning 出版社『APIデザイン・パターン』の第三部を読んでいただけると確信できると思います。

第三部の前半では、1秒以内でリクエストを返すようなAPIを前提として、「統一的な方法で、REST を上手に拡張しようね」ということが書いてあります。実際に、多くのプロジェクトにおいて、そのようなAPIでは(完全に研ぎ澄まされたものではなくとも)ある一定の規則に従ってシグニチャが定められているはずです。

それに対して、バッチやジョブといった部分のAPIはどうでしょうか?第三部の後半では、これらについても統一的なインタフェースを提唱しています。それに対して、実際のプロジェクトでは「そもそも再実行機能がない」とか「適切な実行単位で分割されていない」といった状態も多く見られます。

まさしくこの差......設計の良し悪しの、世間一般における傾向こそが「夜間バッチは無理せず、ユーザーのリクエストに起因する部分を優先しよう!」の理由なのです。

「オブザーバビリティを導入したい!けどココは難しい!」と思ったら、それは悪い設計の証左なので、とりあえずスキップして入れやすいところから入れてチームに布教してください。そうするうちに、自然と「やっぱりココにもトレースとか入れたいよね」という話題が上がります。*2

そこからどうしろって?安心してください。その時点で、あなたはチームを巻き込むことに成功しているのです。 最初から完全な状態のオブザーバビリティを導入しようと躍起になったりせず、適度に肩の力を抜いてこそ、チームの関心をソフトウェア設計の改善に向けることができるのです。

結局のところ、より良いソフトウェアの開発・運用のためには、ソフトウェア設計という問題は避けて通れません。 たとえ Kubernetes などの(比較的)難しい技術要素から逃げることはできても、設計からは逃げられないのです。 だからこそ「無理せず簡単なところから、オブザーバビリティを漸進的に導入する」ということは、輝いて見える難解な技術要素を導入することよりも、確実かつ普遍的にビジネス上の価値を提供できるのです。

Operations Suite は第一歩に最適

さて、いい話っぽくなってしまいましたが、ここでゴリゴリ宣伝させてください。

どのベンダーのソリューションを使っても同じように上手くいく、なんて話はありません。

たとえば、サードパーティのソリューションも、最近は各ベンダー標準化にが対応し、魅力的なものが沢山あります。 しかし、新たに導入しようとすると、契約に際してさまざまな社内手続きが必要になります。

Google Cloud の Operations Suite は、妥当な使い方をしていれば法外な料金を請求されることはないので、すぐに導入できます。このハードルの低さがもたらす効果を侮ってはいけません。*3

その上、同じクラウドに閉じていれば、プロジェクトや組織といった名前空間・権限管理の仕組みにずれが生じることもありません。 権限管理のコストは言うまでもなく、各種テレメトリー シグナルの問い合わせに使うクエリがシンプルになるだけでも、認知負荷の軽減に大きく貢献します。これマジです。

ところで、オブザーバビリティに関する以下のブログに、面白い言及があります。

dev.henry.jp

例えば、昔はCIサーバーにお金をかける発想は乏しかったですが、今はそこにコストをかけるのは当たり前になり、そこに力をかけられるサービスのほうが生き残れるようになりました。

CI のメタファーでいうと、最近は GitHub なら Actions と言った調子で、サードパーティよりも VCS に統合された CI サービスを選択することが増えてきているように思えます。これには様々な理由があると思いますが、オブザーバビリティにおいても、同じような流れ(=コンピューティング サービスに統合されたソリューションが選択されるようになる)が発生するのは想像に難しくありません。

そして、クラウドに閉じたオブザーバビリティ ソリューションの中において、Operations Suite は UI の簡単さで群を抜いているのです!発表の中でデモができれば実感いただきやすかったと思いますが、今回は推しポイントをサッと紹介するに留めます。

たとえば Cloud Logging では、最近のログなら Logs Explorer の画面にちょろっとクエリを書けば OK、昔のログを集計・保全するなら Sink で BigQuery なり Cloud Storage に流せば OK、という感じで、とにかくシンプルです。これは、筆者が Google Cloud に移ってから最も感動した点です。もう、ログを探すためにインフラチームに相談したり、ログ転送コンポーネントの監視やスケーリングに悩む必要はありません!もちろん、ログを吐くときにトレースとスパンのIDを入れておけば、Cloud Trace の Trace Explorer 画面から辿ることができます。

また、Cloud Monitoring でも Metrics Explorer の画面で CHART | TABLE | BOTH という表示オプションを活用すると、Prometheus の式ブラウザに近い体験が得られます。PromQL さえ覚えてしまえば、秘伝のタレと化しているダッシュボードに頼らなくとも、高い解像度でプロアクティブに必要なメトリックを取得できるのです。使い込んでいくと「Range Vector の TABLE 表示がサポートされていない」など惜しいところにも出会いますが、Operations Suite 全体で UI の改善が積極的に行われており、今後の期待が高まります。

まとめ

ということで、手短にまとめます。

  1. 無理せず簡単なところから
  2. でも最終的な理想を忘れずに
  3. Operations Suite は最高!

カヤックではオブザーバビリティ大好きエンジニアを募集しています!

hubspot.kayac.com

*1:主張の詳細が気になる方は、ぜひ本書をお手に取ってください

*2:実際、筆者のチームでもそうなりました。今は検証環境だけ夜間バッチもトレースを有効化して、比較的多量のスパン発行を許容しています。

*3:もちろん Google Cloud を使っているプロジェクトであれば、という前提です。