monitor 漫談:監視は時代とともにどう進化してきたか

私がこの業界に入ったころには、すでにベアメタルから OS をインストールする必要はなくなっていた。とはいえ、コードを push すれば誰かがマシンを用意してくれる今のような時代でもなかった。ちょうどその中間に居合わせて、仮想マシン、クラウド、container、serverless と一通り歩いてきた。振り返ってみると、インフラが抽象化のレイヤーをひとつ積み上げるたびに、自分の手で触れるものはひとつ減っていき、監視が気にかけるものも、それにつれて一段ずつ上へ移っていった。

つらいシステム運用の力仕事

当時、社内でサービスを一式立ち上げるときの流れはこうだった。まず物理マシンを購入し、vSphere をインストールしてハードウェアを仮想化し、仮想マシンを切り出す。そして一台ずつ OS を入れ、アプリケーションを入れていく。ネットワーク面も自前だ。負荷分散が必要なら HAProxy のような load balancer を立て、背後の数台にトラフィックを振り分ける。デプロイの自動化については別の話なのでいずれ改めて書くが — この二つは実のところかなり関係が深い。信頼できる監視がなければ、自動デプロイは目をつぶって本番に出しているのと変わらない。

こういう環境では、監視で何を見るかは完全に自分次第だった。自分のほかに見る人間がいないからだ。

層を重ねて積み上がる監視

まず見るのは、当然ながら OS レベルの基礎リソースだ。CPU usage、memory free space、disk free space、disk IOPS、network byte と packet count。このレイヤーが答えるのは単純な問いで — このマシンにまだ仕事をする体力が残っているか、ということだ。

だがマシンに体力があることは、サービスが生きていることを意味しない。そこでもう一段上、アプリケーションそのものを見る。process の数は正常か、いつのまにかひとつ消えていたり大量に増えていたりしないか、個々の process の memory と cpu usage に異常はないか、サービスが開けているはずの port は本当に開いているか。このレイヤーが答えるのは — それがまだそこに在るか、だ。

そして在るからといって、仕事ができるとは限らない。だからさらに上が health check probe だ。リクエストをひとつ投げて、正常に応答が返ってくるかを見る。このレイヤーが答えるのは — まだ人の相手をしてくれるか、だ。

しかし本当に価値があるのは、そのさらに上のレイヤーだ。アプリケーション自身の telemetry のなかから、ある値を超えたら異常が起きるという指標を見つけ出し、それを選んで監視する。このレイヤーの意味は下の層とはまったく違う — 下の層はどれも問題が起きてから点灯するが、この層は実際に爆発する前に警告をくれる。

最後が対外的な end to end のインターフェースだ。ユーザーが実際にやることを模した script を書いて、返ってきた結果が正しいかどうかを見る。下のすべてのレイヤーが緑でも、この層だけが赤ということはあり得る。そしてユーザーが体感するのは、常にこの層だけだ。

さじ加減の難しい閾値

この手の監視で本当に厄介なのは、数値の集め方ではなく、閾値をどこに置くかだ。

CPU usage が 90% を超えたら、という同じ一本のルールでも、コア数の多いマシンと少ないマシンでは緊急度がまったく違う。disk IOPS も同じで、ハードウェアそのものの能力に大きな差がある。だからハードウェアのスペックそのものを監視の入力パラメータとして扱う必要があり、マシンルーム全体に同じ数値を当てはめるわけにはいかない。

もうひとつのやり方は平均値を見ることだが、ここに踏みやすい落とし穴がある。CPU usage を by core で平均すると、一見とても妥当に思える — だが八コアのマシンで実際には二つの process しか動いておらず、その二つが自分のコアを使い切り、残りの六コアが遊んでいるとしよう。平均を取れば数値は低いままで、ダッシュボードは一面正常に見える。しかしそのサービスにとっては、すでに満杯だ。こういう場合は CPU load のほうが正確だが、load にも誤読の余地があり、結局は process 数とコア数と突き合わせて見る必要がある。ひとつの数値だけを信じるわけにはいかない。

次に、閾値を warning と critical の二段階に切り分けて、アラート自体に緊急度を持たせる。すべてを同じ切迫感で人を叩き起こすのではなく、だ。もうひとつ、とても実務的な話がある。瞬間的な spike は誤報を招きやすいので、flapping detection のある仕組みを選び、連続して超えたときだけ通知が飛ぶようにしたほうがいい — 深夜にヒゲ一本のために何度か起こされれば、この機能の価値は身をもってわかる。

起動二分の時代

クラウド仮想マシンの時代に入って、いちばん直接的に変わったのは起動時間だった。システムが二分で立ち上がるということは、scale out 一式が二、三分で完了するということだ。

これがアラートの性質をまるごと変えた。以前は CPU が跳ね上がった alert は on call の人間を起こすためのもので、起きた人がマシンを立ち上げにいった。いまはこの alert がそのまま scale out のトリガーイベントになる。マシンが自分で生えてきて、人は起きなくていい。運用担当の on call 時間は、こうして大きく削られた。

それに応じて release の重点も移った。当時のリリースでいちばん重要だったのは、検証済みの仮想マシンを image に変換し、auto scaling システムがその image から新しいマシンを直接デプロイできるようにすることだった。そして新しいマシンが立ち上がったあと、それが自動で監視システムに組み込まれること。ここが抜けるとマシンの生成と消滅の速度が監視のカバー速度を上回る。それこそが〈Unknown Known:監視の構築速度がリソース作成に追いつかないとき〉に書いた、あの請求書の出どころだ。

container の登場

とはいえ仮想マシンの維持は、依然として人手のかかる場所だった。マシン自体は自動で生えてくるようになったが、その上で何を動かすか、どのバージョンのパッケージが入っているかは解決されていない — アプリケーションのパッケージ依存は、アップグレード後にシステムがまるごとおかしくなる原因の常連で、しかもたいてい、いちばん対応したくないタイミングでおかしくなる。

そこに container が現れた。アプリケーションとその依存パッケージをすべて同じ image に詰め込み、image ひとつの起動は数秒まで縮む。維持コストとパッケージ依存の問題が一度に大きく減り、auto scaling はここから主流のやり方になった — マシンを一台増やすのに二分かかるのと、インスタンスをひとつ増やすのに二秒で済むのとでは、まったく別の運用モデルだからだ。

監視単位の移り変わり

監視もそれにつれて、仮想マシンではなく container を単位とするようになった。同じ仮想マシンのなかで container を何個も動かせる以上、マシンはもう意味のある最小単位ではない。

container の管理自体も専門のシステムへと発展した。Kubernetes、ECS、Cloud Run といったものが、auto scaling と CPU・memory のリソース管理をひとつの抽象レイヤーにまとめ、仮想マシンレベルの CPU・memory・disk scaling と、container レベルの scaling を分離した。全体のリソースを監視して仮想マシンを増やすかどうかを決め、container レベルには独自の HPA(horizontal pod autoscaling)があって、個々の container、つまり個々のアプリケーションの実負荷に応じて広がっていく。ひとつのクラスタのなかで、二つの層がそれぞれ別に伸縮する。

そして container の管理が抽象化され、API から container のインスタンス一覧を直接取得できるようになったからこそ、このレイヤーの監視が成立するようになった。Prometheus はまさにこの層に生きている。metric server を通じて container のリソース metrics を取得し、統一された telemetry の出力フォーマットを定義することで、収集そのものを標準化した。集めた数値は query と function を組み合わせて演算でき、その結果から warning と critical の閾値を決めて通知につなぐことも、そのまま scale の条件につなぐこともできる。

SLI・SLO・SLA の誕生

さらに先へ進むと、instance レベルの管理すら面倒になってきた。そこでクラウド事業者は、この成熟した技術をもう一枚包んで managed service にした。ECS や EKS の Fargate、Cloud Run といったもので、開発者をできるだけ application layer に寄せ、アプリケーションそのものに集中させる。

これこそが、SRE が SLI・SLO・SLA という指針を定めるようになった背景だ。下のマシンがどれも自分の管轄でなくなったとき、定義できる、そして定義すべきものは、サービス自体の健全性だけになる。まず SLI(service level indicator)を決める。API latency や API response status code といったものだ。次に維持したい目標として SLO(service level objective)を決める。たとえば平均応答時間を 500ms 以内に保つ、というように。そして最後に、そのサービス品質を契約に書き込んで SLA(service level agreement)にする。日・月・年の平均 downtime から、対外的に約束するパーセンテージを換算する — たとえば 99.99%、年間の downtime が 53 分未満、超えれば補償条項がつく。

監視の役割もここで変わった。もはやマシンに異常がないかを見張ってくれるものではなく、対外的に交わしたこの約束が守られていることを、継続的に証明するものになった。

Lambda とユーザーの体感

さらに後期になると、container レベルの維持すら面倒になり、ステートレスなサービスは Lambda function でそのまま動かせるようになった。AWS がトラフィックに応じて自動で増減させ、開発者はマシンという概念にすら触れられなくなる。このとき気にかけるべきものとして残るのは、ユーザーの実際の使用体験だけだ。SLI と SLO は依然として測定の標準的なやり方であり続けるが、その基準はできるだけユーザーの体感に近づけられていく。応答時間を満足度に換算する Apdex のような指標がそれだ。

監視が追っているのは主導権

自分で OS を入れなければならなかった vSphere 上の仮想マシンから、push すれば勝手に増えていく今日の Lambda まで、どの時代も前の時代のいちばん面倒だったことを抽象化してきた。マシンの生成と消滅は auto scaling へ、パッケージ依存は container image へ、リソースの配分は orchestration へ、ノードの維持は managed service へ。

そして監視の重心は、開発者が「まだ触れられるもの」を追いかけて、ずっと上へと移り続けてきた。かつて disk IOPS を見張っていたのは、そのディスクが目の前にあり、しかも自分のほかに見る者がいなかったからだ。いま見なくなったのは、重要でなくなったからではなく、自分の管轄でなくなったからにすぎない。そしてすべてが自分の管轄でなくなった日、監視に残るのはたったひとつ — ユーザーが今日、気持ちよく使えているかどうかだけだ。