DMM.comの、一番深くておもしろいトコロ。

レガシーなアプリケーションの監視を改善するため最初にやったこと

レガシーなアプリケーションの監視を改善するため最初にやったこと

  • このエントリーをはてなブックマークに追加

はじめに

この記事は DMMグループAdvent Calender 2022 の16日目の記事です。

プラットフォーム事業本部マイクロサービスアーキテクトグループ認証認可チームリーダーのAnriです。

この度、弊チームに移管されてきたレガシーなWebアプリケーションの監視について、 アラート対応工数の削減、および対応作業の属人化解消に向け改善を行いました。 本稿では、この取り組みの詳細について解説したいと思います。

半年ほど前、僕のチームへとあるWebアプリケーションの保守運用業務が他チームから移管されてきました。 いくつかのWebAPIサーバーとDBサーバーで構成されるそのアプリケーションは、8年以上も前にリリースされたもので、 モノリシックなアーキテクチャをもち、依存技術も古く、まさにレガシーといった様相を呈していました。 一方で、新規の保守案件はほとんど入ることがなく、またCircleCIやJenkinsにより定型運用作業がある程度自動化されていたため、 保守運用のコストはそこまで高くありませんでした。

しかし、移管直後から問題になったのが、監視でした。 オンコールを引き継いで以降、アラートが発生するたびその内容を確認するのですが、具体的に何をどう対応すればいいのかがわからないのです。 というのも、設定されていた監視項目が、以下のようなものだったのです。

  • ERROR/WARNログ件数の監視
  • サーバーの死活監視
  • CPU/Memory/Disk使用率の監視
  • etc

(レガシーアプリケーションにおいて、ある種典型的な監視パターンとも言えるかと思います)

例えば、あるときERROR/WARNログ件数の監視によるアラートを受けたとき、 確認するとたしかに外部APIへのリクエストがタイムアウトしていたり、DBに投げたクエリが正しく処理されていない旨のログなどが出力されてはいるのです。 しかし、その上でオンコールとしてはどう対処すればいいのか?そもそも対応する必要があるのか?というのが、このアラートだけでは判断できないのです。 障害対応マニュアルのようなものも整備されていなかったので、 仕方なく保守運用経験のあるメンバーにヒアリングしてみたところ「ログの内容や量によって対応方針が異なる」らしく、 結局そのメンバーに指示を仰ぐことで、なんとか事態を収束させることができました。

そこそこ規模が大きく、年季も入っていて、かつモノリシックなアプリケーションにおいて、 エラーログと実際サーバーで起きている症状との関連を見出すのは、なかなか容易なことではありません。 移管後間もなく運用経験のない我々において、このアラート対応をこなせるようになるためには、相当高い学習コストを払う必要があるでしょう。

また、奇妙なことに、エラーログ監視の設定を見ていると、いくつかのエラーログについては件数に関わらず無視するような設定が組み込まれており、 その中には、DB接続エラーやHTTP接続エラーに関するものも含まれていました。 経緯を知るメンバーに確認すると、「WebAPIサーバー起動時にどうしても出てしまうログだから無視している」とのことで、 WebAPIの動作に関わる重要な情報なのではないか?と聞くと、「いままでこれでやってきて、特に問題はなかった」という旨の回答がありました。 ノウハウを持たない我々が、こうした経験則に基づく裏付けのない運用に対し、オーナーシップをもって取り組まなければならないと思うと、相当な困難さを感じました。

こうした状態のまま我々が監視を続けたとして、重大な障害・不具合を取りこぼす可能性はないとは言えないですし、 やっていくにしても今までの担当者による「経験と勘」を我々が身につける必要があります。 これだと、結局経験を積んだ一部のメンバーだけでオンコールを回さざるを得ず、属人化が避けられません。 このままだと到底運用を回していくことはできないと考えた僕は、このWebアプリケーションの監視体制を一から見直すことにしました。

Step 1: SLOの制定

そもそも、エラーログが出ていようが、CPU使用率が100%に達していようが、 Webアプリケーションが稼働を続けていて、クライアントも特に不満を感じておらず、またエンドユーザーの体験も保証できていているのであれば、 運用担当者側ですべきことは特になく、アラートも不要であるはずです。 逆にそれが満たせていない、または満たせなくなる懸念がある場合になってはじめて、何がしかの対応をする必要が出てくるわけです。

