乔克
乔克
Published on 2024-11-15 / 68 Visits
0
0

Kubernetes中的事件收集以及监控告警

随着微服务以及云原生的发展,越来越多的企业都将业务部署运行到 Kubernetes 中,主要是想依托 Kubernetes 的可扩展、可伸缩、自动化以及高稳定性来保障业务的稳定性。

然而,Kubernetes 本身是一个复杂的管理系统,它既然是作为企业业务的基础设施,其本身以及运行在集群内部的业务系统对于企业来说都变得非常重要。为此,在实际工作中,我们会借助需要得监控手段来提升 Kubernetes 本身以及业务的可观测性,常见的有:

  • 使用 cAdvisor 来获取容器的资源指标,比如 cpu、内存;
  • 使用 kube-state-metrics 来获取资源对象的状态指标,比如 Deployment、Pod 的状态;
  • 使用 metrics-server 来获取集群范围内的资源数据指标;
  • 使用 node-exporter 等一系列官方以及非官方的 exporter 来获取特定组件的指标;

在大部分的监控场景中,我们都是对特定资源进行特定监控,比如 Pod,Node 等。但是,在 Kubernetes 中还有一些场景是无法通过资源来表述的,就是说它们不是特定的资源,比如 Pod 调度、重启,在 Kubernetes 中,这类场景主要称之为事件

在 Kubernetes 中,存在两种事件:

  • Warning 事件,事件的状态转换是在非预期的状态之间产生。
  • Normal 事件,期望达到的状态和目前的状态是一致的。

在这里,我们用 Pod 来进行说明。当创建 Pod 的时候,会先进入 Pending 状态,然后在进入 Creating 状态(主要是在拉取镜像),再进去 NotReady 状态(主要是应用启动并且等待健康检测通过),最后进入 Running 状态,这整个过程就会生成 Normal 事件。但是,如果在运行过程中,如果 Pod 因为一些异常原因进入其他状态,比如节点驱逐、OOM 等,在这个状态转换的过程中,就会产生 Warning 事件。在 Kubernetes 中,我们可以通过其他办法来保障业务的稳定性,比如为了避免 Pod 调度到一个节点或者同可用区等而采用亲和性以及反亲和性调度,为了避免节点驱逐导致某个单个 Pod 不可用而采用的 PDB 等,也许某个 Warning 事件并不会对整个业务的稳定性带来致命的影响,但是如果能够通过监控事件的手段来感知集群的某个状态变化是有助于进行查漏补缺的,也有助于我们感知一些容易忽略的问题。

在 Kubernetes 中,所有事件都通过事件系统记录到 APIServer 中,并且最终存入在 Etcd 中,我们可以通过 API 或者 kubectl 进行查看,比如:

image.png

也可以查看某个对象的事件,比如:

image.png

事件包含了时间、类型、对象、原因以及描述等,通过事件我们能够知道应用的部署、调度、运行、停止等整个生命周期,也能通过事件去了解系统中正在发生的一些异常。在 Kubernetes 各个组件的源码中都会定义该组件可能会触发的事件类型,比如在 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 中,默认只保存 1 小时,由于 Etcd 本身并不支持一些复杂的分析操作,只能被动的存在 Etcd 中,并不支持主动推送到其他系统,通常情况下只能手动去查看。

在实际中,我们对 Kubernetes 事件还有其他的需求,比如:

  • 希望对异常的事件做告警处理;
  • 希望查询更长事件的历史事件;
  • 希望对集群事件进行灵活的统计分析;

为此,我们需要单独对 Kubernetes 事件进行收集,以便适用于查询以及告警。

在社区中,有很多工具来做事件的收集以及告警,我常用的两个工具是:

在实际工作中,可以选择使用其中一个,基本都能满足收集以及告警功能。在这里,我将同时使用上面两个插件,用 kube-eventer 来进行告警,用 kube-event-exporter 将事件收集到 ES 中进行查看和分析。

使用 kube-eventer 进行事件告警

