可観測性と監視:エンジニアリングチームのための完全な手引き

どのチームも本番環境を監視しています。けれども「なぜシステムはこう振る舞っているのか」に、長い調査なしで答えられるチームはわずかです。監視と可観測性の違いは道具の選択ではありません。設計の思想であり、未知の障害をどれだけ速く理解し対処できるかを決めるものです。

従来の監視は、何を見ればよいかをあなたが知っている前提に立ちます。CPU のしきい値を決め、ステータスコードを確認し、ディスクが 90% を超えたら誰かを起こす。既知の障害にはそれで足ります。しかし現代の分散システムは、どのダッシュボードも想定しなかった形で壊れます。ある一つのサービスでのわずかな遅延悪化がキューを詰まらせ、それがデータベース接続プールの枯渇として現れ、ピーク時にだけ表面化する。内部状態について自由に問いを立てられなければ、計器なしで飛んでいるのと同じです。

なぜ可観測性は基本的な監視を超えるのか

可観測性とは、外から見える出力から内部状態を推し量れるという、システムの性質です。監視は「何かがおかしい」と告げます。可観測性は「何がおかしかったのか」を突き止めさせてくれます——その障害をまったく予見していなかった場合でも。この違いは決定的です。監視はアラートに駆動され、ダッシュボードに縛られます。可観測性は問いに駆動され、データが豊かです。

システムがログ、メトリクス、トレースにわたって多様性の高い構造化データを出していれば、出来事を突き合わせ、特定の利用者や要求まで降り、しきい値のアラートでは決して捕まらない障害の根本原因にたどり着けます。可観測性に投資したチームは、平均復旧時間を一桁縮めます。推測に費やす時間が減り、証拠にもとづいて動く時間が増えるからです。

「監視は、システムが動いているかを教えます。可観測性は、なぜ動いていないのかを問わせてくれます。後者は、完全には理解していないシステムを運用するための前提条件です——本番のシステムはすべてそれに当たります。」

三つの柱:ログ、メトリクス、トレース

可観測性の土台は、補い合う三種類のデータです。それぞれに役割があります。別々の貯蔵庫ではなく、ひとつながりの信号として扱うことが、実のある調査の鍵になります。

ログ:出来事の書き換えられない記録

ログは、個別の出来事に時刻を添えた記録です。もっとも細かい信号であり、ある瞬間に何が起きたのかをそのまま捉えます。よく構造化された一行は十分な文脈——要求の識別子、サービス名、所要時間、エラーの詳細——を備え、複数の情報源を突き合わせずとも実行の道筋を組み立て直せます。

ログでいちばん多い誤りは、構造のないテキストとして扱うことです。サーバーが三台の頃は、平たいファイルを検索すれば済みました。規模が大きくなれば、構造のないログは雑音です。すべての行が解析可能で、構造化されたメタデータを持ち、アーキテクチャのどのサービスでも同じ体裁に従う必要があります。

メトリクス:時間にわたって集計された測定値

メトリクスは、一定の間隔で測ったシステム状態の数値表現です。効率よく保存し、素早く集計するために設計されています。「毎秒いくつの要求か」「99 パーセンタイルの遅延は」といった問いに答えます。ログと違い、個々の要求の情報は捨てます——細かさを、圧縮と速さと引き換えにするのです。

一般的な種類——カウンタ、ゲージ、ヒストグラム、サマリ——はそれぞれ用途が違います。カウンタは要求総数のような累積値を追います。ゲージはメモリ使用量のような、その時点の値を記録します。ヒストグラムは観測値を設定可能な区間に振り分け、遅延の分布を表すのに向きます。正しい種類を選ぶことが、誤解を招く集計と無駄な多様性を防ぎます。

トレース:要求の端から端までの一生

分散トレースは、ひとつの要求がサービスの境界を越えていく様子を追います。各トレースはスパン——開始と終了の時刻を持つ名前付きの操作で、特定のサービスや関数が行った仕事を捉えたもの——から成ります。マイクロサービス構成で要求の一生をまるごと再構成できる信号は、トレースだけです。

