Kubernetes EFK 日志收集
日志收集架构
Kubernetes集群本身不提供收集日志的解决方案,目前基于ELK日志收集的方案主要有三种
- 在节点运行一个agent收集日志
- 在Pod中包含一个sidecar容器来收集日志 (可以参考K8s容器日志实时收集FileBeat+ES+Kibana)
- 直接通过应用程序将日志信息推送到采集后端 (例kafka,es等)
1、节点级别的日志记录
节点日志采集
通过在每个节点上运行一个日志收集的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
...
2、集群级日志架构
以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个至少不仅投票节点。
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 配置文档 持久化存储 StorageClass
NFS按照参考 Kubernetes PV与PVC
查看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中安装ELK 二进制安装并收集nginx日志
[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