kube-eventer 的告警通道可以企业微信、钉钉以及 webhook。可以根据需要进行选择,每个组件的具体使用方法在项目docs/en 目录中,这里选择使用 webhook 将告警发送到企业微信中。

(1)首先需要在企业微信群里创建一个 webhook 机器人,然后获取 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_configmap=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事件级别: {{ .Type }}\n名称空间: {{ .InvolvedObject.Namespace }}\n事件类型: {{ .InvolvedObject.Kind }}\n事件对象: {{ .InvolvedObject.Name }}\n事件原因: {{ .Reason }}\n发生时间: {{ .LastTimestamp }}\n详细信息: {{ .Message }}"}}
kind: ConfigMap
metadata:
  name: custom-webhook-body
  namespace: monitoring

在 webhook 的配置中增加了 level=Warning,表示只要 Warning 事件才会告警通知,除此之外,还可以通过 namespaces 字段来过来需要告警的命名空间,同 kinds 字段来过滤需要告警的对象,比如只需要发送 Node 的 Warning 事件,则可以写成 level=warning&kinds=Node。再比如,如果不想产生非常多的告警风暴,只发送某些特定原因的告警,比如系统 OOM 的事件,可以增加 reason=SystemOOM 等待。

当 kube-eventer 的 Pod 启动完成后,企业微信即可收到满足条件的事件告警,比如:

image.png

默认的 kube-eventer 没有太多节点问题事件,比如磁盘不可写,内存错误,内核错误等等,这时候需要结合 node-problem-detector 来进行监控。改组件也是将节点问题以事件的形式推送给 APIServer,用户可以通过 kubectl get event 来进行查看。

这里可以采用 Helm 来进行部署,步骤如下:

helm repo add deliveryhero https://charts.deliveryhero.io/
helm install --generate-name deliveryhero/node-problem-detector -n monitoring

部署完成后,如果节点有异常事件,也会通过上面的 webhook 推送到企业微信。

另外,node-problem-detector 也支持将事件以 prometheus 的格式输出,我们可以对其进行采集,只需要修改 helm chart 的 values.yaml,修改部分如下:

metrics:
  # metrics.enabled -- Expose metrics in Prometheus format with default configuration.
  enabled: true

然后使用 helm upgrade 更新即可。当使用 Prometheus 收集完指标,可以导入 Grafana 面板,可以查看事件的一些统计指标,如下:

image.png

使用 kube-event-exporter 收集集群事件

上面使用 kube-eventer 进行事件告警,本质上并没有存储历史事件,而实际中可能需要查询历史事件并且对其做一些事件分析,而 ES 是常用于进行内容收集并通过 kibana 进行查看和分析,所以这里我们将使用 kube-event-exporter 收集 Kubernetes 事件到 ES 中。

kube-event-exporter 可以直接将事件存入 ES,也可以将事件发送到 kafka,然后再通过 Logstash 消费 Kafka 中的数据将其存入 ES。在这里,我基于环境现状,将事件发送给 Kafka,然后再消费收集到 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:
            - -conf=/data/config.yaml
          volumeMounts:
            - mountPath: /data
              name: cfg
      volumes:
        - name: cfg
          configMap:
            name: event-exporter-cfg
  selector:
    matchLabels:
      app: event-exporter
      version: v1

当 kube-event-exporter 的 Pod 启动过后,可以在 kafka 中查看到收集的事件,如下:

image.png

(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 中创建 template,可以在 kibana 中的 dev tools 中进行操作,语句如下:

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 上查看收集到的事件了,如下:

image.png

只要数据有了,不论是查询还是做分析,都变得简单容易了。比如最简单得统计今天事件原因为 Unhealthy 所发生的总次数,可以在 Kibana 中创建图表,如下:

image.png

以上就是在 Kubernetes 中对集群事件进行收集和告警,这是站在巨人的肩膀上直接使用。在企业中还可以对其进行二次开放以将功能更丰富,比如支持对事件告警增加开关,可以任意开启或者关闭某个事件告警。

链接

[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


Comment