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

# はじめに
[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によるリソースとログのアラート通知を構築するという連載の2回目です。

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

[前回](https://qiita.com/nmatsui/items/6d8319f3216bd8786eb9)は、Azure AKS上にPrometheusとGrafanaを構築し、イイカンジにリソース監視をするところまでを解説しました。今回は、Azure AKS上にElasticsearch + Fluentd + Kibanaを立ち上げて各NodeやPodのログを集約し、Curatorで削除運用をまわし、ログに特定の文字列が存在する場合にはGrafanaからSlackへ通知させる、というところまで解説します。

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

||バージョン|
|:--|:--|
|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の確認
[前回](https://qiita.com/nmatsui/items/6d8319f3216bd8786eb9)作成したAzure AKSの環境をそのまま使います。

```bash
$ az aks show --resource-group k8s --name k8saks
$ az aks get-credentials --resource-group k8s --name k8saks
```
```bash
$ kubectl get nodes
NAME                       STATUS    ROLES     AGE       VERSION
aks-nodepool1-14983502-0   Ready     agent     6h        v1.11.1
aks-nodepool1-14983502-1   Ready     agent     6h        v1.11.1
aks-nodepool1-14983502-2   Ready     agent     6h        v1.11.1
```

## Elasticsearch + Fluentd + Kibanaのインストール
[前回](https://qiita.com/nmatsui/items/6d8319f3216bd8786eb9)、CoreOS謹製のHelm Chartを用いてさくっとPrometheus + Grafanaをインストールしました（Azure AKSに適合させるようにパッチするのは、さくっとはいきませんでしたが・・・）。
しかしElasticsearch + Fluentd + Kibanaについては、まるっとインストールしてくれるイイカンジなHelm Chartが見当たりません（個人的に公開されているHelm Chartはありますが、最新版への追従がいまいちだったり）。

そこで今回は、kubernetes本体のリポジトリに内包される[Elasticsearch Add-On](https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/fluentd-elasticsearch)を改造して使うことにします。見る限りではこのリポジトリはきちんとメンテナンスされており、またfluentdのルールもconfigmapにそのまま格納する形となっているため、扱いやすそうだからです。

### Elasticsearchのインストール
#### ElasticsearchのStatefulSet登録
StatefulSetとしてElasticsearchをインストールします。replicaは最低限の2としています。[kubernetes/cluster/addons/fluentd-elasticsearch/es-statefulset.yaml](https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/fluentd-elasticsearch/es-statefulset.yaml)からの変更点は、次の2点です。

* namespaceを`kube-system`から`monitoring`に変更
    * 前回`monitoring`にインストールしたGrafanaと連携できるようにするため
* データ領域は、podのemptyDirではなくPersistentVolumeを使用

データ領域のサイズは、必要に応じて適切に変更してください。

```diff:es-statefulset.yaml
@@ -3,7 +3,7 @@
 kind: ServiceAccount
 metadata:
   name: elasticsearch-logging
-  namespace: kube-system
+  namespace: monitoring
   labels:
     k8s-app: elasticsearch-logging
     kubernetes.io/cluster-service: "true"
@@ -30,7 +30,7 @@
 kind: ClusterRoleBinding
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
-  namespace: kube-system
+  namespace: monitoring
   name: elasticsearch-logging
   labels:
     k8s-app: elasticsearch-logging
@@ -39,7 +39,7 @@
 subjects:
 - kind: ServiceAccount
   name: elasticsearch-logging
-  namespace: kube-system
+  namespace: monitoring
   apiGroup: ""
 roleRef:
   kind: ClusterRole
@@ -51,7 +51,7 @@
 kind: StatefulSet
 metadata:
   name: elasticsearch-logging
-  namespace: kube-system
+  namespace: monitoring
   labels:
     k8s-app: elasticsearch-logging
     version: v6.2.5
@@ -96,9 +96,6 @@
           valueFrom:
             fieldRef:
               fieldPath: metadata.namespace
-      volumes:
-      - name: elasticsearch-logging
-        emptyDir: {}
       # Elasticsearch requires vm.max_map_count to be at least 262144.
       # If your OS already sets up this number to a higher value, feel free
       # to remove this init container.
@@ -108,3 +105,12 @@
         name: elasticsearch-logging-init
         securityContext:
           privileged: true
+  volumeClaimTemplates:
+  - metadata:
+      name: elasticsearch-logging
+    spec:
+      accessModes: ["ReadWriteOnce"]
+      storageClassName: managed-premium
+      resources:
+        requests:
+          storage: 64Gi
```
```bash
$ kubectl apply -f logging/es-statefulset.yaml
```

数分後、ElasticsearchのStatefulSetとPod、及びPersistentVolumeが構築されていることが確認できます。

```bash
$ kubectl get statefulsets --namespace monitoring -l k8s-app=elasticsearch-logging
NAME                    DESIRED   CURRENT   AGE
elasticsearch-logging   2         2         6m
```
```bash
$ kubectl get pods --namespace monitoring -l k8s-app=elasticsearch-logging
NAME                      READY     STATUS    RESTARTS   AGE
elasticsearch-logging-0   1/1       Running   0          7m
elasticsearch-logging-1   1/1       Running   0          4m
```
```bash
$ kubectl get persistentvolumeclaims --namespace monitoring -l k8s-app=elasticsearch-logging
NAME                                            STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
elasticsearch-logging-elasticsearch-logging-0   Bound     pvc-a1c884f6-a36d-11e8-8990-caec6aa008cf   64Gi       RWO            managed-premium   7m
elasticsearch-logging-elasticsearch-logging-1   Bound     pvc-f4494adf-a36d-11e8-8990-caec6aa008cf   64Gi       RWO            managed-premium   5m
```

#### ElasticsearchのService登録
同様に、namespaceを`kube-system`から`monitoring`に変更してElasticsearchのServiceも構築します。

```diff:es-service.yaml
@@ -2,7 +2,7 @@
 kind: Service
 metadata:
   name: elasticsearch-logging
-  namespace: kube-system
+  namespace: monitoring
   labels:
     k8s-app: elasticsearch-logging
     kubernetes.io/cluster-service: "true"
```
```bash
$ kubectl apply -f logging/es-service.yaml
```
```bash
$ kubectl get services --namespace monitoring -l k8s-app=elasticsearch-logging
NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
elasticsearch-logging   ClusterIP   10.0.150.102   <none>        9200/TCP   1m
```

#### Elasticsearchの設定
StatefulSetを起動した段階で、自動的にクラスタリングまで行われます。ついでにShardの移動も許可しておきます。

```bash
$ kubectl exec -it elasticsearch-logging-0 --namespace monitoring -- curl -H "Content-Type: application/json" -X PUT http://elasticsearch-logging:9200/_cluster/settings -d '{"transient": {"cluster.routing.allocation.enable":"all"}}'
```

この段階で、Elasticsearchは2nodeのクラスタとして利用可能になっていいます。

```bash
$ kubectl exec -it elasticsearch-logging-0 --namespace monitoring -- curl -H "Content-Type: application/json" http://elasticsearch-logging:9200/_cluster/health?pretty=true
{
  "cluster_name" : "kubernetes-logging",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 2,
  "number_of_data_nodes" : 2,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}
```

### Fluentdのインストール
#### Fluentdのconfigmap登録
namespaceを`kube-system`から`monitoring`に変更し、Fluentdのconfigmapを登録します。不要なログなどがあれば、この段階でフィルタしてしまえば良いでしょう。

```diff:fluentd-es-configmap.yaml
@@ -2,7 +2,7 @@
 apiVersion: v1
 metadata:
   name: fluentd-es-config-v0.1.4
-  namespace: kube-system
+  namespace: monitoring
   labels:
     addonmanager.kubernetes.io/mode: Reconcile
 data:
```
```bash
$ kubectl apply -f logging/fluentd-es-configmap.yaml
```
```bash
$ kubectl get configmap --namespace monitoring | grep fluentd
fluentd-es-config-v0.1.4             6         48s
```

#### FluentdのDaemonSet登録
今回は、Podのstdout&stderrをnodeごとに起動しているfluentdが集約し、Elasticsearchへと送るパターンとなります。
![node-logging agent pattern](https://d33wubrfki0l68.cloudfront.net/2585cf9757d316b9030cf36d6a4e6b8ea7eedf5a/1509f/images/docs/user-guide/logging/logging-with-node-agent.png)
> 出展: https://kubernetes.io/docs/concepts/cluster-administration/logging/

FluentdはDaemonSetとして起動しますが、[kubernetes/cluster/addons/fluentd-elasticsearch/fluentd-es-ds.yaml](https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/fluentd-elasticsearch/fluentd-es-ds.yaml)からは次の3点を変更しています。

* namespaceを`kube-system`から`monitoring`に変更
* `priorityClassName`は定義しない
    * 2018/08/19時点では、Azure AKSはpriorityClassの作成ができない模様（次のようなエラーになる）
    * `no matches for kind "PriorityClass" in version "scheduling.k8s.io/v1alpha1"`
* nodeSelectorを削除
    * Azure AKSのnodeには、`fluentd-ds-ready`のようなラベルは付いていないため

```diff:fluentd-es-ds.yaml
@@ -2,7 +2,7 @@
 kind: ServiceAccount
 metadata:
   name: fluentd-es
-  namespace: kube-system
+  namespace: monitoring
   labels:
     k8s-app: fluentd-es
     kubernetes.io/cluster-service: "true"
@@ -38,7 +38,7 @@
 subjects:
 - kind: ServiceAccount
   name: fluentd-es
-  namespace: kube-system
+  namespace: monitoring
   apiGroup: ""
 roleRef:
   kind: ClusterRole
@@ -49,7 +49,7 @@
 kind: DaemonSet
 metadata:
   name: fluentd-es-v2.2.0
-  namespace: kube-system
+  namespace: monitoring
   labels:
     k8s-app: fluentd-es
     version: v2.2.0
@@ -73,7 +73,7 @@
         scheduler.alpha.kubernetes.io/critical-pod: ''
         seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
     spec:
-      priorityClassName: system-node-critical
+      # priorityClassName: system-node-critical
       serviceAccountName: fluentd-es
       containers:
       - name: fluentd-es
@@ -95,8 +95,6 @@
           readOnly: true
         - name: config-volume
           mountPath: /etc/fluent/config.d
-      nodeSelector:
-        beta.kubernetes.io/fluentd-ds-ready: "true"
       terminationGracePeriodSeconds: 30
       volumes:
       - name: varlog
```
```bash
$ kubectl apply -f logging/fluentd-es-ds.yaml
```

各Nodeに一つずつFluentdが起動していることを確認します。

```bash
$ kubectl get daemonsets --namespace monitoring -l k8s-app=fluentd-es
NAME                DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
fluentd-es-v2.2.0   3         3         3         3            3           <none>          40s
```
```bash
$ kubectl get pods --namespace monitoring -l k8s-app=fluentd-es -o wide
NAME                      READY     STATUS    RESTARTS   AGE       IP           NODE
fluentd-es-v2.2.0-5dcnm   1/1       Running   0          1m        10.244.1.9   aks-nodepool1-14983502-2
fluentd-es-v2.2.0-qrlws   1/1       Running   0          1m        10.244.2.9   aks-nodepool1-14983502-1
fluentd-es-v2.2.0-sfw8j   1/1       Running   0          1m        10.244.0.8   aks-nodepool1-14983502-0
```

### Kibanaのインストール
#### KibanaのDeployment登録
Kibana自身は落ちても再起動してくれれば問題ないため、replica数は1で起動します。[kubernetes/cluster/addons/fluentd-elasticsearch/kibana-deployment.yaml](https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/fluentd-elasticsearch/kibana-deployment.yaml)からは次の2点を変更しています。

* namespaceを`kube-system`から`monitoring`に変更
* `SERVER_BASEPATH`環境変数を削除
    * proxyではなくport-forwardで接続するため

```diff:kibana-deployment.yaml
@@ -2,7 +2,7 @@
 kind: Deployment
 metadata:
   name: kibana-logging
-  namespace: kube-system
+  namespace: monitoring
   labels:
     k8s-app: kibana-logging
     kubernetes.io/cluster-service: "true"
@@ -31,8 +31,6 @@
         env:
           - name: ELASTICSEARCH_URL
             value: http://elasticsearch-logging:9200
-          - name: SERVER_BASEPATH
-            value: /api/v1/namespaces/kube-system/services/kibana-logging/proxy
         ports:
         - containerPort: 5601
           name: ui
```
```bash
$ kubectl apply -f logging/kibana-deployment.yaml
```
Kibanaのpodが一つだけ起動していることを確認します。

```bash
$ kubectl get deployments --namespace monitoring -l k8s-app=kibana-logging
NAME             DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
kibana-logging   1         1         1            0           1m
```
```bash
$ kubectl get pods --namespace monitoring -l k8s-app=kibana-logging
NAME                              READY     STATUS    RESTARTS   AGE
kibana-logging-7444956bf8-55dd6   1/1       Running   0          1m
```

#### KibanaのService登録
やはりnamespaceを`kube-system`から`monitoring`に変更して、KibanaのServiceを構築します。

```diff:kibana-service.yaml
@@ -2,7 +2,7 @@
 kind: Service
 metadata:
   name: kibana-logging
-  namespace: kube-system
+  namespace: monitoring
   labels:
     k8s-app: kibana-logging
     kubernetes.io/cluster-service: "true"
```
```bash
$ kubectl apply -f logging/kibana-service.yaml
```
```bash
$ kubectl get services --namespace monitoring -l k8s-app=kibana-logging
NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
kibana-logging   ClusterIP   10.0.39.78   <none>        5601/TCP   12s
```

## CuratorのCronJobのインストール
ここまででElasticsearch + Fluentd + Kibanaのインストールは完了しましたが、PersistentVolumeのサイズも有限ですし、不要になったログはバックアップして削除する運用も必要です。そこで[Curator](https://www.elastic.co/guide/en/elasticsearch/client/curator/current/index.html)の出番がやってきます。

今回はサンプルとして、2日以上前のログは消してしまう運用を登録します。

まずはCuratorの設定をconfigmapとして登録します。

```yaml:logging/curator-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: curator-config
  namespace: monitoring
data:
  action_file.yml: |-
    ---
    actions:
      1:
        action: delete_indices
        description: "Clean up ES by deleting old indices"
        options:
          timeout_override:
          continue_if_exception: False
          disable_action: False
          ignore_empty_list: True
        filters:
        - filtertype: age
          source: name
          direction: older
          timestring: '%Y.%m.%d'
          unit: days
          unit_count: 2
          field:
          stats_result:
          epoch:
          exclude: False
  config.yml: |-
    ---
    client:
      hosts:
        - elasticsearch-logging
      port: 9200
      url_prefix:
      use_ssl: False
      certificate:
      client_cert:
      client_key:
      ssl_no_validate: False
      http_auth:
      timeout: 30
      master_only: False
    logging:
      loglevel: INFO
      logfile:
      logformat: default
      blacklist: ['elasticsearch', 'urllib3']
```
```bash
$ kubectl apply -f logging/curator-configmap.yaml
```
```bash
$ kubectl get configmap --namespace monitoring | grep curator
curator-config                       2         20s
```

次に、この設定をもとにCuratorをJobとして走らせるCronJobを登録します。起動するスケジュールはUTCで指定するため、`0 18 * * *`と指定すれば、毎日日本時間の深夜3時にCuratorのJobが走ることになります。

```yaml:logging/curator-cornjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: elasticsearch-curator
  namespace: monitoring
  labels:
    k8s-app: elasticsearch-curator
spec:
  schedule: "0 18 * * *"
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 3
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 120
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - image: bobrik/curator:5.5.4
            name: curator
            args: ["--config", "/etc/config/config.yml", "/etc/config/action_file.yml"]
            volumeMounts:
            - name: config
              mountPath: /etc/config
          volumes:
          - name: config
            configMap:
              name: curator-config
          restartPolicy: OnFailure
```
```bash
$ kubectl apply -f logging/curator-cronjob.yaml
```
```bash
$ kubectl get cronjobs --namespace monitoring -l k8s-app=elasticsearch-curator
NAME                    SCHEDULE     SUSPEND   ACTIVE    LAST SCHEDULE   AGE
elasticsearch-curator   0 18 * * *   False     0         <none>          45s
```

翌日に確認してみると、CronJobが実行され、日本時間の深夜3時にJobが実行されたことがわかります。

```bash
$ kubectl get cronjobs --namespace monitoring -l k8s-app=elasticsearch-curator
NAME                    SCHEDULE     SUSPEND   ACTIVE    LAST SCHEDULE   AGE
elasticsearch-curator   0 18 * * *   False     0         4h              16h
```
```bash
$ kubectl describe jobs elasticsearch-curator-1534701600 --namespace monitoring
Name:           elasticsearch-curator-1534701600
Namespace:      monitoring
#...（省略)...
Controlled By:  CronJob/elasticsearch-curator
Parallelism:    1
Completions:    1
Start Time:     Mon, 20 Aug 2018 03:00:08 +0900
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
#...（省略)...
```

# Elasticsearch + Fluentd + Kibanaの動作確認
では、Elasticsearchにログが集約されているか、Kibanaで確認してみましょう。
Kibanaの5601ポートをforwardし、ブラウザで http://localhost:5601/ にアクセスしてダッシュボードを表示します。

```bash
$ kubectl port-forward $(kubectl get pod --namespace monitoring -l k8s-app=kibana-logging -o template --template "{{(index .items 0).metadata.name}}") --namespace monitoring 5601:5601
```
![スクリーンショット 2018-08-19 16.00.02.png](https://qiita-image-store.s3.amazonaws.com/0/37648/22d5d5e2-052c-9045-65de-2580e78cc4a8.png)

Management -> Index Patterns から、Index Patternとして `logstash-*` を指定し、Time filter fieldとして `@timestamp` を指定して、Indexを作成します。

![スクリーンショット 2018-08-19 16.03.12.png](https://qiita-image-store.s3.amazonaws.com/0/37648/dc668ab0-bd0c-9abc-14f6-7f14fd7f037f.png)

Kuberntesに関する様々メタデータとともに、各Nodeと各PodのログがIndexされていることがわかります。
![スクリーンショット 2018-08-19 16.07.02.png](https://qiita-image-store.s3.amazonaws.com/0/37648/7c99a663-85c7-358c-1d02-58dd107de1aa.png)

# Esaticsearch + Grafanaによるログアラート通知
それでは最後に、Esaticsearch + Grafanaによるログアラート通知を試してみましょう。

なおElasticsearchをDatasourceにしてのAletingは、最新の[Grafana 5.2から導入された新機能](http://docs.grafana.org/guides/whats-new-in-v5-2/)になります。5.2以前のGrafanaの場合、Elasticsaerchのログを収集して可視化することはできますが、Alertを設定することができませんので注意が必要です。

## 任意にエラーログを出力できるPodを登録
検証用に、`/error/`というパスにアクセスするとエラーログを出力する、というテスト用のREST APIをPodとして登録します。このPodの詳細は[nmatsui/kubernetes-monitoring/test-logging-api](https://github.com/nmatsui/kubernetes-monitoring/tree/master/test-logging-api)を参照してください。

```bash
$ kubectl apply -f test-pod/test-pod.yaml
```
起動していることを確認します。

```bash
$ kubectl get pods -l app=test-pod
NAME                        READY     STATUS    RESTARTS   AGE
test-pod-7d9c9b85f9-mktl5   1/1       Running   0          30s
```

ログを出力してみましょう。このPodの3030ポートにport-forwadし、同時に別の端末でこのPodのログもtailしておきます。

```bash
$ kubectl port-forward $(kubectl get pod -l app=test-pod -o template --template "{{(index .items 0).metadata.name}}") 3030:3030
```
```bash
$ kubectl logs -f $(kubectl get pod -l app=test-pod -o template --template "{{(index .items 0).metadata.name}}")
```

curlで http://loalhost:3030/ をGETしてみましょう。下記のようなDEBUGログが1行出力されたことが確認できます。

```text
[2018-08-19T08:43:26.595] [DEBUG] app - request path: /
```

## Grafanaの設定
### ElasticsearchをDataSourceとして追加
Elsaticsearch Serviceの名前とポートを確認し、GrafanaへElasticsearchをDataSourceとして追加します。

```bash
$ kubectl get services --namespace monitoring -l k8s-app=elasticsearch-logging
NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
elasticsearch-logging   ClusterIP   10.0.150.102   <none>        9200/TCP   3h
```

* name: `elasticsearch`
* Type: `Elasticsearch`
* URL: `http://elasticsearch-logging:9200/`
* Access: `Server(Default)`
* Index name: `logstash-*`
* Time field nama: `@timestamp`
* Version: 5.6+

![スクリーンショット 2018-08-19 17.22.10.png](https://qiita-image-store.s3.amazonaws.com/0/37648/88003487-d3be-d50c-90a7-c3d76f7e63ef.png)

### ElasticsearchのDashboardをImpoort
`monitoring/dashboard_elasticsearch.json` をimportします。
ログの数を可視化したGraphと、生のログを表示するTableを持ったDashboardが追加されます。
![スクリーンショット 2018-08-19 17.51.11.png](https://qiita-image-store.s3.amazonaws.com/0/37648/98b7d226-04e9-428c-2cd2-36d63d2408ea.png)

### 通知チャネルにSlackを追加
Grafanaは、Slackへアラートを通知する機能を最初から備えています。
Alerting -> Notification channelsから、新たなSlack Channelを追加してください。

* Name: `slack`
* Type: `Slack`
* Url: `SlackのWebhook URL`

![スクリーンショット 2018-08-19 18.21.00.png](https://qiita-image-store.s3.amazonaws.com/0/37648/6ad11999-770b-429f-6e0d-88596710569c.png)

"Send Test" ボタンを押し、Slackのチャネルへ "[Alerting] Test notification" というメッセージが到着することを確認してください。


## Grafanaへtest-podの`[ERROR]`を検知するパネルを追加
この`Elasticsearch`Dashboardに、test-podが`[ERROR]`というログを出力したら検知するパネルを追加しましょう。
上部の "Add Panel" アイコンを押してGraphを一つ追加し、生成されたGraphの "Edit" して以下のように修正します。

* General
    * Title: `test-pod error`
* Metrics
    * DataSource: `elasticsearch`
    * A
        * Query: `kubernetes.container_name:"test-pod" AND log:"[ERROR]"`
        * Metric: `Count`
* Axis
    * LeftY
        * Y-Min: `0`

![スクリーンショット 2018-08-19 18.01.37.png](https://qiita-image-store.s3.amazonaws.com/0/37648/05b3d438-a301-6a6b-afd7-f5cea613451e.png)
では、エラーメッセージを出力させてみましょう。

```bash
$ curl -i http://localhost:3030/error/
HTTP/1.1 500 Internal Server Error
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 27
ETag: W/"1b-BKZcJDCXkpfhBOSVMNakKtV/a+s"
Date: Sun, 19 Aug 2018 09:03:32 GMT
Connection: keep-alive

{"result":"Error: error!!"}
```
```bash
$ kubectl logs -f $(kubectl get pod -l app=test-pod -o template --template "{{(index .items 0).metadata.name}}")
[2018-08-19T08:37:01.080] [INFO] app - Server running on port 3030
[2018-08-19T08:43:26.595] [DEBUG] app - request path: /
[2018-08-19T09:03:32.730] [ERROR] app - Error: error!!
```
![スクリーンショット 2018-08-19 18.05.13.png](https://qiita-image-store.s3.amazonaws.com/0/37648/0626dd91-da5e-3680-f623-a255f20c0560.png)

見事、PodのログをElasticsearchが収集し、Grafanaでフィルターして可視化することができましたね！

## test-podの`[ERROR]`を検知したらSlackへ通知するAlertを追加
ではこのパネルへ、Alertルールを追加しましょう。Alert Tabの "Create Alert" ボタンを押し、Alertを一つ追加して以下の情報を入力してください。

* Alert Config
    * Name: `test-pod error alert`
    * Evaluate Every: `10s`
    * Conditions
        * WHEN: `sum()`
        * OF: `query(A, 1m, now)`
        * IS ABOVE: `0`
* Notifications
    * send to: `slack`
    * Message: `test-pod raise a ERROR. Please check this pod!`

![スクリーンショット 2018-08-19 19.08.01.png](https://qiita-image-store.s3.amazonaws.com/0/37648/53dc055a-3844-c7fe-e6ca-fcb18cc79192.png)
直近1分間で、エラー検知の合計が0より大きい（つまり1回でもエラー検知された）ならば、slackへ通知します。

では、実際にエラーを発生させてみましょう。先程と同様、curlで http://localhost:3030/error/ をGETしてエラーを発生させてください。

![スクリーンショット 2018-08-19 19.10.06.png](https://qiita-image-store.s3.amazonaws.com/0/37648/b1516608-af6f-ba2a-4f9f-905d9efcb124.png)
![スクリーンショット 2018-08-19 19.10.16.png](https://qiita-image-store.s3.amazonaws.com/0/37648/d1d1e51e-17d4-4424-96bc-145151a9c97b.png)

エラーを検知したPanelが、Alertを送信したことがわかります。また1分弱経過すると、エラー状態が正常状態に自動的に復帰します。![スクリーンショット 2018-08-19 19.11.26.png](https://qiita-image-store.s3.amazonaws.com/0/37648/dbf7e277-2b16-3c14-d31e-84aa78b208a6.png)

Slackを確認すると、確かにエラーメッセージが通知されていることがわかります。
<img width="451" alt="スクリーンショット 2018-08-19 19.15.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/37648/a0678cb8-9fbc-1000-f752-7119afcdce0e.png">

# 最後に
ということで、Azure AKS上にElasticsearch + Fluentd + Kibana + Curaotrの環境を構築してログ集約を行い、加えてGrafanaとも連携させることで、指定したPodで特定の文字列がログ出力された場合にSlackへ通知させることができました。

単なる文字列チェック以上の複雑な条件でアラートを上げたい場合は[ElastAlert](https://github.com/Yelp/elastalert)を導入したほうが良いかもしれませんが、シンプルなアラート条件であればGrafanaで十分だと思います。

それでは皆様、良いKubernetesライフを！