Kubernetes のメトリクスを Prometheus を使って監視する

この記事は [Kubernetes Advent Calendar 2016](http://qiita.com/advent-calendar/2016/kubernetes) の 15 日目の記事です. この記事では Kubernetes でのメトリクスを監視する方法について考えてみます.

Kubernetes を利用する際に気にすべきメトリクスは, Kubernetes クラスタの土台となる**システムのメトリクス**, **クラスタ自体のメトリクス**, そしてクラスタ上で動作する**アプリケーションのメトリクス**に分類することができます. この記事ではこれらのメトリクスを [Prometheus](https://prometheus.io/) を利用して監視する方法について考えていきます.

なお, この記事の検証には [coreos-kubernetes](https://github.com/coreos/coreos-kubernetes/) を利用して [Vagrant 上に Kubernetes クラスタを構築](https://github.com/coreos/coreos-kubernetes/tree/master/multi-node/vagrant)しています. また, Kubernetes と Prometheus のバージョンは下記のとおりです.

プロダクト | バージョン
---|---
Kubernetes | v1.4.6
Prometheus | v1.4.0

また, Prometheus 自体は監視対象の Kubernetes クラスタ上に構築するものとします.

## Prometheus の構築

始めに Kubernetes クラスタ上に Prometheus を構築します.

```
$ kubectl create -f https://raw.githubusercontent.com/kkohtaka/kubernetes-metrics/master/prometheus/service.yml
$ kubectl create -f https://raw.githubusercontent.com/kkohtaka/kubernetes-metrics/master/prometheus/deployment.yml
$ kubectl create -f https://raw.githubusercontent.com/kkohtaka/kubernetes-metrics/master/prometheus/configmap.yml
```

検証に利用した Kubernetes リソースは次のとおりです.

### Service

[kkohtaka/kubernetes-metrics/prometheus/service.yml](https://github.com/kkohtaka/kubernetes-metrics/blob/master/prometheus/service.yml)

Kubernetes クラスタ内に構築した Prometheus に外部からアクセスするために NodePort タイプの Service を作成します. これにより `http://${NODE_IP_ADDRESS}:30090` のような URL で Prometheus にアクセスできるようにします.

### Deployment

[kkohtaka/kubernetes-metrics/prometheus/deployment.yml](https://github.com/kkohtaka/kubernetes-metrics/blob/master/prometheus/deployment.yml)

Prometheus の Pod をデプロイするためのリソースです.

### ConfigMap

[kkohtaka/kubernetes-metrics/prometheus/configmap.yml](https://github.com/kkohtaka/kubernetes-metrics/blob/master/prometheus/configmap.yml)

Prometheus の設定ファイルを Pod から読めるように ConfigMap として作成します.

特に注目すべきなのは設定ファイル中のサービスディスカバリに関する設定です. **`<scrape_config>`** に **`<kubernetes_sd_config>`** を記述することで Prometheus が kube-apiserver を通じてクラスタに状態変化 (Pod の追加など) を監視するようになり, Kubernetes の Pod や Service がエクスポートするメトリクスなど収集するようになります.

設定ファイルの記述の仕方は追って説明します.

## システムのメトリクス

続いて Kubernetes クラスタの土台となるシステムのメトリクスを監視する方法について考えてみます. 今回はシステムのメトリクスのうち, ホストマシンのメトリクスの取得には Node exporter を利用し, コンテナのメトリクスの取得には cAdvisor を利用してみようと思います.

### ホストマシンのシステムメトリクス

[Node exporter](https://github.com/prometheus/node_exporter) は CPU やメモリの使用量など, ホストマシンのメトリクスをエクスポートするための Prometheus の公式なツールです. Prometheus からメトリクスを収集する場合には, 各ホストマシン一台につき一つの Node exporter が動作することが必要なため, Kubernetes クラスタ上では DaemonSet としてデプロイすることにします. これにより一つの Node (=ホストマシン) につき一つの Node expoter プロセスを実行することができます.

この検証では次のような DaemonSet を利用しています.

[kkohtaka/kubernetes-metrics/node-exporter/daemonset.yml](https://github.com/kkohtaka/kubernetes-metrics/blob/master/node-exporter/daemonset.yml)

```
$ kubectl create -f https://raw.githubusercontent.com/kkohtaka/kubernetes-metrics/master/node-exporter/daemonset.yml
```

DaemonSet によりデプロイされた Node exporter の Pod で取得したメトリクスを収集するためには, Prometheus 側の設定を行う必要があります. 次の例では Pod からエクスポートされたメトリクスを収集するように設定しています.

```yaml
# /etc/prometheus.yml

# ...省略...

scrape_configs:

# ...省略...

- job_name: 'kubernetes-pods'

  kubernetes_sd_configs:
  - role: pod

  relabel_configs:
  - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
    action: keep
    regex: true
  - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
    action: replace
    target_label: __metrics_path__
    regex: (.+)
  - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
    action: replace
    regex: (.+):(?:\d+);(\d+)
    replacement: ${1}:${2}
    target_label: __address__
  - action: labelmap
    regex: __meta_kubernetes_pod_label_(.+)
  - source_labels: [__meta_kubernetes_namespace]
    action: replace
    target_label: kubernetes_namespace
  - source_labels: [__meta_kubernetes_pod_name]
    action: replace
    target_label: kubernetes_pod_name

  # ...省略...
```

上記のような設定を Prometheus に施した上で, PodTemplateSpec の metadata に次のような annotation を記述すると, Pod がメトリクスをエクスポートするポートとパスを指定することができます.

```yaml
  # ...省略...

  template:
    metadata:

      # ...省略...

      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '9100'
        prometheus.io/path: /metrics
    spec:
      containers:
      - name: node-exporter
        image: prom/node-exporter:v0.13.0

      # ...省略...
```

Prometheus が Node exporter から収集しているメトリクスの一覧は次のようなコマンドで確認できます.

```
$ curl -g "http://${NODE_IP_ADDRESS}:30090/api/v1/series?match[]={app=\"node-exporter\"}" \
  | jq -r '.data[].__name__' \
  | sort \
  | uniq
...
node_boot_time
node_context_switches
node_cpu
node_disk_bytes_read
node_disk_bytes_written
node_disk_io_now
node_disk_io_time_ms
node_disk_io_time_weighted   
node_disk_read_time_ms
node_disk_reads_completed
...
```

### コンテナのシステムメトリクス

コンテナのシステムメトリクスの取得には [cAdvisor](https://github.com/google/cadvisor) が有名なのでこれを利用してみたいと思います. Kubernetes の場合, cAdvisor は Kubelet に組み込まれているため cAdvisor 自体を新たに導入する必要はありません.

また, cAdvisor の取得したメトリクスを Prometheus で収集するためには次のような記述を Prometheus の設定ファイルに追加します.

```yaml
# /etc/prometheus.yml

# ...省略...

scrape_configs:

# ...省略...

- job_name: 'kubernetes-nodes'

  scheme: https

  tls_config:
    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt

  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token

  kubernetes_sd_configs:
  - role: node

  relabel_configs:
  - action: labelmap
    regex: __meta_kubernetes_node_label_(.+)

# ...省略...
```

これにより各 Node に配置されている Kubelet を通じて, cAdvisor により取得されたメトリクスを Prometheus が収集するようになります.

```
$ curl -g "http://${NODE_IP_ADDRESS}:30090/api/v1/series?match[]={container_name=\"heapster\"}" \
  | jq -r '.data[].__name__' \
  | sort \
  | uniq
...
container_cpu_cfs_periods_total
container_cpu_cfs_throttled_periods_total
container_cpu_cfs_throttled_seconds_total
container_cpu_system_seconds_total
container_cpu_usage_seconds_total
container_cpu_user_seconds_total
container_fs_inodes_free
container_fs_inodes_total
container_fs_io_current
container_fs_io_time_seconds_total
...
```

## Kubernetes クラスタのメトリクス

クラスタのメトリクスの取得には [kube-state-metrics](https://github.com/kubernetes/kube-state-metrics) を利用することにします. 

この検証では次のような Deployment を利用しています.

[kkohtaka/kubernetes-metrics/kube-state-metrics/deployment.yml](https://github.com/kkohtaka/kubernetes-metrics/blob/master/kube-state-metrics/deployment.yml)

```
$ kubectl create -f https://raw.githubusercontent.com/kkohtaka/kubernetes-metrics/master/kube-state-metrics/deployment.yml
```

kube-state-metrics の Pod が取得したメトリクスを収集するための Prometheus 側の設定は, Node exporter の利用よるシステムメトリクスの収集時に設定したもので問題ありません. また, kube-state-metrics の PodTemplateSpec にも Node exporter の場合と同様に次のような設定が必要です.

```yaml
  # ...省略...

  template:
    metadata:

      # ...省略...

      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '8080'
    spec:
      containers:
      - name: kube-state-metrics
        image: gcr.io/google_containers/kube-state-metrics:v0.3.0
        ports:
        - containerPort: 8080

      # ...省略...
```

Prometheus が kube-state-metrics から収集しているメトリクスの一覧は次のようなコマンドで確認できます.

```
$ curl -g "http://${NODE_IP_ADDRESS}:30090/api/v1/series?match[]={app=\"kube-state-metrics\"}" \
  | jq -r '.data[].__name__' \
  | sort \
  | uniq
...
kube_deployment_metadata_generation
kube_deployment_spec_paused
kube_deployment_spec_replicas
kube_deployment_status_observed_generation
kube_deployment_status_replicas
kube_deployment_status_replicas_available
kube_deployment_status_replicas_unavailable
kube_deployment_status_replicas_updated
kube_node_info
kube_node_spec_unschedulable
...
```

## アプリケーションのメトリクス

最後にアプリケーションのメトリクスの監視について考えます. この場合は監視対象のアプリケーションが次のいずれかの状態にあるかで対応方法が変わってきます.

- アプリケーションが Prometheus 形式のメトリクスのエクスポートをサポートしている
- メトリクスのエクスポートをサポートしていないが, 改修することができる
- メトリクスのエクスポートをサポートしておらず, 改修することもできない

### メトリクスのエクスポートをサポートしている場合

[Etcd](https://github.com/coreos/etcd) や [SkyDNS](https://github.com/skynetservices/skydns) などはアプリケーションそのものが Prometheus 形式のメトリクスエクスポートをサポートしているため, 上述した Node expoter や kube-state-metrics と同様の方法でメトリクスを収集することができます.

次の Deployment の例では Etcd のメトリクス収集を設定しています.

```yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: etcd
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: etcd
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '2379'
        prometheus.io/metrics: /metrics
    spec:
      containers:
      - name: etcd
        image: quay.io/coreos/etcd:v3.0.15
        args:
        - etcd
        - -listen-client-urls=http://0.0.0.0:2379,http://0.0.0.0:4001
        - -advertise-client-urls=http://0.0.0.0:2379,http://0.0.0.0:4001
        ports:
        - name: etcd
          containerPort: 2379
          protocol: TCP
```

Prometheus が Etcd から収集しているメトリクスの一覧は次のようなコマンドで確認できます.

```
$ curl -g "http://${NODE_IP_ADDRESS}:30090/api/v1/series?match[]={app=\"etcd\"}" \
  | jq -r '.data[].__name__' \
  | sort \
  | uniq
...
etcd_disk_backend_commit_duration_seconds_bucket
etcd_disk_backend_commit_duration_seconds_count
etcd_disk_backend_commit_duration_seconds_sum
etcd_disk_wal_fsync_duration_seconds_bucket
etcd_disk_wal_fsync_duration_seconds_count
etcd_disk_wal_fsync_duration_seconds_sum
etcd_network_client_grpc_received_bytes_total
etcd_network_client_grpc_sent_bytes_total
etcd_server_has_leader
etcd_server_leader_changes_seen_total
...
```

### アプリケーションを改修することができる場合

アプリケーションが Prometheus 形式のメトリクスのエクスポートをサポートしていないものの, 手を入れて改修することが可能である場合には, [Prometheus のクライアントライブラリ](https://prometheus.io/docs/instrumenting/clientlibs/)を利用して, アプリケーションにメトリクスのエクスポート機能を実装することも可能です.

クライアントライブラリは多数のプログラミング言語向けのものが提供されているため, ほとんどの場合で対応が可能であると考えられます.

### アプリケーションを改修することができない場合

アプリケーションに手を入れることができない場合には, アプリケーションとは異なるプログラムを用意してアプリケーションを監視させ, さらにそのプログラムに Prometheus 形式のメトリクスをエクスポートさせるというアプローチが可能な場合があります. Prometheus ではこのようなプログラムを [Exporter](https://prometheus.io/docs/instrumenting/exporters/) と呼んでいます.

すでに多数の Exporter がサードパーティから提供されており, その一覧が[ここ](https://prometheus.io/docs/instrumenting/exporters/#third-party-exporters)にあります.

また, 目的のアプリケーションの Exporter が提供されていない場合でも, 上述した Prometheus のクライアントライブラリを利用することで Exporter を開発することも可能です.

次の Deployment の例では [Exporter](https://hub.docker.com/r/jecnua/apache-exporter/) を利用して Apache HTTP Server のメトリクス収集を設定しています.

```yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: apache
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: apache
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '9117'
        prometheus.io/path: /metrics
    spec:
      containers:
      - name: apache-exporter
        image: jecnua/apache-exporter
      - name: apache
        image: httpd:2.4
        ports:
        - name: http
          containerPort: 80
          protocol: TCP
        volumeMounts:
        - name: config-volume
          mountPath: /usr/local/apache2/conf
      # ...省略...
```

Expoter を通じて Prometheus が Apache HTTP Server から収集するメトリクスは次のようなコマンドで確認できます.

```
$ curl -g "http://${NODE_IP_ADDRESS}:30090/api/v1/series?match[]={app=\"apache\"}" \
  | jq -r '.data[].__name__' \
  | sort \
  | uniq
...
apache_accesses_total
apache_connections
apache_exporter_scrape_failures_total
apache_scoreboard
apache_sent_kilobytes_total
apache_up
apache_uptime_seconds_total
apache_workers
...
```

## まとめ

Prometheus を利用して Kubernetes の様々なレイヤーにおけるメトリクスを収集することができました. 各レイヤーでメトリクスを取得するために利用したツールや手法は次の表のとおりです.

対象レイヤー | メトリクス取得のためのツール･手法
--- | --- | ---
システム (ホストマシン) | [Node expoter](https://github.com/prometheus/node_exporter) + DaemonSet
システム (コンテナ) | [cAdvisor](https://github.com/google/cadvisor) (Kubelet)
Kubernetes クラスタ | [kube-state-metrics](https://github.com/kubernetes/kube-state-metrics)
アプリケーション (Prometheus 対応) | そのまま取得可能
アプリケーション (Prometheus 未対応, 改修可) | [Prometheus クライアントライブラリ](https://prometheus.io/docs/instrumenting/clientlibs/)で実装
アプリケーション (Prometheus 未対応, 改修不可) | [Prometheus Exporter](https://prometheus.io/docs/instrumenting/exporters/) を利用

メトリクスを**監視する**というタイトルで記事を書き始めましたが, メトリクスを**収集する**ところまでで終わってしまいました. 次は今回収集したメトリクスをどのように**可視化する**かについてまとめたいと思います.

## References

- [Configuration | Prometheus](https://prometheus.io/docs/operating/configuration/)
- [Prometheus and Kubernetes up and running | CoreOS Blog](https://coreos.com/blog/prometheus-and-kubernetes-up-and-running.html)
- [Monitoring in the Kubernetes era | Datadog Blog](https://www.datadoghq.com/blog/monitoring-kubernetes-era/)
- [Monitoring Kubernetes performance metrics| Datadog Blog](https://www.datadoghq.com/blog/monitoring-kubernetes-performance-metrics/)
- [How to collect and graph Kubernetes metrics | Datadog Blog](https://www.datadoghq.com/blog/how-to-collect-and-graph-kubernetes-metrics/)
- [prometheus-kubernetes.yml](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml)