Kubernetes中部署ELK Stack日志收集平台

运维 系统运维
ELK是Elasticsearch、Logstash、Kibana三大开源框架首字母大写简称。市面上也被称为Elastic Stack。其中Elasticsearch是一个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架。

[[411979]]

主要内容

  • 1 ELK概念
  • 2 K8S需要收集哪些日志
  • 3 ELK Stack日志方案
  • 4 容器中的日志怎么收集
  • 5 部署操作步骤

准备环境

一套正常运行的k8s集群,kubeadm安装部署或者二进制部署即可

1 ELK概念

ELK是Elasticsearch、Logstash、Kibana三大开源框架首字母大写简称。市面上也被称为Elastic Stack。其中Elasticsearch是一个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架。像类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用Elasticsearch作为底层支持框架,可见Elasticsearch提供的搜索能力确实强大,市面上很多时候我们简称Elasticsearch为es。Logstash是ELK的中央数据流引擎,用于从不同目标(文件/数据存储/MQ)收集的不同格式数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elasticsearch/kafka等)。Kibana可以将elasticsearch的数据通过友好的页面展示出来,提供实时分析的功能。

通过上面对ELK简单的介绍,我们知道了ELK字面意义包含的每个开源框架的功能。市面上很多开发只要提到ELK能够一致说出它是一个日志分析架构技术栈总称,但实际上ELK不仅仅适用于日志分析,它还可以支持其它任何数据分析和收集的场景,日志分析和收集只是更具有代表性。并非唯一性。我们本教程主要也是围绕通过ELK如何搭建一个生产级的日志分析平台来讲解ELK的使用。

官方网站:https://www.elastic.co/cn/products/

图片

2 日志管理平台

在过往的单体应用时代,我们所有组件都部署到一台服务器中,那时日志管理平台的需求可能并没有那么强烈,我们只需要登录到一台服务器通过shell命令就可以很方便的查看系统日志,并快速定位问题。随着互联网的发展,互联网已经全面渗入到生活的各个领域,使用互联网的用户量也越来越多,单体应用已不能够支持庞大的用户的并发量,尤其像中国这种人口大国。那么将单体应用进行拆分,通过水平扩展来支持庞大用户的使用迫在眉睫,微服务概念就是在类似这样的阶段诞生,在微服务盛行的互联网技术时代,单个应用被拆分为多个应用,每个应用集群部署进行负载均衡,那么如果某项业务发生系统错误,开发或运维人员还是以过往单体应用方式登录一台一台登录服务器查看日志来定位问题,这种解决线上问题的效率可想而知。日志管理平台的建设就显得极其重要。通过Logstash去收集每台服务器日志文件,然后按定义的正则模板过滤后传输到Kafka或redis,然后由另一个Logstash从KafKa或redis读取日志存储到elasticsearch中创建索引,最后通过Kibana展示给开发者或运维人员进行分析。这样大大提升了运维线上问题的效率。除此之外,还可以将收集的日志进行大数据分析,得到更有价值的数据给到高层进行决策。

3 K8S中的ELK Stack日志采集方案

图片

方案一:Node上部署一个日志收集程序 使用DaemonSet的方式去给每一个node上部署日志收集程序logging-agent 然后使用这个agent对本node节点上的/var/log和/var/lib/docker/containers/两个目录下的日志进行采集 或者把Pod中容器日志目录挂载到宿主机统一目录上,这样进行收集

图片

因为使用stdout的方式,只需要在宿主机上收集每个容器中的日志/var/log和/var/lib/docker/containers (目录要根据docker info中的dir进行修改,容器会将日志转化为JSON格式,是docker中的配置起的作用)

方案二:Pod中附加专用日志收集的容器 每个运行应用程序的Pod中增加一个日志收集容器,使用emtyDir共享日志目录让日志收集程序读取到。

图片

方案三:应用程序直接推送日志 这个方案需要开发在代码中修改直接把应用程序直接推送到远程的存储上,不再输入出控制台或者本地文件了,使用不太多,超出Kubernetes范围

图片

4 K8S中日志采集应该注意的问题

问题1: 一个K8S集群我们需要收集哪些日志?

这里只是以主要收集日志为例:

K8S系统的组件日志

K8S Cluster里面部署的应用程序日志 -标准输出 -日志文件

问题2: 我们需要收集的日志在哪里,如何去收集当下比较常用的runtime?

docker和containerd的容器日志及相关参数

问题3: 是否需要做日志的标准化规范

基本格式

采用json格式输出,为了方便采集,日志应该使用一行输出

定义

所有运行在k8s集群内的业务应用所输出的所有日志。

必要字段

  • level

日志等级字段,字段值统一为小写。

  • debug :指明细致的事件信息,对调试应用最有用。
  • info:指明描述信息,从粗粒度上描述了应用运行过程
  • warn:指明潜在的有害状况。
  • error: 指明错误事件,但应用可能还能继续运行
  • fatal:指明非常严重的错误事件,可能会导致应用终止执行。

