Kubernetes基于EFK进行统一的日志管理方案

1,877 阅读5分钟
原文链接: mp.weixin.qq.com

1、统一日志管理的整体方案

通过应用和系统日志可以了解Kubernetes集群内所发生的事情,对于调试问题和监视集群活动来说日志非常有用。对于大部分的应用来说,都会具有某种日志机制。因此,大多数容器引擎同样被设计成支持某种日志机制。对于容器化应用程序来说,最简单和最易接受的日志记录方法是将日志内容写入到标准输出和标准错误流。 

但是,容器引擎或运行时提供的本地功能通常不足以支撑完整的日志记录解决方案。例如,如果一个容器崩溃、一个Pod被驱逐、或者一个Node死亡,应用相关者可能仍然需要访问应用程序的日志。因此,日志应该具有独立于Node、Pod或者容器的单独存储和生命周期,这个概念被称为群集级日志记录。群集级日志记录需要一个独立的后端来存储、分析和查询日志。Kubernetes本身并没有为日志数据提供原生的存储解决方案,但可以将许多现有的日志记录解决方案集成到Kubernetes集群中。在Kubernetes中,有三个层次的日志:

  • 基础日志 

  • Node级别的日志 

  • 群集级别的日志架构

1.1 基础日志 

kubernetes基础日志即将日志数据输出到标准输出流,可以使用kubectl logs命令获取容器日志信息。如果Pod中有多个容器,可以通过将容器名称附加到命令来指定要访问哪个容器的日志。例如,在Kubernetes集群中的devops命名空间下有一个名称为nexus3-f5b7fc55c-hq5v7的Pod,就可以通过如下的命令获取日志:

  1. $ kubectl logs nexus3-f5b7fc55c-hq5v7 --namespace=devops

1.2 Node级别的日志 

容器化应用写入到stdout和stderr的所有内容都是由容器引擎处理和重定向的。例如,docker容器引擎会将这两个流重定向到日志记录驱动,在Kubernetes中该日志驱动被配置为以json格式写入文件。docker json日志记录驱动将每一行视为单独的消息。当使用docker日志记录驱动时,并不支持多行消息,因此需要在日志代理级别或更高级别上处理多行消息。

默认情况下,如果容器重新启动,kubectl将会保留一个已终止的容器及其日志。如果从Node中驱逐Pod,那么Pod中所有相应的容器也会连同它们的日志一起被驱逐。Node级别的日志中的一个重要考虑是实现日志旋转,这样日志不会消耗Node上的所有可用存储。Kubernetes目前不负责旋转日志,部署工具应该建立一个解决方案来解决这个问题。

在Kubernetes中有两种类型的系统组件:运行在容器中的组件和不在容器中运行的组件。例如:

  • Kubernetes调度器和kube-proxy在容器中运行。 

  • kubelet和容器运行时,例如docker,不在容器中运行。

在带有systemd的机器上,kubelet和容器运行时写入journaId。如果systemd不存在,它们会在/var/log目录中写入.log文件。在容器中的系统组件总是绕过默认的日志记录机制,写入到/var/log目录,它们使用golg日志库。可以找到日志记录中开发文档中那些组件记录严重性的约定。

类似于容器日志,在/var/log目录中的系统组件日志应该被旋转。这些日志被配置为每天由logrotate进行旋转,或者当大小超过100mb时进行旋转。

1.3 集群级别的日志架构 

Kubernetes本身没有为群集级别日志记录提供原生解决方案,但有几种常见的方法可以采用:

  • 使用运行在每个Node上的Node级别的日志记录代理; 

  • 在应用Pod中包含一个用于日志记录的sidecar。 

  • 将日志直接从应用内推到后端。

经过综合考虑,本文采用通过在每个Node上包括Node级别的日志记录代理来实现群集级别日志记录。日志记录代理暴露日志或将日志推送到后端的专用工具。通常,logging-agent是一个容器,此容器能够访问该Node上的所有应用程序容器的日志文件。

因为日志记录必须在每个Node上运行,所以通常将它作为DaemonSet副本、或一个清单Pod或Node上的专用本机进程。然而,后两种方法后续将会被放弃。使用Node级别日志记录代理是Kubernetes集群最常见和最受欢迎的方法,因为它只为每个节点创建一个代理,并且不需要对节点上运行的应用程序进行任何更改。但是,Node级别日志记录仅适用于应用程序的标准输出和标准错误。

Kubernetes本身并没有指定日志记录代理,但是有两个可选的日志记录代理与Kubernetes版本打包发布:和谷歌云平台一起使用的Stackdriver和Elasticsearch,两者都使用自定义配置的fluentd作为Node上的代理。在本文的方案中,Logging-agent 采用 Fluentd,而 Logging Backend 采用 Elasticsearch,前端展示采用Grafana。即通过 Fluentd 作为 Logging-agent 收集日志,并推送给后端的Elasticsearch;Grafana从Elasticsearch中获取日志,并进行统一的展示。

2、安装统一日志管理的组件

在本文中采用使用Node日志记录代理的方面进行Kubernetes的统一日志管理,相关的工具采用:

  • 日志记录代理(logging-agent):日志记录代理用于从容器中获取日志信息,使用Fluentd; 

  • 日志记录后台(Logging-Backend):日志记录后台用于处理日志记录代理推送过来的日志,使用Elasticsearch; 

  • 日志记录展示:日志记录展示用于向用户显示统一的日志信息,使用Kibana。

在Kubernetes中通过了Elasticsearch 附加组件,此组件包括Elasticsearch、Fluentd和Kibana。Elasticsearch是一种负责存储日志并允许查询的搜索引擎。Fluentd从Kubernetes中获取日志消息,并发送到Elasticsearch;而Kibana是一个图形界面,用于查看和查询存储在Elasticsearch中的日志。在安装部署之前,对于环境的要求如下:

2.1 安装部署Elasticsearch 

Elasticsearch是一个基于Apache Lucene(TM)的开源搜索和数据分析引擎引擎,Elasticsearch使用Java进行开发,并使用Lucene作为其核心实现所有索引和搜索的功能。它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。Elasticsearch不仅仅是Lucene和全文搜索,它还提供如下的能力:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索; 

  • 分布式的实时分析搜索引擎; 

  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据。

在Elasticsearch中,包含多个索引(Index),相应的每个索引可以包含多个类型(Type),这些不同的类型每个都可以存储多个文档(Document),每个文档又有多个属性。索引 (index) 类似于传统关系数据库中的一个数据库,是一个存储关系型文档的地方。Elasticsearch 使用的是标准的 RESTful API 和 JSON。此外,还构建和维护了很多其他语言的客户端,例如 Java, Python, .NET, 和 PHP。

