背景
上一章讲的是测试环境搭建EFK,本文讲述的是线上环境如何搭建EFK集群,由于线上使用的是阿里云提供的kubernetes,线上数据的存储方式,kibana的访问方式,日志的过滤方式,服务的调度方式都不一样,因此需要在原有的基础上进行修改。

创建 Elasticsearch 集群

创建命名空间

  1. $ kubectl create -f kube-logging.yaml
  2. apiVersion: v1
  3. kind: Namespace
  4. metadata:
  5. name: logging

创建了一个命名空间来存放我们的日志相关资源,接下来可以部署 EFK 相关组件,由于在测试环境,首先开始部署一个单节点的 Elasticsearch 。

创建elasticsearch service 服务

  1. $ kubectl create -f elasticsearch-svc.yaml
  2. kind: Service
  3. apiVersion: v1
  4. metadata:
  5. name: elasticsearch
  6. namespace: logging
  7. labels:
  8. app: elasticsearch
  9. spec:
  10. selector:
  11. app: elasticsearch
  12. clusterIP: None
  13. ports:
  14. - port: 9200
  15. name: rest
  16. - port: 9300
  17. name: inter-node

查看部署情况

  1. $ kubectl get svc -n logging
  2. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  3. elasticsearch ClusterIP None <none> 9200/TCP,9300/TCP 27h

我们已经为 Pod 设置了无头服务和一个稳定的域名.elasticsearch.logging.svc.cluster.local,接下来我们通过 StatefulSet 来创建具体的 Elasticsearch 的 Pod 应用。

Kubernetes StatefulSet 允许我们为 Pod 分配一个稳定的标识和持久化存储,Elasticsearch 需要稳定的存储来保证 Pod 在重新调度或者重启后的数据依然不变,所以需要使用 StatefulSet 来管理 Pod。

创建Elasticsearch StatefulSet 服务

数据持久化

由于我们使用的阿里云的服务,这里我们需要修改两个地方,storageClassName采用阿里云提供的存储类型
第二个修改的地方就是需要的存储的大小storage;

  1. ......
  2. volumeClaimTemplates:
  3. - metadata:
  4. name: data
  5. labels:
  6. app: elasticsearch
  7. spec:
  8. accessModes: [ "ReadWriteOnce" ]
  9. storageClassName: alicloud-disk-essd
  10. resources:
  11. requests:
  12. storage: 100Gi
  13. ......

我们这里使用 volumeClaimTemplates 来定义持久化模板,Kubernetes 会使用它为 Pod 创建 PersistentVolume,设置访问模式为ReadWriteOnce,这意味着它只能被 mount 到单个节点上进行读写,然后最重要的是使用了一个 StorageClass 对象,这里我们就直接使用阿里云提供的存储类型。

我们指定了每个 PersistentVolume 的大小为 100GB,我们可以根据自己的实际需要进行调整该值。

创建statefulset