そこで僕は、監視を見直す第一歩としてまず「このWebアプリケーションがクライアントに対して果たすべき品質とは何か?」ということを検討しました。 このWebアプリケーションは、DMMにある多くのサービス (動画配信、電子書籍、etc) を構成するクライアントに対し、WebAPI経由で機能を提供しています。 例えば、仮にこのアプリケーションが機能停止してしまえば、最悪それらのサービスが提供不可能な状態に陥る可能性があるため、クライアントとしては高い可用性を期待するでしょうし、 WebAPIのレスポンスタイムが長くなれば、各サービスのパフォーマンスに影響を及ぼす恐れがあるため、レイテンシもなるべく低くあってほしいと思われているのではないでしょうか。 まずはこのWebアプリケーションが目標とする品質水準をSLO (=Service Level Objective, サービスレベル目標) として定量的に定義し、 これを守ることを目的として監視をデザインしていくことで、運用担当者の主観に依らない、明確な運用ポリシーを実現することを、 監視を改善するための大枠の戦略として据えました。 これが、上で述べた監視対応の属人化やオーナーシップの欠如といった課題に対する、僕が考えた解決策です。

一方で、このアプリケーションはリリースされてからすでに8年以上稼働しています。 したがって、過去アプリケーションの運用チームと各クライアントとの間で、 品質に関する何らかの合意や取り決めがあってもおかしくないと思い、移管元のチームなどにヒアリングをしてみました。 しかし、結果的にそうしたものは特にないらしいことがわかりました (これもよくあるパターン…) 。 仕方がないので、僕はこのWebアプリケーションのSLOを一から定義することにしました。

SLO制定へのアプローチ

正攻法で行くのであれば、アプリケーションを利用するクライアント全てに 「お使いのWebAPIについて、どういった品質を期待していますか?」「レイテンシーは何ms以内を期待していますか?」「エラーレートは?」などとヒアリングし、 その中で得られた最も厳しい目標をSLOとして採用する、といった方法が考えられるでしょう。 これなら、どのクライアントにも不満を与えることはなさそうです。

しかし、先述の通りこのアプリケーションはかなり古くから稼働しており、今でも多くのクライアントから利用されていて、社内に多くのステークホルダーを抱えています。 それなりに規模の大きい弊社において、これら全てのクライアントと管轄部署を洗い出して、 しらみつぶしにヒアリングしていくことを考えると、相当な時間と手間がかかることが予想されます。 結果、このアプローチは現実的でないとして却下しました。

代替として、現状のアプリケーションのユースケースや、パフォーマンスに関係する各種メトリクスをまず僕が精査し、SLOのたたき台を作ることにしました。 その上で、このアプリケーションを管轄するプラットフォーム事業本部の責任者 (本部長兼サービスマネージャー、以下SM) にレビューしてもらい、 承認を得る形で、ファーストステップを踏むことにしました。 本部長はこのアプリケーションに長らく関わってきた経験をもち、機能に関する知識や各サービスとの関連について多くのノウハウを持っていたので、 SLOについても適切な判断の上でレビューし、責任をもってもらえると考えたのです。 この選択をしたおかげで、社内の数多くの部署と折衝することなく、スピーディーに事を運ぶことができました。

最終的に制定したSLO

先述の通り、僕はこのアプリケーションの品質として期待されているのは可用性とパフォーマンスだと考えました。 そこで僕は、アプリケーションが提供するWebAPIの「エラーレート」と「レイテンシ」に着目し、 これらをSLI (=Service Level Indicator, サービスレベル指標) として計測していくことにしました。 そして、それらについて次のような目標を定義しました (具体的な数値は伏せます) 。

WebAPIのエラーレートが5分以上 X %を上回り続けることのないようにする WebAPIのレイテンシが5分以上 X msを上回り続けることのないようにする

「5分以上」という条件をつけているのは、サービスの運用上どうしてもこれらの指標が瞬間的に上下することがあり、 一方そうした時のクライアントへの影響はある程度許容されることが運用経験上わかっていたからです。

その上で、上記の目標を月間稼働時間のうち99.95%満たすことをSLOと定義しました。 すなわち、ダウンタイム (目標を満たせていない期間) を月あたり21分35秒以内に納めるのが、 アプリケーション運用における目標となります。

少し苦労した点として、レイテンシ目標の設定がありました。 エラーレートについては、現状の水準で各種ステークホルダーから特にクレームが来ていないのは分かっていたので そこから多少バッファをとって「これぐらいなら守れるだろう」というところを設定すればよかったのですが、 レイテンシに関しては、SMから「X ms以内にしてほしい」という明確な要求をもらっていました。 ところが、当時のWebAPIのレイテンシは既にその基準を超過してしまっていたため、 パフォーマンスを改善するか、SMに要求レベルを引き下げてもらえないか交渉する必要がありました。 この時は、幸いにもパフォーマンス改善の見通しを立てることができ、結果的にSMの要求をクリアできました (パフォーマンス改善の詳細については、拙稿Goエンジニアがk8sクラスタでノイジーネイバー問題に遭遇し、解決するまでの記録で詳しく述べています) 。

SLOに基づくアラート対応フロー