下面是Elasticsearch的YAML配置文件,在此配置文件中,定义了一个名称为elasticsearch-logging的ServiceAccount,并授予其能够对命名空间、服务和端点读取的访问权限;并以StatefulSet类型部署Elasticsearch。

  1. # RBAC authn and authz

  2. apiVersion: v1

  3. kind: ServiceAccount

  4. metadata:

  5.  name: elasticsearch-logging

  6.  namespace: kube-system

  7.  labels:

  8.    k8s-app: elasticsearch-logging

  9.    kubernetes.io/cluster-service: "true"

  10.    addonmanager.kubernetes.io/mode: Reconcile

  11. ---

  12. kind: ClusterRole

  13. apiVersion: rbac.authorization.k8s.io/v1

  14. metadata:

  15.  name: elasticsearch-logging

  16.  labels:

  17.    k8s-app: elasticsearch-logging

  18.    kubernetes.io/cluster-service: "true"

  19.    addonmanager.kubernetes.io/mode: Reconcile

  20. rules:

  21. - apiGroups:

  22.  - ""

  23.  resources:

  24.  - "services"

  25.  - "namespaces"

  26.  - "endpoints"

  27.  verbs:

  28.  - "get"

  29. ---

  30. kind: ClusterRoleBinding

  31. apiVersion: rbac.authorization.k8s.io/v1

  32. metadata:

  33.  namespace: kube-system

  34.  name: elasticsearch-logging

  35.  labels:

  36.    k8s-app: elasticsearch-logging

  37.    kubernetes.io/cluster-service: "true"

  38.    addonmanager.kubernetes.io/mode: Reconcile

  39. subjects:

  40. - kind: ServiceAccount

  41.  name: elasticsearch-logging

  42.  namespace: kube-system

  43.  apiGroup: ""

  44. roleRef:

  45.  kind: ClusterRole

  46.  name: elasticsearch-logging

  47.  apiGroup: ""

  48. ---

  49. # Elasticsearch deployment itself

  50. apiVersion: apps/v1

  51. kind: StatefulSet

  52. metadata:

  53.  name: elasticsearch-logging

  54.  namespace: kube-system

  55.  labels:

  56.    k8s-app: elasticsearch-logging

  57.    version: v6.2.5

  58.    kubernetes.io/cluster-service: "true"

  59.    addonmanager.kubernetes.io/mode: Reconcile

  60. spec:

  61.  serviceName: elasticsearch-logging

  62.  replicas: 2

  63.  selector:

  64.    matchLabels:

  65.      k8s-app: elasticsearch-logging

  66.      version: v6.2.5

  67.  template:

  68.    metadata:

  69.      labels:

  70.        k8s-app: elasticsearch-logging

  71.        version: v6.2.5

  72.        kubernetes.io/cluster-service: "true"

  73.    spec:

  74.      serviceAccountName: elasticsearch-logging

  75.      containers:

  76.      - image: k8s.gcr.io/elasticsearch:v6.2.5

  77.        name: elasticsearch-logging

  78.        resources:

  79.          # need more cpu upon initialization, therefore burstable class

  80.          limits:

  81.            cpu: 1000m

  82.          requests:

  83.            cpu: 100m

  84.        ports:

  85.        - containerPort: 9200

  86.          name: db

  87.          protocol: TCP

  88.        - containerPort: 9300

  89.          name: transport

  90.          protocol: TCP

  91.        volumeMounts:

  92.        - name: elasticsearch-logging

  93.          mountPath: /data

  94.        env:

  95.        - name: "NAMESPACE"

  96.          valueFrom:

  97.            fieldRef:

  98.              fieldPath: metadata.namespace

  99.      volumes:

  100.      - name: elasticsearch-logging

  101.        emptyDir: {}

  102.      # Elasticsearch requires vm.max_map_count to be at least 262144.

  103.      # If your OS already sets up this number to a higher value, feel free

  104.      # to remove this init container.

  105.      initContainers:

  106.      - image: alpine:3.6

  107.        command: ["/sbin/sysctl", "-w", "vm.max_map_count=262144"]

  108.        name: elasticsearch-logging-init

  109.        securityContext:

  110.          privileged: true

通过执行如下的命令部署Elasticsearch:

  1. $ kubectl create -f {path}/es-statefulset.yaml

下面Elasticsearch的代理服务YAML配置文件,代理服务暴露的端口为9200。

  1. apiVersion: v1

  2. kind: Service

  3. metadata:  

  4.  name: elasticsearch-logging  

  5.  namespace: kube-system  

  6.  labels:    

  7.    k8s-app: elasticsearch-logging    

  8.    kubernetes.io/cluster-service: "true"    

  9.    addonmanager.kubernetes.io/mode: Reconcile    

  10.    kubernetes.io/name: "Elasticsearch"

  11. spec:  

  12.  ports:  

  13.  - port: 9200    

  14.    protocol: TCP    

  15.    targetPort: db  

  16.  selector:    

  17.    k8s-app: elasticsearch-logging

通过执行如下的命令部署Elasticsearch的代理服务:

  1. $ kubectl create -f {path}/es-service.yaml

2.2 安装部署Fluentd 

Fluentd是一个开源数据收集器,通过它能对数据进行统一收集和消费,能够更好地使用和理解数据。Fluentd将数据结构化为JSON,从而能够统一处理日志数据,包括:收集、过滤、缓存和输出。Fluentd是一个基于插件体系的架构,包括输入插件、输出插件、过滤插件、解析插件、格式化插件、缓存插件和存储插件,通过插件可以扩展和更好的使用Fluentd。

