美文网首页k8s那点事儿我用 LinuxLinux学习|Gentoo/Arch/FreeBSD
k8s八 | 基于EFK实现日志管理与日志报警

k8s八 | 基于EFK实现日志管理与日志报警

作者: Anson前行 | 来源:发表于2020-05-14 15:55 被阅读0次

    一、日志采集方案

    image.png image.png

    大致一下分为三种方案来做日志采集:

    • 在节点上运行一个 agent 来收集日志
    • 在 Pod 中包含一个 sidecar 容器来收集应用日志
    • 直接在应用程序中将日志信息推送到采集后端

    因为方案一在业界使用更为广泛,所以下面基于方案一来做k8s的日志采集。

    二、架构选型

    1. 存储层: Elasticsearch 是一个实时的、分布式的可扩展的搜索引擎,允许进行全文、结构化搜索,它通常用于索引和搜索大量日志数据,也可用于搜索许多不同类型的文档。

    2. 展示层:Kibana 是 Elasticsearch 的一个功能强大的数据可视化 Dashboard,Kibana 允许你通过 web 界面来浏览 Elasticsearch 日志数据。

    3. 缓存层: 需要收集大数据量的日志一般使用Redis、kafka做为中间缓存层来缓冲数据。

    4. 采集层:

    • Fluentd:是一个流行的开源数据收集器, 具有众多插件,通过获取容器日志文件、过滤和转换日志数据,然后将数据传递到 Elasticsearch 集群,在该集群中对其进行索引和存储。
    • Fluentd-bit: 更适用于嵌入设备等资源受限的场景。占用系统资源较少,在插件可以满足需求的同时,无疑是更好的选择。另外Fluent Bit 提供了输出插件,可以把数据发给 Fluentd,因此他们可以在系统中作为独立服务互相协作。对比如下


      在这里插入图片描述
    • Logstash:ES官方推荐,使用它有很多插件,灵活性很高,但由于是java语言编写,占用资源较高,一般作为过滤格式使用,当然也可以单独使用。
    • Filebeat: ES官方新一代采集工具,是一个轻量级的日志传输工具,使用Golang语言编写,占用资源低,一般作为采集日志使用,当然也可以单独使用,同样它和Logstash可以互相协作。

    详见:详解日志采集工具--Logstash、Filebeat、Fluentd、Logagent对比

    相关架构图如下:

    image.png
    image.jpg

    本文所使用架构
    Fluentd(采集),Elasticsearch (存储),kibana(展示)

    三、部署Elasticsearch 集群

    使用3个 Elasticsearch Pod 来避免高可用下多节点集群中出现的“脑裂”问题

    创建一个名为 logging 的 namespace

    $ kubectl  create  namespace logging
    

    1. 创建无头服务

    编写elasticsearch-svc.yaml

    kind: Service
    apiVersion: v1
    metadata:
      name: elasticsearch
      namespace: logging
      labels:
        app: elasticsearch
    spec:
      selector:
        app: elasticsearch
      clusterIP: None
      ports:
        - port: 9200
          name: rest
        - port: 9300
          name: inter-node
    

    创建服务资源对象

    $ kubectl create -f elasticsearch-svc.yaml
    $ kubectl  get svc -n logging
    NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
    elasticsearch   ClusterIP   None            <none>        9200/TCP,9300/TCP   13d
    

    2. 部署StorageClass持久化存储

    集群使用NFS 作为后端存储资源,在主节点安装NFS,共享/data/k8s/目录。

    $ systemctl stop firewalld.service
    $ yum -y install nfs-utils rpcbind
    $ mkdir -p /data/k8s
    $ chmod 755 /data/k8s
    $ vim /etc/exports
    /data/k8s  *(rw,sync,no_root_squash)
    $ systemctl start rpcbind.service
    $ systemctl start nfs.service
    

    创建nfs-client 的自动配置程序Provisioner, nfs-client.yaml

    kind: Deployment
    apiVersion: extensions/v1beta1
    metadata:
      name: nfs-client-provisioner
    spec:
      replicas: 1
      strategy:
        type: Recreate
      template:
        metadata:
          labels:
            app: nfs-client-provisioner
        spec:
          serviceAccountName: nfs-client-provisioner
          containers:
            - name: nfs-client-provisioner
              image: quay.io/external_storage/nfs-client-provisioner:latest
              volumeMounts:
                - name: nfs-client-root
                  mountPath: /persistentvolumes
              env:
                - name: PROVISIONER_NAME
                  value: fuseim.pri/ifs
                - name: NFS_SERVER
                  value: 172.16.1.100
                - name: NFS_PATH
                  value: /data/k8s
          volumes:
            - name: nfs-client-root
              nfs:
                server: 172.16.1.100
                path: /data/k8s
    

    创建ServiceAccount,然后绑定集群对应的操作权限:nfs-client-sa.yaml

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: nfs-client-provisioner
    
    ---
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: nfs-client-provisioner-runner
    rules:
      - apiGroups: [""]
        resources: ["persistentvolumes"]
        verbs: ["get", "list", "watch", "create", "delete"]
      - apiGroups: [""]
        resources: ["persistentvolumeclaims"]
        verbs: ["get", "list", "watch", "update"]
      - apiGroups: ["storage.k8s.io"]
        resources: ["storageclasses"]
        verbs: ["get", "list", "watch"]
      - apiGroups: [""]
        resources: ["events"]
        verbs: ["list", "watch", "create", "update", "patch"]
      - apiGroups: [""]
        resources: ["endpoints"]
        verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
    
    ---
    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: run-nfs-client-provisioner
    subjects:
      - kind: ServiceAccount
        name: nfs-client-provisioner
        namespace: default
    roleRef:
      kind: ClusterRole
      name: nfs-client-provisioner-runner
      apiGroup: rbac.authorization.k8s.io
    

    创建StorageClass,elasticsearch-storageclass.yaml

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: es-data-db
    provisioner: fuseim.pri/ifs
    

    部署服务资源对象

    $ kubectl create -f nfs-client.yaml
    $ kubectl create -f nfs-client-sa.yaml
    $ kubectl create -f elasticsearch-storageclass.yaml 
    $ kubectl  get pods 
    NAME                                      READY   STATUS    RESTARTS   AGE
    nfs-client-provisioner-5b486d9c65-9fzjz   1/1     Running   9          13d
    $ kubectl get storageclass
    NAME                PROVISIONER      AGE
    es-data-db          fuseim.pri/ifs   13d
    

    3. 使用StatefulSet 创建Es Pod

    Elasticsearch 需要稳定的存储来保证 Pod 在重新调度或者重启后的数据依然不变,所以我们需要使用 StatefulSet 控制器来管理 Pod。

    编写elasticsearch-statefulset.yaml

    apiVersion: apps/v1
    kind: StatefulSet    
    metadata:
      name: es   #定义了名为 es 的 StatefulSet 对象
      namespace: logging
    spec:
      serviceName: elasticsearch  #和前面创建的 Service 相关联,这可以确保使用以下 DNS 地址访问 StatefulSet 中的每一个 Pod:es-[0,1,2].elasticsearch.logging.svc.cluster.local,其中[0,1,2]对应于已分配的 Pod 序号。
      replicas: 3  #3个副本
      selector:    #设置匹配标签为app=elasticsearch
        matchLabels:
          app: elasticsearch    
      template:    #定义Pod模板
        metadata:
          labels: 
            app: elasticsearch
        spec: 
          initContainers:  #初始化容器,在主容器执行前运行
          - name: increase-vm-max-map  #第一个Init容器用来增加操作系统对mmap计数的限制
            image: busybox  
            command: ["sysctl", "-w", "vm.max_map_count=262144"]
            securityContext:
              privileged: true
          - name: increase-fd-ulimit   #第二个Init容器用来执行ulimit命令,增加打开文件描述符的最大数量
            image: busybox
            command: ["sh", "-c", "ulimit -n 65536"]
            securityContext:
              privileged: true
          containers:   
          - name: elasticsearch
            image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
            ports:
            - name: rest
              containerPort: 9200
            - name: inter
              containerPort: 9300
            resources:
              limits:
                cpu: 1000m
              requests:
                cpu: 1000m
            volumeMounts:
            - name: data
              mountPath: /usr/share/elasticsearch/data
            env: #声明变量
            - name: cluster.name  # #Elasticsearch 集群的名称
              value: k8s-logs  
            - name: node.name #节点的名称,
              valueFrom: 
                fieldRef:
                  fieldPath: metadata.name
            - name: cluster.initial_master_nodes
              value: "es-0,es-1,es-2"
            - name: discovery.zen.minimum_master_nodes   #将其设置为(N/2) + 1,N是我们的群集中符合主节点的节点的数量。我们有3个 Elasticsearch 节点,因此我们将此值设置为2(向下舍入到最接近的整数)。
              value: "2"
            - name: discovery.seed_hosts #设置在 Elasticsearch 集群中节点相互连接的发现方法。
              value: "elasticsearch"
            - name: ES_JAVA_OPTS  #设置为-Xms512m -Xmx512m,告诉JVM使用512 MB的最小和最大堆。您应该根据群集的资源可用性和需求调整这些参数。
              value: "-Xms512m -Xmx512m"
            - name: network.host
              value: "0.0.0.0"
      volumeClaimTemplates:   #持久化模板
      - metadata:
          name: data 
          labels:
            app: elasticsearch
        spec:
          accessModes: [ "ReadWriteOnce" ] #只能被 mount 到单个节点上进行读写
          storageClassName:  es-data-db
          resources:
            requests:
              storage: 100Gi
    

    创建服务资源对象

    $ kubectl create -f elasticsearch-statefulset.yaml
    statefulset.apps/es-cluster created
    $ kubectl get pods -n logging
    NAME                      READY     STATUS    RESTARTS   AGE
    es-cluster-0              1/1       Running   0          20h
    es-cluster-1              1/1       Running   0          20h
    es-cluster-2              1/1       Running   0          20h
    

    验证ES服务是否正常
    将本地端口9200转发到 Elasticsearch 节点(如es-cluster-0)对应的端口:

    $ kubectl port-forward es-cluster-0 9200:9200 --namespace=logging
    Forwarding from 127.0.0.1:9200 -> 9200
    Forwarding from [::1]:9200 -> 9200
    

    打开另一个终端请求es,出现以下内容则为部署成功。

    $ curl http://localhost:9200/_cluster/state?pretty
    
    {
      "cluster_name" : "k8s-logs",
      "cluster_uuid" : "z9Hz1q9OS0G6GQBlWkOjuA",
      "version" : 19,
      "state_uuid" : "fjeJfNjjRkmFnX_1x_kzpg",
      "master_node" : "zcRrv4jnTfKFWGGdORpZKg",
      "blocks" : { },
      "nodes" : {
        "cqZH5iFOTYCKkNHiZK6uoQ" : {
          "name" : "es-0",
          "ephemeral_id" : "l3VAgaBYSLeY0wA9_CdWkw",
          "transport_address" : "192.168.85.195:9300",
          "attributes" : {
            "ml.machine_memory" : "8202764288",
            "ml.max_open_jobs" : "20",
            "xpack.installed" : "true"
          }
        },
        "zcRrv4jnTfKFWGGdORpZKg" : {
          "name" : "es-1",
          "ephemeral_id" : "LrD2UIReRfuIlGEArmwYuw",
          "transport_address" : "192.168.148.77:9300",
          "attributes" : {
            "ml.machine_memory" : "14543122432
    -------
    

    四、部署Kibana服务

    Kibana作为前端展示层应用,不需要存储大量数据,所以我们使用Deployment控制器来管理Kibana的Pod。

    编写 kibana.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: kibana
      namespace: logging
      labels:
        app: kibana
    spec:
      ports:
      - port: 5601
      type: NodePort
      selector:
        app: kibana
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: kibana
      namespace: logging
      labels:
        app: kibana
    spec:
      selector:
        matchLabels:
          app: kibana
      template:
        metadata:
          labels:
            app: kibana
        spec:
          containers:
          - name: kibana
            image: docker.elastic.co/kibana/kibana:7.6.2
            resources:
              limits:
                cpu: 1000m
              requests:
                cpu: 1000m
            env:
            - name: ELASTICSEARCH_HOSTS
              value: http://elasticsearch:9200
            ports:
            - containerPort: 5601
    

    使用 kubectl 工具创建:

    $ kubectl create -f kibana.yaml
    service/kibana created
    deployment.apps/kibana created
    $ kubectl get pods --namespace=logging
    NAME                     READY   STATUS    RESTARTS   AGE
    es-0                     1/1     Running   0          42h
    es-1                     1/1     Running   1          42h
    es-2                     1/1     Running   0          42h
    kibana-945bc5c69-gkb58   1/1     Running   1          16h
    $ kubectl get svc  --namespace=logging
    NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE
    elasticsearch   ClusterIP   None         <none>        9200/TCP,9300/TCP   42h
    kibana          NodePort    10.97.4.21   <none>        5601:32230/TCP      16h
    

    在浏览器中访问:http://<任意节点IP>:32230


    在这里插入图片描述

    五、部署Fluentd采集插件

    由于我们使用的采集方案,只需要在每台节点上部署一个采集器即可,对资源没有过大的消耗,所以选择对插件支持更多,使用更加广泛的Fluentd 来作为日志收集工具。下面我们使用DasemonSet 控制器来部署 Fluentd 应用,以确保在集群中的每个节点上始终运行一个 Fluentd 收集容器。

    1. 编写fluentd的ConfigMap文件

    编写Fluentd的配置文件:fluentd-configmap.yaml

    kind: ConfigMap
    apiVersion: v1
    metadata:
      name: fluentd-config
      namespace: logging
    data:
      system.conf: |-
        <system>
          root_dir /tmp/fluentd-buffers/
        </system>
      containers.input.conf: |-                   # 日志源配置
        <source>
          @id fluentd-containers.log              # 日志源唯一标识符,后面可以使用该标识符进一步处理
          @type tail                              # Fluentd 内置的输入方式,其原理是不停地从源文件中获取新的日志。
          path /var/log/containers/*.log          # 挂载的服务器Docker容器日志地址
          pos_file /var/log/es-containers.log.pos # 检查点 Fluentd重启后会从该文件中的位置恢复日志采集
          tag raw.kubernetes.*                    # 设置日志标签
          read_from_head true
          <parse>                                 # 多行格式化成JSON
            @type multi_format                    # 使用 multi-format-parser 解析器插件
            <pattern>
              format json                         # JSON解析器
              time_key time                       # 指定事件时间的时间字段
              time_format %Y-%m-%dT%H:%M:%S.%NZ   # 时间格式
            </pattern>
            <pattern>
              format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
              time_format %Y-%m-%dT%H:%M:%S.%N%:z
            </pattern>
          </parse>
        </source>
            
        <match raw.kubernetes.**>           # 匹配tag为raw.kubernetes.**日志信息
          @id raw.kubernetes
          @type detect_exceptions           # 使用detect-exceptions插件处理异常栈信息
          remove_tag_prefix raw             # 移除 raw 前缀
          message log                       
          stream stream                     
          multiline_flush_interval 5
          max_bytes 500000
          max_lines 1000
        </match>
    
        <filter **>  # 拼接日志
          @id filter_concat
          @type concat                # Fluentd Filter 插件,用于连接多个事件中分隔的多行日志。
          key message
          multiline_end_regexp /\n$/  # 以换行符“\n”拼接
          separator ""
        </filter> 
    
        <filter kubernetes.**>        # 添加 Kubernetes metadata 数据
          @id filter_kubernetes_metadata
          @type kubernetes_metadata
        </filter>
    
        <filter kubernetes.**>        # 修复ES中的JSON字段
          @id filter_parser
          @type parser                # multi-format-parser多格式解析器插件
          key_name log                # 在要解析的记录中指定字段名称。
          reserve_data true           # 在解析结果中保留原始键值对。
          remove_key_name_field true  # key_name 解析成功后删除字段。
          <parse>
            @type multi_format
            <pattern>
              format json
            </pattern>
            <pattern>
              format none
            </pattern>
          </parse>
        </filter>
    
        <filter kubernetes.**>           # 删除一些多余的属性
          @type record_transformer
          remove_keys $.docker.container_id,$.kubernetes.container_image_id,$.kubernetes.pod_id,$.kubernetes.namespace_id,$.kubernetes.master_url,$.kubernetes.labels.pod-template-hash
        </filter>
    
    
        <filter kubernetes.**>          # 只采集具有logging=true标签的Pod日志
          @id filter_log
          @type grep
          <regexp>
            key $.kubernetes.labels.logging
            pattern ^true$
          </regexp>
        </filter>
    
      forward.input.conf: |-             # 监听配置,一般用于日志聚合用
        <source>
          @id forward
          @type forward
        </source>
    
      output.conf: |-                    # 路由配置,将处理后的日志数据发送到ES
        <match **>                       # 标识一个目标标签,后面是一个匹配日志源的正则表达式,我们这里想要捕获所有的日志并将它们发送给 Elasticsearch,所以需要配置成**
          @id elasticsearch              # 目标的一个唯一标识符
          @type elasticsearch            # 支持的输出插件标识符,输出到 Elasticsearch
          @log_level info                # 指定要捕获的日志级别,我们这里配置成 info,表示任何该级别或者该级别以上(INFO、WARNING、ERROR)的日志都将被路由到 Elsasticsearch。
          include_tag_key true
          host elasticsearch             # 定义 Elasticsearch 的地址
          port 9200
          logstash_format true           # Fluentd 将会以 logstash 格式来转发结构化的日志数据
          logstash_prefix k8s            # 设置 index 前缀为 k8s
          request_timeout    30s
          <buffer>                       # Fluentd 允许在目标不可用时进行缓存
            @type file
            path /var/log/fluentd-buffers/kubernetes.system.buffer
            flush_mode interval
            retry_type exponential_backoff
            flush_thread_count 2
            flush_interval 5s
            retry_forever
            retry_max_interval 30
            chunk_limit_size 2M
            queue_limit_length 8
            overflow_action block
          </buffer>
        </match>
    

    在上面的文件中我们首先定义了日志源,然后经过滤和组装获得我们需要的格式,最后将数据输出到Elasticsearch集群中。

    2. 使用Daemonset部署Fluentd Pod

    新建一个 fluentd-daemonset.yaml

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: fluentd-es
      namespace: logging
      labels:
        k8s-app: fluentd-es
        kubernetes.io/cluster-service: "true"
        addonmanager.kubernetes.io/mode: Reconcile
    ---
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: fluentd-es
      labels:
        k8s-app: fluentd-es
        kubernetes.io/cluster-service: "true"
        addonmanager.kubernetes.io/mode: Reconcile
    rules:
    - apiGroups:
      - ""
      resources:
      - "namespaces"
      - "pods"
      verbs:
      - "get"
      - "watch"
      - "list"
    ---
    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: fluentd-es
      labels:
        k8s-app: fluentd-es
        kubernetes.io/cluster-service: "true"
        addonmanager.kubernetes.io/mode: Reconcile
    subjects:
    - kind: ServiceAccount
      name: fluentd-es
      namespace: logging
      apiGroup: ""
    roleRef:
      kind: ClusterRole
      name: fluentd-es
      apiGroup: ""
    ---
    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: fluentd-es
      namespace: logging
      labels:
        k8s-app: fluentd-es
        version: v2.0.4
        kubernetes.io/cluster-service: "true"
        addonmanager.kubernetes.io/mode: Reconcile
    spec:
      selector:
        matchLabels:
          k8s-app: fluentd-es
          version: v2.0.4
      template:
        metadata:
          labels:
            k8s-app: fluentd-es
            kubernetes.io/cluster-service: "true"
            version: v2.0.4
          annotations:
            scheduler.alpha.kubernetes.io/critical-pod: ''
        spec:
          serviceAccountName: fluentd-es
          containers:
          - name: fluentd-es
            image: cnych/fluentd-elasticsearch:v2.0.4
            env:
            - name: FLUENTD_ARGS
              value: --no-supervisor -q
            resources:
              limits:
                memory: 500Mi
              requests:
                cpu: 100m
                memory: 200Mi
            volumeMounts:
            - name: varlog
              mountPath: /var/log
            - name: varlibdockercontainers
              mountPath: /var/lib/docker/containers
              readOnly: true
            - name: config-volume
              mountPath: /etc/fluent/config.d
          nodeSelector:  #节点选择
            beta.kubernetes.io/fluentd-ds-ready: "true" #节点需有这个标签才会部署收集
          tolerations: #添加容忍
          - key: node-role.kubernetes.io/master
            operator: Exists
            effect: NoSchedule
          terminationGracePeriodSeconds: 30
          volumes:
          - name: varlog
            hostPath:
              path: /var/log
          - name: varlibdockercontainers
            hostPath:
              path: /var/lib/docker/containers
          - name: config-volume
            configMap:
              name: fluentd-config
    

    刚才我们使用ConfigMap对象编写的Fluentd的配置文件,也已经通过volumes 挂载到了Fluentd 容器中。我们也可以通过给节点打标签的方式,灵活控制哪些节点的日志可以被收集。在上面文件中我们定义了nodeSelector字段,来收集集群中含有这个beta.kubernetes.io/fluentd-ds-ready: "true"标签的节点日志。

    为需要收集日志的节点添加标签

    $ kubectl    label nodes  k8s-node01   beta.kubernetes.io/fluentd-ds-ready=true
    $ kubectl    label nodes  kubesphere   beta.kubernetes.io/fluentd-ds-ready=true
    $ kubectl get nodes --show-labels
    NAME         STATUS   ROLES    AGE    VERSION   LABELS
    k8s-node01   Ready    <none>   200d   v1.15.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/fluentd-ds-ready=true,beta.kubernetes.io/os=linux,es=log,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node01,kubernetes.io/os=linux
    kubesphere   Ready    master   203d   v1.15.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/fluentd-ds-ready=true,beta.kubernetes.io/os=linux,es=log,kubernetes.io/arch=amd64,kubernetes.io/hostname=kubesphere,kubernetes.io/os=linux,node-role.kubernetes.io/master=
    

    部署资源对象

    $ kubectl create -f fluentd-configmap.yaml
    configmap "fluentd-config" created
    $ kubectl create -f fluentd-daemonset.yaml
    serviceaccount "fluentd-es" created
    clusterrole.rbac.authorization.k8s.io "fluentd-es" created
    clusterrolebinding.rbac.authorization.k8s.io "fluentd-es" created
    daemonset.apps "fluentd-es" created
    $ kubectl get pods -n logging
    NAME                      READY   STATUS    RESTARTS   AGE
    es-0                      1/1     Running   1          2d
    es-1                      1/1     Running   1          2d
    es-2                      1/1     Running   1          2d
    fluentd-es-7rf2v          1/1     Running   0          82m
    fluentd-es-bm974          1/1     Running   0          82m
    kibana-5b7f674fd8-z6k6h   1/1     Running   1          3h59m
    $ kubectl  get svc -n logging
    NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
    elasticsearch   ClusterIP   None            <none>        9200/TCP,9300/TCP   18d
    kibana          NodePort    10.111.51.138   <none>        5601:31284/TCP      18d
    

    3. 测试应用日志收集

    在上面Fluentd的配置文件中,我们指定了只收集具有logging=true标签的Pod日志, 现在我们部署一个简单的测试应用, 新建 dummylogs.yaml文件

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: dummylogs
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: dummylogs
      template:
        metadata:
          labels:
            app: dummylogs
            logging: "true"  # 要采集日志需要加上该标签
        spec:
          containers:
          - name: dummy
            image: cnych/dummylogs:latest
            args:
            - msg-processor
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: dummylogs2
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: dummylogs2
      template:
        metadata:
          labels:
            app: dummylogs2
            logging: "true"  # 要采集日志需要加上该标签
        spec:
          containers:
          - name: dummy
            image: cnych/dummylogs:latest
            args:
            - msg-receiver-api
    

    使用 kubectl 工具创建该 Pod:

    $ kubectl create -f dummylogs.yaml
    $  kubectl  get pod 
    NAME                        READY   STATUS             RESTARTS   AGE
    dummylogs-59677dd44d-44z9c  1/1     Running            21         2d8h
    dummylogs-59677dd44d-s64rj  1/1     Running            19         2d8h
    dummylogs2-67498f8b6d-dgz7c 1/1     Running            21         2d8h
    dummylogs2-67498f8b6d-v6db5 1/1     Running            18         2d8h
    $ kubectl  logs -f dummylogs-59677dd44d-44z9c
    {"LOGLEVEL":"INFO","serviceName":"msg-processor","serviceEnvironment":"staging","message":"Information event from service msg-processor staging - events received and processed.","eventsNumber":25}
    {"LOGLEVEL":"INFO","serviceName":"msg-processor","serviceEnvironment":"staging","message":"Information event from service msg-processor staging - events received and processed.","eventsNumber":49}
    

    这个 Pod将日志信息打印到 stdout,所以正常来说 Fluentd 会收集到这个日志数据,在 Kibana 中也就可以找到对应的日志数据了,现在我们到kibana上去添加这个索引。


    在这里插入图片描述

    可以使用过滤器筛选一些日志进行分析


    在这里插入图片描述

    六、实现基于日志的报警

    在应用层面我们可以使用 Promethus 对应用的各项指标进行监控,但是在业务层面,应用的日志中也会产生一些错误日志,影响业务的正常运行,所以我们还需要对错误日志进行监控报警,可以使用 elastalert 组件来完成这个工作。

    ElastAlert支持以下方式报警:

    • Command
    • Email
    • JIRA
    • OpsGenie
    • SNS
    • HipChat
    • Slack
    • Telegram
    • Debug
    • Stomp

    下面我们使用邮件方式报警,编写资源文件elastalert.yaml

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: elastalert-config
      namespace: logging
      labels:
        app: elastalert
    data:
      elastalert_config: |-            #  elastalert配置文件  
        ---
        rules_folder: /opt/rules       # 指定规则的目录
        scan_subdirectories: false
        run_every:                     # 多久从 ES 中查询一次
          minutes: 1
        buffer_time:
          minutes: 15
        es_host: elasticsearch
        es_port: 9200
        writeback_index: elastalert
        use_ssl: False
        verify_certs: True
        alert_time_limit:             # 失败重试限制
          minutes: 2880
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: elastalert-rules
      namespace: logging
      labels:
        app: elastalert
    data:
      rule_config.yaml: |-        # elastalert规则文件
        name: dummylogs error     # 规则名字,唯一值
        es_host: elasticsearch
        es_port: 9200
    
        type: any                 # 报警类型
        index: k8s-*              # es索引
    
        filter:                   # 过滤
        - query:
            query_string:
              query: "LOGLEVEL:ERROR"  # 报警条件
    
        alert:                    # 报警类型
        - "email"
        smtp_host: smtp.qq.com
        smtp_port: 587
        smtp_auth_file: /opt/auth/smtp_auth_file.yaml
        email_reply_to: 123456789@qq.com   #发送邮箱
        from_addr: 123456789qq.com
        email:                  # 接受邮箱
        - "xxxxx@163.com"
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: elastalert
      namespace: logging
      labels:
        app: elastalert
    spec:
      selector:
        matchLabels:
          app: elastalert
      template:
        metadata:
          labels:
            app: elastalert
        spec:
          containers:
          - name: elastalert
            image: jertel/elastalert-docker:0.2.4
            imagePullPolicy: IfNotPresent
            volumeMounts:
            - name: config
              mountPath: /opt/config
            - name: rules
              mountPath: /opt/rules
            - name: auth
              mountPath: /opt/auth
            resources:
              limits:
                cpu: 50m
                memory: 256Mi
              requests:
                cpu: 50m
                memory: 256Mi
          volumes:
          - name: auth
            secret:
              secretName: smtp-auth
          - name: rules
            configMap:
              name: elastalert-rules
          - name: config
            configMap:
              name: elastalert-config
              items:
              - key: elastalert_config
                path: elastalert_config.yaml
    

    使用邮件进行报警的时候,需要指定一个smtp_auth_file 的文件,文件中包含用户名和密码:(smtp_auth_file.yaml)

    user: "xxxxx@qq.com"       # 发送的邮箱地址
    password: "exawdasqq12"   # 不是qq邮箱的登录密码,是授权码
    

    然后使用上面的文件创建一个对应的 Secret 资源对象:

    $ kubectl create secret generic smtp-auth --from-file=smtp_auth_file.yaml -n logging
    

    创建资源对象

    $ kubectl apply -f elastalert.yaml
    $ kubectl get pods -n logging -l app=elastalert
    NAME                        READY   STATUS    RESTARTS   AGE
    elastalert-ff5f7c9c-4948j   1/1     Running   0          9m17s
    $ kubectl  logs -f  elastalert-ff5f7c9c-4948j -n logging
    Elastic Version: 7.6.2
    Reading Elastic 6 index mappings:
    Reading index mapping 'es_mappings/6/silence.json'
    Reading index mapping 'es_mappings/6/elastalert_status.json'
    Reading index mapping 'es_mappings/6/elastalert.json'
    Reading index mapping 'es_mappings/6/past_elastalert.json'
    Reading index mapping 'es_mappings/6/elastalert_error.json'
    Index elastalert already exists. Skipping index creation.
    

    我们的示例应用会隔一段时间就产生 ERROR 级别的错误日志,所以正常情况下我们就可以收到如下所示的邮件信息了:


    在这里插入图片描述

    上篇文章:k8s七 | 服务守护进程DaemonSet
    系列文章:深入理解Kuerneters
    参考资料:从Docker到Kubernetes进阶-阳明


    关注公众号回复【k8s】关键词获取视频教程及更多资料:


    前行技术圈

    相关文章

      网友评论

        本文标题:k8s八 | 基于EFK实现日志管理与日志报警

        本文链接:https://www.haomeiwen.com/subject/ikhvwhtx.html