2022.10.11

PrometheusによるKubernetes環境の異常検知改善

はじめに

こんにちは。次世代システム研究室のT.Tです。

2022年9月27日に行われた社内の研究発表会の内容をご紹介します。研究発表では、Prometheusを利用して認証サービスで障害発生前に異常を検知する監視システムの構成案についてご紹介しました。その他、認証サービスの紹介とそこで発生した実際の障害の説明、Prometheusのアーキテクチャといった部分を主に取り上げました。詳細は発表資料を公開してありますので、そちらからご覧いただけます。


スライドの概要

  1. モチベーション
  2. 関連技術紹介
    1. Prometheus
    2. kube-prometheus-stack
    3. Grafana
    4. Metrics in Kubernetes
  3. Prometheusのアーキテクチャ
  4. Prometheusによる監視改善
    1. 障害を引き起こした原因
    2. ディスク使用量増加傾向の監視
    3. ディスク使用量増加の異常検知によるアラート
    4. Dos攻撃等の監視
  5. Grafana OnCallによるアラート改善
  6. 信頼性の向上
  7. まとめ

本ブログでは、これからPrometheusの導入を検討する方に向けて、Prometheusのアーキテクチャの基本的な部分をハンズオンと合わせてご説明します。

1.Prometheusの概要

Prometheusは主にシステム監視とそれに連動したアラート機能を提供するものです。特徴としてはサービス固有のメトリクスを時系列データとして収集して保存し、PromQLというクエリ言語でデータを操作してアラートを上げます。メトリクスの例としてはWebサーバーにおけるリクエスト数やリクエストの処理時間といったものが挙げられます。

2.Prometheusのアーキテクチャ

2.1.Prometheusのアーキテクチャ

Prometheusのアーキテクチャは以下の図のようになっており、Prometheus serverとExporterによるメトリクスのスクレイプとメトリクスの保存、メトリクスの取得や加工をするためのクエリ言語のPromQL、データを可視化するためのVisualization、Alertmanagerによるアラートといったもので構成されています。

https://prometheus.io/docs/introduction/overview/#architecture

メトリクスを取得して可視化する部分にあたるスクレイプとVisualizationにフォーカスすると以下の図のようになります。

2.2.スクレイプ

ExporterはmetricsのHTTPエンドポイントを提供し、Prometheus serverはそのエンドポイントにクエリを送信してメトリクスを取得します。このデータを取得する仕組みがスクレイプです。

2.3.データモデル

Prometheus serverは取得したメトリクスを時系列データとして保持します。時系列データはtimestamp、メトリクス名、メトリクス値、ラベルで構成され、各時系列データはメトリクス名とラベルで一意に識別できます。

2.4.PromQL

Prometheus serverが収集したメトリクスはPromQLにより取得することができます。Prometheus serverにはPromQLを実行するためにHTTP API(api/v1/query、api/v1/query_range等)が提供されています。

PromQLにはデータ型と時系列データを取得するセレクター、演算子、関数等があります。
データ型

  • Instant Vector: 時系列上に単一データを持つ型、グラフ化できる
  • Range Vector: 時系列上に複数データを持つ型、グラフ化できない
  • Scalar: 浮動小数型
時系列セレクター
  • Instant Vector Selectors: Instant Vector型を取得する、メトリクスも含む
  • Range Vector Selectors: Instant Vector型からRange Vector型を取得する

2.5.Visualization

PromQLで取得したデータはGrafana等の可視化ツールでグラフ化できます。

3.気温を観測する例

前項でPrometheusのアーキテクチャの概要に触れましたが、この内容だけだと分かり辛いのでここで気温を観測する例を見てみます。次のような例になります。

  • 1日の気温を1時間ごとに計測してPrometheus serverでメトリクスとして取得
  • 0時に気温10℃、12時まで1℃/時ずつ上昇、12時以降1℃/時ずつ下降

気温はExporterのmetricsエンドポイントを介して温度計からスクレイプします。Prometheus serverでスクレイプしたメトリクスにtimestampをを付加して保存します。

保存したメトリクスはPrometheus serverのHTTP APIにPromQLを発行して取得できます。temparatureがメトリクスなのでこれをPromQLとして渡すとメトリクスが取得できます。temparatureは時系列上に単一データを持つInstant Vectorで、時系列のグラフとして可視化できます。

次にRange Vectorの例を見てみます。Range VectorはInstant Vectorに[]リテラルを追記すると取得できます。[]にはRange Vectorを取得する期間を指定します。2時間のRange Vectorを取得する場合は2hを指定します。temparature[2h]でRange Vectorを取得すると、今回の例の場合は以下の図のように各時系列値上に3つのベクトル値が紐付けられる形になります。

上の図のRange VectorのイメージのようにRange Vectorの時点では特に意味がありません。Range Vectorと関数を組み合わせることで意味のある指標に変換できます。以下の図ではavg_over_timeとdeltaの関数と組み合わせることで、それぞれ直近2時間ごとの平均気温と直近2時間の気温変化が取得できています。avg_over_timeはRange Vector内の平均値、deltaはRange Vectorの時系列上の最初と最後の値の差分を返す関数です。

4.ハンズオン