トレースがなければ、ページ表示の遅さは関わる数十のサービスのどれのせいにもできてしまいます。トレースがあれば、ボトルネックは 800 ミリ秒かかるユーザーサービスのデータベース問い合わせであり、他のスパンはすべて 50 ミリ秒未満で終わっている、と特定できます。この精度は、ログやメトリクスだけでは得られません。

「ログは何が起きたかを教えます。メトリクスは何回起きたかを教えます。トレースはそれらがどうつながっているかを教えます。憶測なしで本番の障害を切り抜けるには、三つとも必要です。」

OpenTelemetry の導入と計装

OpenTelemetry は、テレメトリを生成し、収集し、送り出すための業界標準です。言語をまたいで一組の API と開発キットを提供し、ログ、メトリクス、トレースをベンダーに依存しない形式で出します。採用すれば特定の製品への縛りが消え、商用のプラットフォームでも自前の経路でも、計装はそのまま働きます。

自動計装と手動計装

多くの開発キットは自動計装に対応しています。広く使われるフレームワークやライブラリに、コードなしで差し込む仕組みです。初期化の呼び出し一つで、HTTP サーバー、データベースクライアント、メッセージキュー、gRPC 呼び出しを計装できます。自動計装はよくある経路を覆いますが、業務上重要なコード経路、自作のミドルウェア、領域固有の処理には手動の計装が要ります。

import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
import { PgInstrumentation } from "@opentelemetry/instrumentation-pg";
import { Resource } from "@opentelemetry/resources";
import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";

const sdk = new NodeSDK({
  resource: new Resource({
    [SEMRESATTRS_SERVICE_NAME]: "payment-service",
  }),
  traceExporter: new OTLPTraceExporter({
    url: "http://otel-collector:4317",
  }),
  metricExporter: new OTLPMetricExporter({
    url: "http://otel-collector:4317",
  }),
  instrumentations: [
    new HttpInstrumentation(),
    new ExpressInstrumentation(),
    new PgInstrumentation(),
  ],
});

sdk.start();
process.on("SIGTERM", () => sdk.shutdown());

OpenTelemetry コレクタの構え

OpenTelemetry コレクタは、テレメトリを受け取り、処理し、送り出す、ベンダーに依存しない中継役です。本番の可観測性の経路にとって背骨にあたります。ホストごとに、あるいは Kubernetes の中で群として配置すれば、計装と送り先の選択が切り離され、データがプラットフォームに届く前にまとめ、絞り、間引き、整えることができます。

コレクタには、トレースが出そろってから間引く処理を設定できます。遅い要求やエラーになった要求のトレースは丸ごと残し、健全な通信の大半は捨てるのです。保存の費用は大きく下がる一方、いちばん大事な要求を調べる力は損なわれません。

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
  filter:
    error_mode: ignore
    traces:
      span:
        - 'attributes["http.target"] == "/healthz"'
        - 'attributes["http.target"] == "/metrics"'
  attributes:
    actions:
      - key: environment
        value: production
        action: upsert

exporters:
  otlp:
    endpoint: "collector.example.com:443"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, filter, batch, attributes]
      exporters: [otlp]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch, attributes]
      exporters: [otlp]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch, attributes]
      exporters: [otlp]

構造化ログの作法

構造化ログとは、機械が解析できる形式——たいていは JSON——で、サービス間で一貫した項目名を用いてログを出すことです。これによりログは「探す問題」から「問い合わせる問題」へ変わります。要求の識別子、サービス名、エラーコード、所要時間について全チームが同じ取り決めを使えば、独自の解析器を書かずとも、基盤全体に対してその場で問いを立てられます。

ログの体裁を設計する

構造化されたログの出来事には、最低限これらの属性が要ります。時刻、深刻度、サービス名、トレース識別子、スパン識別子、そして本文です。そのうえで領域固有の項目を足しますが、命名の取り決めは守ってください。たとえば表記を一つに揃え、利用者に関する項目には共通の接頭辞を付け、エラーの詳細は本文に連結せず入れ子の対象に収めます。

  • ログとトレースを結び付けるため、trace_id と span_id は常に含めること
  • 開発者の都合ではなく、行動につながる合図に対応したログ水準(debug、info、warn、error)を使うこと
  • 個人情報や秘密、トークンといった機微なデータは、開発環境であっても決して記録しないこと
  • 本文は固定し、変わる値は構造化された項目に置くこと。そうすれば集計できます
  • すべてのサービスで時刻の形式を揃えること。RFC 3339 か Unix ミリ秒に統一します

