Prometheus+Grafana & Elasticsearch+Fluentd+KibanaをAKS上に構築し、ログアラートをGrafanaで行う手順（1/2）

# はじめに
システムを運用する上で、リソース監視やログ監視、アラート通知はいつだって必要になります。Kubernetesを運用する場合でも同じです。
クラウド上でManaged Kubernetesを構築する場合、クラウドが備える監視サービスとアラートサービスを使うことが多いと思います。しかし例えば既存システムのノウハウの有効活用がしたい場合、あるいはポータビリティを重視する場合など、リソースとログの監視とアラートを自前で準備したくなることもあるでしょう。

そこで今回は、[Microsoft Azire AKS](https://docs.microsoft.com/ja-jp/azure/aks/)のKubernetes上に、[Prometheus](https://prometheus.io/) & [Grafana](https://grafana.com/)によるリソース監視、[Elasticsearch](https://www.elastic.co/jp/products/elasticsearch) & [fluentd](https://www.fluentd.org/) & [Kibana](https://www.elastic.co/products/kibana)によるログ集約、及びGrafanaによるリソースとログのアラート通知を構築しようと思います。

記事が長くなってしまったので、今回はAKS上にPrometheusとGrafanaを構築するところまで解説します。
後半のAElasticsearch + Fluentd + Kibanaを立ち上げて各NodeやPodのログを集約し、ログに特定の文字列が存在する場合にはGrafanaからSlackへ通知させる、というところは[次回](https://qiita.com/nmatsui/items/ef7cf8f5c957f82d2ca1)を御覧ください。

第一回: https://qiita.com/nmatsui/items/6d8319f3216bd8786eb9
第二回: https://qiita.com/nmatsui/items/ef7cf8f5c957f82d2ca1

# 検証した環境
* クラウド側

||バージョン|
|:--|:--|
|Microsoft Azure AKS|1.11.1|

* クライアント側

||バージョン|
|:--|:--|
|kubectl|1.11.2|
|azure-cli|2.0.44|
|helm|2.9.1|

検証で用いたyaml等の詳細は、githubに公開しています。[nmatsui/kubernetes-monitoring](https://github.com/nmatsui/kubernetes-monitoring)を参照してください。

# 環境構築
## Microsoft Azure AKSの準備
azコマンドを用いてリソースグループを作成し、AKSを起動します。この際、Dsv3-seriesのようなPremium Storageを使えるvm sizeを指定します。

```bash
$ az group create --name k8s --location japaneast
$ az aks create --resource-group k8s --name k8saks --node-count 3 --ssh-key-value $HOME/.ssh/azure.pub --node-vm-size Standard_D2s_v3 --kubernetes-version 1.11.1
$ az aks get-credentials --resource-group k8s --name k8saks
```

## Helmの準備
PrometeusとGrafanaは、[CoreOS](https://coreos.com/)が公開したHelm Chartである[coreos/prometheus-operator](https://github.com/coreos/prometheus-operator/tree/master/helm/prometheus-operator)と[coreos/kube-prometheus](https://github.com/coreos/prometheus-operator/tree/master/helm/kube-prometheus)を用いてインストールします。
ただしRBACが有効になっているKubernetesの場合、様々なリソースを内部的に操作するHelmには、かなり強い権限を与えておかないと動作しません。本来は必要最小限の権限を探るべきなのですが、今回はまるっとスーパーユーザー権限（`cluster-admin`）を与えてしまいます。

```yaml:rbac/tiller-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system
```
```bash
$ kubectl apply -f rbac/tiller-rbac.yaml
```
```bash
$ helm init --service-account tiller
$ helm repo update
$ helm repo add coreos https://s3-eu-west-1.amazonaws.com/coreos-charts/stable/
```

tiller podが起動していることを確認しておきましょう。

```bash
$ kubectl get pod --namespace kube-system -l app=helm -l name=tiller
NAME                            READY     STATUS    RESTARTS   AGE
tiller-deploy-759cb9df9-fqcrv   1/1       Running   0          2m
```

## Prometheus & Grafanaのインストール
[coreos/prometheus-operator](https://github.com/coreos/prometheus-operator/tree/master/helm/prometheus-operator)と[coreos/kube-prometheus](https://github.com/coreos/prometheus-operator/tree/master/helm/kube-prometheus)を用いて、PrometheusとGrafanaをインストールします。

ただし2018/08/19時点のデフォルト設定をそのまま使うと、次のような問題が発生します。そのため設定を上書きしてインストールします。なおストレージの容量等は、必要に応じて書き換えてください。

* PrometheusやAlertManagerでpersistent volumeが利用されない
* ElasticsearchのAlertingに対応したGrafana 5.2系ではなく、5.0系がインストールされる

```yaml:monitoring/kube-prometheus-azure.yaml
global:
  rbacEnable: true

alertmanager:
  image:
    repository: quay.io/prometheus/alertmanager
    tag: v0.15.1
  storageSpec:
    volumeClaimTemplate:
      metadata:
        name: pg-alertmanager-storage-claim
      spec:
        storageClassName: managed-premium
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 30Gi

prometheus:
  image:
    repository: quay.io/prometheus/prometheus
    tag: v2.3.2
  storageSpec:
    volumeClaimTemplate:
      metadata:
        name: pg-prometheus-storage-claim
      spec:
        storageClassName: managed-premium
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 30Gi

grafana:
  image:
    repository: grafana/grafana
    tag: 5.2.2
  auth:
    anonymous:
      enabled: "false"
```

> デフォルト設定がどうなっているのかは、関連するhelmのvalues.yamlを確認してください。
> [grafanaのvalues.yaml](https://github.com/coreos/prometheus-operator/blob/master/helm/grafana/values.yaml)
> [prometheusのvalues.yaml](https://github.com/coreos/prometheus-operator/blob/master/helm/prometheus/values.yaml)
> [alertmanagerのvalues.yaml](https://github.com/coreos/prometheus-operator/blob/master/helm/alertmanager/values.yaml)
> [kube-prometheusのvalues.yaml](https://github.com/coreos/prometheus-operator/blob/master/helm/kube-prometheus/values.yaml)

### prometheus-operatorのインストール
namespaceを`monitoring`と指定して、`coreos/prometheus-operator`をインストールします。

```bash
$ helm install coreos/prometheus-operator --name pg-op --namespace monitoring
```
ネットワークの状況によっては、watchが切断されて`Error: watch closed before Until timeout`のようなエラーが出る場合がありますが、Kubernetesクラスタ側で構築は進んでいます。
構築が成功していれば、prometheus-operatorのjobが一つ完了し、podが一つ起動しているはずです。

```bash
$ kubectl get jobs --namespace monitoring -l app=prometheus-operator -l release=pg-op
NAME                                      DESIRED   SUCCESSFUL   AGE
pg-op-prometheus-operator-create-sm-job   1         1            5m
```
```bash
$ kubectl get pods --namespace monitoring -l app=prometheus-operator -l release=pg-op
NAME                                            READY     STATUS      RESTARTS   AGE
pg-op-prometheus-operator-688494b68f-lrcst      1/1       Running     0          5m
pg-op-prometheus-operator-create-sm-job-fnsgq   0/1       Completed   0          5m
```

### PrometheusとGrafanaのインストール
namespaceを`monitoring`と指定し、`coreos/kube-prometheus`を用いてPrometheusとGrafanaをインストールします。

```bash
$ helm install coreos/kube-prometheus --name pg --namespace monitoring -f monitoring/kube-prometheus-azure.yaml
```
構築が成功していれば、AlertManagerとPrometheus、及びGrafanaが立ち上がり、各ノードにnode-exporterが一つずつ起動しているはずです。

* AlertManager

```bash
$ kubectl get persistentvolumeclaims --namespace monitoring -l app=alertmanager
NAME                                   STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
alertmanager-pg-db-alertmanager-pg-0   Bound     pvc-3c2ef8c2-a340-11e8-8990-caec6aa008cf   30Gi       RWO            managed-premium   5m
```
```bash
$ kubectl get pods -n monitoring -l app=alertmanager
NAME                READY     STATUS    RESTARTS   AGE
alertmanager-pg-0   2/2       Running   0          5m
```

* Prometheus

```bash
$ kubectl get persistentvolumeclaims --namespace monitoring -l app=prometheus
NAME                                                     STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
prometheus-pg-prometheus-db-prometheus-pg-prometheus-0   Bound     pvc-3c7fb880-a340-11e8-8990-caec6aa008cf   30Gi       RWO            managed-premium   6m
```
```bash
$ kubectl get pods --namespace monitoring -l app=prometheus
NAME                         READY     STATUS    RESTARTS   AGE
prometheus-pg-prometheus-0   3/3       Running   1          6m
```

* Grafana

```bash
$ kubectl get pods --namespace monitoring -l app=pg-grafana
NAME                          READY     STATUS    RESTARTS   AGE
pg-grafana-75cdf6b96d-njxwb   2/2       Running   0          7m
```

* node-exporter

```bash
$ kubectl get daemonsets --namespace monitoring
NAME               DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
pg-exporter-node   3         3         3         3            3           <none>          7m
```
```bash
$ kubectl get pods -o wide -n monitoring -l app=pg-exporter-node
NAME                     READY     STATUS    RESTARTS   AGE       IP           NODE
pg-exporter-node-4wtfd   1/1       Running   0          8m        10.240.0.4   aks-nodepool1-14983502-2
pg-exporter-node-9mjdg   1/1       Running   0          8m        10.240.0.5   aks-nodepool1-14983502-1
pg-exporter-node-l2gnx   1/1       Running   0          8m        10.240.0.6   aks-nodepool1-14983502-0
```

## Azure AKSに合わせてパッチ
GCPだとこれで問題なく動くはずですが、2018/08/19時点では、Azure AKSにあわせていくつかパッチを当てる必要があります。

### `kube-dns-v20`のmetricsをexport
デフォルトでは、Azure AKSのkube-dnsはmetricsをexportしないようです。kube-dnsの状態をprometheusにexportするsidecarを突っ込みます。

```yaml:monitoring/kube-dns-metrics-patch.yaml
spec:
  template:
    spec:
      containers:
      - name: kubedns
        env:
        - name: PROMETHEUS_PORT
          value: "10055"
      - name: sidecar
        image: k8s.gcr.io/k8s-dns-sidecar-amd64:1.14.10
        livenessProbe:
          httpGet:
            path: /metrics
            port: 10054
            scheme: HTTP
          initialDelaySeconds: 60
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 5
        args:
        - --v=2
        - --logtostderr
        - --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.cluster.local
        - --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.cluster.local
        ports:
        - containerPort: 10054
          name: metrics
          protocol: TCP
        resources:
          requests:
            memory: 20Mi
            cpu: 10m
```
```bash
$ kubectl patch deployment -n kube-system kube-dns-v20 --patch "$(cat monitoring/kube-dns-metrics-patch.yaml)"
```
> https://github.com/Azure/AKS/issues/345 を参照

### kubelet exporterが利用するportをhttpsからhttpに変更
デフォルトでは、Azure AKSではhttpsでのexportがうまくいかないようです。kubeletsの状態をprometheusにexportするポートをhttpsからhttpに変更します。

```bash
$ kubectl get servicemonitors pg-exporter-kubelets --namespace monitoring -o yaml | sed 's/https/http/' | kubectl replace -f -
```

> https://github.com/coreos/prometheus-operator/issues/926 を参照

### Kubernetesのcontrol planeのexporterを削除
デフォルトのAzure AKSは、apiserverの状態を外部から取得することができないようです。残念ながら2018/08/19時点では良い回避策が無いようで、あきらめてkubernetesのcontrol planeのexporterを削除してしまいます。

```bash
$ kubectl delete servicemonitor pg-exporter-kubernetes --namespace monitoring
```

> https://github.com/coreos/prometheus-operator/issues/1522

### Kubernetesのcontrol planeに関わるAlertの削除
control planeのexporterを削除してしまったため、Kubernetesからいくつかの値が取れなくなり、`coreos/kube-prometheus`が設定した次のAlertが上がりっぱなしになってしまいます。

* alert: DeadMansSwitch
* alert: K8SApiserverDown
* alert: K8SControllerManagerDown
* alert: K8SSchedulerDown

Prometheusのexporterやalertのルールは、configmap `prometheus-pg-prometheus-rulefiles` に定義されており、次のコマンドで確認することができます。ではこのconfigmapを修正すれば良いのかというと、実はそうではありません。

```bash
$ kubectl get configmap prometheus-pg-prometheus-rulefiles --namespace monitoring -o yaml
```

このconfigmapは`coreos/prometheus-operator`が動的に生成しており、**直接書き換えても強制的にもとのconfigmapに戻されてしまいます。**
実は`coreos/kube-prometheus`が登録したcustom resourceである **`PrometheusRule`** を書き換えるのが、正しい手順となります。

> この事実に気がつくまで、3時間を要しました・・・

`coreos/kube-prometheus`が生成した`PrometheusRule`は10個あり、次のコマンドで確かめることができます。このうち、次の4つを修正することになります。

```bash
$ kubectl get prometheusrules --namespace monitoring
```

|PrometheusRule|削除するalert|
|:--|:--|
|pg-kube-prometheus|DeadMansSwitch|
|pg-exporter-kubernetes|K8SApiserverDown|
|pg-exporter-kube-controller-manager|K8SControllerManagerDown|
|pg-exporter-kube-scheduler|K8SSchedulerDown|

```bash
$ kubectl edit prometheusrule pg-kube-prometheus --namespace monitoring
```
```diff:pg-kube-prometheus
       for: 10m
       labels:
         severity: warning
-    - alert: DeadMansSwitch
-      annotations:
-        description: This is a DeadMansSwitch meant to ensure that the entire Alerting
-          pipeline is functional.
-        summary: Alerting DeadMansSwitch
-      expr: vector(1)
-      labels:
-        severity: none
     - expr: process_open_fds / process_max_fds
       record: fd_utilization
     - alert: FdExhaustionClose
```
```bash
$ kubectl edit prometheusrule pg-exporter-kubernetes --namespace monitoring
```
```diff:pg-exporter-kubernetes
       for: 10m
       labels:
         severity: critical
-    - alert: K8SApiserverDown
-      annotations:
-        description: No API servers are reachable or all have disappeared from service
-          discovery
-        summary: No API servers are reachable
-      expr: absent(up{job="apiserver"} == 1)
-      for: 20m
-      labels:
-        severity: critical
     - alert: K8sCertificateExpirationNotice
       annotations:
         description: Kubernetes API Certificate is expiring soon (less than 7 days)
```

```bash
$ kubectl edit prometheusrule pg-exporter-kube-controller-manager --namespace monitoring
```
```diff:pg-exporter-kube-controller-manager
 spec:
   groups:
   - name: kube-controller-manager.rules
-    rules:
-    - alert: K8SControllerManagerDown
-      annotations:
-        description: There is no running K8S controller manager. Deployments and replication
-          controllers are not making progress.
-        runbook: https://coreos.com/tectonic/docs/latest/troubleshooting/controller-recovery.html#recovering-a-controller-manager
-        summary: Controller manager is down
-      expr: absent(up{job="kube-controller-manager"} == 1)
-      for: 5m
-      labels:
-        severity: critical
+    rules: []
```

```bash
$ kubectl edit prometheusrule pg-exporter-kube-scheduler --namespace monitoring
```
```diff:pg-exporter-kube-scheduler
       labels:
         quantile: "0.5"
       record: cluster:scheduler_binding_latency_seconds:quantile
-    - alert: K8SSchedulerDown
-      annotations:
-        description: There is no running K8S scheduler. New pods are not being assigned
-          to nodes.
-        runbook: https://coreos.com/tectonic/docs/latest/troubleshooting/controller-recovery.html#recovering-a-scheduler
-        summary: Scheduler is down
-      expr: absent(up{job="kube-scheduler"} == 1)
-      for: 5m
-      labels:
-        severity: critical
```

# 動作確認
やっと環境構築が終わりました。では、PrometheusとGrafanaのWebコンソールを開き、その状態を確認してみましょう。
ただし今回は、PrometheusもGrafanaもClusterIPとして構築しています。そのためAKSの外部からWebコンソールを使うことはできません。そこで各々ポートフォワードしてWebコンソールを使います。

## Prometheusの確認
Prometheusの 9090ポートをforwardします。

```bash
$ kubectl port-forward $(kubectl get pod --namespace monitoring -l prometheus=kube-prometheus -l app=prometheus -o template --template "{{(index .items 0).metadata.name}}") --namespace monitoring 9090:9090
```

正しく環境構築されていれば、Webブラウザから http://localhost:9090/ を開くと、PrometheusのWebコンソールが表示されます。
 `insert metric at cluster` のプルダウンリストを見れば、Prometheusがexportしているmetricsを確認できます。Kubernetesのpodやvolumeのmetrics、nodeのmetrics等がexportされているはずです。
![スクリーンショット 2018-08-19 11.48.30.png](https://qiita-image-store.s3.amazonaws.com/0/37648/92e498bc-e0db-7a09-4454-bc7874adf04e.png)

また http://localhost:9090/targets を開くと、Prometheusがmetricsを収集するtargetが表示されます。DownしているTargetが無いことを確認してください。
![スクリーンショット 2018-08-19 11.53.05.png](https://qiita-image-store.s3.amazonaws.com/0/37648/1191c6be-87ba-eb81-d9d3-7c50230f08e0.png)


最後に http://localhost:9090/alerts を開くと、Alertの一覧が表示されます。FireしているAlertが無いことを確認してください。
![スクリーンショット 2018-08-19 11.55.42.png](https://qiita-image-store.s3.amazonaws.com/0/37648/d8fa042f-e19a-db6c-78ec-118280916210.png)


## Grafanaの確認
Prometheusの 3000ポートをforwardします。

```bash
$ kubectl port-forward $(kubectl get pod --namespace monitoring -l app=pg-grafana -o template --template "{{(index .items 0).metadata.name}}") --namespace monitoring 3000:3000
```

正しく環境構築されていれば、Webブラウザから http://localhost:3000/ を開くと、Grafanaのログイン画面が表示されます。初期パスワードはadmin/adminです（初回ログイン時にパスワードの変更を促されます）。
![スクリーンショット 2018-08-19 12.10.56.png](https://qiita-image-store.s3.amazonaws.com/0/37648/42486af8-0d27-4d35-8281-60ce52dd5656.png)


ログインすると、Home画面が表示されます。まずはConfiguration -> DataSourceからPrometheusの設定を行います。
![スクリーンショット 2018-08-19 12.13.12.png](https://qiita-image-store.s3.amazonaws.com/0/37648/4a69efbb-a77f-c986-1154-7717a222a0f2.png)

`coreos/kube-prometeus`が自動的にPrometheusをDataSourceとして登録するのですが、なぜかヘンなURLが設定されている場合があります。Prometheus Serviceの名前を確認し、正しいURL（今回は `http://pg-prometheus:9090/`）に変更してください。

```bash
$ kubectl get services --namespace monitoring -l app=prometheus -l prometheus=pg
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
pg-prometheus   ClusterIP   10.0.105.143   <none>        9090/TCP   3h
```
![スクリーンショット 2018-08-19 12.28.18.png](https://qiita-image-store.s3.amazonaws.com/0/37648/49095e25-ad0c-7f97-3071-f2c99b997d43.png)

ここまで設定を進めれば、GrafanaがPrometeusからmetricsを取得し各Dashboardが機能します。デフォルトで登録されているDashbordを確認してみてください。
（ただしKuberntesのControl Planeのexporterを削除してしまっているため、Control Planeに関するPanelはN/Aになっています）
![スクリーンショット 2018-08-19 12.30.23.png](https://qiita-image-store.s3.amazonaws.com/0/37648/44fd4967-8464-5be8-70f3-eb1ce431453d.png)
![スクリーンショット 2018-08-19 12.30.42.png](https://qiita-image-store.s3.amazonaws.com/0/37648/164fa245-2f6b-7480-762c-899352b6c49e.png)
![スクリーンショット 2018-08-19 12.31.21.png](https://qiita-image-store.s3.amazonaws.com/0/37648/b90c21e8-254f-96d1-f140-84aad5703229.png)

なおデフォルトで登録されるDashboardには、Persistent Volumeの使用量を表示するものがありません。追加しておきましょう。
GrafanaのImport機能を用いて `monitoring/dashboard_persistent_volumes.json` をimportしてください。
![スクリーンショット 2018-08-19 12.39.33.png](https://qiita-image-store.s3.amazonaws.com/0/37648/d3d1813a-6bd9-0727-7a80-6ca373f07f44.png)

Persitent Volumeの使用量を表示するDashboardとPanelが追加されます。
![スクリーンショット 2018-08-19 12.40.46.png](https://qiita-image-store.s3.amazonaws.com/0/37648/32c13d28-d22c-d4da-f07a-ccea5a8c437b.png)

# 次回は
ということで、Azure AKS上にPrometheusとGrafanaを構築し、イイカンジにリソース監視をすることができました。
[次回](https://qiita.com/nmatsui/items/ef7cf8f5c957f82d2ca1)は、今回のAKSにElasticsearch+Fluentd+Kibanaを追加してKuberntes上のログをElasticserchに集約し、加えてElasticsearchとGrafanaを連携させ、特定のログが出力された場合にSlackへ通知を飛ばしたいと思います。