Tabelog Tech Blog

食べログの開発者による技術ブログです

Kubernetes、何をどうやって監視する? ~ 食べログにおけるオンプレKubernetes監視事例紹介 ~

目次

はじめに

食べログ 技術部 SREチームの下國 峰昌と申します。2019年に中途入社し、チーム名の変更はありつつSRE一本でやってきました。

食べログでは、システムごとにSREチームが存在するのではなく、ほぼ全てのシステムを1つのSREチームが横断的に見ています。また、ハードウェア、ネットワーキング、OSのレイヤーは、別部署が全社を横断的に見ており、SREはそれ以上のレイヤであるミドルウェアやオーケストレーションの構成管理、CI/CD、モニタリングなどを見ています。

この記事では、食べログにおけるKubernetesの監視戦略と、その実現例についてご紹介します。

食べログにおけるKubernetes化のモチベーションとその進み具合

食べログのアプリケーションは、オンプレミスのデータセンターへデプロイされており、巨大なMonorepoと、その周辺システムであるいくつかの独立したリポジトリで構成されています。食べログのメインサービスは、ほぼ前者の巨大なMonorepo内に実装されています。このMonorepoの、デプロイ/ロールバックの高速化、Blue/Greenデプロイメントやカナリアリリースによるユーザ影響を最小限に抑えたリリース、大量のサーバに対するインフラ作業のトイル撲滅を目的に、Kubernetes化を進めています。

まずは新規サービスでGoogle Kubernetes Engineを採用し、手応えを掴んだことから、その後の新規サービスの構築にはオンプレKubernetesを採用しています。その後、オンプレで稼働していた独立リポジトリのいくつかをKubernetesへ無事に移行しました。今は、食べログのメインサービスである巨大MonorepoのKubernetes化に取り組んでいます。MonorepoのWebサーバのKubernetes化は完了し、アプリケーションサーバのKubernetes化に着手しています。また、運用系ツールやCI/CDツールも、オンプレKubernetesにて稼働しています。

詳しくは以前登壇したクルーズ株式会社様のイベントTECHHILLSスライドをご参照ください。

Kubernetesというインフラにおける監視戦略

Kubernetesというインフラ基盤は、多種多様なコンポーネントが複雑に連動することによって稼働します。コンセプトレベルでも多様な要素が存在します。その上で稼働するアプリケーションの安定運用のために、いったい何を監視すれば良いのでしょうか。ここでは、食べログがKubernetesの運用を通して学んだ監視戦略と、その実現についてご紹介します。

監視システムは作り込むのではなく買う

Kubernetesのコアコンポーネントkube-apiserverなどがデフォルトでPrometheus形式のメトリクスに対応していることもあり、Kubernetesにおけるメトリクス監視にはPrometheusを利用することが多いと思います。Prometheusはデフォルトでは15日でデータが揮発してしまうため、より長期間のメトリクスデータを閲覧したい場合は、そのデータをどこかへ永続化する必要があります。

食べログでは、このPrometheusデータの永続化に、初めは自前で構築したThanos & Google Cloud Storageを利用していました。ThanosはPrometheusデータのダウンサンプリングや、古いデータの削除をしたり、多数のPrometheusインスタンスへのクエリを一箇所から行えたり便利でした。しかし、Kubernetes上で稼働するアプリケーションが増えるにつれ、Google Cloud Storageの料金がかさんでいったり、Thanos自体の複雑性も相まって運用管理にかかるコストがかさんできたりしました。

これを解決するために、Prometheusデータの永続化先に、NewRelic Promehteus remote writeを採用しました。これはPrometheusのremote writeをNewRelicに対して行うだけで、メトリクスデータのダウンサンプリングや古いデータの削除はNewRelic側にて自動で行ってくれます。

もちろん、監視データがもっと増えれば、自前でそのシステムを構築した方が安上がりかもしれません。しかし、そのステージに至るまでは、監視システムは自前で作り込むより買った方が、より早く安定した監視基盤を整えることができます。

監視の多様なデザインパターンを紹介しているオライリーの入門 監視でも、監視のデザインパターンとして「作るのではなく買う」ことが推奨されています。

あらゆるコンポーネントのゴールデンシグナルを観測する

再びオライリーの入門 監視を引用しますが、監視は「ユーザ視点での監視」から始めるのが大切と言われています。これはその通りで、まずはユーザリクエストのゴールデンシグナル(レイテンシ/トラフィック量/エラー数/サチュレーション)から監視を始めるのが一番です。しかし、その次には一歩進んで、バックエンドAPIのゴールデンシグナルや、Kubernetesコアコンポーネントのゴールデンシグナルも監視することが大切です。

ユーザリクエストのゴールデンシグナルの悪化は、「何か悪いことが起きている」ことを示しますが、実際のところ「どこが悪いのか」に答えてくれることはほぼありません。あらゆるコンポーネントのゴールデンシグナルを観測することが、複雑なインフラストラクチャであるKubernetes上で動くアプリケーションのトラブルシュートに役立ちます。

アプリケーションに分散トレースやAPMを導入することでも、これに近いことは実現できます。しかし、「Kubernetes基盤そのものに何か障害が起きている」際には、これらの監視ツールはあまり役に立たないことが多いです。アプリケーションに実装された分散トレースやAPMは、その外の世界のことを観測できないからです。そして、そのような障害のトラブルシュートは、往々にして難しいため、あらゆるコンポーネントのゴールデンシグナルを観測しておくことが大切になってきます。

なるべく一箇所からあらゆるメトリクス/ログをクエリできるようにする

インフラのレイヤで障害が発生すると、大抵の場合、その上で稼働するアプリケーションにも影響が発生します。そのような場合、アプリケーションの監視データと、インフラの監視データが分かれた場所に存在していると、その根本原因に気付くことが難しくなります。

全てのコンポーネントの監視データを1枚にまとめたダッシュボードを作成するのは、ダッシュボードが巨大になりがちで難しいですが、少なくともそれらの監視データを一箇所からクエリできるようにしておくと、何かあった際のトラブルシュートが早くなります。そのために、監視データの保存場所は、なるべく一箇所にまとめた方が良いです。

メトリクスデータには決められたラベルを付与する

一箇所からメトリクスをクエリするためには、異なるメトリクスデータにも同じラベルがついている必要があります。どのKubernetesクラスタか、どのワークロードか、どんな種類のアプリケーションかなど、必ず付与すべきラベルを整理しておきます。

食べログにおけるKubernetes監視のwhatとhow

ここまでお話しした監視戦略に沿って、食べログでは何をどうやって監視しているかをお話しします。

監視データの置き場

まずは各種監視データをどこに保管しているのかご紹介します。

ログデータ置き場

Google Cloudを利用しています。ログデータは、Google Cloudのログルーター機能を利用し、用途に応じて、以下の3箇所へデータを保存しています。

  • 短期閲覧用: Cloud Logging
    • 見やすくリアルタイムで閲覧できるが、費用が高め
  • 長期保管用: Cloud Storage
    • 保管コストが最も安いが、検索などはできない
  • 分析用途: BigQuery
    • 保管コストもそこそこ安く、クエリの柔軟性が非常に高い

このログデータも、後述するNewRelicへ寄せられるとさらにデータが一箇所にまとまるのでデータへのクエリがしやすくなりますが、今の所Google Cloudのこれらプロダクトの使い勝手が非常に優れているため、ログデータのみGoogle Cloudへ保管しています。

メトリクスデータ置き場

全てNewRelicへ保管しています。先述の通り、NewRelic側でデータのダウンサンプリングや古いデータの削除を自動で行ってくれるため便利です。

監視している内容

以下全て、メトリクスデータには適切なラベルを付与します。そうすることで、複数のメトリクスへのクエリを一つの条件で行うことができます。

ゴールデンシグナルの監視

  • アプリケーション
    • フロントエンドも、バックエンドAPIも含みます
    • いずれもアクセスログをmtailを利用してログからメトリクスへ加工し、それをPrometheusでスクレイピングし、NewRelicへデータを保管しています。
  • ストレージ(データベース、キーバリューストアなど)
    • こちらはそれぞれのコンポーネント専用のPrometheus用exporterをインストールし、メトリクスを取得しています。こちらも同様Prometheusでスクレイピング、NewRelicへ保管しています。
  • Kubernetesコアコンポーネント
    • kube-apiserver: Prometheusで取得できます
    • kubelet: こちらもPrometheusで取得できます
    • コンテナランタイム: 実装によりますが、監視のインターフェースがない場合は、kubelet側で受け取ったレスポンスを監視します
  • Kubernetes上のネットワーク用コンポーネント
    • CNIプラグイン: 実装によりますが、Prometheus用の監視のインターフェースがあることが多いです
    • Ingress Controller

容量監視

物理リソース(CPU/メモリ/ディスク)の残容量を監視します。

Kubernetesノードレベルではもちろん、ワークロード(Deployment/StatefulSetなど)やPodレベルでも過剰に物理リソースを使っていないか監視します。また、コンテナレベルでは必ずクオータを設定し、1コンテナの暴走がクラスタ全体へ影響を与えないようにします。

コンテナの世界では、VM時代よりリソースを絞って運用することが多いため、CPU Throttlingが発生していないか(CPUのクオータが足枷となってアプリケーションのパフォーマンスが落ちていないか)や、メモリ残量が十分か(コンテナレベルでOOMが発生するとpreStopフックを通らないため、アプリケーション終了処理が適切に行われない)も監視します。

また、Kubernetesクラスタレベルの容量監視も必要です。Kubernetesは、以下の構成をサポートしているため、反対にこれらの制限を上回っていないかの監視が必要です。この容量を超えると、クラスタ全体がスローダウンするなど、正常に動作しなくなります。

  • 1ノードにつきPodが110個以上存在しない
  • 5000ノード以上存在しない
  • Podの総数が150000個以上存在しない
  • コンテナの総数が300000個以上存在しない

これらすべて、NewRelic Kubernetes Integrationというコンポーネントを、各KubernetesノードへDaemonSetで展開し、メトリクスデータを取得しています。取得したデータはそのままNewRelicへ送信しています。

ロギング

