在AKS上部署Prometheus+Grafana和Elasticsearch+Fluentd+Kibana,以Grafana进行日志警报的步骤(2/2)

首先

这是关于在Microsoft Azure AKS上搭建基于Prometheus和Grafana的资源监控,以及基于Elasticsearch、fluentd和Kibana的日志聚合,还有基于Grafana的资源和日志报警通知的系列文章的第二部分。

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

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

上一次,我们在Azure AKS上构建了Prometheus和Grafana,并解释了如何进行资源监控。这一次,我们将在Azure AKS上启动Elasticsearch + Fluentd + Kibana,将各个节点和Pod的日志收集起来,并使用Curator进行删除操作,如果日志中存在特定的字符串,则通过Grafana向Slack发送通知。

验证环境

    クラウド側
バージョンMicrosoft Azure AKS1.11.1
    クライアント側
バージョンkubectl1.11.2azure-cli2.0.44helm2.9.1

验证所使用的YAML等详细信息已在GitHub上公开。请参考nmatsui/kubernetes-monitoring。

环境建设

确认Microsoft Azure AKS

我们将继续使用上一次创建的Azure AKS环境。

$ az aks show --resource-group k8s --name k8saks
$ az aks get-credentials --resource-group k8s --name k8saks
$ 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。

在之前,我使用了经过CoreOS精心制作的Helm Chart,快速安装了Prometheus + Grafana(尽管需要进行一些修补以适应Azure AKS,但这一点并不容易)。然而,对于Elasticsearch + Fluentd + Kibana,我找不到一个完整的Helm Chart来一键安装(尽管个人有公开的Helm Chart可用,但更新版本的追踪不太理想)。

所以这次,我们决定修改Kubernetes核心仓库中的Elasticsearch附加组件并使用它。从目前来看,该仓库得到了很好的维护,并且fluentd的规则也直接存储在configmap中,这使得它易于处理。

安装Elasticsearch

注册Elasticsearch的StatefulSet

将Elasticsearch作为StatefulSet进行安装。副本数量设定为最小的2个。来自kubernetes/cluster/addons/fluentd-elasticsearch/es-statefulset.yaml文件的更改有以下两点。

    • namespaceをkube-systemからmonitoringに変更

前回monitoringにインストールしたGrafanaと連携できるようにするため

データ領域は、podのemptyDirではなくPersistentVolumeを使用

请根据需要适当调整数据领域的大小。

@@ -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
$ kubectl apply -f logging/es-statefulset.yaml

数分钟后,可以确认Elasticsearch的StatefulSet和Pod以及PersistentVolume已经成功构建。

$ kubectl get statefulsets --namespace monitoring -l k8s-app=elasticsearch-logging
NAME                    DESIRED   CURRENT   AGE
elasticsearch-logging   2         2         6m
$ 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
$ 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 的服务

同样地,我们将命名空间从kube-system更改为monitoring,并构建Elasticsearch的服务。

@@ -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"
$ kubectl apply -f logging/es-service.yaml
$ 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。

$ 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可以作为一个由两个节点组成的集群可用。

$ 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注册

将命名空间从kube-system更改为monitoring,并注册Fluentd的configmap。如果有不需要的日志等内容,可以在此阶段进行过滤。

@@ -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:
$ kubectl apply -f logging/fluentd-es-configmap.yaml
$ kubectl get configmap --namespace monitoring | grep fluentd
fluentd-es-config-v0.1.4             6         48s

以DaemonSet方式注册Fluentd

node-logging agent pattern

出展:https://kubernetes.io/docs/concepts/cluster-administration/logging/

Fluentd作为DaemonSet启动,但与kubernetes/cluster/addons/fluentd-elasticsearch/fluentd-es-ds.yaml文件相比,做了以下三点修改。

    • 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のようなラベルは付いていないため

@@ -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
$ kubectl apply -f logging/fluentd-es-ds.yaml

我会检查每个节点上是否都已经启动了一个Fluentd。

$ 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
$ 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部署的注册

只要Kibana自身重新启动,即使崩溃也没有问题,因此我们选择使用1个副本来启动。我们在kubernetes/cluster/addons/fluentd-elasticsearch/kibana-deployment.yaml中进行了以下两个更改。

    • namespaceをkube-systemからmonitoringに変更

SERVER_BASEPATH環境変数を削除

proxyではなくport-forwardで接続するため