日志等级会作为日志采集和日志报警的依据。在生产系环境日志采集器只会采集INFO以上等级的日志,日志报警会拉取error级别以上进行告警。

  • msg

日志主要内容。

  • remote_ip

请求的源ip

  • project

服务名加版本号,如srv-oc-task@1.0.0-cb5d0af

  • time

日志打印时间,统一使用 UTC 时间格式。

  • func

日志所在代码里的目录和行数

可选字段(可选字段按需使用,日志采集后会解析下列字段)

  • request_url

该请求的url

  • status

该请求返回http状态码

  • cost

本次请求花费时间,单位为ms

  • method

该条请求的http方法

  • _id

日志id

ingress日志

统一使用nginx-ingress暴露业务,因此在集群初始化之后,部署的nginx-ingress需要规定一下字段,采用json格式输出,为了方便采集,日志应该使用一行输出。

  • 字段要求
  1. log-format-upstream: '{"@timestamp":"$time_iso8601","host":"$server_addr""clientip" 
  2.     : "$remote_addr""size" : "$body_bytes_sent" ,"requesttime":"$request_time","upstremtime":"$upstream_response_time","upstremhost":"$upstream_addr","httphost":"$host","referer":"$http_referer","xff":"$http_x_forwarded_for","agent":"$http_user_agent","clientip":"$remote_addr","request":"$request","uri":"$uri","status":"$status"}' 

5 部署操作步骤

本次部署采集方案采用为方案一:在Node上部署一个日志收集程序

支持的cpu架构

  • amd64
  • arm64

支持k8s的runtime类别

  • docker
  • containerd

ELK Stack各组件版本

  • elasticsearch:7.9.3
  • filebeat:7.9.3
  • kibana:7.9.3
  • logstash:7.9.3

支持的k8s版本

  • v1.15.0+以上版本

本次部署的yaml见项目地址: https://github.com/sunsharing-note/ELK-Stack

5.1 单节点方式部署ES