完整的 Elasticsearch StatefulSet 资源清单文件内容如下:

  1. apiVersion: apps/v1
  2. kind: StatefulSet
  3. metadata:
  4. name: es
  5. namespace: logging
  6. spec:
  7. serviceName: elasticsearch
  8. replicas: 3
  9. selector:
  10. matchLabels:
  11. app: elasticsearch
  12. template:
  13. metadata:
  14. labels:
  15. app: elasticsearch
  16. spec:
  17. initContainers:
  18. - name: increase-vm-max-map
  19. image: busybox
  20. command: ["sysctl", "-w", "vm.max_map_count=262144"]
  21. securityContext:
  22. privileged: true
  23. - name: increase-fd-ulimit
  24. image: busybox
  25. command: ["sh", "-c", "ulimit -n 65536"]
  26. securityContext:
  27. privileged: true
  28. - name: create
  29. image: busybox:1.28
  30. command: ['mkdir', '-p', '/usr/share/elasticsearch/data/nodes/']
  31. securityContext:
  32. runAsUser: 0
  33. volumeMounts:
  34. - mountPath: /usr/share/elasticsearch/data
  35. name: data
  36. - name: file-permissions
  37. image: busybox:1.28
  38. command: ['chown', '-R', '1000:1000', '/usr/share/elasticsearch/']
  39. securityContext:
  40. runAsUser: 0
  41. volumeMounts:
  42. - mountPath: /usr/share/elasticsearch/data
  43. name: data
  44. containers:
  45. - name: elasticsearch
  46. image: registry-intl.us-east-1.aliyuncs.com/roxe-public/elasticsearch:7.6.2
  47. ports:
  48. - name: rest
  49. containerPort: 9200
  50. - name: inter
  51. containerPort: 9300
  52. resources:
  53. limits:
  54. cpu: 1000m
  55. requests:
  56. cpu: 1000m
  57. volumeMounts:
  58. - name: data
  59. mountPath: /usr/share/elasticsearch/data
  60. env:
  61. - name: cluster.name
  62. value: k8s-logs
  63. - name: node.name
  64. valueFrom:
  65. fieldRef:
  66. fieldPath: metadata.name
  67. - name: cluster.initial_master_nodes
  68. value: "es-0"
  69. - name: discovery.zen.minimum_master_nodes
  70. value: "1"
  71. - name: discovery.seed_hosts
  72. value: "elasticsearch"
  73. - name: ES_JAVA_OPTS
  74. value: "-Xms512m -Xmx512m"
  75. - name: network.host
  76. value: "0.0.0.0"
  77. tolerations:
  78. - key: "node-role.kubernetes.io/master"
  79. operator: "Equal"
  80. value: ""
  81. effect: "NoSchedule"
  82. volumeClaimTemplates:
  83. - metadata:
  84. name: data
  85. labels:
  86. app: elasticsearch
  87. spec:
  88. accessModes: [ "ReadWriteOnce" ]
  89. storageClassName: alicloud-disk-essd
  90. resources:
  91. requests:
  92. storage: 100Gi
  • cluster.name:Elasticsearch 集群的名称,我们这里命名成 k8s-logs。node.name:节点的名称,通过 metadata.name 来获取。这将解析为 es-[0,1,2],取决于节点的指定顺序。
  • discovery.seed_hosts:此字段用于设置在 Elasticsearch 集群中节点相互连接的发现方法。由于我们之前配置的无头服务,我们的 Pod 具有唯一的 DNS 域es-[0,1,2].elasticsearch.logging.svc.cluster.local,因此我们相应地设置此变量。
  • discovery.zen.minimum_master_nodes:我们将其设置为(N/2) + 1,N是我们的群集中符合主节点的节点的数量。如果线上有3个 Elasticsearch 节点,因此我们将此值设置为2(向下舍入到最接近的整数)。
  • ES_JAVA_OPTS:这里我们设置为-Xms512m -Xmx512m,告诉JVM使用512 MB的最小和最大堆。应该根据群集的资源可用性和需求调整这些参数。

直接使用kubectl工具部署即可

  1. $ kubectl create -f elasticsearch-statefulset.yaml

添加成功后,可以看到logging命名空间下所有的资源

  1. $ kubectl get sts -n logging
  2. NAME READY AGE
  3. es 0/1 2m21s
  4. $ kubectl get pods -n logging
  5. NAME READY STATUS RESTARTS AGE
  6. es-0 0/1 Pending 0 2m31s

创建 Kibana 服务

Elasticsearch 集群启动成功了,接下来我们可以来部署 Kibana 服务,新建一个名为 kibana.yaml 的文件,对应的文件内容如下:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: kibana
  5. namespace: logging
  6. labels:
  7. app: kibana
  8. spec:
  9. ports:
  10. - port: 5601
  11. type: ClusterIP
  12. selector:
  13. app: kibana
  14. ---
  15. apiVersion: apps/v1
  16. kind: Deployment
  17. metadata:
  18. name: kibana
  19. namespace: logging
  20. labels:
  21. app: kibana
  22. spec:
  23. selector:
  24. matchLabels:
  25. app: kibana
  26. template:
  27. metadata:
  28. labels:
  29. app: kibana
  30. spec:
  31. containers:
  32. - name: kibana
  33. image: registry-intl.us-east-1.aliyuncs.com/123/kibana:7.6.2
  34. resources:
  35. limits:
  36. cpu: 1000m
  37. requests:
  38. cpu: 1000m
  39. env:
  40. - name: ELASTICSEARCH_HOSTS
  41. value: http://elasticsearch:9200
  42. ports:
  43. - containerPort: 5601
  44. ---
  45. apiVersion: extensions/v1beta1
  46. kind: Ingress
  47. metadata:
  48. name: kibana-ingress
  49. namespace: logging
  50. spec:
  51. tls:
  52. - hosts:
  53. - kibana.123.top
  54. rules:
  55. - host: kibana.123.top
  56. http:
  57. paths:
  58. - path: /
  59. backend:
  60. serviceName: kibana
  61. servicePort: 5601