Fluentd的整体处理过程如下,通过Input插件获取数据,并通过Engine进行数据的过滤、解析、格式化和缓存,最后通过Output插件将数据输出给特定的终端。 

在本文中, Fluentd 作为 Logging-agent 进行日志收集,并将收集到的日子推送给后端的Elasticsearch。对于Kubernetes来说,DaemonSet确保所有(或一些)Node会运行一个Pod副本。因此,Fluentd被部署为DaemonSet,它将在每个节点上生成一个pod,以读取由kubelet,容器运行时和容器生成的日志,并将它们发送到Elasticsearch。为了使Fluentd能够工作,每个Node都必须标记beta.kubernetes.io/fluentd-ds-ready=true。

下面是Fluentd的ConfigMap配置文件,此文件定义了Fluentd所获取的日志数据源,以及将这些日志数据输出到Elasticsearch中。

  1. kind: ConfigMap

  2. apiVersion: v1

  3. metadata:

  4.  name: fluentd-es-config-v0.1.4

  5.  namespace: kube-system

  6.  labels:

  7.    addonmanager.kubernetes.io/mode: Reconcile

  8. data:

  9.  system.conf: |-

  10.    <system>

  11.      root_dir /tmp/fluentd-buffers/

  12.    </system>

  13.  containers.input.conf: |-

  14.    <source>

  15.      @id fluentd-containers.log

  16.      @type tail

  17.      path /var/log/containers/*.log

  18.      pos_file /var/log/es-containers.log.pos

  19.      time_format %Y-%m-%dT%H:%M:%S.%NZ

  20.      tag raw.kubernetes.*

  21.      read_from_head true

  22.      <parse>

  23.        @type multi_format

  24.        <pattern>

  25.          format json

  26.          time_key time

  27.          time_format %Y-%m-%dT%H:%M:%S.%NZ

  28.        </pattern>

  29.        <pattern>

  30.          format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/

  31.          time_format %Y-%m-%dT%H:%M:%S.%N%:z

  32.        </pattern>

  33.      </parse>

  34.    </source>

  35.    # Detect exceptions in the log output and forward them as one log entry.

  36.    <match raw.kubernetes.**>

  37.      @id raw.kubernetes

  38.      @type detect_exceptions

  39.      remove_tag_prefix raw

  40.      message log

  41.      stream stream

  42.      multiline_flush_interval 5

  43.      max_bytes 500000

  44.      max_lines 1000

  45.    </match>

  46.  output.conf: |-

  47.    # Enriches records with Kubernetes metadata

  48.    <filter kubernetes.**>

  49.      @type kubernetes_metadata

  50.    </filter>

  51.    <match **>

  52.      @id elasticsearch

  53.      @type elasticsearch

  54.      @log_level info

  55.      include_tag_key true

  56.      host elasticsearch-logging

  57.      port 9200

  58.      logstash_format true

  59.      <buffer>

  60.        @type file

  61.        path /var/log/fluentd-buffers/kubernetes.system.buffer

  62.        flush_mode interval

  63.        retry_type exponential_backoff

  64.        flush_thread_count 2

  65.        flush_interval 5s

  66.        retry_forever

  67.        retry_max_interval 30

  68.        chunk_limit_size 2M

  69.        queue_limit_length 8

  70.        overflow_action block

  71.      </buffer>

  72.    </match>

通过执行如下的命令创建Fluentd的ConfigMap:

  1. $ kubectl create -f {path}/fluentd-es-configmap.yaml

Fluentd本身的YAML配置文件如下所示:

  1. apiVersion: v1

  2. kind: ServiceAccount

  3. metadata:

  4.  name: fluentd-es

  5.  namespace: kube-system

  6.  labels:

  7.    k8s-app: fluentd-es

  8.    kubernetes.io/cluster-service: "true"

  9.    addonmanager.kubernetes.io/mode: Reconcile

  10. ---

  11. kind: ClusterRole

  12. apiVersion: rbac.authorization.k8s.io/v1

  13. metadata:

  14.  name: fluentd-es

  15.  labels:

  16.    k8s-app: fluentd-es

  17.    kubernetes.io/cluster-service: "true"

  18.    addonmanager.kubernetes.io/mode: Reconcile

  19. rules:

  20. - apiGroups:

  21.  - ""

  22.  resources:

  23.  - "namespaces"

  24.  - "pods"

  25.  verbs:

  26.  - "get"

  27.  - "watch"

  28.  - "list"

  29. ---

  30. kind: ClusterRoleBinding

  31. apiVersion: rbac.authorization.k8s.io/v1

  32. metadata:

  33.  name: fluentd-es

  34.  labels:

  35.    k8s-app: fluentd-es

  36.    kubernetes.io/cluster-service: "true"

  37.    addonmanager.kubernetes.io/mode: Reconcile

  38. subjects:

  39. - kind: ServiceAccount

  40.  name: fluentd-es

  41.  namespace: kube-system

  42.  apiGroup: ""

  43. roleRef:

  44.  kind: ClusterRole

  45.  name: fluentd-es

  46.  apiGroup: ""

  47. ---

  48. apiVersion: apps/v1

  49. kind: DaemonSet

  50. metadata:

  51.  name: fluentd-es-v2.2.0

  52.  namespace: kube-system

  53.  labels:

  54.    k8s-app: fluentd-es

  55.    version: v2.2.0

  56.    kubernetes.io/cluster-service: "true"

  57.    addonmanager.kubernetes.io/mode: Reconcile

  58. spec:

  59.  selector:

  60.    matchLabels:

  61.      k8s-app: fluentd-es

  62.      version: v2.2.0

  63.  template:

  64.    metadata:

  65.      labels:

  66.        k8s-app: fluentd-es

  67.        kubernetes.io/cluster-service: "true"

  68.        version: v2.2.0

  69.      # This annotation ensures that fluentd does not get evicted if the node

  70.      # supports critical pod annotation based priority scheme.

  71.      # Note that this does not guarantee admission on the nodes (#40573).

  72.      annotations:

  73.        scheduler.alpha.kubernetes.io/critical-pod: ''

  74.        seccomp.security.alpha.kubernetes.io/pod: 'docker/default'

  75.    spec:

  76.      priorityClassName: system-node-critical

  77.      serviceAccountName: fluentd-es

  78.      containers:

  79.      - name: fluentd-es

  80.        image: k8s.gcr.io/fluentd-elasticsearch:v2.2.0

  81.        env:

  82.        - name: FLUENTD_ARGS

  83.          value: --no-supervisor -q

  84.        resources:

  85.          limits:

  86.            memory: 500Mi

  87.          requests:

  88.            cpu: 100m

  89.            memory: 200Mi

  90.        volumeMounts:

  91.        - name: varlog

  92.          mountPath: /var/log

  93.        - name: varlibdockercontainers

  94.          mountPath: /var/lib/docker/containers

  95.          readOnly: true

  96.        - name: config-volume

  97.          mountPath: /etc/fluent/config.d

  98.      nodeSelector:

  99.        beta.kubernetes.io/fluentd-ds-ready: "true"

  100.      terminationGracePeriodSeconds: 30

  101.      volumes:

  102.      - name: varlog

  103.        hostPath:

  104.          path: /var/log

  105.      - name: varlibdockercontainers

  106.        hostPath:

  107.          path: /var/lib/docker/containers

  108.      - name: config-volume

  109.        configMap:

  110.          name: fluentd-es-config-v0.1.4

通过执行如下的命令部署Fluentd:

  1. $ kubectl create -f {path}/fluentd-es-ds.yaml

2.3 安装部署Kibana 

Kibana是一个开源的分析与可视化平台,被设计用于和Elasticsearch一起使用的。通过kibana可以搜索、查看和交互存放在Elasticsearch中的数据,利用各种不同的图表、表格和地图等,Kibana能够对数据进行分析与可视化。Kibana部署的YAML如下所示,通过环境变量ELASTICSEARCH_URL,指定所获取日志数据的Elasticsearch服务,此处为:http://elasticsearch-logging:9200,elasticsearch.cattle-logging是elasticsearch在Kubernetes中代理服务的名称。在Fluented配置文件中,有下面的一些关键指令:

  • source指令确定输入源。 

  • match指令确定输出目标。 

  • filter指令确定事件处理管道。 

  • system指令设置系统范围的配置。 

  • label指令将输出和过滤器分组以进行内部路由 

  • @include指令包含其他文件。

  1. apiVersion: apps/v1

  2. kind: Deployment

  3. metadata:

  4.  name: kibana-logging

  5.  namespace: kube-system

  6.  labels:

  7.    k8s-app: kibana-logging

  8.    kubernetes.io/cluster-service: "true"

  9.    addonmanager.kubernetes.io/mode: Reconcile

  10. spec:

  11.  replicas: 1

  12.  selector:

  13.    matchLabels:

  14.      k8s-app: kibana-logging

  15.  template:

  16.    metadata:

  17.      labels:

  18.        k8s-app: kibana-logging

  19.      annotations:

  20.        seccomp.security.alpha.kubernetes.io/pod: 'docker/default'

  21.    spec:

  22.      containers:

  23.      - name: kibana-logging

  24.        image: docker.elastic.co/kibana/kibana-oss:6.2.4

  25.        resources:

  26.          # need more cpu upon initialization, therefore burstable class

  27.          limits:

  28.            cpu: 1000m

  29.          requests:

  30.            cpu: 100m

  31.        env:

  32.          - name: ELASTICSEARCH_URL

  33.            value: http://elasticsearch-logging:9200

  34.        ports:

  35.        - containerPort: 5601

  36.          name: ui

  37.          protocol: TCP

通过执行如下的命令部署Kibana的代理服务:

  1. $ kubectl create -f {path}/kibana-deployment.yaml

下面Kibana的代理服务YAML配置文件,代理服务的类型为NodePort。

  1. apiVersion: v1

  2. kind: Service

  3. metadata:

  4.  name: kibana-logging

  5.  namespace: kube-system

  6.  labels:

  7.    k8s-app: kibana-logging

  8.    kubernetes.io/cluster-service: "true"

  9.    addonmanager.kubernetes.io/mode: Reconcile

  10.    kubernetes.io/name: "Kibana"

  11. spec:

  12.  type: NodePort

  13.  ports:

  14.  - port: 5601

  15.    protocol: TCP

  16.    targetPort: ui

  17.  selector:

  18.    k8s-app: kibana-logging

通过执行如下的命令部署Kibana的代理服务:

  1. $ kubectl create -f {path}/kibana-service.yaml

3、日志数据展示

通过如下命令获取Kibana的对外暴露的端口:

  1. $ kubectl get svc --namespace=kube-system

从输出的信息可以知道,kibana对外暴露的端口为30471,因此在Kubernetes集群外可以通过:http://{NodeIP}:30471 访问kibana。 

通过点击“Discover”,就能够实时看看从容器中获取到的日志信息: 

作者简介: 季向远,北京神舟航天软件技术有限公司产品经理。本文版权归原作者所有。

参考资料

1.《Logging Architecture》地址:

https://kubernetes.io/docs/concepts/cluster-administration/logging/

2.《Kubernetes Logging with Fluentd》地址:

https://docs.fluentd.org/v0.12/articles/kubernetes-fluentd

3.《Quickstart Guide》地址:

https://docs.fluentd.org/v0.12/articles/quickstart

4.《fluentd-elasticsearch》地址:

https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/fluentd-elasticsearch

5.《Elasticsearch》地址:

https://www.elastic.co/products/elasticsearch

6.《What is Fluentd?》地址:

https://www.fluentd.org/architecture

7.《Configuration File Syntax》地址:

https://docs.fluentd.org/v1.0/articles/config-file

8.《A Practical Introduction to Elasticsearch》地址:

https://www.elastic.co/blog/a-practical-introduction-to-elasticsearch