kubernetes日志收集系统部署流程及踩坑记录

kubernetes日志收集系统部署流程及踩坑记录

  • 前言
  • ELKB日志方案
    • ELK概述
    • 方案选择
  • K8S中ELKB日志采集方案
    • filebeat部署
      • configMap
      • daemonset方式部署
      • 最终filebeat部署脚本
    • 部署logstash
    • es、kafka部署流程
  • 部署过程中的思考
  • 踩坑记录
    • 配置相关
    • 其他

前言

日志系统的重要度不言而喻,开发、测试、用户体验都等都与其密切相关。最近帮助客户完成了容器化日志收集系统的部署,踩了很多坑。本文以k8s下ELKB日志采集方案为例,详细介绍各个组件功能及日志采集过程中遇到的一些问题。

ELKB日志方案

日志包含系统日志、应用服务日志、访问日志等,以往若需要查看日志需登录系统设备,通过vim、cat 等方式查看,操作繁琐、定位问题困难。费事费力还不能达到理想的效果。因此需要一个集中的统一日志管理分析平台,该系统具有分析统计聚合等功能,并具有用户友好的可视化界面。开源实时分析日志ELK应用而生。

ELK概述

ELK是elasticsearch、logstash、kibana三个系统的首字母组合 ,是业界用来搭理海量日志分析平台的绝佳选择。其中elasticsearch是一个开源的分布式搜索引擎,其底层基于lucene实现,主打分布式的近实时文件存储、分析和检索, 是当前流行的企业级搜索引擎。后面准备写一篇es及lucene底层原理解析。
logstash是常见的日志收集中间件,通过它可以轻松的对目标日志进行收集和处理,仅需要简单的配置即可完成部署流程。
Kibana提供了强大的数据可视化和报表统计能力,是针对es restfulApi的上层应用,能清晰的发现当前es集群中的索引构建情况及索引状态。

方案选择

传统的elk的日志收集体系架构,直接将系统日志输出至logstash,缺点是Logstash是重量级日志收集server,占用cpu资源高且内存占用高。故此在容器化部署过程中通常会采用filebeat/flunt等轻量级监控软件来进行日志初始收集。下面介绍几种常见的日志收集方案。

Beat + Elasticsearch + Kibana
这种方案比较适用于测试小型输入流,或用作demo演示,无法直接应用于真是业务场景中。该方法通过filebeat直接对接es,并通过kibana来可视化日志输出。

Beat + logstash + Elasticsearch + Kibana
相较于上方案,该方法在满足了小业务流量的日志收集需求,一定程度上解决了ELK中Logstash的不足,但是随着Beats 收集的每秒数据量越来越大,Logstash 可能无法承载这么大量日志的处理,面对超大型流量突袭,极有可能对logstash及es造成极大的业务压力,最终导致系统崩盘。

Beat + kafka + logstash + Elasticsearch + Kibana
随着 Beats 收集的每秒数据量越来越大,Logstash 可能无法承载这么大量日志的处理。虽然可以增加 Logstash 节点数量,提高每秒数据的处理速度,但是仍需考虑可能 Elasticsearch 无法承载这么大量的日志的写入。此时,我们可以考虑 引入消息队列 ,进行缓存:

  • Beats 收集数据,写入数据到消息队列中。
  • Logstash 从消息队列中,读取数据,写入 Elasticsearch 中

K8S中ELKB日志采集方案

容器化部署流程架构如下图所示:
kubernetes支持两种部署模式:deployment模式及daemonset模式。其中daemonset模式为在每个k8s节点下配置一个单一容器。本文采用daemonset的方式配置为每个k8s节点部署一个logging-agent(filebeat),考虑到filebeat轻量级及与k8s兼容的api访问能力,在部署过程中注意需要指定filebeat用户认证。

kubernetes日志收集系统部署流程及踩坑记录_第1张图片

filebeat部署

首先来看filebeat常见的配置方式, 以收集k8s默认日志为例:
一般而言,k8s中filebeat部署大致必须包含有几个部分:

configMap

为了降低耦合度及后续的维护难度,创建filebeat容器的时候将filebeat.yml配置文件以configmap的方式实现。用来指定filebeat内部配置(如监控目录、输出类型、是否包含k8s元数据等)
在filebeat.inputs下可以定义监控日志的路径,必须包含有type及paths字段

- apiVersion: v1  
  kind: ConfigMap   # 指定类型为configmap
  metadata:         # 定义filebete元数据
    name: filebeat-config
    labels:
      k8s-app: filebeat
      kubernetes.io/cluster-service: "true"
      app: filebeat-config
  data:             # 定义processor (包含哪些元数据、监控路径、输出类型)
    filebeat.yml: |
      processors:
        - add_cloud_metadata:   
      filebeat.modules:  # 定义filebeatModule
      - module: system
      filebeat.inputs:   # 定义收集日志路径
      - type: log
        paths:   # 定义收集路径为k8s的标准输出
          - /var/log/containers/*.log
        symlinks: true
        # json.message_key: log
        # json.keys_under_root: true
      output.elasticsearch: # 表示直接输出之es
        hosts: ['es-single:9200']
      logging.level: info   

daemonset方式部署

使用daemonset方式部署filebeat, 其中有两个较为重要的参数:volumnMounts及volumn,分别指定了filebeat及容器节点的映射路径,其字段为一一对应。

- apiVersion: extensions/v1beta1
  kind: DaemonSet 
  metadata:
    name: filebeat
    labels:
      k8s-app: filebeat
      kubernetes.io/cluster-service: "true"
  spec:
    template:
      metadata:
        name: filebeat
        labels:
          app: filebeat
          k8s-app: filebeat
          kubernetes.io/cluster-service: "true"
      spec:
        containers:
        - image: docker.elastic.co/beats/filebeat:6.4.0
          name: filebeat
          args: [
            "-c", "/home/filebeat-config/filebeat.yml",
            "-e",
          ]
          securityContext:
            runAsUser: 0
          volumeMounts:
          - name: filebeat-storage
            mountPath: /var/log/containers
          - name: varlogpods
            mountPath: /var/log/pods
          - name: varlibdockercontainers
            mountPath: /var/lib/docker/containers
          - name: "filebeat-volume"
            mountPath: "/home/filebeat-config"
        nodeSelector:
          role: front
        volumes:
          - name: filebeat-storage
            hostPath:
              path: /var/log/containers
          - name: varlogpods
            hostPath:
              path: /var/log/pods
          - name: varlibdockercontainers
            hostPath:
              path: /var/lib/docker/containers
          - name: filebeat-volume
            configMap:
              name: filebeat-config

最终filebeat部署脚本

daemonset方式filebeat部署脚步如下,注意在在后指定了filebeat用户认证

apiVersion: v1
items:
- apiVersion: v1
  kind: ConfigMap
  metadata:
    name: filebeat-config
    labels:
      k8s-app: filebeat
      kubernetes.io/cluster-service: "true"
      app: filebeat-config
  data:
    filebeat.yml: |
      processors:
        - add_cloud_metadata:
      filebeat.modules:
      - module: system
      filebeat.inputs:
      - type: log
        paths:
          - /var/log/containers/*.log
        symlinks: true
        # json.message_key: log
        # json.keys_under_root: true
      output.elasticsearch:
        hosts: ['es-single:9200']
      logging.level: info        
- apiVersion: extensions/v1beta1
  kind: DaemonSet 
  metadata:
    name: filebeat
    labels:
      k8s-app: filebeat
      kubernetes.io/cluster-service: "true"
  spec:
    template:
      metadata:
        name: filebeat
        labels:
          app: filebeat
          k8s-app: filebeat
          kubernetes.io/cluster-service: "true"
      spec:
        containers:
        - image: docker.elastic.co/beats/filebeat:6.4.0
          name: filebeat
          args: [
            "-c", "/home/filebeat-config/filebeat.yml",
            "-e",
          ]
          securityContext:
            runAsUser: 0
          volumeMounts:
          - name: filebeat-storage
            mountPath: /var/log/containers
          - name: varlogpods
            mountPath: /var/log/pods
          - name: varlibdockercontainers
            mountPath: /var/lib/docker/containers
          - name: "filebeat-volume"
            mountPath: "/home/filebeat-config"
        nodeSelector:
          role: front
        volumes:
          - name: filebeat-storage
            hostPath:
              path: /var/log/containers
          - name: varlogpods
            hostPath:
              path: /var/log/pods
          - name: varlibdockercontainers
            hostPath:
              path: /var/lib/docker/containers
          - name: filebeat-volume
            configMap:
              name: filebeat-config
- apiVersion: rbac.authorization.k8s.io/v1beta1
  kind: ClusterRoleBinding
  metadata:
    name: filebeat
  subjects:
  - kind: ServiceAccount
    name: filebeat
    namespace: default
  roleRef:
    kind: ClusterRole
    name: filebeat
    apiGroup: rbac.authorization.k8s.io
- apiVersion: rbac.authorization.k8s.io/v1beta1
  kind: ClusterRole
  metadata:
    name: filebeat
    labels:
      k8s-app: filebeat
  rules:
  - apiGroups: [""] # "" indicates the core API group
    resources:
    - namespaces
    - pods
    verbs:
    - get
    - watch
    - list
- apiVersion: v1
  kind: ServiceAccount
  metadata:
    name: filebeat
    namespace: default
    labels:
      k8s-app: filebeat

部署成功之后可以在pod里查看相应filebeat容器信息。

查看容器控件命令:
kubectl get pod -n {namespace} -owide

进入容器命令:
kubectl exec -ti filebeat-xxx -n {namespace} bash

部署logstash

logstash可以选择安装包部署或yaml方式部署,流程较为统一且十分简单。但在部署过程中需要注意几个点:

  1. 需要依赖jdk环境,如果监测没有安装jdk,会报出安装失败
  2. logstash配置一般分为: inputfilteroutput三部分,其中在配置过滤器时,可以考虑使用grok来进行快速分词。
  3. 若需配置多个pipeline运行时,需要对logstash.yml及pipeline.yml进行配置。

logstash的标准配置文件样例:

input{
    stdout{}
}
filter{
    grok{
        match => {"message" => "content"}
    }
    mutate{
        remove_field => ["timestamp"]
    }
}
output{
    elasticsearch {      
        hosts => "localhost:9200"
        index => "nginx-access-log-%{+YYYY.MM.dd}"   
    }
}

es、kafka部署流程

es及kafka是常用的日志中间件,在k8s部署过程中可以采用deployment部署来实现流程化部署,也可采用阿里云、华为云等云厂商提供的中间件服务来实现简单易用的服务部署使用,下面提供了常用的部署yaml。
kafka.yaml

version: '3'
services:
 zookeeper:
   image: zookeeper:latest
   container_name: zookeeper
   volumes:
     - /Users/home/docker/kafka/zookeeper/data:/data
     - /Users/home/docker/kafka/zookeeper/datalog:/datalog
   ports:
     - 2181:2181
   restart: always
 kafka:
   image: docker.io/kafka
   container_name: kafka
   volumes:
       - /Users/home/docker/kafka/data:/kafka
   ports:
     - 9092:9092
   environment:
     KAFKA_ADVERTISED_HOST_NAME: 192.168.3.3
     KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
     KAFKA_ADVERTISED_PORT: 9092
     KAFKA_LOG_RETENTION_HOURS: 120
     KAFKA_MESSAGE_MAX_BYTES: 10000000
     KAFKA_REPLICA_FETCH_MAX_BYTES: 10000000
     KAFKA_GROUP_MAX_SESSION_TIMEOUT_MS: 60000
     KAFKA_NUM_PARTITIONS: 3
     KAFKA_DELETE_RETENTION_MS: 1000
   restart: always
 kafka-manager:  
   image: kafkamanager/kafka-manager
   container_name: kafka-manager
   environment:
       ZK_HOSTS: 192.168.3.3
   ports:  
     - 9001:9000
   restart: always

es.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: es-deployment
  namespace: my-namespace
  labels:
    app: es-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: es-pod
  template:
    metadata:
      labels:
        app: es-pod
    spec:
      nodeSelector:
        deploy.elk: "true"
      restartPolicy: Always
      containers:
      - name: tsale-server-container
        image: "10.68.60.103:5000/elasticsearch:7.8.0"
        ports:
        - containerPort: 9200
        env:
        - name: node.name
          value: "es01"
        - name: cluster.name
          value: "tsale-sit2-es"
        - name: discovery.seed_hosts
          value: "10.68.60.111"
        - name: cluster.initial_master_nodes
          value: "es01"
        - name: bootstrap.memory_lock
          value: "false"
        - name: ES_JAVA_OPTS
          value: "-Xms512m -Xmx1g"
        resources:
          limits:
            memory: "1G"
            cpu: "1"
          requests:
            memory: "512Mi"
            cpu: "500m"
        volumeMounts:
        - mountPath: "/usr/share/elasticsearch/data"
          name: "es-data-volume"
        - mountPath: "/usr/share/elasticsearch/logs"
          name: "es-logs-volume"
      imagePullSecrets:
      - name: regcred
      volumes:
      - name: "es-data-volume"
        hostPath:
          path: "/opt/apps-mgr/es/data"
          type: DirectoryOrCreate
      - name: "es-logs-volume"
        hostPath:
          path: "/opt/apps-mgr/es/logs"
          type: DirectoryOrCreate

部署过程中的思考

  1. 常见的几种日志收集(logging-agent)工具的对比
日志收集 说明
Logstash logstash是常见的日志收集、处理方案,提供了非常强大的日志处理功能,能通过grok等工具轻松实现日志过滤和拆分。但是相较于其他日志收集agent而言,logstash会消耗更多的系统资源,对节点配置提出了更高的要求
Fluentd Logstash 和 Fluentd 都具有收集并处理 log 的能力,功能上二者旗鼓相当, Fluentd 抽象性做得更好,对用户屏蔽了底层细节的繁琐。
Filebeat Filebeats 是一个轻量级的收集本地 log 数据的方案,从官方对其的评价可以看出 Filebeats 功能比较单一,它仅仅只能收集本地的 log,但并不能对收集到的 Log 做什么处理,所以通常 Filebeats 通常需要将收集到的 log 发送到 Logstash 做进一步的处理。但是也正因为其轻量级的特点,适合作为节点内部的日志监控插件

1.为何要用kafka?如若不用会产生哪些问题,用kafka的好处及带来的问题
在以往的beat中还不支持MQ, 仅支持es/logstash的output。在后续的更新中,filebeat提供了对于mq的支持。这种架构比较适合于日志规模比较庞大的情况。但由于 Logstash 日志解析节点和 Elasticsearch 的负荷比较重,可将他们配置为集群模式,以分担负荷。引入消息队列,均衡了网络传输,从而降低了网络闭塞,尤其是丢失数据的可能性,但依然存在 Logstash 占用系统资源过多的问题。

  1. 如果在filebeat中需要配置多个日志收集路径,每种日志都有不同的结构,如何做好分词?
    在configMap中可以配置多个日志收集路径,如果遇到按podId命名的文件夹,可以采用**(多级目录) / *(单目录)来适配。实例如下:

    filebeat.inputs:
      - type: log
        paths:
          - /var/log/containers/*.log
      - type: log
        paths:
          - /var/lib/pod/**/kubernetes~io-empty/*.log
    

    对于分词,可以在logstash中通过grok采用正则过滤,实例格式为(?(.*))

    filter {
      grok {
        match => {
          "message" => [
                    "\[(?(%{MONTHNUM}/%{MONTHDAY}/%{YEAR})\s+%{TIME}\s+%{WORD})\]\s+%{BASE16NUM}\s+(?([\w|\S]+))\s+%{WORD:LogLevel}\s+(?[\w|\W]*)"
                ]
          }
        remove_field => ["message"]
      }
    }
    
  2. 监控中间件能否不使用filebeat,有没有其他中间可以替代,区别又是什么?
    同上

  3. 中间件的底层原理(ES、logstash、kafka),关注如何才能用的更好,性能更优
    该问题涉及非常深入,见后续更新

踩坑记录

配置相关

  1. Filebeat 如何读取多个日志目录(收集标准输出、spring日志、nginx访问日志等)?
    主要涉及几个part:

    1. filebeat指定监控目录
    filebeat.inputs:
      - type: log
        paths:
          - /var/log/containers/*.log
      - type: log
        paths:
          - /var/lib/pod/**/kubernetes~io-empty/*.log
    
    1. filebeat配置卷绑定,注意name, 一一对应
    volumeMounts:
          - name: filebeat-storage
            mountPath: /var/log/containers
          - name: varlibdockercontainers
            mountPath: /var/lib/docker/containers
        volumes:
          - name: filebeat-storage
            hostPath:
              path: /var/log/containers
          - name: varlogpods
    
    1. logstash配置日志输入输出
    2. depolyment配置目录绑定
  2. Filebeat 如何区分不同日志来源?
    通过指定field

    filebeat.inputs:
          - type: log
            paths:
              - /var/log/containers/*.log
          - fields:
            logtype: container
    
  3. 如何配置 Logstash 与 Elasticsearch 集群通信?
    最简单的做法是把集群中所有的 Elasticsearch 节点的 IP 或者是 hostname 信息都在 hosts 中配上(它支持数组)。但是如果集群比较大,或者是集群节点变动频繁的话,还需要维护这个 hosts 值,不太方便。比较推荐的做法是只配集群中某个节点的信息,可以是 client 节点,也可以是 master 节点或者是 data 节点。因为不管是哪个节点,都知道该它所在集群的信息(集群规模,各节点角色)。这样,Logstash 与任意节点通信时都会先拿到集群信息,然后再决定应该给哪个节点发送数据输出请求

    output{
           elasticsearch{
                user => "test-logstash"
                password => "xxxxx"
                hosts => ["http://es-ip1:9200","http://es-ip2:9200","http://es-ip3:9200"]
                index => "testLog-%{+YYYYMMdd}"
                ilm_enabled => false
                manage_template => false
           }
    }
    

4.filebeat如何多行日志合并展示(如java错误日志,输出栈信息)
通过配置multiline*

filebeat.inputs:
- type: log
  enable: true
  paths:
    - /var/lib/kubelet/*.log
  multiline.type: pattern
  multiline.pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}'
  multiline.negate: true
  multiline.match: after

5.如何输出k8s节点信息(日志调试使用pod信息)
指定processors,在其中增加kubernets元数据

processors:
- add_kubernetes_metadata:
    default_indexers.enabled: false
    default_matchers.enabled: false
    indexers:
      - pod_uid:
    matchers:
      - logs_path:
          logs_path: '/var/lib/kubelet/pods/'
          resource_type: 'pod'
  1. logstash如何配置多个pipeline输出
    第一步,修改pipeline.yml,增加多个pipelineName
    第二步,指定logstash.yml文件路径启动,指定方式: /bin/logstash --path.settings /etc/logstash

其他

  1. kafka为何与filebeat不通
    检查是否同一vpc网络,两者安全组出入要开放9092(kafka)

  2. 安全组配置设定
    在同一vpc下,注意开放9200(es)、9300(kibana)、9092&9011(kafka)、5044(logstash)

  3. es写入为何突然停止
    注意检查写入容量是否充足,es默认配置下,存储达80%则转变为只读

  4. kibana索引pattern配置完成,日志为何不输出
    注意刷新pattern,先配置,再刷新,区分索引的时候用时间戳

你可能感兴趣的:(kubernetes,elk)