基本的にはシンプルなアーキテクチャかつログ欠損の生じにくいノードレベルでのロギングが推奨されています。しかし、食べログでは、アプリケーションから出力されるログの種類が多いため、サイドカーパターンでアプリケーションログを収集しています。

このアーキテクチャでは、Podの終了時にログが欠損しないように、ログをすべて出力しきってからPod/コンテナを終了するようpreStopを適切に設定する必要があります。具体的には、サービス用メインコンテナのpidファイルを、他監視用コンテナとemptyDirで共用しておきます。Pod終了時には、このpidファイルが消えたことを確認したのち、ログの送信完了/監視対象から外れるの待ち、それからPodを落とすようコンテナのpreStopのロジックを実装しています。また、ログ送信時に送信先(Google Cloud)へ疎通できないままPodが落ちてログが欠損することのないよう、PodからGoogle Cloudの間には、Fluent-bitを利用してログアグリゲータを構築し、ログのバッファリング機構を設けています。

Kubernetesコアコンポーネントや、Ingress Controllerなどは、ノードレベルでのロギングを採用しています。

また、KubernetesクラスタレベルでのイベントログであるKubernetes Eventsは、nri-kube-eventsを利用してNewRelicへ永続化しています。どこかへ永続化しないと、Kubernetes Eventsは1時間で揮発してしまいます。

監視ツールの監視

各種メトリクスデータが欠損していないか、ログを正常に送信できているか、それぞれのコンポーネントのエラーレートを監視することで、「監視できていたつもりができていなかった」を防いでいます。

食べログにおける監視失敗事例

以上でご紹介した監視戦略に基づいて監視していますが、ここに至るまでには数多くの失敗もありました。監視の不備がどんなトラブルを招いたのか、それによってどのような学びを得たのか、ご紹介します。

事例1: Pod総数爆増によるクラスタ全体のスローダウン

Kubernetesクラスタ内に、CI/CDのためにtektonというコンポーネントを導入していました。tektonはジョブを実行するために、大量のPodを生成します。ある日、tektonのジョブが高頻度で実行されたことにより、Kubernetesクラスタ全体がスローダウンしました。スローダウンとは、Podの生成指示を行ってからPodが実際にスケジュールされ、実行されるまでが遅かったり、kubectlコマンドの応答が遅かったりすることを指します。

原因究明の結果、Kubernetesクラスタ全体のキャパシティの制約である「1ノードにつきPodが110個以上存在しない」という条件を超過し、1ノードに大量のPodが存在していることがわかりました。Podが大量に存在すると、kubeletが自身のノード上に存在するPodの状態をCRIを通してコンテナランタイムへ問い合わせるのに非常に時間がかかり、これがクラスタ全体のスローダウンに繋がっていることがわかりました。

この事象から、「Kubernetesクラスタ自体の容量監視」も必要であること、kubeletやそのバックエンドのコンテナランタイムのゴールデンシグナルも監視すべきであること、ということを学びました。

事例2: 同一DeploymentのPodが同時にevictされたことによるサイト閲覧障害

先述の通り、食べログではサイドカーパターンを利用してアプリケーションログを収集しています。サービス用メインコンテナが出力したログをサイドカーが回収できるように、この2つのコンテナはログディレクトリをemptyDirを利用して共有していました。emptyDirが肥大化してノードのディスクを埋め尽くさないよう、全てのemptyDirには容量制限をかけていました。

ある日、あるアプリケーションの全Podが一斉にダウンしました。Kubernetesのオートヒーリング機構により、その事象は数秒で復帰しました。とはいえこの原因を調査したところ、同一Deploymentへ均等にアクセスされるとログの肥大化速度もほぼ均等になるため、そのDeployment配下のPodのemptyDir容量制限にもほぼ同時に達してしまって全Podが同時にEvictされる、といったものでした。当時はKubernetes Eventsを永続化していなかったため、調査に時間がかかりました。

この事象から、「Kubernetes Eventsも重要な情報源であるため永続化すべきであること」、「同一Deploymentで均等に増加し続けるデータをemptyDirに配置している場合、全Podが同時にEvictされないよう、起動時のpostStartでランダムサイズのファイルをemptyDirへ書き込んでおくこと」が必要だと学びました。

できる限りノードレベルのロギングパターンを採用する方が望ましいですが、アプリケーションの仕様などでサイドカーでのロギングパターンを採用する場合は、このようなケアも必要です。

おわりに

食べログは、オンプレKubernetesへのインフラ刷新を通して、監視をどのように行うべきか悩み、改善を続けてきた結果、今の形になっています。クラウドのマネージドKubernetesでは、これらの監視はクラウドベンダがわりとよしなにやってくれることも多いですが、このような監視の観点は、一定マネージドKubernetesの監視にも応用できる部分があるのではないかと思います。

もちろん、まだ監視が足りない部分は残っています。運用を通して直面した課題を乗り越えることで、監視戦略自体も随時アップデートしていっています。今回の記事を通して、システムの安定稼働のための監視戦略を検討/実装していく面白さが伝われば幸いです。少しでも興味を持っていただけたら、ぜひ以下の採用情報ページを辿って見てください。