Kubernetes中的事件收集以及監(jiān)控告警

Kubernetes中的事件監(jiān)控
隨著微服務(wù)以及云原生的發(fā)展,越來(lái)越多的企業(yè)都將業(yè)務(wù)部署運(yùn)行到Kubernetes中,主要是想依托Kubernetes的可擴(kuò)展、可伸縮、自動(dòng)化以及高穩(wěn)定性來(lái)保障業(yè)務(wù)的穩(wěn)定性。
然而,Kubernetes本身是一個(gè)復(fù)雜的管理系統(tǒng),它既然是作為企業(yè)業(yè)務(wù)的基礎(chǔ)設(shè)施,其本身以及運(yùn)行在集群內(nèi)部的業(yè)務(wù)系統(tǒng)對(duì)于企業(yè)來(lái)說(shuō)都變得非常重要。為此,在實(shí)際工作中,我們會(huì)借助需要的監(jiān)控手段來(lái)提升Kubernetes本身以及業(yè)務(wù)的可觀測(cè)性,常見(jiàn)的有:
- 使用cAdvisor來(lái)獲取容器的資源指標(biāo),比如cpu、內(nèi)存;
- 使用kube-state-metrics來(lái)獲取資源對(duì)象的狀態(tài)指標(biāo),比如Deployment、Pod的狀態(tài);
- 使用metrics-server來(lái)獲取集群范圍內(nèi)的資源數(shù)據(jù)指標(biāo);
- 使用node-exporter等一系列官方以及非官方的exporter來(lái)獲取特定組件的指標(biāo);
在大部分的監(jiān)控場(chǎng)景中,我們都是對(duì)特定資源進(jìn)行特定監(jiān)控,比如Pod,Node等。但是,在Kubernetes中還有一些場(chǎng)景是無(wú)法通過(guò)資源來(lái)表述的,就是說(shuō)它們不是特定的資源,比如Pod調(diào)度、重啟,在Kubernetes中,這類場(chǎng)景主要稱之為事件。
在Kubernetes中,存在兩種事件:
- Warning事件,事件的狀態(tài)轉(zhuǎn)換是在非預(yù)期的狀態(tài)之間產(chǎn)生。
- Normal事件,期望達(dá)到的狀態(tài)和目前的狀態(tài)是一致的。
在這里,我們用Pod來(lái)進(jìn)行說(shuō)明。當(dāng)創(chuàng)建Pod的時(shí)候,會(huì)先進(jìn)入Pending狀態(tài),然后再進(jìn)入Creating狀態(tài)(主要是在拉取鏡像),再進(jìn)去NotReady狀態(tài)(主要是應(yīng)用啟動(dòng)并且等待健康檢測(cè)通過(guò)),最后進(jìn)入Running狀態(tài),這整個(gè)過(guò)程就會(huì)生成Normal事件。但是,如果在運(yùn)行過(guò)程中,如果Pod因?yàn)橐恍┊惓T蜻M(jìn)入其他狀態(tài),比如節(jié)點(diǎn)驅(qū)逐、OOM等,在這個(gè)狀態(tài)轉(zhuǎn)換的過(guò)程中,就會(huì)產(chǎn)生Warning事件。在Kubernetes中,我們可以通過(guò)其他辦法來(lái)保障業(yè)務(wù)的穩(wěn)定性,比如為了避免Pod調(diào)度到一個(gè)節(jié)點(diǎn)或者同可用區(qū)等而采用親和性以及反親和性調(diào)度,為了避免節(jié)點(diǎn)驅(qū)逐導(dǎo)致某個(gè)單個(gè)Pod不可用而采用的PDB等,也許某個(gè)Warning事件并不會(huì)對(duì)整個(gè)業(yè)務(wù)的穩(wěn)定性帶來(lái)致命的影響,但是如果能夠通過(guò)監(jiān)控事件的手段來(lái)感知集群的某個(gè)狀態(tài)變化是有助于進(jìn)行查漏補(bǔ)缺的,也有助于我們感知一些容易忽略的問(wèn)題。
在Kubernetes中,所有事件都通過(guò)事件系統(tǒng)記錄到APIServer中,并且最終存入在Etcd中,我們可以通過(guò)API或者kubectl進(jìn)行查看,比如:

也可以查看某個(gè)對(duì)象的事件,比如:

事件包含了時(shí)間、類型、對(duì)象、原因以及描述等,通過(guò)事件我們能夠知道應(yīng)用的部署、調(diào)度、運(yùn)行、停止等整個(gè)生命周期,也能通過(guò)事件去了解系統(tǒng)中正在發(fā)生的一些異常。在Kubernetes各個(gè)組件的源碼中都會(huì)定義該組件可能會(huì)觸發(fā)的事件類型,比如在kubelet的源碼中定義了許多的事件類型,如下:
package events
// Container event reason list
const (
CreatedContainer = "Created"
StartedContainer = "Started"
FailedToCreateContainer = "Failed"
FailedToStartContainer = "Failed"
KillingContainer = "Killing"
PreemptContainer = "Preempting"
BackOffStartContainer = "BackOff"
ExceededGracePeriod = "ExceededGracePeriod"
)
// Pod event reason list
const (
FailedToKillPod = "FailedKillPod"
FailedToCreatePodContainer = "FailedCreatePodContainer"
FailedToMakePodDataDirectories = "Failed"
NetworkNotReady = "NetworkNotReady"
)
......Kubernetes事件最終是存在Etcd中,默認(rèn)只保存1小時(shí),由于Etcd本身并不支持一些復(fù)雜的分析操作,只能被動(dòng)地存在Etcd中,并不支持主動(dòng)推送到其他系統(tǒng),通常情況下只能手動(dòng)去查看。
在實(shí)際中,我們對(duì)Kubernetes事件還有其他的需求,比如:
- 希望對(duì)異常的事件做告警處理;
- 希望查詢更長(zhǎng)事件的歷史事件;
- 希望對(duì)集群事件進(jìn)行靈活的統(tǒng)計(jì)分析;
為此,我們需要單獨(dú)對(duì)Kubernetes事件進(jìn)行收集,以便適用于查詢以及告警。
在社區(qū)中,有很多工具來(lái)做事件的收集以及告警,我常用的兩個(gè)工具是:
- kube-eventer:阿里云推出的事件收集工具;
- kube-event-exporter:Github上另外一個(gè)事件收集工作;
在實(shí)際工作中,可以選擇使用其中一個(gè),基本都能滿足收集以及告警功能。在這里,我將同時(shí)使用上面兩個(gè)插件,用kube-eventer來(lái)進(jìn)行告警,用kube-event-exporter將事件收集到ES中進(jìn)行查看和分析。
使用kube-eventer進(jìn)行事件告警
kube-eventer的告警通道可以是企業(yè)微信、釘釘以及webhook??梢愿鶕?jù)需要進(jìn)行選擇,每個(gè)組件的具體使用方法在項(xiàng)目的docs/en目錄中,這里選擇使用webhook將告警發(fā)送到企業(yè)微信中。
(1)首先需要在企業(yè)微信群里創(chuàng)建一個(gè)webhook機(jī)器人,然后獲取webhook地址。
(2)在Kubernetes集群中部署kube-eventer。
# cat kube-eventer.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
name: kube-eventer
name: kube-eventer
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: kube-eventer
template:
metadata:
labels:
app: kube-eventer
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ''
spec:
dnsPolicy: ClusterFirstWithHostNet
serviceAccount: kube-eventer
containers:
- image: registry.aliyuncs.com/acs/kube-eventer:v1.2.7-ca03be0-aliyun
name: kube-eventer
command:
- "/kube-eventer"
- "--source=kubernetes:https://kubernetes.default.svc.cluster.local"
## .e.g,dingtalk sink demo
#- --sink=dingtalk:[your_webhook_url]&label=[your_cluster_id]&level=[Normal or Warning(default)]
#- --sink=webhook:https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=07055f32-a04e-4ad7-9cb1-d22352769e1c&level=Warning&label=oa-k8s
- --sink=webhook:http://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=888888-888-8888-8888-d35c52ff2e0b&level=Warning&header=Content-Type=application/json&custom_body_cnotallow=custom-webhook-body&custom_body_configmap_namespace=monitoring&method=POST
env:
# If TZ is assigned, set the TZ value as the time zone
- name: TZ
value: "Asia/Shanghai"
volumeMounts:
- name: localtime
mountPath: /etc/localtime
readOnly: true
- name: zoneinfo
mountPath: /usr/share/zoneinfo
readOnly: true
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 500m
memory: 250Mi
volumes:
- name: localtime
hostPath:
path: /etc/localtime
- name: zoneinfo
hostPath:
path: /usr/share/zoneinfo
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kube-eventer
rules:
- apiGroups:
- ""
resources:
- events
- configmaps
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kube-eventer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kube-eventer
subjects:
- kind: ServiceAccount
name: kube-eventer
namespace: monitoring
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kube-eventer
namespace: monitoring
---
apiVersion: v1
data:
content: >-
{"msgtype": "text","text": {"content": "集群事件告警\n事件級(jí)別: {{ .Type }}\n名稱空間: {{ .InvolvedObject.Namespace }}\n事件類型: {{ .InvolvedObject.Kind }}\n事件對(duì)象: {{ .InvolvedObject.Name }}\n事件原因: {{ .Reason }}\n發(fā)生時(shí)間: {{ .LastTimestamp }}\n詳細(xì)信息: {{ .Message }}"}}
kind: ConfigMap
metadata:
name: custom-webhook-body
namespace: monitoring在webhook的配置中增加了level=Warning,表示只要Warning事件才會(huì)告警通知,除此之外,還可以通過(guò)namespaces字段來(lái)過(guò)來(lái)需要告警的命名空間,通過(guò)kinds字段來(lái)過(guò)濾需要告警的對(duì)象,比如只需要發(fā)送Node的Warning事件,則可以寫成level=warning&kinds=Node。再比如,如果不想產(chǎn)生非常多的告警風(fēng)暴,只發(fā)送某些特定原因的告警,比如系統(tǒng)OOM的事件,可以增加reasnotallow=SystemOOM等待。
當(dāng)kube-eventer的Pod啟動(dòng)完成后,企業(yè)微信即可收到滿足條件的事件告警,比如:

使用kube-event-exporter收集集群事件
上面使用kube-eventer進(jìn)行事件告警,本質(zhì)上并沒(méi)有存儲(chǔ)歷史事件,而實(shí)際中可能需要查詢歷史事件并且對(duì)其做一些事件分析,而ES是常用于進(jìn)行內(nèi)容收集并通過(guò)kibana進(jìn)行查看和分析,所以這里我們將使用kube-event-exporter收集Kubernetes事件到ES中。
kube-event-exporter可以直接將事件存入ES,也可以將事件發(fā)送到kafka,然后再通過(guò)Logstash消費(fèi)Kafka中的數(shù)據(jù)將其存入ES。在這里,我基于環(huán)境現(xiàn)狀,將事件發(fā)送給Kafka,然后再消費(fèi)收集到ES。
(1)部署kube-event-exporter
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: monitoring
name: event-exporter
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: event-exporter
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: ServiceAccount
namespace: monitoring
name: event-exporter
---
apiVersion: v1
kind: ConfigMap
metadata:
name: event-exporter-cfg
namespace: monitoring
data:
config.yaml: |
logLevel: error
logFormat: json
route:
routes:
- match:
- receiver: "kafka"
drop:
- kind: "Service"
receivers:
- name: "kafka"
kafka:
clientId: "kubernetes"
topic: "kubenetes-event"
brokers:
- "192.168.100.50:9092"
- "192.168.100.51:9092"
- "192.168.100.52:9092"
compressionCodec: "snappy"
layout: #optional
kind: "{{ .InvolvedObject.Kind }}"
namespace: "{{ .InvolvedObject.Namespace }}"
name: "{{ .InvolvedObject.Name }}"
reason: "{{ .Reason }}"
message: "{{ .Message }}"
type: "{{ .Type }}"
timestamp: "{{ .GetTimestampISO8601 }}"
cluster: "sda-pre-center"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: event-exporter
namespace: monitoring
spec:
replicas: 1
template:
metadata:
labels:
app: event-exporter
version: v1
spec:
serviceAccountName: event-exporter
containers:
- name: event-exporter
image: ghcr.io/resmoio/kubernetes-event-exporter:latest
imagePullPolicy: IfNotPresent
args:
- -cnotallow=/data/config.yaml
volumeMounts:
- mountPath: /data
name: cfg
volumes:
- name: cfg
configMap:
name: event-exporter-cfg
selector:
matchLabels:
app: event-exporter
version: v1當(dāng)kube-event-exporter的Pod啟動(dòng)過(guò)后,可以在kafka中查看到收集的事件,如下:

(2)部署logstash將事件存入ES
kind: Deployment
apiVersion: apps/v1
metadata:
name: kube-event-logstash
namespace: log
labels:
app: kube-event-logstash
spec:
replicas: 1
selector:
matchLabels:
app: kube-event-logstash
template:
metadata:
creationTimestamp: null
labels:
app: kube-event-logstash
annotations:
kubesphere.io/restartedAt: '2024-02-22T09:03:36.215Z'
spec:
volumes:
- name: kube-event-logstash-pipeline-config
configMap:
name: kube-event-logstash-pipeline-config
defaultMode: 420
containers:
- name: kube-event-logstash
image: 'logstash:7.8.0'
env:
- name: XPACK_MONITORING_ELASTICSEARCH_HOSTS
value: 'http://192.168.100.100:8200'
- name: XPACK_MONITORING_ELASTICSEARCH_USERNAME
value: jokerbai
- name: XPACK_MONITORING_ELASTICSEARCH_PASSWORD
value: JeA9BiAgnNRzVrp5JRVQ4vYX
- name: PIPELINE_ID
value: kube-event-logstash
- name: KAFKA_SERVER
value: '192.168.100.50:9092,192.168.100.51:9092,192.168.100.52:9092'
- name: ES_SERVER
value: 'http://192.168.100.100:8200'
- name: ES_USER_NAME
value: jokerbai
- name: ES_USER_PASSWORD
value: JeA9BiAgnNRzVrp5JRVQ4vYX
- name: NODE_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: PIPELINE_BATCH_SIZE
value: '4000'
- name: PIPELINE_BATCH_DELAY
value: '100'
- name: PIPELINE_WORKERS
value: '4'
- name: LS_JAVA_OPTS
value: '-Xms2g -Xmx3500m'
resources:
limits:
cpu: '2'
memory: 4Gi
requests:
cpu: '2'
memory: 4Gi
volumeMounts:
- name: kube-event-logstash-pipeline-config
mountPath: /usr/share/logstash/pipeline
livenessProbe:
tcpSocket:
port: 9600
initialDelaySeconds: 39
timeoutSeconds: 5
periodSeconds: 30
successThreshold: 1
failureThreshold: 2
readinessProbe:
tcpSocket:
port: 9600
initialDelaySeconds: 39
timeoutSeconds: 5
periodSeconds: 30
successThreshold: 1
failureThreshold: 2
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
securityContext: {}
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node/category
operator: In
values:
- app
schedulerName: default-scheduler
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
---
kind: ConfigMap
apiVersion: v1
metadata:
name: kube-event-logstash-pipeline-config
namespace: log
data:
logstash.conf: |-
input {
kafka {
id => "kafka_plugin_id"
bootstrap_servers => "${KAFKA_SERVER}"
client_id => "logstash"
group_id => "logstash"
decorate_events => true
topics => ["kubenetes-event"]
codec => json {
charset => "UTF-8"
}
}
}
output {
elasticsearch {
hosts => "${ES_SERVER}"
user => "${ES_USER_NAME}"
password => "${ES_USER_PASSWORD}"
index => "kubernetes-event-%{+YYYY.MM}"
manage_template => false
template_name => "kubernetes-event"
}
}部署之前,先在ES中創(chuàng)建template,可以在kibana中的dev tools中進(jìn)行操作,語(yǔ)句如下:
PUT _template/kubernetes-event
{
"index_patterns" : [
"*kubernetes-event*"
],
"settings": {
"index": {
"highlight": {
"max_analyzed_offset": "10000000"
},
"number_of_shards": "2",
"number_of_replicas": "0"
}
},
"mappings": {
"properties": {
"cluster": {
"type": "keyword"
},
"kind": {
"type": "keyword"
},
"message": {
"type": "text"
},
"name": {
"type": "keyword"
},
"namespace": {
"type": "keyword"
},
"reason": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"timestamp": {
"type": "keyword"
}
}
},
"aliases": {}
}然后再在Kubernetes集群中部署Logstash。然后就可以在Kibana上查看收集到的事件了,如下:

只要數(shù)據(jù)有了,不論是查詢還是做分析,都變得簡(jiǎn)單容易了。比如最簡(jiǎn)單得統(tǒng)計(jì)今天事件原因?yàn)閁nhealthy所發(fā)生的總次數(shù),可以在Kibana中創(chuàng)建圖表,如下:

以上就是在Kubernetes中對(duì)集群事件進(jìn)行收集和告警,這是站在巨人的肩膀上直接使用。在企業(yè)中還可以對(duì)其進(jìn)行二次開(kāi)放以將功能更豐富,比如支持對(duì)事件告警增加開(kāi)關(guān),可以任意開(kāi)啟或者關(guān)閉某個(gè)事件告警。
鏈接
[1] https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/events/event.go?spm=a2c6h.12873639.article-detail.7.c8585c576FGh7o&file=event.go。
[2] https://github.com/AliyunContainerService/kube-eventer。
[3] https://github.com/resmoio/kubernetes-event-exporter。

