单节点部署ELK的方法较简单,可以参考下面的yaml编排文件,整体就是创建一个es,然后创建kibana的可视化展示,创建一个es的service服务,然后通过ingress的方式对外暴露域名访问

  • 首先,编写es的yaml,这里部署的是单机版,在k8s集群内中,通常当日志量每天超过20G以上的话,还是建议部署在k8s集群外部,支持分布式集群的架构,这里使用的是有状态部署的方式,并且使用的是hostpath才持久化,因此需要给node打上es的落盘节点标签,才能运行该yaml
  1. #需要提前给es落盘节点打上标签 
  2. kubectl label node xxxx es=data 
  3. [root@k8s-master fek]# cat es.yaml 
  4. --- 
  5. apiVersion: v1 
  6. kind: Service 
  7. metadata: 
  8.   name: elasticsearch-logging 
  9.   namespace: kube-system 
  10.   labels: 
  11.     k8s-app: elasticsearch-logging 
  12.     kubernetes.io/cluster-service: "true" 
  13.     addonmanager.kubernetes.io/mode: Reconcile 
  14.     kubernetes.io/name"Elasticsearch" 
  15. spec: 
  16.   ports: 
  17.   - port: 9200 
  18.     protocol: TCP 
  19.     targetPort: db 
  20.   selector: 
  21.     k8s-app: elasticsearch-logging 
  22. --- 
  23. # RBAC authn and authz 
  24. apiVersion: v1 
  25. kind: ServiceAccount 
  26. metadata: 
  27.   name: elasticsearch-logging 
  28.   namespace: kube-system 
  29.   labels: 
  30.     k8s-app: elasticsearch-logging 
  31.     kubernetes.io/cluster-service: "true" 
  32.     addonmanager.kubernetes.io/mode: Reconcile 
  33. --- 
  34. kind: ClusterRole 
  35. apiVersion: rbac.authorization.k8s.io/v1 
  36. metadata: 
  37.   name: elasticsearch-logging 
  38.   labels: 
  39.     k8s-app: elasticsearch-logging 
  40.     kubernetes.io/cluster-service: "true" 
  41.     addonmanager.kubernetes.io/mode: Reconcile 
  42. rules: 
  43. - apiGroups: 
  44.   - "" 
  45.   resources: 
  46.   - "services" 
  47.   - "namespaces" 
  48.   - "endpoints" 
  49.   verbs: 
  50.   - "get" 
  51. --- 
  52. kind: ClusterRoleBinding 
  53. apiVersion: rbac.authorization.k8s.io/v1 
  54. metadata: 
  55.   namespace: kube-system 
  56.   name: elasticsearch-logging 
  57.   labels: 
  58.     k8s-app: elasticsearch-logging 
  59.     kubernetes.io/cluster-service: "true" 
  60.     addonmanager.kubernetes.io/mode: Reconcile 
  61. subjects: 
  62. - kind: ServiceAccount 
  63.   name: elasticsearch-logging 
  64.   namespace: kube-system 
  65.   apiGroup: "" 
  66. roleRef: 
  67.   kind: ClusterRole 
  68.   name: elasticsearch-logging 
  69.   apiGroup: "" 
  70. --- 
  71. # Elasticsearch deployment itself 
  72. apiVersion: apps/v1 
  73. kind: StatefulSet #使用statefulset创建Pod 
  74. metadata: 
  75.   name: elasticsearch-logging #pod名称,使用statefulSet创建的Pod是有序号有顺序的 
  76.   namespace: kube-system  #命名空间 
  77.   labels: 
  78.     k8s-app: elasticsearch-logging 
  79.     kubernetes.io/cluster-service: "true" 
  80.     addonmanager.kubernetes.io/mode: Reconcile 
  81.     srv: srv-elasticsearch 
  82. spec: 
  83.   serviceName: elasticsearch-logging #与svc相关联,这可以确保使用以下DNS地址访问Statefulset中的每个pod (es-cluster-[0,1,2].elasticsearch.elk.svc.cluster.local
  84.   replicas: 1 #副本数量,单节点 
  85.   selector: 
  86.     matchLabels: 
  87.       k8s-app: elasticsearch-logging #和pod template配置的labels相匹配 
  88.   template: 
  89.     metadata: 
  90.       labels: 
  91.         k8s-app: elasticsearch-logging 
  92.         kubernetes.io/cluster-service: "true" 
  93.     spec: 
  94.       serviceAccountName: elasticsearch-logging 
  95.       containers: 
  96.       - image: docker.io/library/elasticsearch:7.9.3 
  97.         name: elasticsearch-logging 
  98.         resources: 
  99.           # need more cpu upon initialization, therefore burstable class 
  100.           limits: 
  101.             cpu: 1000m 
  102.             memory: 2Gi 
  103.           requests: 
  104.             cpu: 100m 
  105.             memory: 500Mi 
  106.         ports: 
  107.         - containerPort: 9200 
  108.           name: db 
  109.           protocol: TCP 
  110.         - containerPort: 9300 
  111.           name: transport 
  112.           protocol: TCP 
  113.         volumeMounts: 
  114.         - name: elasticsearch-logging 
  115.           mountPath: /usr/share/elasticsearch/data/   #挂载点 
  116.         env: 
  117.         - name"NAMESPACE" 
  118.           valueFrom: 
  119.             fieldRef: 
  120.               fieldPath: metadata.namespace 
  121.         - name"discovery.type"  #定义单节点类型 
  122.           value: "single-node" 
  123.         - name: ES_JAVA_OPTS #设置Java的内存参数,可以适当进行加大调整 
  124.           value: "-Xms512m -Xmx2g"  
  125.       volumes: 
  126.       - name: elasticsearch-logging 
  127.         hostPath: 
  128.           path: /data/es/ 
  129.       nodeSelector: #如果需要匹配落盘节点可以添加nodeSelect 
  130.         es: data 
  131.       tolerations: 
  132.       - effect: NoSchedule 
  133.         operator: Exists 
  134.       # Elasticsearch requires vm.max_map_count to be at least 262144. 
  135.       # If your OS already sets up this number to a higher value, feel free 
  136.       # to remove this init container. 
  137.       initContainers: #容器初始化前的操作 
  138.       - name: elasticsearch-logging-init 
  139.         image: alpine:3.6 
  140.         command: ["/sbin/sysctl""-w""vm.max_map_count=262144"] #添加mmap计数限制,太低可能造成内存不足的错误 
  141.         securityContext:  #仅应用到指定的容器上,并且不会影响Volume 
  142.           privileged: true #运行特权容器 
  143.       - name: increase-fd-ulimit 
  144.         image: busybox 
  145.         imagePullPolicy: IfNotPresent 
  146.         command: ["sh""-c""ulimit -n 65536"] #修改文件描述符最大数量 
  147.         securityContext: 
  148.           privileged: true 
  149.       - name: elasticsearch-volume-init #es数据落盘初始化,加上777权限 
  150.         image: alpine:3.6 
  151.         command: 
  152.           - chmod 
  153.           - -R 
  154.           - "777" 
  155.           - /usr/share/elasticsearch/data/ 
  156.         volumeMounts: 
  157.         - name: elasticsearch-logging 
  158.           mountPath: /usr/share/elasticsearch/data/ 
  • 使用刚才编写好的yaml文件创建Elasticsearch,然后检查是否启动,如下所示能看到一个elasticsearch-0 的pod副本被创建,正常运行;如果不能正常启动可以使用kubectl describe查看详细描述,排查问题
  1. [root@k8s-master fek]# kubectl get pod -n kube-system 
  2. NAME                        READY   STATUS             RESTARTS   AGE 
  3. coredns-5bd5f9dbd9-95flw    1/1     Running            0          17h 
  4. elasticsearch-0             1/1     Running            1          16m 
  • 然后,需要部署一个Kibana来对搜集到的日志进行可视化展示,使用Deployment的方式编写一个yaml,seivice中使用的是nodeport 25601端口对外进行暴露访问,直接引用了es,也可以选择使用ingress进行暴露
  1. [root@k8s-master fek]# cat kibana.yaml 
  2. --- 
  3. apiVersion: v1 
  4. kind: Service 
  5. metadata: 
  6.   name: kibana 
  7.   namespace: kube-system 
  8.   labels: 
  9.     k8s-app: kibana 
  10.     kubernetes.io/cluster-service: "true" 
  11.     addonmanager.kubernetes.io/mode: Reconcile 
  12.     kubernetes.io/name"Kibana" 
  13.     srv: srv-kibana 
  14. spec: 
  15.   type: NodePort #采用nodeport方式进行暴露,端口默认为25601 
  16.   ports: 
  17.   - port: 5601 
  18.     nodePort: 25601 
  19.     protocol: TCP 
  20.     targetPort: ui 
  21.   selector: 
  22.     k8s-app: kibana 
  23. --- 
  24. apiVersion: apps/v1 
  25. kind: Deployment 
  26. metadata: 
  27.   name: kibana 
  28.   namespace: kube-system 
  29.   labels: 
  30.     k8s-app: kibana 
  31.     kubernetes.io/cluster-service: "true" 
  32.     addonmanager.kubernetes.io/mode: Reconcile 
  33.     srv: srv-kibana 
  34. spec: 
  35.   replicas: 1 
  36.   selector: 
  37.     matchLabels: 
  38.       k8s-app: kibana 
  39.   template: 
  40.     metadata: 
  41.       labels: 
  42.         k8s-app: kibana 
  43.       annotations: 
  44.         seccomp.security.alpha.kubernetes.io/pod: 'docker/default' 
  45.     spec: 
  46.       containers: 
  47.       - name: kibana 
  48.         image: docker.io/kubeimages/kibana:7.9.3 #该镜像支持arm64和amd64两种架构 
  49.         resources: 
  50.           # need more cpu upon initialization, therefore burstable class 
  51.           limits: 
  52.             cpu: 1000m 
  53.           requests: 
  54.             cpu: 100m 
  55.         env: 
  56.           - name: ELASTICSEARCH_HOSTS 
  57.             value: http://elasticsearch-logging:9200 
  58.         ports: 
  59.         - containerPort: 5601 
  60.           name: ui 
  61.           protocol: TCP 
  62. --- 
  63. apiVersion: extensions/v1beta1 
  64. kind: Ingress 
  65. metadata: 
  66.   name: kibana 
  67.   namespace: kube-system 
  68. spec: 
  69.   rules: 
  70.   - host: kibana.ctnrs.com 
  71.     http: 
  72.       paths: 
  73.       - path: / 
  74.         backend: 
  75.           serviceName: kibana 
  76.           servicePort: 5601 
  • 使用刚才编写好的yaml创建kibana,可以看到最后生成了一个kibana-b7d98644-lshsz的pod,并且正常运行
  1. [root@k8s-master fek]# kubectl apply -f kibana.yaml 
  2. deployment.apps/kibana created 
  3. service/kibana created 
  4. [root@k8s-master fek]# kubectl get pod -n kube-system 
  5. NAME                        READY   STATUS             RESTARTS   AGE 
  6. coredns-5bd5f9dbd9-95flw    1/1     Running            0          17h 
  7. elasticsearch-0             1/1     Running            1          16m 
  8. kibana-b7d98644-48gtm       1/1     Running            1          17h 
  • 最后在浏览器中,输入http://(任意一节点的ip):25601,就会进入kibana的web界面,已设置了不需要进行登陆,当前页面都是全英文模式,可以修改上网搜一下修改配置文件的位置,建议使用英文版本