従来の運用体制において、アラートが発生したときの明確な対応基準は存在しませんでした。 その中でアラート対応を行う上では、ある程度判断能力をもつ、経験豊富なメンバーが不可欠でした。 この状況ではそもそもオンコールローテーションに加えられるメンバーは限られますし、そのメンバーに負荷が集中してしまうことになります。

この度新たにSLOを定めたのに併せ、アラート対応に関する方針も変更しました。 アラートが発生したとき、状況確認や原因調査は必ず行うものとし、 大きな障害・不具合につながる可能性があるかどうか、エンドユーザーの体験を著しく損ねていないか (エンドユーザーから問い合わせが来ていないか等) を確認するようにしました。 そして、それらが確認できたら (あるいは強い懸念があれば) 然るべき復旧対応を行うようにしました。 一方でそうした恐れがない場合、月間稼働時間がSLOで保証すべき最低ラインを下回らないかぎりは、 SLIとして定めたレイテンシやエラーレートの根本的な改善は行わなくてよいものとしました。

SLOという基準があることで、リーダーの僕や経験豊富なメンバーが不在であっても、 オンコールがある程度自律的に対応可否を判断できるようになるので、 比較的経験が少ないメンバーもオンコールローテーションに加えられるようになりました。 それでも、要所要所で経験豊富なメンバーに頼らざるを得ない状況を完全になくすところまでは現状至っていないですが、 負荷の集中度合いをある程度軽減することはできました。

Step 2:監視ルールの整理

SLIに関連する監視ルールの追加

新たにSLOを制定したので、まずはその元となるSLIについての監視ルールを定義しました。

  • WebAPIのエラーレートが5分以上 X %を上回り続けたらアラートを発報する
  • WebAPIのレイテンシが5分以上 X msを上回り続けるらアラートを発報する

しかし、仮にこれらのアラートが発生するということは、すでにクライアントやエンドユーザーへ影響が及んでいるということになります。 そうしたクリティカルな事態は、可能な限り事前に検知し、未然に防げるようにしておきたいものです (例えば、冗長化されたWebAPIサーバーのうち半数がダウンしたとしたら、ピーク帯のリクエストを捌ききれず、エラーレートとレイテンシが上昇してしまう恐れがあります) 。 このため、SLIを大きく左右する事態を未然にキャッチするための監視ルールを新たに追加することにしました。

  • WebAPIサーバーの稼働台数が全体のX %を下回ったらアラートを発報
    • Xの値は「ピーク帯のリクエストを捌くための最低台数 (バッファ込み)  / 全台数 * 100」と定義
    • 複数のサーバーダウンが重なり、リクエストが捌ききれなくなるような事態を事前に察知するために設定
  • WebAPIサーバー全体へのスループットが5分以上 N (rps) を上回ったらアラートを発報
    • Nの値は、現行のサーバー構成で処理しきれる最大のスループット (キャパシティ) に相当
    • 想定以上のリクエストがサーバーに殺到し、捌ききれなくなるような事態を検知するために設定
    • WebAPIサーバーがオンプレクラウド上のVMで動いており、迅速なキャパシティ拡大が困難なため
  • WebAPIサーバー全体へのスループットが5分以上 N (rps) を下回ったらアラートを発報
    • Nの値は、平時の最低スループットの半分以下に設定
    • スループットが極端に下がってしまっているような事態を検知するために設定
    • SLI/SLOでの観点とはやや異なるが、スループットが極端に下がるということは何らかの異常事態 (ex. ネットワーク障害) によりサーバーにリクエストが届いていない可能性があるため
  • ほか、DBサーバーに関わる死活監視
    • 本アプリケーションはさまざまなDB製品に依存しており、解説が困難なので割愛
    • SLIに関連しない監視項目の削除

一方で、既にあった監視項目のうち、SLIとの関連が薄い (あるいは無い) 以下のようなものは削除しました。

  • 期間内のERROR / WARNログの数がN件を上回ったらアラートを発報
    • 理由は前述
  • WebAPIサーバーが (1台でも) ダウンしたらアラートを発報
    • WebAPIサーバーは複数台で冗長化されており、1台ダウンしたとしてもサービスの継続稼働に影響しないため
      • ただし、長時間ダウンしているサーバーがあった場合、週次の運用定例等で確認できるようにして、後で復旧対応を実施
  • WebAPIサーバーのCPU/Memory/Disk使用率が閾値を上回ったらアラートを発報
    • 過去の運用経験のなかで、これらのメトリクスとSLIへの関連がみられなかったため.
      • ただし、週次の運用定例でこれらのメトリクスを定点観測し、異常な数値がみられたら対応を都度検討

Step 3: 監視の実装

