日志收集架构
Kubernetes集群本身不提供收集日志的解决方案,目前基于ELK日志收集的方案主要有三种
- 在节点运行一个agent收集日志
- 在Pod中包含一个sidecar容器来收集日志 (可以参考下面的文章)
- 直接通过应用程序将日志信息推送到采集后端 (例kafka,es等)
节点级别的日志记录
节点日志采集
通过在每个节点上运行一个日志收集的Agent来采集数据,日志采集agent是一种专用工具,用于将日志数据推送到统一的后端。一般来说,这种agent用一个容器来运行,可以访问该节点上所有应用程序容器的日志文件所在目录
由于这种agent必须在每个节点上运行,所以需要使用DaemonSet控制器运行该应用程序。在节点运行agent,不需要对节点上运行的应用程序进行更改,对应用程序没有任何侵入性,但是这种方法也仅仅适合于日志输出到stdout和stderr的应用程序日志。容器情况下就会把这些日志输出到宿主机上的一个JSON文件之中,同样也可以通过docker log
或者kubectl logs
查看对应的日志,但是如果是直接写入到文件中,则并不是输出到stdout中并不能通过命令行查看到
优点: 每个Node仅需部署一个日志收集程序,资源消耗少,对应用无入侵
因为使用stdout的方式,只需要在宿主机上收集每个容器中的日志/var/log和/var/lib/docker/containers (目录要根据docker info中的dir进行修改)
容器会将日志转化为JSON格式,是docker中的配置起的作用。
配置如下
[root@k8s-01 ~]# docker info ... Logging Driver: json-file ...
集群级日志架构
以sidecar容器收集日志,如果我们的应用程序的日志是输出到容器中的某个日志文件的话,使用agent节点的方式是无法采集到的
对于容器输出到文件的方式可以直接在Pod中启动另外一个sidecar容器(例如filebeat)
使用sidecar有两种方式
- 通过sidecar容器将应用程序中的日志进行重定向打印,把日志输出到stdout或者stderr
- 使用sidecar运行日志采集agent
我个人认为,第一种方案和第二种方案都有优缺点,首先说第一种方案缺点是要重定向日志,并且如果日志分的比较细,比如Tomcat启动日志和业务日志是分开的,用第一种就没有优势。第二种缺点就是浪费资源,因为每次启动除了业务日志,还有sidecar日志要跟随业务启动。
第一种方式
使用sidecar将应用程序日志重定向就可以在接入前面agent采集自动获取日志信息,不需要在配置其他。虽然这个方式可以通过sidecar将应用程序日志转化为stdout输出,但是有一个明显的缺点,就是日志不仅会在原容器文件保留,还会通过stdout输出后占用磁盘空间,这样无形中就增加了一倍磁盘空间
下面实例是通过sidecar将容器本地的日志重定向到stdout中
apiVersion: v1 kind: Pod metadata: name: counter spec: containers: - name: count image: busybox args: - /bin/sh - -c - > i=0; while true; do echo "$(date) INFO $i" >> /var/log/1.log; #模拟Tomcat将日志追加到文件中 i=$((i+1)); sleep 1; done volumeMounts: - name: varlog mountPath: /var/log - name: count-log-1 image: busybox args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log'] #使用tail -f 打印日志 volumeMounts: - name: varlog mountPath: /var/log volumes: - name: varlog emptyDir: {}
创建完成后,我们就可以通过stdout查看到日志
[root@k8s-01 test]# kubectl logs counter count-log-1 Fri May 1 18:33:26 UTC 2020 INFO 0 Fri May 1 18:33:27 UTC 2020 INFO 1 Fri May 1 18:33:28 UTC 2020 INFO 2 Fri May 1 18:33:29 UTC 2020 INFO 3 Fri May 1 18:33:30 UTC 2020 INFO 4 Fri May 1 18:33:31 UTC 2020 INFO 5 Fri May 1 18:33:32 UTC 2020 INFO 6
这时候我们可以在对应Pod宿主机上找到日志文件路径
[root@k8s-04 2db38c31797ef8830486739d7098ee81261fd36b32b6c6959a0f1cf014ab7f68]# pwd /var/lib/docker/containers/2db38c31797ef8830486739d7098ee81261fd36b32b6c6959a0f1cf014ab7f68 #前面的日志路径,对应的就是容器ID [root@k8s-04 2db38c31797ef8830486739d7098ee81261fd36b32b6c6959a0f1cf014ab7f68]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2db38c31797e busybox "/bin/sh -c 'tail -n…" 6 minutes ago Up 6 minutes k8s_count-log-1_counter_default_8c341c58-ec9c-4266-a190-779d514fadf0_0 77b4c4933cd1 busybox "/bin/sh -c 'i=0; wh…" 6 minutes ago Up 6 minutes k8s_count_counter_default_8c341c58-ec9c-4266-a190-779d514fadf0_0 0c7b3e8d7e48 registry.cn-beijing.aliyuncs.com/abcdocker/pause-amd64:3.1 "/pause" 6 minutes ago Up 6 minutes k8s_POD_counter_default_8c341c58-ec9c-4266-a190-779d514fadf0_0
第二种方式
如果觉得第一种过于麻烦,会保留多个日志则可以使用以sidecar运行日志收集直接将日志推送到es中。
但是sidecar容器中运行日志采集代理程序会导致大量资源消耗,因为每个Pod都需要启动2个容器,业务容器和采集代理程序,另外无法使用kubectl logs命令来访问这些日志,因为他们不受kubelet控制
下面是Kubernetes官方的一个fluentd的配置文件实例,使用Configmap保存配置文件,通过Pod中启动sidecar容器代理采集agent将日志进行收集然后上传,当然我们也可以使用logstash、filebeat等代替
apiVersion: v1 kind: ConfigMap metadata: name: fluentd-config data: fluentd.conf: | <source> type tail format none path /var/log/1.log pos_file /var/log/1.log.pos tag count.format1 </source> <source> type tail format none path /var/log/2.log pos_file /var/log/2.log.pos tag count.format2 </source> <match **> type google_cloud </match>
上面的配置文件是收集原文件/var/log/1.log
和/var/log/2.log
的日志数据,然后通过google_cloud这个插件将数据推送到Stackdriver后端去 (也可以推送到es和kafka)
上面是创建了fluentd的配置文件,下面在Deployment应用中添加2个容器即可
apiVersion: v1 kind: Pod metadata: name: counter spec: containers: - name: count image: busybox args: - /bin/sh - -c - > i=0; while true; do echo "$i: $(date)" >> /var/log/1.log; echo "$(date) INFO $i" >> /var/log/2.log; i=$((i+1)); sleep 1; done volumeMounts: - name: varlog mountPath: /var/log - name: count-agent image: k8s.gcr.io/fluentd-gcp:1.30 env: - name: FLUENTD_ARGS value: -c /etc/fluentd-config/fluentd.conf volumeMounts: - name: varlog mountPath: /var/log - name: config-volume mountPath: /etc/fluentd-config volumes: - name: varlog emptyDir: {} - name: config-volume configMap: name: fluentd-config
Pod创建完成后,容器count-agent就会从count容器中的日志进行收集然后上传。
通过应用程序收集日志
除了上面通过宿主机运行agent采集和在容器中创建sidecar外,还有一种方案就是在代码层面实现,通过代码层面直接将日志输出到对应的后端存储
更多内容可以参阅官方文档https://v1-15.docs.kubernetes.io/zh/docs/concepts/cluster-administration/logging/
传统架构图
本次使用stdout的方式获取日志
- Kubernetes 1.18
- Elasticsearch 7.6.2
- Fluend 3.0.1
- Kibana 7.6.2
本次环境项目地址: https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/fluentd-elasticsearch
本次使用镜像均同步至阿里云
docker.elastic.co/elasticsearch/elasticsearch:7.6.2 docker.elastic.co/kibana/kibana:7.6.2 quay.io/fluentd_elasticsearch/fluentd:v3.0.1 同步如下 (有需要直接pull tag标记,或者将yaml中镜像修改下方即可) registry.cn-beijing.aliyuncs.com/abcdocker/fluentd:v3.0.1 registry.cn-beijing.aliyuncs.com/abcdocker/elasticsearch:7.6.2 registry.cn-beijing.aliyuncs.com/abcdocker/kibana:7.6.2
安装Elasticsearch
如果已经有ES集群,这里可以不用安装。或者您不想在Kubernetes集群内安装ES,可以直接参考下面的文章。直接在宿主机上安装,和在kubernetes效果一样的。
Kubernetes StatefulSet允许我们为Pod分配一个稳定的标识和持久化存储,Elasticsearch需要稳定的存储来保证Pod在重新调度或者重启后的数据依旧不变,所以要试用StatefulSet来管理Pod
我这里使用NFS进行模式后端存储,当然使用Ceph或者其他存储也是可以的
#创建命名空间 kubectl create namespace elk #使用StatefulSet创建ES集群 wget down.i4t.com/kubernetes/es/es.yaml kubectl apply -f es.yaml
创建es svc
#这里的service使用无头服务 wget down.i4t.com/kubernetes/es/es-svc.yaml kubect apply -f es-svc.yaml
es.yaml配置文件参数解释
[root@k8s-01 elk]# cat es.yaml apiVersion: apps/v1 kind: StatefulSet #使用statefulset创建Pod metadata: name: es-test #pod名称,使用statefulSet创建的Pod是有序号有顺序的 namespace: elk #命名空间 spec: serviceName: elasticsearch #与svc相关联,这可以确保使用以下DNS地址访问Statefulset中的每个pod (es-cluster-[0,1,2].elasticsearch.elk.svc.cluster.local) replicas: 3 #副本数量 selector: matchLabels: app: elasticsearch #和pod template配置的labels相匹配 template: metadata: labels: app: elasticsearch spec: #nodeSelector: #如果需要匹配几台对应的节点可以添加nodeSelect # es: log initContainers: #容器初始化前的操作 - name: increase-vm-max-map image: busybox imagePullPolicy: IfNotPresent command: ["sysctl", "-w", "vm.max_map_count=262144"] #添加mmap计数限制,太低可能造成内存不足的错误 securityContext: #仅应用到指定的容器上,并且不会影响Volume privileged: true #运行特权容器 - name: increase-fd-ulimit image: busybox imagePullPolicy: IfNotPresent command: ["sh", "-c", "ulimit -n 65536"] #修改文件描述符最大数量 securityContext: privileged: true containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2 imagePullPolicy: IfNotPresent ports: - name: rest containerPort: 9200 #Restapi - name: inter containerPort: 9300 #节点通信 resources: limits: cpu: 1000m #限制cpu (虚拟环境可以修改小一点) requests: cpu: 1000m volumeMounts: #容器挂载 - name: data #名称 mountPath: /usr/share/elasticsearch/data #挂载点 env: #设置相关环境变量 - name: cluster.name #es集群的名称 value: k8s-logs - name: node.name #节点名称 valueFrom: #通过匹配上面metadata.name来当节点的名称 fieldRef: fieldPath: metadata.name - name: cluster.initial_master_nodes #初始化集群引导,需要是pod的名称 value: "es-test-0,es-test-1,es-test-2" - name: discovery.zen.minimum_master_nodes #节点数量,高可用集群至少3个主节点,其中2个至少不仅投票节点。https://blog.csdn.net/zuodaoyong/article/details/104719508 value: "2" - name: discovery.seed_hosts #用于es集群中节点互相连接发现 value: "elasticsearch" - name: ES_JAVA_OPTS #设置Java的内存参数 value: "-Xms512m -Xmx512m" - name: network.host value: "0.0.0.0" volumeClaimTemplates: #定义持久化模板 - metadata: name: data labels: app: elasticsearch spec: accessModes: [ "ReadWriteOnce" ] #访问模式 storageClassName: managed-nfs-storage #storageclass对象 (这里我使用nfs创建的storageclass,当然也可以修改为ceph或者本地存储) resources: requests: storage: 30Gi #pv资源大小
es镜像比较大,如果下载比较慢可以通过我的镜像直接load导入
wget http://down.i4t.com/kubernetes/es/es.tar
相关参数文档,请阅读官网
https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html
文章中引用了NFS和storageclass,需要的可以前往
StorageClass 配置文档
NFS按照参考
查看Pod IP测试Pod是否正常
[root@k8s-01 elk]# kubectl get pod -n elk -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES es-test-0 1/1 Running 7 4h5m 172.30.120.2 k8s-01 es-test-1 1/1 Running 0 6h20m 172.30.144.3 k8s-05 es-test-2 1/1 Running 0 6h19m 172.30.248.3 k8s-04
我们还可以查看一下pv和pvc是否正常创建和绑定
[root@k8s-01 ~]# kubectl get pv,pvc -n elk NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE persistentvolume/pvc-3d622c74-74d5-4f75-934c-bfcdfc22f647 30Gi RWO Delete Bound elk/data-es-abcdocker-0 managed-nfs-storage 23h persistentvolume/pvc-c83d1aad-031d-4b5b-90fd-1467f4fd9997 30Gi RWO Delete Bound elk/data-es-abcdocker-1 managed-nfs-storage 23h persistentvolume/pvc-f85a272a-3e4f-4686-b15f-208008412320 30Gi RWO Delete Bound elk/data-es-abcdocker-2 managed-nfs-storage 23h NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/data-es-abcdocker-0 Bound pvc-3d622c74-74d5-4f75-934c-bfcdfc22f647 30Gi RWO managed-nfs-storage 23h persistentvolumeclaim/data-es-abcdocker-1 Bound pvc-c83d1aad-031d-4b5b-90fd-1467f4fd9997 30Gi RWO managed-nfs-storage 23h persistentvolumeclaim/data-es-abcdocker-2 Bound pvc-f85a272a-3e4f-4686-b15f-208008412320 30Gi RWO managed-nfs-storage 23h [root@k8s-01 ~]#
使用无头服务创建service
解释: Headless Service 无头服务 该服务不会分配ClusterIP,也不会通过kube-proxy做反向代理和负载均衡。而是通过DNS提供稳定的网络ID来访问,DNS会将headless service的后端直接解析为PodIP列表,主要提供StatefulSet使用
[root@k8s-01 elk]# cat es-svc.yaml kind: Service apiVersion: v1 metadata: name: elasticsearch namespace: elk labels: app: elasticsearch spec: selector: app: elasticsearch clusterIP: None ports: - port: 9200 name: rest - port: 9300 name: inter-node
svc参数解释
指定匹配标签app=elasticsearch,当我们将elasticsearch statefulset与此服务关联时,服务将返回标签带有app=elasticsearch的Elasticsearch Pod的DNS A记录
查看创建完毕后的pod和svc
[root@k8s-01 elk]# kubectl apply -f es-svc.yaml [root@k8s-01 elk]# kubectl get all -n elk NAME READY STATUS RESTARTS AGE pod/es-test-0 1/1 Running 7 4h8m pod/es-test-1 1/1 Running 0 6h23m pod/es-test-2 1/1 Running 0 6h22m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/elasticsearch ClusterIP None 9200/TCP,9300/TCP 12m NAME READY AGE statefulset.apps/es 3/3 6h24m
es设置了无头服务和一个稳定的域名.elasticsearch.elk.svc.cluster.local
接下来可以获取到当前Pod节点的信息
[root@k8s-01 elk]# curl 172.30.248.3:9200 { "name" : "es-test-2", "cluster_name" : "k8s-logs", "cluster_uuid" : "UKHhz9ECQaCU3FN-t_sNKA", "version" : { "number" : "7.6.2", "build_flavor" : "default", "build_type" : "docker", "build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f", "build_date" : "2020-03-26T06:34:37.794943Z", "build_snapshot" : false, "lucene_version" : "8.4.0", "minimum_wire_compatibility_version" : "6.8.0", "minimum_index_compatibility_version" : "6.0.0-beta1" }, "tagline" : "You Know, for Search" }
查看集群状态
$ curl 172.30.248.3:9200/_cluster/state?pretty |less { "cluster_name" : "k8s-logs", "cluster_uuid" : "4IXFctNTT0-KONKlD2K-0Q", "version" : 40, "state_uuid" : "GUTDz1iKQayYyI_4AHHGIA", "master_node" : "SSOXZ5n5TFSpL12UtHhX-w", "blocks" : { }, "nodes" : { "LA8ToV02TFOrFHGwVyzpeg" : { "name" : "es-abcdocker-1", "ephemeral_id" : "FYJWZKTrTg6aPRXaefFM8w", "transport_address" : "10.244.0.6:9300", "attributes" : { "ml.machine_memory" : "4128923648", "ml.max_open_jobs" : "20", "xpack.installed" : "true" } }, "tBVCVr4ATzO5juMtgdTtSA" : { "name" : "es-abcdocker-0", "ephemeral_id" : "65TM1xFXRaGZISaNaV5mFA", "transport_address" : "10.244.2.11:9300", "attributes" : { "ml.machine_memory" : "4128923648", "ml.max_open_jobs" : "20", "xpack.installed" : "true" } }, "SSOXZ5n5TFSpL12UtHhX-w" : { "name" : "es-abcdocker-2", "ephemeral_id" : "rQp2Bo0-RtWqG2scbGsc_w", "transport_address" : "10.244.1.5:9300", "attributes" : { "ml.machine_memory" : "4128923648", "xpack.installed" : "true", "ml.max_open_jobs" : "20" } } }, "metadata" : { "cluster_uuid" : "4IXFctNTT0-KONKlD2K-0Q", ... #可以查看到当前集群的数据结构
通过_cat/nodes?v
查看集群中所有的节点,这里我们可以看到master节点是es-test-1 (es-test-1为pod名称)
[root@k8s-01 ~]# curl 10.244.1.5:9200/_cat/nodes?v ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name 10.244.0.6 13 97 3 0.01 0.05 0.07 dilm - es-abcdocker-1 10.244.2.11 10 97 2 0.00 0.03 0.00 dilm - es-abcdocker-0 10.244.1.5 7 97 2 0.24 0.17 0.11 dilm * es-abcdocker-2
高可用性(HA)群集至少需要三个主节点,其中至少两个不是仅投票节点。即使其中一个节点发生故障,这样的群集也将能够选举一个主节点。
创建Kibana服务
和ES一样,kibana也是可以不在kubernetes集群中安装,同样也可以安装在集群外,集群外安装可以查阅之前的ELK文章,这里不过多说明。本次环境在kubernetes中安装
[root@k8s-01 elk]# cat kibana.yaml apiVersion: v1 kind: Service metadata: name: kibana namespace: elk labels: app: kibana spec: ports: - port: 5601 type: NodePort selector: app: kibana --- apiVersion: apps/v1 kind: Deployment metadata: name: kibana namespace: elk 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: 200m requests: cpu: 200m env: - name: ELASTICSEARCH_HOSTS value: http://elasticsearch:9200 #由于是一个headless service,所以该域将解析为3个 Elasticsearch Pod的IP地址列表 ports: - containerPort: 5601
创建Kibana
[root@k8s-01 elk]# kubectl apply -f kibana.yaml service/kibana created deployment.apps/kibana created
查看创建Pod状态
[root@k8s-01 elk]# kubectl get pod,svc -n elk NAME READY STATUS RESTARTS AGE pod/es-test-0 1/1 Running 0 61m pod/es-test-1 1/1 Running 0 62m pod/es-test-2 1/1 Running 0 62m pod/kibana-8686d977d7-t9dc5 1/1 Running 0 74m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/elasticsearch ClusterIP None 9200/TCP,9300/TCP 74m service/kibana NodePort 10.254.252.9 5601:31882/TCP 74m
访问Kibana
任意集群节点:31882
Fluentd
采集日志客户端采用Fluentd来进行采集日志,使用ConfigMap的形式来存储Fluentd的配置。
这次环境我们日志格式为stdout的方式,日志输出到每个宿主机的/var/log/containers/
下面,所以我们Fluentd安装使用DaemonSet方式即可。
# 参数解释 kind: ConfigMap apiVersion: v1 metadata: name: fluentd-config namespace: elk #flunentd所属命名空间 data: system.conf: |- <system> root_dir /tmp/fluentd-buffers/ #系统日志信息 </system> containers.input.conf: |- #容器输入日志配置信息 #日志源配置 <source> @id fluentd-containers.log #表示引用该日志源的唯一标志符 @type tail #Fluentd内置命令,tail表示Fluentd从上次读取的位置通过tail不断获取数据 path /var/log/containers/*.log #日志路径 (docker stdout默认输出路径,如果修改docker引擎配置这里也需要修改) pos_file /var/log/es-containers.log.pos #检查点,将从文件中获取上一次检查数据的时间 time_format %Y-%m-%dT%H:%M:%S.%NZ #时间格式 localtime tag raw.kubernetes.* #用来将日志源与目标或者过滤器匹配的自定义字符串,Fluentd匹配源/目标标签 format json #JSON解析器 read_from_head true </source> 路由配置 <match **> #标示一个目标,后面是一个匹配日志源的正则表达式,获取所有日志*.*并且全部发送给ES @id elasticsearch #ID唯一标示符 @type elasticsearch #支持的输出插件标示符 (本选项为内置命令,除了输出到es,还可以输出到kafka、logstash等) @log_level info #日志级别 include_tag_key true host elasticsearch #es地址 (由于我们es为无头服务,地址就是elasticsearch) port 9200 #es端口 logstash_format true # es服务队日志数据构建反索引进行搜索,设置为true,fluentd将会以logstash格式来转发构建的日志数据 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>
创建Fluentd配置文件,文件内容如下
cat >> fluentd-configmap.yaml <<EOF kind: ConfigMap apiVersion: v1 metadata: name: fluentd-config namespace: elk 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 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 /^(?.+) (?stdout|stderr) [^ ]* (?.*)$/ time_format %Y-%m-%dT%H:%M:%S.%N%:z </pattern> </parse> </source> # 在日志输出中检测异常,并将其作为一条日志转发 # https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions <match raw.kubernetes.**> # 匹配tag为raw.kubernetes.**日志信息 @id raw.kubernetes @type detect_exceptions # 使用detect-exceptions插件处理异常栈信息 remove_tag_prefix raw # 移除 raw 前缀 message log stream stream #stdout输出 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> # 添加 Kubernetes metadata 数据 <filter kubernetes.**> @id filter_kubernetes_metadata #将获取到的日志转化,添加pod信息、命名空间,labels标签等 @type kubernetes_metadata </filter> # JSON格式转换,如果原来是json格式,将把json格式内容从新进行转化 # 插件地址:https://github.com/repeatedly/fluent-plugin-multi-format-parser <filter kubernetes.**> @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> # 只保留具有logging=true标签的Pod日志,如果添加只会收集带有logging=true标签的pod #<filter kubernetes.**> # @id filter_log # @type grep # <regexp> # key $.kubernetes.labels.logging # pattern ^true$ # </regexp> #</filter> ###### 监听配置,一般用于日志聚合用 ###### forward.input.conf: |- # 监听通过TCP发送的消息 <source> @id forward @type forward </source> output.conf: |- <match **> @id elasticsearch @type elasticsearch @log_level info include_tag_key true host elasticsearch port 9200 logstash_format true logstash_prefix k8s # 设置 index 前缀为 k8s request_timeout 30s <buffer> @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> EOF
创建fluentd configmap,检查configmap状态
[root@k8s-01 ~]# kubectl apply -f fluentd-configmap.yaml configmap/fluentd-config created [root@k8s-01 ~]# kubectl get configmap -n elk NAME DATA AGE fluentd-config 4 7s
接下来创建fluentd-daemonset
cat >fluentd-ds.yaml <<EOF apiVersion: v1 kind: ServiceAccount metadata: name: fluentd-es namespace: elk 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: elk apiGroup: "" roleRef: kind: ClusterRole name: fluentd-es apiGroup: "" --- apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-es namespace: elk labels: k8s-app: fluentd-es spec: selector: matchLabels: k8s-app: fluentd-es template: metadata: labels: k8s-app: fluentd-es kubernetes.io/cluster-service: "true" # 此注释确保如果节点被驱逐,fluentd不会被驱逐,支持关键的基于 pod 注释的优先级方案。 annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: serviceAccountName: fluentd-es containers: - name: fluentd-es image: quay.io/fluentd_elasticsearch/fluentd:v3.0.1 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 #docker日志存放路径 (默认情况下) readOnly: true - name: config-volume mountPath: /etc/fluent/config.d #nodeSelector: #这里可以设置需要打标签的节点,我们目前只有3台都需要采集,所以可以注释这个标签 # beta.kubernetes.io/fluentd-ds-ready: "true" tolerations: - operator: Exists terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers - name: config-volume configMap: name: fluentd-config EOF
接下来创建
kubectl apply -f fluentd-ds.yaml
我们等待一会可以在es中看到fluentd收集的日志索引
[root@k8s-01 ~]# curl 10.244.0.6:9200/_cat/indices green open .kibana_task_manager_1 X54N-pZATVmvH7ZbWTgQRw 1 1 2 2 40.3kb 20.1kb green open .apm-agent-configuration NDxFtJcnTl-RjZEpRYnGIA 1 1 0 0 566b 283b green open .kibana_1 DhJEAzOGTHqyOOLCoflmzw 1 1 10 1 62.1kb 31kb green open k8s-2020.07.15 fqUmr9QeRMq4O29eZHV8LA 1 1 42 0 618.1kb 312.9kb #其中命名为k8s-*的为我们下一步需要在kibana中添加的选项
访问kibana
[root@k8s-01 ~]# kubectl get svc -n elk NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE elasticsearch ClusterIP None 9200/TCP,9300/TCP 2d2h kibana NodePort 10.105.190.52 5601:31978/TCP 2d2h #在浏览器中访问任意节点IP:31978端口
进入到kibana中,我们配置索引
设置-->索引-->配置索引
这里已经获取到我们在es中查看的索引了,点击下一步
在该页面中配置使用哪个字段按时间过滤日志数据,在下拉列表中,选择@timestamp字段,然后点击Create index pattern
,创建完成后,点击左侧导航菜单中的Discover,然后就可以看到一些直方图和最近采集到的日志数据了
添加完成后,我们点击左边Discover就可以看到数据
这边还可以筛选过滤一下,只看几个字段的数据
测试数据
cat<<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx:alpine name: nginx ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx spec: selector: app: nginx type: NodePort ports: - protocol: TCP port: 80 targetPort: 80 nodePort: 30001
接下来我们直接访问宿主机的IP:30001端口,查看nginx日志
[root@k8s-01 ~]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 443/TCP 2d6h nginx NodePort 10.102.132.199 80:30001/TCP 2d6h
看完了,不错。打算部署一下看效果