在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发送通知。
验证环境
- クラウド側
- クライアント側
验证所使用的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
出展: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
从“索引模式”管理中,选择logstash-*作为索引模式,指定@timestamp为时间过滤字段,并创建索引。
使用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+
导入 Elasticsearch 的仪表盘
在通知频道中添加 Slack
Grafana自带了向Slack发送警报通知的功能。
请通过Alerting -> Notification channels添加新的Slack频道。
-
- Name: slack
Type: Slack
Url: SlackのWebhook URL
请点击“发送测试”按钮,确保“[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
$ 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!!
不錯啊!看到了,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!
让我们实际产生一个错误。与之前一样,使用curl发送GET请求到 http://localhost:3030/error/ 并引发错误。
最後 : 最后一个
因此,在Azure AKS上構建Elasticsearch + Fluentd + Kibana + Curaotr的環境,並進行日誌集合,此外還與Grafana進行連接,這樣一來,當特定Pod中輸出的日誌中包含特定字符時,可以將通知發送至Slack。
如果您希望在复杂条件而不仅仅是字符串检查时触发警报,可能最好引入ElastAlert,但如果只是简单的警报条件,我认为Grafana就足够了。
请大家享受美好的Kubernetes生活!