よくある落とし穴

いちばん高くつく誤りは、記録しすぎることです。利用者の識別子やネットワークアドレスのような多様性の高い項目は、ログの量を桁違いに膨らませます。量の多い診断ログには間引きを使い、詳細な記録は特定のトレース識別子やエラー時に限りましょう。二つ目の誤りは項目名の不統一です。あるサービスが user_id、別が customerId、さらに別が user.id を使えば、変換の層なしにサービスをまたいだ問い合わせはできません。

「問い合わせられないログの行は、存在しないのと変わりません。構造化ログは読みやすさの話ではなく、一行一行を可観測性基盤の一級市民にする話です。」

マイクロサービスにおける分散トレーシング

分散トレーシングは、マイクロサービス構成を調べるうえでもっとも効果の大きい単一の道具です。二十のサービスにまたがるトレースは、時間がどこで費やされ、どの要求が失敗し、エラーがどう伝わるかを正確に示します。トレースがなければ、遅い購入手続きの調査は、十数のサービスのログをのぞいて因果を当てずっぽうに探る作業になります。

トレース文脈の受け渡し

トレースの文脈は、サービスの境界すべてで受け渡されねばなりません。HTTP のヘッダ、メッセージキューのメタデータ、gRPC のメタデータ、さらには定期実行や背後の処理といった非同期の境界も越えてです。HTTP、gRPC、メッセージングの計装ライブラリを使えば、OpenTelemetry がこれを自動で扱います。トレース識別子は入口の関門から下流のすべてのサービスへ流れ、道々でスパンを集めていきます。

  • 入口はすべて計装すること。API の関門、負荷分散装置、受け口の制御装置です
  • メッセージキューをまたぐ文脈は、ヘッダかメッセージのメタデータで受け渡すこと
  • ログの出力にトレース識別子を含め、ログとトレースを突き合わせられるようにすること
  • エラーや高い遅延を伴うトレースを残す間引き方を選ぶこと
  • 業務上の文脈——顧客の区分、商品番号、地域——を伝えるため、スパンに独自の属性を足すこと

トレースを読み解く

よく注釈されたトレースは、要求の要となる経路を浮かび上がらせます。もっとも長いスパンに注目してください。それがボトルネックです。エラーの出来事を含むスパンや、属性の種類が異様に多いスパンを探します。成功した要求と失敗した要求のトレースを見比べれば、傾向が見えてきます。特定の下流呼び出しが時間切れになるとトレースが繰り返し示すなら、それは依存先の問題であり、アプリケーションの欠陥ではありません。

アラート:何に鳴らすか、そして麻痺をどう避けるか

アラート疲れは、障害対応にとって最大の脅威です。一つの当番で五十件も届けば、どれも無視されるようになります。健全な設計の狙いは、あらゆる異常を検知することではありません。人の判断を要する、少数で信号の濃い通知に絞ることです。それ以外は、ダッシュボードかログの問い合わせに任せるべきです。

アラートの階層

アラートは三つの層に整理します。第一層は当番をすぐ呼び出します。利用者に見える問題——エラー率の急上昇、サービスの完全停止、誤差予算の消費速度がしきい値を超えた状態——を示すからです。第二層は翌営業日の仕分けのために記録を作ります。遅延の上昇や、劣化はしているが落ちてはいない部品です。第三層はお知らせにすぎません。期限の近い証明書、上限に近づく保存容量などです。

  • 原因ではなく症状に鳴らすこと。利用者が見ているのは 5xx のエラーです。CPU 使用率ではなく、そちらに鳴らします
  • しきい値を一定時間超え続けた場合にのみ発火する、複数条件のアラートを使うこと(たとえば五分間)
  • 信号の質を保つため、呼び出しを伴うアラートは一人あたり一当番で三件までにすること
  • 四半期ごとに規則を見直し、間引くこと。古びたアラートは仕組みへの信頼を損ないます
  • どのアラートにも手順書への導線を添え、対応する人が最初の三手を分かるようにすること