@@ -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
$ kubectl apply -f logging/kibana-deployment.yaml

我要确认只有一个 Kibana 的 pod 被启动。

$ 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
$ 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的服务注册

我需要将namespace从kube-system更改为monitoring,并构建Kibana的Service。

@@ -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"
$ kubectl apply -f logging/kibana-service.yaml
$ 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登场了。

这次将把删除两天前或更早的日志作为样本注册进行操作。

首先,我们将Curator的设置注册为configmap。

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']
$ kubectl apply -f logging/curator-configmap.yaml
$ kubectl get configmap --namespace monitoring | grep curator
curator-config                       2         20s

接下来,您需要根据此设置将Curator作为Job在CronJob中注册。为了设置UTC启动时间表,您可以指定为0 18 * * *,这样Curator的Job就会每天在日本时间的凌晨3点运行。

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
$ kubectl apply -f logging/curator-cronjob.yaml
$ 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点执行了任务。

$ 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
$ 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端口转发,并在浏览器中访问 http://localhost:5601/ 来显示仪表盘。

$ 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

从“索引模式”管理中,选择logstash-*作为索引模式,指定@timestamp为时间过滤字段,并创建索引。

スクリーンショット 2018-08-19 16.03.12.png
スクリーンショット 2018-08-19 16.07.02.png

使用Elasticsearch + Grafana进行日志报警通知。

让我们最后尝试一下使用Elasticsearch + Grafana进行日志警报的通知。

然而,将Elasticsearch作为数据源进行警报是从最新的Grafana 5.2开始引入的新功能。对于早于5.2版本的Grafana来说,可以收集并可视化Elasticsaerch的日志,但无法设置警报,所以需要注意。

可以注册一个可以随意输出错误日志的Pod。

将一个测试用的REST API注册为Pod,用于当访问/error/路径时输出错误日志进行验证。有关该Pod的详细信息,请参阅nmatsui/kubernetes-monitoring/test-logging-api。

$ kubectl apply -f test-pod/test-pod.yaml

确认正在运行。

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

让我们输出日志。将此Pod的3030端口转发,并同时在另一个终端上跟踪此Pod的日志。

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

让我们使用curl以GET的方式访问http://loalhost:3030/。您将能够确认下方类似DEBUG日志的一行输出。

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

Grafana的配置

将Elasticsearch作为DataSource添加进来。

确认Elasticsearch Service的名称和端口,然后将Elasticsearch作为DataSource添加到Grafana中。

$ 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

导入 Elasticsearch 的仪表盘

スクリーンショット 2018-08-19 17.51.11.png

在通知频道中添加 Slack

Grafana自带了向Slack发送警报通知的功能。
请通过Alerting -> Notification channels添加新的Slack频道。

    • Name: slack

Type: Slack

Url: SlackのWebhook URL

スクリーンショット 2018-08-19 18.21.00.png

请点击“发送测试”按钮,确保“[Alerting]测试通知”消息已经到达Slack频道。

添加一个面板来监测test-pod在Grafana中的[ERROR]。

在Elasticsearch仪表板上,如果test-pod输出[ERROR]日志,则添加一个用于检测的面板。
点击顶部的“添加面板”图标,添加一个图表,然后点击生成的图表的“编辑”并进行以下修改。

    • 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
$ 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!!"}
$ 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

不錯啊!看到了,Pod的日誌被Elasticsearch收集起來,並通過Grafana進行了篩選和可視化!

新增一个Alert,用于在检测到test-pod的[ERROR]时向Slack发送通知。

现在让我们向此面板添加警报规则。请点击“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

让我们实际产生一个错误。与之前一样,使用curl发送GET请求到 http://localhost:3030/error/ 并引发错误。

スクリーンショット 2018-08-19 19.10.16.png
スクリーンショット 2018-08-19 19.11.26.png
スクリーンショット 2018-08-19 19.15.13.png

最後 : 最后一个

因此,在Azure AKS上構建Elasticsearch + Fluentd + Kibana + Curaotr的環境,並進行日誌集合,此外還與Grafana進行連接,這樣一來,當特定Pod中輸出的日誌中包含特定字符時,可以將通知發送至Slack。

如果您希望在复杂条件而不仅仅是字符串检查时触发警报,可能最好引入ElastAlert,但如果只是简单的警报条件,我认为Grafana就足够了。

请大家享受美好的Kubernetes生活!

广告
将在 10 秒后关闭
bannerAds