图片

5.2 Node上部署一个filebeat采集器采集k8s组件日志

  • es和kibana部署好了之后,我们如何采集pod日志呢,我们采用方案一的方式,是要在每一个node上中部署一个filebeat的采集器,采用的是7.9.3版本,除此之外我已经按照文中4小节里面的问题2中对docker或者containerd的runtime进行了标准的日志落盘
  1. [root@k8s-master fek]# cat filebeat.yaml 
  2. --- 
  3. apiVersion: v1 
  4. kind: ConfigMap 
  5. metadata: 
  6.   name: filebeat-config 
  7.   namespace: kube-system 
  8.   labels: 
  9.     k8s-app: filebeat 
  10. data: 
  11.   filebeat.yml: |- 
  12.     filebeat.inputs: 
  13.     - type: container 
  14.       paths: 
  15.         - /var/log/containers/*.log #这里是filebeat采集挂载到pod中的日志目录 
  16.       processors: 
  17.         - add_kubernetes_metadata: #添加k8s的字段用于后续的数据清洗 
  18.             host: ${NODE_NAME} 
  19.             matchers: 
  20.             - logs_path: 
  21.                 logs_path: "/var/log/containers/" 
  22.     #output.kafka:  #如果日志量较大,es中的日志有延迟,可以选择在filebeat和logstash中间加入kafka 
  23.     #  hosts: ["kafka-log-01:9092""kafka-log-02:9092""kafka-log-03:9092"
  24.     # topic: 'topic-test-log' 
  25.     #  version: 2.0.0 
  26.     output.logstash: #因为还需要部署logstash进行数据的清洗,因此filebeat是把数据推到logstash中 
  27.        hosts: ["logstash:5044"
  28.        enabled: true 
  29. --- 
  30. # Source: filebeat/templates/filebeat-service-account.yaml 
  31. apiVersion: v1 
  32. kind: ServiceAccount 
  33. metadata: 
  34.   name: filebeat 
  35.   namespace: kube-system 
  36.   labels: 
  37.     k8s-app: filebeat 
  38.  
  39. --- 
  40. # Source: filebeat/templates/filebeat-role.yaml 
  41. apiVersion: rbac.authorization.k8s.io/v1beta1 
  42. kind: ClusterRole 
  43. metadata: 
  44.   name: filebeat 
  45.   labels: 
  46.     k8s-app: filebeat 
  47. rules: 
  48. - apiGroups: [""] # "" indicates the core API group 
  49.   resources: 
  50.   - namespaces 
  51.   - pods 
  52.   verbs: 
  53.   - get 
  54.   - watch 
  55.   - list 
  56.  
  57. --- 
  58. # Source: filebeat/templates/filebeat-role-binding.yaml 
  59. apiVersion: rbac.authorization.k8s.io/v1beta1 
  60. kind: ClusterRoleBinding 
  61. metadata: 
  62.   name: filebeat 
  63. subjects: 
  64. - kind: ServiceAccount 
  65.   name: filebeat 
  66.   namespace: kube-system 
  67. roleRef: 
  68.   kind: ClusterRole 
  69.   name: filebeat 
  70.   apiGroup: rbac.authorization.k8s.io 
  71.  
  72. --- 
  73. # Source: filebeat/templates/filebeat-daemonset.yaml 
  74. apiVersion: apps/v1 
  75. kind: DaemonSet 
  76. metadata: 
  77.   name: filebeat 
  78.   namespace: kube-system 
  79.   labels: 
  80.     k8s-app: filebeat 
  81. spec: 
  82.   selector: 
  83.     matchLabels: 
  84.       k8s-app: filebeat 
  85.   template: 
  86.     metadata: 
  87.       labels: 
  88.         k8s-app: filebeat 
  89.     spec: 
  90.       serviceAccountName: filebeat 
  91.       terminationGracePeriodSeconds: 30 
  92.       containers: 
  93.       - name: filebeat 
  94.         image: docker.io/kubeimages/filebeat:7.9.3 #该镜像支持arm64和amd64两种架构 
  95.         args: [ 
  96.           "-c""/etc/filebeat.yml"
  97.           "-e","-httpprof","0.0.0.0:6060" 
  98.         ] 
  99.         #ports: 
  100.         #  - containerPort: 6060 
  101.         #    hostPort: 6068 
  102.         env: 
  103.         - name: NODE_NAME 
  104.           valueFrom: 
  105.             fieldRef: 
  106.               fieldPath: spec.nodeName 
  107.         - name: ELASTICSEARCH_HOST 
  108.           value: elasticsearch-logging 
  109.         - name: ELASTICSEARCH_PORT 
  110.           value: "9200" 
  111.         securityContext: 
  112.           runAsUser: 0 
  113.           # If using Red Hat OpenShift uncomment this: 
  114.           #privileged: true 
  115.         resources: 
  116.           limits: 
  117.             memory: 1000Mi 
  118.             cpu: 1000m 
  119.           requests: 
  120.             memory: 100Mi 
  121.             cpu: 100m 
  122.         volumeMounts: 
  123.         - name: config #挂载的是filebeat的配置文件 
  124.           mountPath: /etc/filebeat.yml 
  125.           readOnly: true 
  126.           subPath: filebeat.yml 
  127.         - name: data #持久化filebeat数据到宿主机上 
  128.           mountPath: /usr/share/filebeat/data 
  129.         - name: varlibdockercontainers #这里主要是把宿主机上的源日志目录挂载到filebeat容器中,如果没有修改docker或者containerd的runtime进行了标准的日志落盘路径,可以把mountPath改为/var/lib 
  130.           mountPath: /data/var/ 
  131.           readOnly: true 
  132.         - name: varlog #这里主要是把宿主机上/var/log/pods和/var/log/containers的软链接挂载到filebeat容器中 
  133.           mountPath: /var/log/ 
  134.           readOnly: true 
  135.         - name: timezone 
  136.           mountPath: /etc/localtime 
  137.       volumes: 
  138.       - name: config 
  139.         configMap: 
  140.           defaultMode: 0600 
  141.           name: filebeat-config 
  142.       - name: varlibdockercontainers 
  143.         hostPath: #如果没有修改docker或者containerd的runtime进行了标准的日志落盘路径,可以把path改为/var/lib 
  144.           path: /data/var/ 
  145.       - name: varlog 
  146.         hostPath: 
  147.           path: /var/log/ 
  148.       # data folder stores a registry of read status for all files, so we don't send everything again on a Filebeat pod restart 
  149.       - name: inputs 
  150.         configMap: 
  151.           defaultMode: 0600 
  152.           name: filebeat-inputs 
  153.       - name: data 
  154.         hostPath: 
  155.           path: /data/filebeat-data 
  156.           type: DirectoryOrCreate 
  157.       - name: timezone 
  158.         hostPath: 
  159.           path: /etc/localtime 
  160.       tolerations: #加入容忍能够调度到每一个节点 
  161.       - effect: NoExecute 
  162.         key: dedicated 
  163.         operator: Equal 
  164.         value: gpu 
  165.       - effect: NoSchedule 
  166.         operator: Exists 
  • 部署之后,检查是否成功创建,能看到两个命名为filebeat-xx的pod副本分别创建在两个nodes上
  1. [root@k8s-master elk]# kubectl apply -f filebeat.yaml 
  2. [root@k8s-master elk]# kubectl get pod -n kube-system 
  3. NAME                        READY   STATUS    RESTARTS   AGE 
  4. coredns-5bd5f9dbd9-8zdn5    1/1     Running   0          10h 
  5. elasticsearch-0             1/1     Running   1          13h 
  6. filebeat-2q5tz              1/1     Running   0          13h 
  7. filebeat-k6m27              1/1     Running   2          13h 
  8. kibana-b7d98644-tllmm       1/1     Running   0          10h 

5.3 增加logstash来对采集到的原始日志进行业务需要的清洗

  • 这里主要是结合业务需要和对日志的二次利用,grafana展示,所以加入了logstash进行日志的清洗,需要是对ingrss的字段类型进行了转换,业务服务日志进行了字段的变更和类型转换,大家可以根据自己的业务需求进行调整
  1. [root@k8s-master fek]# cat logstash.yaml 
  2. --- 
  3. apiVersion: v1 
  4. kind: Service 
  5. metadata: 
  6.   name: logstash 
  7.   namespace: kube-system 
  8. spec: 
  9.   ports: 
  10.   - port: 5044 
  11.     targetPort: beats 
  12.   selector: 
  13.     type: logstash 
  14.   clusterIP: None 
  15. --- 
  16. apiVersion: apps/v1 
  17. kind: Deployment 
  18. metadata: 
  19.   name: logstash 
  20.   namespace: kube-system 
  21. spec: 
  22.   selector: 
  23.     matchLabels: 
  24.       type: logstash 
  25.   template: 
  26.     metadata: 
  27.       labels: 
  28.         type: logstash 
  29.         srv: srv-logstash 
  30.     spec: 
  31.       containers: 
  32.       - image: docker.io/kubeimages/logstash:7.9.3 #该镜像支持arm64和amd64两种架构 
  33.         name: logstash 
  34.         ports: 
  35.         - containerPort: 5044 
  36.           name: beats 
  37.         command: 
  38.         - logstash 
  39.         - '-f' 
  40.         - '/etc/logstash_c/logstash.conf' 
  41.         env: 
  42.         - name"XPACK_MONITORING_ELASTICSEARCH_HOSTS" 
  43.           value: "http://elasticsearch-logging:9200" 
  44.         volumeMounts: 
  45.         - name: config-volume 
  46.           mountPath: /etc/logstash_c/ 
  47.         - name: config-yml-volume 
  48.           mountPath: /usr/share/logstash/config/ 
  49.         - name: timezone 
  50.           mountPath: /etc/localtime 
  51.         resources: #logstash一定要加上资源限制,避免对其他业务造成资源抢占影响 
  52.           limits: 
  53.             cpu: 1000m 
  54.             memory: 2048Mi 
  55.           requests: 
  56.             cpu: 512m 
  57.             memory: 512Mi 
  58.       volumes: 
  59.       - name: config-volume 
  60.         configMap: 
  61.           name: logstash-conf 
  62.           items: 
  63.           - key: logstash.conf 
  64.             path: logstash.conf 
  65.       - name: timezone 
  66.         hostPath: 
  67.           path: /etc/localtime 
  68.       - name: config-yml-volume 
  69.         configMap: 
  70.           name: logstash-yml 
  71.           items: 
  72.           - key: logstash.yml 
  73.             path: logstash.yml 
  74.  
  75. --- 
  76. apiVersion: v1 
  77. kind: ConfigMap 
  78. metadata: 
  79.   name: logstash-conf 
  80.   namespace: kube-system 
  81.   labels: 
  82.     type: logstash 
  83. data: 
  84.   logstash.conf: |- 
  85.     input { 
  86.       beats { 
  87.       port => 5044 
  88.       } 
  89.      } 
  90.      filter{  
  91.      # 处理ingress日志 
  92.      if [kubernetes][container][name] == "nginx-ingress-controller" { 
  93.             json { 
  94.        source => "message" 
  95.        target => "ingress_log" 
  96.      } 
  97.      if [ingress_log][requesttime] { 
  98.          mutate { 
  99.         convert => ["[ingress_log][requesttime]""float"
  100.     } 
  101.     } 
  102.      if [ingress_log][upstremtime] { 
  103.          mutate { 
  104.         convert => ["[ingress_log][upstremtime]""float"
  105.     } 
  106.     } 
  107.      if [ingress_log][status] { 
  108.          mutate { 
  109.         convert => ["[ingress_log][status]""float"
  110.     } 
  111.     } 
  112.      if  [ingress_log][httphost] and [ingress_log][uri] { 
  113.          mutate { 
  114.         add_field => {"[ingress_log][entry]" => "%{[ingress_log][httphost]}%{[ingress_log][uri]}"
  115.     } 
  116.          mutate{ 
  117.          split => ["[ingress_log][entry]","/"
  118.        } 
  119.          if [ingress_log][entry][1] { 
  120.          mutate{ 
  121.          add_field => {"[ingress_log][entrypoint]" => "%{[ingress_log][entry][0]}/%{[ingress_log][entry][1]}"
  122.          remove_field => "[ingress_log][entry]" 
  123.          } 
  124.          } 
  125.          else
  126.           mutate{ 
  127.           add_field => {"[ingress_log][entrypoint]" => "%{[ingress_log][entry][0]}/"
  128.           remove_field => "[ingress_log][entry]" 
  129.          } 
  130.          } 
  131.     } 
  132.     } 
  133.      # 处理以srv进行开头的业务服务日志 
  134.      if [kubernetes][container][name] =~ /^srv*/ { 
  135.        json { 
  136.        source => "message" 
  137.        target => "tmp" 
  138.      } 
  139.     if [kubernetes][namespace] == "kube-system" { 
  140.        drop{} 
  141.      } 
  142.        if [tmp][level] { 
  143.        mutate{ 
  144.        add_field => {"[applog][level]" => "%{[tmp][level]}"
  145.      } 
  146.        if [applog][level] == "debug"
  147.        drop{} 
  148.      } 
  149.      } 
  150.        if [tmp][msg]{ 
  151.       mutate{ 
  152.        add_field => {"[applog][msg]" => "%{[tmp][msg]}"
  153.      } 
  154.      } 
  155.        if [tmp][func]{ 
  156.       mutate{ 
  157.        add_field => {"[applog][func]" => "%{[tmp][func]}"
  158.      } 
  159.      } 
  160.       if [tmp][cost]{ 
  161.         if "ms" in [tmp][cost]{ 
  162.         mutate{ 
  163.           split => ["[tmp][cost]","m"
  164.           add_field => {"[applog][cost]" => "%{[tmp][cost][0]}"
  165.           convert => ["[applog][cost]""float"
  166.         } 
  167.         } 
  168.         else
  169.         mutate{ 
  170.         add_field => {"[applog][cost]" => "%{[tmp][cost]}"
  171.       } 
  172.       } 
  173.      } 
  174.       if [tmp][method]{ 
  175.       mutate{ 
  176.        add_field => {"[applog][method]" => "%{[tmp][method]}"
  177.      } 
  178.      } 
  179.       if [tmp][request_url]{ 
  180.       mutate{ 
  181.        add_field => {"[applog][request_url]" => "%{[tmp][request_url]}"
  182.      } 
  183.      } 
  184.        if [tmp][meta._id]{ 
  185.        mutate{ 
  186.        add_field => {"[applog][traceId]" => "%{[tmp][meta._id]}"
  187.      } 
  188.      } 
  189.        if [tmp][project] { 
  190.        mutate{ 
  191.        add_field => {"[applog][project]" => "%{[tmp][project]}"
  192.      } 
  193.      } 
  194.        if [tmp][time] { 
  195.        mutate{ 
  196.        add_field => {"[applog][time]" => "%{[tmp][time]}"
  197.      } 
  198.      } 
  199.        if [tmp][status] { 
  200.        mutate{ 
  201.        add_field => {"[applog][status]" => "%{[tmp][status]}"
  202.        convert => ["[applog][status]""float"
  203.      } 
  204.      } 
  205.      } 
  206.     mutate{ 
  207.       rename => ["kubernetes""k8s"
  208.       remove_field => "beat" 
  209.       remove_field => "tmp" 
  210.       remove_field => "[k8s][labels][app]" 
  211.     } 
  212.     } 
  213.     output
  214.       elasticsearch { 
  215.         hosts => ["http://elasticsearch-logging:9200"
  216.         codec => json 
  217.         index => "logstash-%{+YYYY.MM.dd}" #索引名称以logstash+日志进行每日新建 
  218.         } 
  219.       } 
  220. --- 
  221.  
  222. apiVersion: v1 
  223. kind: ConfigMap 
  224. metadata: 
  225.   name: logstash-yml 
  226.   namespace: kube-system 
  227.   labels: 
  228.     type: logstash 
  229. data: 
  230.   logstash.yml: |- 
  231.     http.host: "0.0.0.0" 
  232.     xpack.monitoring.elasticsearch.hosts: http://elasticsearch-logging:9200 

5.4 在kibana的web界面进行配置日志可视化

首先登录kibana界面之后,打开菜单中的stack management模块

图片

点开索引管理,可以发现,已经有采集到的日志索引了

图片

为避免es日志占用磁盘空间越来越大,因此我们可以根据业务需要增加一个索引生命周期策略,点击index lifecycle policites

图片

Policy name写为logstash-history-ilm-policy,不能随意更改,后续的模版中会引用

图片
图片

为了能够在kibana中能够discover查看日志,因此需要设置一个索引匹配,选择index patterns,然后创建

图片
图片
图片

由于我们是部署的单节点,因此创建的索引使用默认的索引模版会产生一个1副本,所以会发现索引都是yellow,解决办法如下

在菜单中打开,dev tools

图片

然后调用api进行更改,会把所有的索引副本数全部改为0

图片
  1. PUT _all/_settings 
  2.     "number_of_replicas": 0 

为了根本解决和链接索引生命周期策略,标准化日志字段中的map类型,因此我们需要修改默认的template

  1. PUT _template/logstash 
  2.     "order": 1, 
  3.     "index_patterns": [ 
  4.       "logstash-*" 
  5.     ], 
  6.     "settings": { 
  7.       "index": { 
  8.       "lifecycle" : { 
  9.           "name" : "logstash-history-ilm-policy" 
  10.         }, 
  11.         "number_of_shards""2"
  12.         "refresh_interval""5s"
  13.         "number_of_replicas" : "0" 
  14.       } 
  15.     }, 
  16.     "mappings": { 
  17.         "properties": { 
  18.           "@timestamp": { 
  19.             "type""date" 
  20.           }, 
  21.           "applog": { 
  22.             "dynamic"true
  23.             "properties": { 
  24.               "cost": { 
  25.                 "type""float" 
  26.               }, 
  27.               "func": { 
  28.                 "type""keyword" 
  29.               }, 
  30.               "method": { 
  31.                 "type""keyword" 
  32.               } 
  33.             } 
  34.           }, 
  35.           "k8s": { 
  36.             "dynamic"true
  37.             "properties": { 
  38.               "namespace": { 
  39.                 "type""keyword" 
  40.               }, 
  41.               "container": { 
  42.                 "dynamic"true
  43.                 "properties": { 
  44.                   "name": { 
  45.                     "type""keyword" 
  46.                   } 
  47.                 } 
  48.               }, 
  49.               "labels": { 
  50.                 "dynamic"true
  51.                 "properties": { 
  52.                   "srv": { 
  53.                     "type""keyword" 
  54.                   } 
  55.                 } 
  56.               } 
  57.             } 
  58.           }, 
  59.           "geoip": { 
  60.             "dynamic"true
  61.             "properties": { 
  62.               "ip": { 
  63.                 "type""ip" 
  64.               }, 
  65.               "latitude": { 
  66.                 "type""float" 
  67.               }, 
  68.               "location": { 
  69.                 "type""geo_point" 
  70.               }, 
  71.               "longitude": { 
  72.                 "type""float" 
  73.               } 
  74.             } 
  75.           } 
  76.       } 
  77.     }, 
  78.     "aliases": {} 
  79.   } 
图片

最后验证索引和discover

图片
图片

写在最后

日志采集只是业务可观测性中的一部分,并且对于日志不光有Elastic Stack,也有Loki、Splunk或者托管云上的日志收集方案等,条条大路通罗马,不管怎么做,最终到达效果即可,没有哪个方案绝对的好,只能是在什么业务场景最适合,最能反应出业务的问题,快速排查到业务上问题才是好的

本文转载自微信公众号「运维开发故事」,可以通过以下二维码关注。转载本文请联系运维开发故事公众号。

 

责任编辑:姜华 来源: 运维开发故事
相关推荐

2021-07-20 08:32:16

Kubernetes日志平台

2022-04-29 09:04:35

日志平台开发

2017-08-08 11:14:47

AzureKubernetes多容器应用程序

2023-08-31 07:46:54

KubernetesLoki

2023-10-16 07:39:02

ELKpod日志

2022-08-31 08:30:32

kubernetesMetalLB

2023-10-20 15:08:28

pod日志采集

2022-06-20 11:15:08

日志容器Kubernetes

2021-11-02 17:27:40

部署高可用Kubernetes

2024-03-11 00:01:00

PromtailLoki服务器

2024-04-11 09:45:31

2023-09-05 07:40:37

PythonSDKAPI

2017-03-07 10:37:26

微软Azure Stack

2020-05-18 14:55:34

监控系统架构技术

2021-01-18 09:08:44

树莓派Ceph开源

2023-09-13 08:56:51

2015-02-03 09:14:38

SDNWAN

2019-10-09 08:00:17

Kubernetes容器云平台

2024-02-23 14:57:40

点赞
收藏

51CTO技术栈公众号