上面我们定义了三个资源对象,Service 、Deployment和ingress,唯一需要注意的是我们使用 ELASTICSEARCH_HOSTS 这个环境变量来设置Elasticsearch 集群的端点和端口,直接使用 Kubernetes DNS 即可,此端点对应服务名称为 elasticsearch,由于是一个 headless service,所以该域将解析为 Elasticsearch Pod 的 IP 地址列表。

配置完成后,直接使用 kubectl 工具创建:

  1. $ kubectl create -f kibana.yaml
  2. service/kibana created
  3. deployment.apps/kibana created

创建完成后,可以查看 Kibana Pod 的运行状态:

  1. $ kubectl get pod,svc -n logging
  2. NAME READY STATUS RESTARTS AGE
  3. pod/es-0 1/1 Running 0 30m
  4. pod/es-1 1/1 Running 0 29m
  5. pod/es-2 1/1 Running 0 29m
  6. pod/kibana-6f7cdd4bb5-pk6x7 1/1 Running 0 5m
  7. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  8. service/elasticsearch ClusterIP None <none> 9200/TCP,9300/TCP 87m
  9. service/kibana ClusterIP 192.168.118.25 <none> 5601/TCP 5m

如果部署过程中出现imagePullBackOff的情况,可以手动拉下镜像保存到本地镜像仓库,然后替换kibana.yaml镜像即可。

如果 Pod 已经是 Running 状态了,证明应用已经部署成功了,然后可以通过 ingress 域名 来访问 Kibana 这个服务,如果看到如下界面证明 Kibana 已经成功部署到了 Kubernetes集群之中。

image.png

部署 Fluentd

image.png
Fluentd 是一个高效的日志聚合器,是用 Ruby 编写的,并且可以很好地扩展。对于大部分企业来说,Fluentd 足够高效并且消耗的资源相对较少,另外一个工具Fluent-bit更轻量级,占用资源更少,但是插件相对 Fluentd 来说不够丰富,所以整体来说,Fluentd 更加成熟,使用更加广泛,所以我们这里也同样使用 Fluentd 来作为日志收集工具。

工作原理

Fluentd 通过一组给定的数据源抓取日志数据,处理后(转换成结构化的数据格式)将它们转发给其他服务,比如 Elasticsearch、对象存储等等。Fluentd 支持超过300个日志存储和分析服务,所以在这方面是非常灵活的。

主要运行步骤如下:

  • 首先 Fluentd 从多个日志源获取数据;
  • 结构化并且标记这些数据;
  • 然后根据匹配的标签将数据发送到多个目标服务去。

image.png

配置

一般来说我们是通过一个配置文件来告诉 Fluentd 如何采集、处理数据的,下面简单和大家介绍下 Fluentd 的配置方法。

日志源配置