消費速度によるアラート

消費速度のアラートは、誤差予算が見込みより速く費やされているときに鳴ります。固定のしきい値と違い、これはサービス目標に直結しています。三十日で 99.9% の可用性が目標なら、停止に使える時間はおよそ四十三分です。見込まれる消費のままでは期間内に予算が尽きてしまうとき、アラートが鳴り、目標を割る前に手を打つ余裕を与えてくれます。

役に立つダッシュボードを作る

多くのダッシュボードは、誰も見ないグラフの墓場です。ダッシュボードが役に立つのは、解釈を要さずに特定の問いへ答えるときです。よいものは、一つの役割と一つの用途のために作られています。障害を仕分けるための当番用、容量計画のための週次用、サービス目標の達成を追うチーム用です。

当番用ダッシュボード

当番用は一画面に収め、四つの問いに答えるべきです。サービスは生きているか。エラー率はどれくらいか。遅延の分布はどうか。誤差予算は割ってしまったか。どのグラフにもはっきりしたしきい値の線を引き、今の値が健全かどうかが一目で分かるようにします。当番用に六つを超えるグラフを置かないこと——障害のさなかでは、頭の負担が物を言います。

  • RED の指標から始めること。速度(毎秒の要求数)、エラー(失敗した要求)、時間(遅延の分位数)です
  • 基盤には USE の指標を足すこと。資源ごとの利用率、飽和、エラーです
  • サービス目標の達成度と予算の消費速度を、目立つ状態表示として置くこと
  • どのグラフも、その裏にあるログやトレースの問い合わせへつなぎ、ひと押しで掘り下げられるようにすること
  • 遅延のグラフには対数目盛を使うこと。等間隔の目盛は、裾の大事なばらつきを隠します

ありがちな悪い型

もっともありがちなのは、基盤が出すあらゆる指標を並べたダッシュボードです。この「緑の壁」は誤った安心を生み、障害のさなかに信号を見つけることを不可能にします。ほかにも、時系列に円グラフを使う、揃わない軸に複数の指標を積む、しきい値の線に名前を付けない、といった型があります。説明の注釈が要るグラフは、そもそもダッシュボードに置くべきではありません。

SLI、SLO、誤差予算

サービス水準の指標、目標、そして誤差予算は、チームと利用者のあいだの約束ごとです。指標は生の測定値——遅延、エラー率、処理量です。目標は約束する水準——要求の 99.9% を 200 ミリ秒未満で返す、といったものです。誤差予算は許される失敗の幅——その 0.1% の余地が、約束を破る恐れなしに配信し、試し、繰り返す許可をチームに与えます。

意味のある指標を選ぶ

よい指標は利用者に向いており、測ることができ、行動につながります。可用性(成功した要求の割合)、遅延(しきい値未満の要求の割合)、新しさ(返したデータの古さ)が、ウェブサービスでもっとも一般的です。肝心なのは利用者の側から測ることです。サーバー上では 50 ミリ秒で終わる要求も、遅い移動体通信では二秒かかるなら、利用者にとっては失敗です。

現実的な目標を置く

99.999% の目標は見栄えがしますが、工学的な費用はきわめて大きくなります。九が一つ増えるたびに、冗長化、試験、運用の道具立てにおよそ十倍の投資が要ります。多くのサービスは 99.9% から始め、より高い目標は顧客に面した要の経路のために取っておきましょう。達成できることについて正直であってください。守れない目標は、目標がないよりたちが悪い。失敗を当たり前にし、測定への信頼を蝕むからです。

誤差予算がもたらす速さ

誤差予算は、信頼性を制約から測れるリスクへと変えます。予算に余裕があるうちは、チームは余地があると分かったうえで自由に配信できます。使い切れば、重要でないリリースを止め、信頼性の仕事だけに向き合います。こうして、あるリリースが「十分に安全か」をめぐる主観的な議論に代わり、明快でデータに根ざした判断の道筋ができあがります。

サーバーレスとエッジにおける可観測性

