[Prometheus] Node ExporterでCPUとメモリとディスクを監視する

## 目次
- [[目次] Prometheusで監視システムを作る with Docker](https://qiita.com/Esfahan/items/7dc780501f418a71ad9d)

## 概要
PrometheusでCPUとメモリを監視するためのPromQLの例。

## 関連記事
Prometheus、Node Exporterの環境構築手順はこちらを参照。
[Prometheus環境を構築手順](https://qiita.com/Esfahan/items/0feaedfd771f49ac7ee4)

PromQLを書くための基礎知識はこちらを参照。
[PrometheusでPromQLを扱う前に知っておいたほうがよい知識](https://qiita.com/Esfahan/items/2b8f8763b648d8bb3eb9)


## CPU使用率
| PromQL | 意味 |
| --- | --- | --- |
| node_cpu_seconds_total | 各コア，各モードのCPU使用時間の累計を記録したメトリック(秒) |

example.com:9100(node-exporterが入っている)の各mode(idleを除く)の直近１分間のCPU使用時間の平均を計算するPromQL。

```prometheus
rate(node_cpu_seconds_total{mode!="idle"}[1m])
```

![Screen Shot 2021-06-08 at 18.59.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/a52860bc-dd8e-3cf1-0f64-5222f58cf1f6.png)

以下の表は各modeとその意味をまとめたもの。上記の例では、結果からidleを取り除いている。
引用元：https://www.codetd.com/ja/article/12249521

| mode| desc |
| --- | --- |
| user | ユーザープロセスがCPUを使用する時間 |
| system | カーネルプロセスがCPUを使用する時間 |
| nice | ユーザープロセススペースで優先度が変更されたプロセスによって使用されるCPU時間 |
| idle | アイドル（誰も使用しない）CPU時間 |
| iowait  | ioを待機しているCPU時間 |
| irq | ハード割り込みのCPU時間(hardware interrupts) |
| softirq | ソフト割り込みのCPU時間(software interrupts) |
| steal | ハイパーバイザーによって使用されるCPU時間 |

topコマンドを実行すると以下のような行があるが、ここに表示されているus, sy, niなどが、これらのmodeに当たる。

```
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
```

### マルチコアCPUの場合
マルチコアノードの場合、以下で各modeの平均値を出す。
`without(cpu)`は、各CPUの平均を出すため、計算時にCPU番号を無視するための式。

```prometheus
avg without(cpu) (rate(node_cpu_seconds_total{instance="example.com:9100",  mode!="idle"}[1m]))
```

`avg`の他に、`avg_over_time`もある。
`avg`、`rate`の組み合わせで算出するのに対し、`avg_over_time`は単純にその時間の範囲の平均値を算出する。
以下のように使う。

```prometheus
avg_over_time(node_cpu_seconds_total{mode!="idle"}[1m])
```

参考：[サンプルで学ぶ！PromQLで自在にグラフを描こう (Prometheus + Grafana)](https://qiita.com/nekonok/items/4390a2db8be34da9d238#avg_over_time)

### CPU使用率が80%を超えているものを出力する例

```prometheus
avg without(cpu) (rate(node_cpu_seconds_total{instance="example.com:9100",  mode!="idle"}[1m])) >= 0.8
```

![Screen Shot 2021-06-08 at 19.02.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/5b2b4286-ff26-c44d-b8c8-ce23578c2687.png)

## ロードアベレージ
| PromQL | 意味 |
| --- | --- |
| node_load1 | 1分間のロードアベレージ |
| node_load5 | 5分間のロードアベレージ |
| node_load15 | 15分間のロードアベレージ |

![Screen Shot 2021-06-08 at 21.12.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/8a650645-dea2-b96a-0ff0-907534ccb1f8.png)

normalizeする場合はCPUのコア数で割る。
FYI: [Which is Better: CPU Load or Normalized CPU Load?](https://developer.qualcomm.com/forum/qdn-forums/increase-app-performance/trepn-profiler/28696)

### ロードアベレージが1を超えたものを出力（normalizeする）

```prometheus
# ロードアベレージ / CPUコア数
node_load1 / count(count(node_cpu_seconds_total{instance="example.com:9100"}) without (mode)) without (cpu) > 1
```

![Screen Shot 2021-06-08 at 21.32.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/23097d15-a445-579f-83f3-1965fa1f7b12.png)

## メモリ使用率
| PromQL | 意味 |
| --- | --- |
| node_memory_MemTotal_bytes | メモリ総容量 |
| node_memory_MemAvailable_bytes | メモリ空き容量 |

```prometheus
# メモリ総容量 - メモリ空き容量 = 現在の使用量
node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes
```
### メモリ使用率が80%以上のものを出力

```prometheus
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes >= 0.8
```

![Screen Shot 2021-06-08 at 21.36.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/70629451-33d7-64d9-ed71-ca869b76ecb8.png)

## ディスク容量
| PromQL | 意味 |
| --- | --- |
| node_filesystem_size_bytes | ディスク総容量(bytes) |
| node_filesystem_avail_bytes | 残りディスク容量(bytes) |

ディスク使用率

```prometheus
1 - (node_filesystem_avail_bytes / node_filesystem_size_bytes)
```

## inode使用率
| PromQL | 意味 |
| --- | --- |
| node_filesystem_files_free | freeのinode数 |
| node_filesystem_files | 総inode数 |

inode使用率

```prometheus
1 - (node_filesystem_files_free / node_filesystem_files)
```

## I/OリクエストによるCPU使用率
| PromQL | 意味 |
| --- | --- |
| node_disk_io_time_seconds_total | IOに費やされた時間の合計秒数 |

Prometheusの`rate(node_disk_io_time_seconds_total[1m])`は、`iostat`コマンドの%utilの値と同義。

%util = デバイスへのI/OリクエストによるCPU使用率
参考：[iostat 見方](http://kohago.blogspot.com/2013/11/iostat.html)

### I/O負荷を与えてみる
I/O負荷を与え、iostatコマンドとPormQL `rate(node_disk_io_time_seconds_total[1m])`の結果を比べてみる。

#### 負荷を与える
stress-ngコマンドを使い30GBの書き込みを行い負荷を与える。
参考：[stress-ngコマンドでCPU、メモリ、ディスクに負荷をかける](https://qiita.com/Esfahan/items/6a25dcc8b680cb76c81e)

```console
$ stress-ng -d 3 --hdd-bytes 30G
```

#### iostatの結果

| option | desc |
| --- | --- |
| -d | デバイス使用率のみを出力する |
| -t | 計測した時刻を出力する |
| -x | ディスク統計情報を出力する |
| 10 | 10秒ごとの結果を出力する |

```bash
$ iostat -dtx 10
Linux 3.10.0-1127.19.1.el7.x86_64 (example.com) 	06/09/2021 	_x86_64_	(8 CPU)

# stress-ngコマンド実行前
06/09/2021 03:35:04 PM
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
vda               0.00     0.14    0.19    1.52    16.90    44.59    72.03     0.00    4.61    0.56    5.11   0.11   0.02
scd0              0.00     0.00    0.00    0.00     0.00     0.00     9.96     0.00    0.72    0.72    0.00   0.72   0.00

# stress-ngコマンド実行後
06/09/2021 03:35:14 PM
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
vda               0.00     0.10    2.10 2051.00    91.60 1031840.25  1005.24    86.22   82.18    0.14   82.26   0.33  67.81
scd0              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00

# stress-ngコマンド実行後、utilが100%近くなっている
06/09/2021 03:35:24 PM
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
vda               0.00     2.40    0.20 2980.60     3.20 1498235.60  1005.26   127.44   84.47    4.50   84.48   0.34  99.97
scd0              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
```

#### PromQLの結果
I/OリクエストによるCPU使用率

```prometheus
rate(node_disk_io_time_seconds_total[1m])
```

stress-ngコマンド実行前
![Screen Shot 2021-06-09 at 15.34.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/bfa8390d-7845-0c04-6e70-fd7947f871c4.png)

stress-ngコマンド実行後
1.0、つまり100%になっている。
![Screen Shot 2021-06-09 at 15.36.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/601ea84f-e46c-b918-603c-8754ab4202f9.png)


`iostat`コマンドの%utiでも、PromQLの結果、どちらも100%近くになっていることを確認できた。

## I/Oレイテンシ
| PromQL | 意味 |
| --- | --- |
| node_disk_read_time_seconds_total | IOのreadに費やされた時間の合計秒数 |
| node_disk_reads_completed_total | 正常に完了したreadの合計数 |
| node_disk_write_time_seconds_total | IOのwriteに費やされた時間の合計秒数 |
| node_disk_writes_completed_total | 正常に完了したwriteの合計数 |

これらは/proc/diskstatsから値を取得している。

node_disk_read_time_seconds_total
![Screen Shot 2021-06-09 at 23.43.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/74e6c0a3-bc83-0aeb-b05b-9b47fdd5f863.png)

node_disk_reads_completed_total
![Screen Shot 2021-06-09 at 23.43.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/244fbdcf-05cf-ff6e-153b-8b760b430fba.png)

node_disk_write_time_seconds_total
![Screen Shot 2021-06-09 at 23.44.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/236b111c-4e03-fcce-934c-fae7a2822991.png)

node_disk_writes_completed_total
![Screen Shot 2021-06-09 at 23.44.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/d5705bb9-0470-f09b-2c59-d5119ef4d67b.png)

```console
$ cat /proc/diskstats
 252       0 vda 4682174 4304 929662806 2502865 27994455 2461794 1996846903 172438082 0 3754119 98035986
 252       1 vda1 152 0 1216 61 0 0 0 0 0 61 61
 252       2 vda2 4681923 4304 929656982 2502765 27816578 2461786 1996846823 172315078 0 2975909 96782251
  11       0 sr0 53 0 528 38 0 0 0 0 0 38 38
```

PromQLの結果と/proc/diskstatsで値が同じことが確認できる。
小数点の位置が異なるものもあるが、millisecondsかsecondsかで計測しているかの違い（だと思われる）。

| PromQL | sr0(Prom) | sr0(diskstats) | vda(Prom) | vda(diskstats) |
| --- | --: | --: | --: | --: |
| node_disk_read_time_seconds_total<br>(/proc/diskstats:7列目) | 0.038 | 38 | 2502.8650000000002 | 2502865 |
| node_disk_reads_completed_total<br>(/proc/diskstats:4列目)| 53 | 53 | 4682174 | 4682174 |
| node_disk_write_time_seconds_total<br>(/proc/diskstats:11列目) | 0 | 0 | 172438.082 | 172438082 |
| node_disk_writes_completed_total<br>(/proc/diskstats:8列目) | 0 | 0 | 27994455 | 27994455 |


### Read
IOのreadに費やされた時間の合計秒数 / 正常に完了したreadの合計数 = Read１回あたりにかかった秒数

```prometheus
rate(node_disk_read_time_seconds_total[1m]) / rate(node_disk_reads_completed_total[1m])
```

### Write
IOのwriteに費やされた時間の合計秒数 / 正常に完了したwriteの合計数 = Write１回あたりにかかった秒数

```prometheus
rate(node_disk_write_time_seconds_total[1m]) / rate(node_disk_writes_completed_total[1m])
```

## 平均I/Oリクエストサイズ

| PromQL | 意味 |
| --- | --- |
| node_disk_io_time_weighted_seconds_total | The weighted number of seconds spent doing I/Os |

"The weighted number of seconds"は直訳すると「加重秒数」だが、意味がよく分からない。加重平均なら分かるが。何かに重み付けをした上での秒数なのか？何に？サイズ？I/O数？

[加重平均とは - 【プログラマーのための統計学】平均値の算出方法は1つじゃない](https://qiita.com/Esfahan/items/fec6587bfc0227132856#%E5%8A%A0%E9%87%8D%E5%B9%B3%E5%9D%87)

これは/proc/diskstatsの最後の列の値を取得している。diskstatsの最後の列の値についての説明は以下。

> the weighted number of milliseconds spent doing I/Os
>
> With the exception of operations in progress, all of the fields are cumulative counters that increase over time. The last field, the weighted number of milliseconds spent doing I/O operations, is special because it includes operations currently in-flight; it is basically the sum of the time spent for every operation, plus those not yet completed:
引用元：https://www.xaprb.com/blog/2010/01/09/how-linux-iostat-computes-its-results/

PromQLと/proc/diskstatsの値を比較してみる。

![Screen Shot 2021-06-10 at 0.32.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/79d9627a-989a-4d74-eacb-06c99787fb9b.png)

```
$ cat /proc/diskstats
 252       0 vda 4682204 4304 929664814 2502877 27998500 2462121 1996915380 172444038 0 3754432 98041060
 252       1 vda1 152 0 1216 61 0 0 0 0 0 61 61
 252       2 vda2 4681953 4304 929658990 2502777 27820589 2462113 1996915300 172321011 0 2976171 96787196
  11       0 sr0 53 0 528 38 0 0 0 0 0 38 38
```

PromQLの結果と/proc/diskstatsで値が同じことが確認できる。
小数点の位置が違うのは、以下の違いがあるため。

- /proc/diskstats => the weighted number of milliseconds spent doing I/Os
- node_disk_io_time_weighted_seconds_total => the weighted number of seconds spent doing I/Os

| PromQL | sr0(Prom) | sr0(diskstats) | vda(Prom) | vda(diskstats) |
| --- | --: | --: | --: | --: |
| node_disk_io_time_weighted_seconds_total<br>(/proc/diskstats:最後の列目) | 0.038 | 38 | 98041.06 | 98041060 |

加重秒数の意味が分かっていないが、I/Oリクエストの平均サイズの算出は以下、らしい。

```prometheus
rate(node_disk_io_time_weighted_seconds_total[1m])
```

負荷をかけてから実行するとこんな感じ。

```console
$ stress-ng -d 3 --hdd-bytes 30G
```

![Screen Shot 2021-06-10 at 1.08.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/fb6744cf-dc52-6746-b249-88b4950c5ae7.png)


## alert.rules記述例

```yaml:alert.rules
groups:
  - name: sample001
    rules:
      - alert: cpu_usage_exceeds_80%
        # CPU使用率が80%を超えているもの
        expr: avg without(cpu) (rate(node_cpu_seconds_total{mode!="idle"}[1m])) >= 0.8
        for: 5m
        labels:
          severity: critical
        annotations:
          firing_text: "[{{ $labels.env }}] {{ $labels.instance }} CPU usage exceeds 80%"
          resolved_text: "[{{ $labels.env }}] {{ $labels.instance }} CPU usage falls"
      - alert: cpu_load_avg_exceeds_1
        # ロードアベレージが1を超えたもの
        expr: node_load1 / count(count(node_cpu_seconds_total) without (mode)) without (cpu) > 1
        for: 5m
        labels:
          severity: critical
        annotations:
          firing_text: "[{{ $labels.env }}] {{ $labels.instance }} CPU load average exceeds 1"
          resolved_text: "[{{ $labels.env }}] {{ $labels.instance }} CPU load average falls"
      - alert: memory_usage_exceeds_80%
        # メモリ使用率が80%以上のもの
        expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes >= 0.8
        for: 5m
        labels:
          severity: critical
        annotations:
          firing_text: "[{{ $labels.env }}] {{ $labels.instance }} Memory usage exceeds 80%"
          resolved_text: "[{{ $labels.env }}] {{ $labels.instance }} Memory usage falls"
      - alert: disk_usage_exceeds_80%
        # ディスク使用率が80%以上のもの
        expr: 1 - (node_filesystem_avail_bytes / node_filesystem_size_bytes) >= 0.8
        for: 5m
        labels:
          severity: critical
        annotations:
          firing_text: "[{{ $labels.env }}] {{ $labels.instance }} Disk usage exceeds 80%"
          resolved_text: "[{{ $labels.env }}] {{ $labels.instance }} Disk usage falls"
      - alert: disk_usage_exceeds_80%
        # inode使用率が80%以上のもの
        expr: 1 - (node_filesystem_files_free / node_filesystem_files) >= 0.8
        for: 5m
        labels:
          severity: critical
        annotations:
          firing_text: "[{{ $labels.env }}] {{ $labels.instance }} Inode usage exceeds 80%"
          resolved_text: "[{{ $labels.env }}] {{ $labels.instance }} Inode usage falls"
      - alert: io_usage_exceeds_80%
        # I/OリクエストによるCPU使用率が80%以上のもの
        expr: rate(node_disk_io_time_seconds_total[1m]) >= 0.8
        for: 5m
        labels:
          severity: critical
        annotations:
          firing_text: "[{{ $labels.env }}] {{ $labels.instance }} I/O usage exceeds 80%"
          resolved_text: "[{{ $labels.env }}] {{ $labels.instance }} I/O usage falls"
```

## その他
ちなみに、Node ExporterではCPUエラーやメモリエラーは取れない。

## 参考
- [モニタリングツールPrometheusを使う](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0562?page=4)
- [Prometheus でNodeのモニタリング](https://christina04.hatenablog.com/entry/prometheus-node-monitoring)
- [サンプルで学ぶ！PromQLで自在にグラフを描こう (Prometheus + Grafana)](https://qiita.com/nekonok/items/4390a2db8be34da9d238)
- [Prometheusクエリ道場](https://qiita.com/t_nakayama0714/items/1231751e72804d52c20a)
- [Understanding Machine CPU usage](https://www.robustperception.io/understanding-machine-cpu-usage)
- [A Deep Dive into Kubernetes Metrics — Part 2](https://medium.com/orangesys/a-deep-dive-into-kubernetes-metrics-part-2-dc830c9ce103)
- [Kubernetesにおけるストレージ関連のメトリクス一覧](https://qiita.com/ysakashita/items/cb471a561501b344534c)