4.1.ハンズオンの概要

気温を観測する例の内容をハンズオンとして見て行きたいと思います。ハンズオンでは1時間ごとの気温変化では確認に時間が掛かるため、1分ごとに気温変化するように変更しています。Prometheus serverと温度計のExporterをDockerコンテナとして起動し、スクレイプとPromQLの実行、グラフ化、APIの実行をハンズオンします。

4.2.Prometheus serverのセットアップ

Prometheus serverをDockerのコンテナとして起動します。以下のprometheus.ymlを用意します。

# my global config
global:
  scrape_interval: 1m # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
      - targets: ["localhost:9090", "localhost:8000"]

/path/toをprometheus.ymlを作成したディレクトリに置き換えてPrometheus serverを起動します。

$ docker run --rm --net=host -p 9090:9090 -v /path/to/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

http://localhost:9090でPrometheus serverが起動していることが確認できます。

4.3.温度計のExporterのセットアップ

温度計のExporterとしてmetricsエンドポイントを持つPythonのHTTPサーバーをDockerのコンテナとして起動します。以下は気温を観測する例の気温変化を1時間から1分に短縮して気温を返すコードです。起動時間に10(℃)を返し、以後1分ごとに変化した気温のメトリクス値を返します。これをmetrics.pyとして保存します。

from http.server import BaseHTTPRequestHandler, HTTPServer
import datetime

address = ('localhost', 8000)
start = datetime.datetime.now()

class MetricsHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'text/plain; charset=utf-8')
        self.end_headers()
        temparature = self.calculate_temparature()
        self.wfile.write('temparature {t}'.format(t=temparature).encode('utf-8'))

    def calculate_temparature(self):
        diff = datetime.datetime.now() - start
        minute = divmod(diff.total_seconds(), 60)[0]
        duration_minute = divmod(minute, 24)[1]
        return 10 + duration_minute if duration_minute <= 12 else 34 - duration_minute

with HTTPServer(address, MetricsHTTPRequestHandler) as server:
    server.serve_forever()

/path/toをmetrics.pyを作成したディレクトリに置き換えてPrometheus serverを起動します。

$ docker run --rm --net=host -v /path/to:/usr/src/ python python3 /usr/src/metrics.py

http://localhost:8000で温度計のExporterが起動していることが確認できます。

4.4.PromQLの実行

http://localhost:9090に表示されているExpressionフォームからPrometheus serverにPromQLを送信できます。temparatureとdelta(temparature[2m])をGraphのタグで実行するとそれぞれ以下の図のような結果が得られます。

Range Vectorを返すtemparature[2m]を同様にGraphのタグで実行するとグラフ化できないデータのためエラーになります。Tableタブで実行すると複数の値が取得できることが確認できます。

4.5.APIの実行

Prometheus serverはHTTP APIを提供していてGUIで実行したtemparatureとtemparature[2m]のクエリはcurlで以下のように実行できます。query_rangeはstartとendで指定した時間の範囲内の値を返し、queryは指定した時刻の値を返します。少し紛らわしいですが、Range Vectorのクエリにquery_rangeは使えません。

$ curl -g 'http://localhost:9090/api/v1/query_range?query=temparature&start=2022-10-06T02:16:00.000Z&end=2022-10-06T02:40:00.000Z&step=1s'
{"status":"success",...,"values":[[1665022560,"10"],[1665022561,"10"],...,[1665024000,"10"]]}]}}

$ curl -g 'http://localhost:9090/api/v1/query?query=temparature[2m]&time=2022-10-06T02:16:00.000Z'
{"status":"success",...,"values":[[1665022451.566,"11"],[1665022511.566,"10"]]}]}}

4.6.概要図との相違点についての補足

気温を観測する例では、Range Vectorのクエリを発行した際にAPIのqueryに発行したように複数のtimestampに対する結果が返る図で説明していましたが、実際にはそのようなAPIはなく図のような結果を得るためにはtimestampごとにクエリを発行する必要があります。
またRange Vectorの説明の所で、各時系列上で3つのベクトル値が対応するという説明をしていましたが、先述のAPIでtemparature[2m]を実行した結果には2つのベクトル値しか対応していません。これはメトリクスのtimestampがms単位で記録されていることによるもので、3つのベクトル値を対応させるためには以下のようにクエリのtimestampにms単位で指定する必要があります。
$ curl -g 'http://localhost:9090/api/v1/query?query=temparature[2m]&time=2022-10-06T02:16:11.566Z'
{"status":"success",...,"values":[[1665022451.566,"11"],[1665022511.566,"10"],[1665022571.566,"11"]]}]}}

ここで取得されている最新のtimestampを日時で確認してみるとAPIで渡した引数に一致することが分かります。

$ docker run --rm python python -c 'import datetime; print(datetime.datetime.fromtimestamp(1665022571.566))'
2022-10-06 02:16:11.566000

5.まとめ

簡単なメトリクスの例でPrometheus serverの基本的な機能に触れてみました。kube-prometheus-stackのようなより複雑な構成でメトリクスを扱う際もハンズオンでご紹介したシンプルな構成で事前に検証すると導入を進め易くなると思います。

次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

皆さんのご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事