元より、プラットフォーム事業本部内の各種アプリケーションにおいては、 監視ツールとしてDatadogが採用されており、本アプリケーションにもそれはすでに導入されていました。 だから、上記に挙げたほとんどの監視項目はDatadog (およびDatadog APM) の標準機能を利用して実装できました。

一方、エラーレートの監視だけは、標準機能を利用するだけでは実現できませんでした。 最も大きな障壁となったのが、本アプリケーションのWebAPIは 「いかなるエラーレスポンスも200 OK HTTPステータスで返却する (代わりに、レスポンスに独自定義されたエラーコードを含めることで、エラーの有無を表現する) 」 という奇妙な仕様です。 このせいで、レスポンスのHTTPステータスを元にしたエラーレートの算出が不可能な状況に陥っていました。

これを解決するため、Datadogのログベースメトリクス機能を利用することにしました。 これは、Datadogに送信したログに含まれる数値や文字列をカスタムメトリクスとして収集し、監視などで扱えるようにするものです。 本アプリケーションのWebAPIサーバーは、レスポンスに含まれるエラーコードをアクセスログに出力していたため、 ここからエラーコードを抽出して、メトリクスとして収集し、リクエストの総数でこれを割ることで、エラーレートを定義しました。

Step 4: 監視の運用と継続的な改善

監視を見直したことで、アラート対応にかかるコストを削減できた…かというとそうでもなく、 監視ルールの追加や修正により、むしろ見直し直後はそれまでよりも頻繁にアラートが発生してしまい、 対応工数は膨れ上がってしまっていました。

というのも、見直し当初は検知漏れを防ぐために全体的に監視ルールのアラート条件をシビアに設定していて、 アプリケーションの動作が少し安定しなくなっただけでもアラートが発報されるようになっていたのです。 これはある程度予見していたことで、運用の中で継続的に監視ルールを見直し、アラート条件を緩和していくことをあらかじめ想定に入れていました。 そうすることで、本当にクリティカルな事態を取りこぼさず、かつ平時はアラートが一切発生しない、 安全かつ省エネな運用体制を構築できると考えたのです。

現在は、以下のようにアラート対応フローを決めて運用しています (詳細は端折っています) 。

アラート対応フロー

基本的に、「アラート発生したけど特に問題なさそうなので様子見だけしておきます」というのはNGとしています。 アラート発生して何の問題もないのであれば、そもそも監視ルールが不当に厳しい可能性があるので、 閾値を緩める余地があれば緩める等、監視ルールそのものの調整をしたり、 監視項目がそもそもSLIに影響しなさそうで、かつ障害・不具合の事前検知にも使えなさそうであれば、ルール自体の削除をしたりして、 なるべく無駄なアラート対応をしなくて済むよう検討するようにしています。

また、障害時の定形運用作業を自動化する取り組みも並行して行い、対応不要となったアラートに関してはその発生を抑制する方向で調整しました。 例として、このアプリケーションのWebAPIサーバーがVMで稼働しているため、 ハイパーバイザー障害によりアプリケーションがVMごと強制シャットダウンしたりすることもままあるのですが、 そうしたとき、かつては手動でやっていた復旧対応 (サーバーの再起動等) を自動で行えるよう設定を組み込むなどして、自動復旧ができるようにしました。 こうした取り組みにより、WebAPIサーバー (単体) の死活監視については (よほど長時間ヘルスチェックが通らない場合を除いて) アラートを発報させないようにしました。

こうした取り組みの結果、下図で示されるように、最近のアラート発生件数 (Incidents) を移管当初に比べ減少させることができました。

移管後からxx gの月次アラート発生件数

まとめ

現在、このアプリケーションの監視については、新卒含めたチームメンバー9人全員でオンコールをローテーションしており、 以前よりも非属人的な運用が実現できつつあります。 経験の浅いメンバーがアラートを受けたとき、どう対応すればいいのかを判断してもらうのはなかなか難しかったりするのですが、 「そもそも対応すべきかどうか」で迷うことはなくなったので、必要があれば他のメンバーにエスカレーションするなりしつつ、迅速に対応を進められるようになりました。

実は、監視の見直しあたっては、(本稿では書ききれないですが)本当に様々な問題に直面していて、スムーズにいかないことも多々ありました。 SLI / SLOなどという理屈はありつつも、結局この手の話に銀の弾丸はなく、泥臭く手を動かしながら頑張っていくしかないと、改めて痛感させられました。

最後に

マイクロサービスアーキテクトグループ認証認可チームでは、マイクロサービスにおける認証認可基盤の開発を通し、DMM全体での開発生産性を向上させるチャレンジを行っています。

弊チームでは、この取り組みに一緒に挑戦して頂けるエンジニアを募集しています。カジュアル面談もやっておりますので、ぜひご検討ください。

dmm-corp.com

引き続きDMMアドベントカレンダー2022もお楽しみください。