比如我们这里为了收集 Kubernetes 节点上的所有容器日志,就需要做如下的日志源配置:

  1. <source>
  2. @id fluentd-containers.log
  3. @type tail # Fluentd 内置的输入方式,其原理是不停地从源文件中获取新的日志。
  4. path /var/log/containers/*.log # 挂载的服务器Docker容器日志地址
  5. pos_file /var/log/es-containers.log.pos
  6. tag raw.kubernetes.* # 设置日志标签
  7. read_from_head true
  8. <parse> # 多行格式化成JSON
  9. @type multi_format # 使用 multi-format-parser 解析器插件
  10. <pattern>
  11. format json # JSON 解析器
  12. time_key time # 指定事件时间的时间字段
  13. time_format %Y-%m-%dT%H:%M:%S.%NZ # 时间格式
  14. </pattern>
  15. <pattern>
  16. format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
  17. time_format %Y-%m-%dT%H:%M:%S.%N%:z
  18. </pattern>
  19. </parse>
  20. </source>

上面配置部分参数说明如下:

id:表示引用该日志源的唯一标识符,该标识可用于进一步过滤和路由结构化日志数据
type:Fluentd 内置的指令,tail 表示 Fluentd 从上次读取的位置通过 tail 不断获取数据,另外一个是 http 表示通过一个 GET 请求来收集数据。
path:tail 类型下的特定参数,告诉 Fluentd 采集 /var/log/containers 目录下的所有日志,这是 docker 在 Kubernetes 节点上用来存储运行容器 stdout 输出日志数据的目录。
pos_file:检查点,如果 Fluentd 程序重新启动了,它将使用此文件中的位置来恢复日志数据收集。
tag:用来将日志源与目标或者过滤器匹配的自定义字符串,Fluentd 匹配源/目标标签来路由日志数据。

路由配置

上面是日志源的配置,接下来看看如何将日志数据发送到 Elasticsearch:

  1. <match **>
  2. @id elasticsearch
  3. @type elasticsearch
  4. @log_level info
  5. include_tag_key true
  6. type_name fluentd
  7. host "#{ENV['OUTPUT_HOST']}"
  8. port "#{ENV['OUTPUT_PORT']}"
  9. logstash_format true
  10. <buffer>
  11. @type file
  12. path /var/log/fluentd-buffers/kubernetes.system.buffer
  13. flush_mode interval
  14. retry_type exponential_backoff
  15. flush_thread_count 2
  16. flush_interval 5s
  17. retry_forever
  18. retry_max_interval 30
  19. chunk_limit_size "#{ENV['OUTPUT_BUFFER_CHUNK_LIMIT']}"
  20. queue_limit_length "#{ENV['OUTPUT_BUFFER_QUEUE_LIMIT']}"
  21. overflow_action block
  22. </buffer>
  • match:标识一个目标标签,后面是一个匹配日志源的正则表达式,我们这里想要捕获所有的日志并将它们发送给 Elasticsearch,所以需要配置成**。
  • id:目标的一个唯一标识符。
  • type:支持的输出插件标识符,我们这里要输出到 Elasticsearch,所以配置成 elasticsearch,这是 Fluentd 的一个内置插件。
  • log_level:指定要捕获的日志级别,我们这里配置成 info,表示任何该级别或者该级别以上(INFO、WARNING、ERROR)的日志都将被路由到 Elsasticsearch。
  • host/port:定义 Elasticsearch 的地址,也可以配置认证信息,我们的 Elasticsearch 不需要认证,所以这里直接指定 host 和 port 即可。
  • logstash_format:Elasticsearch 服务对日志数据构建反向索引进行搜索,将 logstash_format 设置为 true,Fluentd 将会以 logstash 格式来转发结构化的日志数据。
  • Buffer: Fluentd 允许在目标不可用时进行缓存,比如,如果网络出现故障或者 Elasticsearch 不可用的时候。缓冲区配置也有助于降低磁盘的 IO。

过滤

由于 Kubernetes 集群中应用太多,也还有很多历史数据,所以我们可以只将某些应用的日志进行收集,比如我们只采集具有 logging=true 这个 Label 标签的 Pod 日志,这个时候就需要使用 filter,如下所示:

  1. # 删除无用的属性
  2. <filter kubernetes.**>
  3. @type record_transformer
  4. remove_keys $.docker.container_id,$.kubernetes.container_image_id,$.kubernetes.pod_id,$.kubernetes.namespace_id,$.kubernetes.master_url,$.kubernetes.labels.pod-template-hash
  5. </filter>
  6. # 只保留具有logging=true标签的Pod日志
  7. <filter kubernetes.**>
  8. @id filter_log
  9. @type grep
  10. <regexp>
  11. key $.kubernetes.labels.logging
  12. pattern ^true$
  13. </regexp>
  14. </filter>

安装

要收集 Kubernetes 集群的日志,直接用 DasemonSet 控制器来部署 Fluentd 应用,这样,它就可以从 Kubernetes 节点上采集日志,确保在集群中的每个节点上始终运行一个 Fluentd 容器。当然可以直接使用 Helm 来进行一键安装,为了能够了解更多实现细节,我们这里还是采用手动方法来进行安装。

fluentd配置文件

首先,我们通过 ConfigMap 对象来指定 Fluentd 配置文件,新建 fluentd-configmap.yaml 文件,文件内容如下:

  1. kind: ConfigMap
  2. apiVersion: v1
  3. metadata:
  4. name: fluentd-config
  5. namespace: logging
  6. data:
  7. system.conf: |-
  8. <system>
  9. root_dir /tmp/fluentd-buffers/
  10. </system>
  11. containers.input.conf: |-
  12. <source>
  13. @id fluentd-containers.log
  14. @type tail # Fluentd 内置的输入方式,其原理是不停地从源文件中获取新的日志。
  15. path /var/log/containers/*.log # 挂载的服务器Docker容器日志地址
  16. pos_file /var/log/es-containers.log.pos
  17. tag raw.kubernetes.* # 设置日志标签
  18. read_from_head true
  19. <parse> # 多行格式化成JSON
  20. @type multi_format # 使用 multi-format-parser 解析器插件
  21. <pattern>
  22. format json # JSON解析器
  23. time_key time # 指定事件时间的时间字段
  24. time_format %Y-%m-%dT%H:%M:%S.%NZ # 时间格式
  25. </pattern>
  26. <pattern>
  27. format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
  28. time_format %Y-%m-%dT%H:%M:%S.%N%:z
  29. </pattern>
  30. </parse>
  31. </source>
  32. # 在日志输出中检测异常,并将其作为一条日志转发
  33. # https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions
  34. <match raw.kubernetes.**> # 匹配tag为raw.kubernetes.**日志信息
  35. @id raw.kubernetes
  36. @type detect_exceptions # 使用detect-exceptions插件处理异常栈信息
  37. remove_tag_prefix raw # 移除 raw 前缀
  38. message log
  39. stream stream
  40. multiline_flush_interval 5
  41. max_bytes 500000
  42. max_lines 1000
  43. </match>
  44. <filter **> # 拼接日志
  45. @id filter_concat
  46. @type concat # Fluentd Filter 插件,用于连接多个事件中分隔的多行日志。
  47. key message
  48. multiline_end_regexp /\n$/ # 以换行符“\n”拼接
  49. separator ""
  50. </filter>
  51. # 添加 Kubernetes metadata 数据
  52. <filter kubernetes.**>
  53. @id filter_kubernetes_metadata
  54. @type kubernetes_metadata
  55. </filter>
  56. # 修复 ES 中的 JSON 字段
  57. # 插件地址:https://github.com/repeatedly/fluent-plugin-multi-format-parser
  58. <filter kubernetes.**>
  59. @id filter_parser
  60. @type parser # multi-format-parser多格式解析器插件
  61. key_name log # 在要解析的记录中指定字段名称。
  62. reserve_data true # 在解析结果中保留原始键值对。
  63. remove_key_name_field true # key_name 解析成功后删除字段。
  64. <parse>
  65. @type multi_format
  66. <pattern>
  67. format json
  68. </pattern>
  69. <pattern>
  70. format none
  71. </pattern>
  72. </parse>
  73. </filter>
  74. # 删除一些多余的属性
  75. <filter kubernetes.**>
  76. @type record_transformer
  77. remove_keys $.docker.container_id,$.kubernetes.container_image_id,$.kubernetes.pod_id,$.kubernetes.namespace_id,$.kubernetes.master_url,$.kubernetes.labels.pod-template-hash
  78. </filter>
  79. # 只保留具有logging=true标签的Pod日志
  80. <filter kubernetes.**>
  81. @id filter_log
  82. @type grep
  83. <regexp>
  84. key $.kubernetes.labels.logging
  85. pattern ^true$
  86. </regexp>
  87. </filter>
  88. ###### 监听配置,一般用于日志聚合用 ######
  89. forward.input.conf: |-
  90. # 监听通过TCP发送的消息
  91. <source>
  92. @id forward
  93. @type forward
  94. </source>
  95. output.conf: |-
  96. <match **>
  97. @id elasticsearch
  98. @type elasticsearch
  99. @log_level info
  100. include_tag_key true
  101. host elasticsearch
  102. port 9200
  103. logstash_format true
  104. logstash_prefix k8s # 设置 index 前缀为 k8s
  105. request_timeout 30s
  106. <buffer>
  107. @type file
  108. path /var/log/fluentd-buffers/kubernetes.system.buffer
  109. flush_mode interval
  110. retry_type exponential_backoff
  111. flush_thread_count 2
  112. flush_interval 5s
  113. retry_forever
  114. retry_max_interval 30
  115. chunk_limit_size 2M
  116. queue_limit_length 8
  117. overflow_action block
  118. </buffer>
  119. </match>

fluentd-daemonset配置

上面配置文件中我们只配置了 docker 容器日志目录,收集到数据经过处理后发送到 elasticsearch:9200 服务。
然后新建一个 fluentd-daemonset.yaml 的文件,文件内容如下:

  1. apiVersion: v1
  2. kind: ServiceAccount
  3. metadata:
  4. name: fluentd-es
  5. namespace: logging
  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: logging
  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
  52. namespace: logging
  53. labels:
  54. k8s-app: fluentd-es
  55. kubernetes.io/cluster-service: "true"
  56. addonmanager.kubernetes.io/mode: Reconcile
  57. spec:
  58. selector:
  59. matchLabels:
  60. k8s-app: fluentd-es
  61. template:
  62. metadata:
  63. labels:
  64. k8s-app: fluentd-es
  65. kubernetes.io/cluster-service: "true"
  66. # 此注释确保如果节点被驱逐,fluentd不会被驱逐,支持关键的基于 pod 注释的优先级方案。
  67. annotations:
  68. scheduler.alpha.kubernetes.io/critical-pod: ''
  69. spec:
  70. serviceAccountName: fluentd-es
  71. containers:
  72. - name: fluentd-es
  73. image: quay.io/fluentd_elasticsearch/fluentd:v3.0.1
  74. env:
  75. - name: FLUENTD_ARGS
  76. value: --no-supervisor -q
  77. resources:
  78. limits:
  79. memory: 500Mi
  80. requests:
  81. cpu: 100m
  82. memory: 200Mi
  83. volumeMounts:
  84. - name: varlog
  85. mountPath: /var/log
  86. - name: varlibdockercontainers
  87. mountPath: /var/lib/docker/containers
  88. readOnly: true
  89. - name: config-volume
  90. mountPath: /etc/fluent/config.d
  91. terminationGracePeriodSeconds: 30
  92. volumes:
  93. - name: varlog
  94. hostPath:
  95. path: /var/log
  96. - name: varlibdockercontainers
  97. hostPath:
  98. path: /var/lib/docker/containers
  99. - name: config-volume
  100. configMap:
  101. name: fluentd-config

安装fluentd

分别创建上面的 ConfigMap 对象和 DaemonSet:

  1. $ kubectl create -f fluentd-configmap.yaml
  2. configmap "fluentd-config" created
  3. $ kubectl create -f fluentd-daemonset.yaml
  4. serviceaccount "fluentd-es" created
  5. clusterrole.rbac.authorization.k8s.io "fluentd-es" created
  6. clusterrolebinding.rbac.authorization.k8s.io "fluentd-es" created
  7. daemonset.apps "fluentd-es" created

创建完成后,查看对应的 Pods 列表,检查是否部署成功:

  1. $ kubectl get pod -n logging
  2. NAME READY STATUS RESTARTS AGE
  3. es-0 1/1 Running 0 156m
  4. fluentd-es-lhjnr 1/1 Running 0 53s
  5. fluentd-es-tpmlk 1/1 Running 0 64s
  6. kibana-7dc465fbc8-hh95t 1/1 Running 0 75m

Fluentd 启动成功后,这个时候就可以发送日志到 ES 了。

测试

下面我们部署一个简单的测试应用, 新建 counter.yaml 文件,文件内容如下:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: counter
  5. spec:
  6. containers:
  7. - name: count
  8. image: busybox
  9. args: [/bin/sh, -c,
  10. 'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

该 Pod 只是简单将日志信息打印到 stdout,所以正常来说 Fluentd 会收集到这个日志数据,在 Kibana 中也就可以找到对应的日志数据了,使用 kubectl 工具创建该 Pod:

  1. $ kubectl create -f counter.yaml
  2. $ kubectl get pods
  3. NAME READY STATUS RESTARTS AGE
  4. counter 1/1 Running 0 9h

Pod 创建并运行后,回到 Kibana Dashboard 页面,点击左侧最下面的 management 图标,然后点击 Kibana 下面的 Index Patterns 开始导入索引数据:
image.png
在这里可以配置我们需要的 Elasticsearch 索引,前面 Fluentd 配置文件中我们采集的日志使用的是 logstash 格式,定义了一个 k8s 的前缀,所以这里只需要在文本框中输入k8s-*即可匹配到 Elasticsearch 集群中采集的 Kubernetes 集群日志数据,然后点击下一步,进入以下页面:
image.png
在该页面中配置使用哪个字段按时间过滤日志数据,在下拉列表中,选择@timestamp字段,然后点击Create index pattern
image.png

创建完成后,点击左侧导航菜单中的Discover,然后就可以看到一些直方图和最近采集到的日志数据了。
image.png
当我们把鼠标放到搜索栏时,会出现所有字段,我们点击某个需要的字段,就可以筛选我们需要的字段。
image.png

image.png

定时清理ES日志


镜像打包

set -o nounset set -o errexit set -o xtrace

max days of logs to keep, default=7

max_days_of_log=7

if [[ “$#” -gt 0 && $1 =~ ^[1-9][0-9]{0,2}$ ]];then max_days_of_log=$1 fi

echo -e “\n[INFO] rotate job starts, try to keep $max_days_of_log days of logs.”

curl elasticsearch:9200/_cat/indices? > /tmp/indices || \ { echo “[ERROR] Can not connect to elastic!”; exit 1; }

curr_days_of_log=$(cat /tmp/indices|grep k8s|wc -l)

curr_days_of_log=$((${curr_days_of_log}-2))

if [[ “$max_days_of_log” -gt “$curr_days_of_log” ]];then echo “[WARN] No need to rotate the ES indices!” exit 0 fi

first_day=$(date -d “$max_days_of_log days ago” +’%Y.%m.%d’)

rotate=$(cat /tmp/indices|grep k8s|cut -d’ ‘ -f3|cut -d’-‘ -f2|sort|sed -n “1,/$first_day/“p)

for day in $rotate;do curl -X DELETE elasticsearch:9200/k8s-$day done

echo -e “\n[INFO] Success to rotate the ES indices!” exit 0

  1. 上面脚本需要修改的地方就是替换es的连接地址elasticsearch:9200,另外一处需要修改的地方就是索引的名称,此处为索引是以"k8s开头"
  2. - **Dockerfile**
  3. ```css
  4. # Dockerfile for Rotating the indices in elastic of the EFK deployment
  5. #
  6. # @author: gjmzj
  7. # @repo: https://github.com/kubeasz/mirrorepo/es-index-rotator
  8. # @ref: https://github.com/easzlab/kubeasz/tree/master/dockerfiles/es-index-rotator
  9. FROM alpine:3.8
  10. COPY rotate.sh /bin/rotate.sh
  11. RUN echo "===> Installing essential tools..." && \
  12. apk --update add bash curl coreutils && \
  13. echo "===> Cleaning up cache..." && \
  14. rm -rf /var/cache/apk/* && \
  15. chmod +x /bin/rotate.sh
  16. CMD ["/bin/rotate.sh"]

此部分dockerfile不需要修改。

将修改过后的脚本和dockerfile放到同一个文件夹进行镜像打包,打包推送命令如下:

  1. docker build . -t registry-intl.us-east-1.aliyuncs.com/123/es-index-rotator:0.2.2
  2. docker push registry-intl.us-east-1.aliyuncs.com/123/es-index-rotator:0.2.2

部署定时任务容器

和es容器在同一个命名空间部署一个定时任务容器,每天1点三分定时删除最近20天以前的日志。

  1. apiVersion: batch/v1beta1
  2. kind: CronJob
  3. metadata:
  4. name: es-index-rotator
  5. namespace: logging
  6. spec:
  7. # 每天1点3分执行
  8. schedule: "3 1 */1 * *"
  9. jobTemplate:
  10. spec:
  11. template:
  12. spec:
  13. containers:
  14. - name: es-index-rotator
  15. image: registry-intl.us-east-1.aliyuncs.com/123/es-index-rotator:0.2.2
  16. # 保留最近20天日志
  17. command:
  18. - /bin/rotate.sh
  19. - "20"
  20. - "k8s" # fluented 默认创建的index形如'k8s-2021.08.01'
  21. restartPolicy: OnFailure
  22. concurrencyPolicy: Forbid
  23. successfulJobsHistoryLimit: 2
  24. failedJobsHistoryLimit: 1

[

](https://www.qikqiak.com/post/install-efk-stack-on-k8s/)