サーバーレスとエッジでの計算は、固有の難しさを持ち込みます。関数は短命で、基盤は覆い隠され、実行の場は世界中の接続点に散っています。エージェントに頼る従来の監視は成り立ちません。エージェントを置く実機がないからです。ここでは別の手が要ります。コードから押し出すテレメトリ、コールドスタートへの細やかなトレーシング、そして世界規模の配備における多様性の慎重な管理です。

サーバーレスならではの作法

コールドスタートは遅延の主因です。初期化を要求の処理とは別に計装し、起動の遅さと業務処理の遅さを切り分けられるようにしてください。構造化ログで呼び出しの文脈——要求の識別子、地域、関数の版、実行環境——を捉え、呼び出し回数、所要時間、エラー率の独自メトリクスは非同期に送り出して、応答の経路を塞がないようにします。

  • コールドスタートの時間を独自のメトリクスとして測ること。事業者の標準の指標では見えません
  • 利用する基盤の拡張の仕組みに合う、OpenTelemetry の文脈伝播器を選ぶこと
  • テレメトリはまとめて送り出し、関数の実行時間に遅れを足さないこと
  • 独自メトリクスの窓口がない環境では、ログから導く指標を用意すること
  • すべてのテレメトリに配備環境、地域、関数の版を付け、絞り込みが効くようにすること

エッジで考えるべきこと

エッジの基盤は、世界中の数十の地点でコードを走らせます。この地理的な散らばりのため、従来の中央集約は現実的ではありません。エッジ向けに作られた道具を使い、各地点で集めたうえで、小さな負担で中央へまとめてください。地域ごとのエラー率を見張ること——ある地域の回線障害が一つの地理でサービスを損ねていても、世界平均は健全に見えることがあります。

可観測性の文化を育てる

道具と経路は必要ですが、それだけでは足りません。可観測性のいちばん難しいところは文化です。テストのあるコードと同じくらい、計装されたコードをチームが尊ぶ必要があります。新しい機能はどれも、単体テストやコードレビューと同じように、可観測性の計装を「完了の定義」に含めるべきです。そのためには足場が要ります。共有のライブラリ、書き記された取り決め、そして各チームにこの主題を担う人。

可観測性を一級の関心事にする

まず、標準の項目、メトリクスの命名、トレーシングの要件をサービスごとに定めたテレメトリ設計書を作ってください。新しいサービスを立ち上げる手順に組み込みます。最低限の計装を強いる自動検査を継続的インテグレーションに用意し、たとえば新しい HTTP のハンドラを足しながら対応するトレースの計装や構造化ログのないマージ要求は退けます。

  • どの機能についても、可観測性の計装を完了の定義に含めること
  • ダッシュボードとトレースの問い合わせだけで調査する練習の日を、定期的に設けること
  • うまくいった例を称えること。よいテレメトリが素早い解決につながった振り返りを共有します
  • 持ち回りの担当を決め、反復ごとにチーム横断でテレメトリの質を見てもらうこと
  • 共有の計装ライブラリに投資し、正しいやり方を誤ったやり方より楽にすること

回り続ける手応え

可観測性は一度きりの導入ではなく、続いていく輪です。計装し、届け、観察し、学び、良くする。障害が死角を暴いたら——追っていなかった指標、欠けていたログの項目、捨てられていたトレース——それを可観測性基盤の欠陥とみなして直してください。時とともにテレメトリは満ち、調査は速くなり、どんな障害でも——見たことのないものでさえ——理解できるという確信がチームに育ちます。

「可観測性の目的は障害を防ぐことではありません。障害が起きたとき、利用者が気づく前に解決できるだけのデータと道具と確信を、チームが持っている状態にすることです。」

可観測性を取り入れるのは旅であって、買い物ではありません。小さく始めてください。要となるサービスを一つ、端から端まで計装し、本当に役立つダッシュボードを一つ作り、意味のあるサービス目標を一つ書く。価値には自分で語らせましょう。障害のさなかに「推し量る」と「分かっている」の違いをいちど味わえば、チームはもう後戻りしたいとは思わないはずです。

See what your own repository can account for.

Thirty minutes on a repository you choose, including the part the record cannot attribute.