使用 fluentd 抓取 k8s 的组件日志并推送至 EFK 日志栈
2020-05-22 tech kubernetes log fluentd 14 mins 4934 字
以容器方式启动的组件,常常随着容器的停止而销毁,给平时定位问题带来了一定的难度。
这一篇记录如何使用 fluentd 采集 kubernetes 的容器组件的日志,并推送到es中。本文适用的 kubernetes 版本为 v1.10。
日志类型
k8s系统组件有两种类型:在容器中运行的和不在容器中运行的。例如:
- 在容器中运行的 kube-scheduler 和 kube-proxy。
- 不在容器中运行的 kubelet 和容器运行时(例如 Docker)
kubelet和docker的日志都由 journald 进行管理,定位问题时问题不大。
容器组件日志位置
文件夹 /var/log/containers
下包含了所有组件的日志链接。
进入这个文件夹我们可以看到一家人的组件排的整整齐齐:
$ ls /var/log/containers
calico-node-xxx.log
coredns-xxx.log
kube-apiserver-xxx.log
kube-controller-manager-xxx.log
kube-proxy-xxx.log
kube-scheduler-xxx.log
traefik-ingress-controller-xxx.log
实际上这是个软链接,后边还跟了两层软链接:
-> /var/log/pods
-> /var/lib/docker/containers/
更多需要考虑的
想象一下我们使用 kibana 查看日志,在满屏的日志中,我们需要区分这些日志:
- 不同的节点
- 同一节点的不同组件
- 同一组件的不同容器
其中同一节点不同组件,我们可以在 fluentd 中对 日志来源打 tag 标记。可以参考 fluentd 官方帮助 https://docs.fluentd.org/input/tail
不同容器可以通过 fluentd 配置把文件名附到日志字段。
节点名也同样如此。那么fluentd 容器如何获得节点名呢?但是这有个窍门,为 fluentd 容器增加一个环境变量:
env:
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
更多实用的变量可参考官方:https://kubernetes.cn/zh/docs/tasks/inject-data-application/environment-variable-expose-pod-information/
开始
基本思路是fluentd 的配置文件使用 configmap 进行保存。
然后将所有组件的日志和configmap都挂在到 fluentd 容器中。
创建 fluentd 配置文件
注意 fluentd 要配置 read_from_head true
,否则会有组件启动日志薛定谔采集的问题。
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-for-k8s-component
data:
fluent.conf: |-
<source>
@type tail
read_from_head true
path /var/log/containers/kube-apiserver-*.log
path_key file_name
pos_file /log/fluentd/pos/pos-kube-apiserver.log
tag runtime-kube-apiserver
limit_recently_modified 7d
multiline_flush_interval 1s
<parse>
@type json
time_key time
time_type string
time_format %Y-%m-%dT%H:%M:%S.%N%z
</parse>
</source>
... ...
# 依葫芦画瓢把一系列的日志搞进来
... ...
# 为每条记录附加节点字段
<filter runtime-*>
@type record_transformer
<record>
nodename "#{ENV['MY_NODE_NAME']}"
</record>
</filter>
# es地址
<match runtime-*>
@type forward
<server>
host 1.2.3.4
port 12345
weight 50
</server>
<server>
host 1.2.3.4
port 12346
weight 50
</server>
<buffer tag,time>
@type file
path /var/log/td-agent/buffer/docker
timekey 3600
timekey_wait 0s
timekey_use_utc false
flush_mode interval
flush_interval 10s
chunk_limit_size 20M
</buffer>
</match>
创建 fluentd damenset
需要注意的有:
- 采集容器在每个节点上都有,所以要避免 calico 分配IP导致路由表难看,直接使用 host 的IP即可。
- 注意污染和容忍的配置,要容忍所有污染,否则一旦新增一个污染就取不到这个节点的日志了。
- 镜像尽可能选新的
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-for-k8s-component
labels:
k8s-app: fluentd-for-k8s-component
version: v1
spec:
selector:
matchLabels:
k8s-app: fluentd-for-k8s-component
template:
metadata:
labels:
k8s-app: fluentd-for-k8s-component
version: v1
spec:
containers:
- name: fluentd
image: "fluent/fluentd:v1.4"
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
env:
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: fluentd-pos
mountPath: /log/fluentd/pos
- name: fluentd-config
mountPath: /fluentd/etc/fluent.conf
subPath: fluent.conf
- name: var-log-containers
mountPath: /var/log/containers
readOnly: true
- name: var-log-pods
mountPath: /var/log/pods
readOnly: true
- name: var-log-real
mountPath: /var/lib/docker/containers
readOnly: true
securityContext:
runAsUser: 0
terminationGracePeriodSeconds: 30
volumes:
- name: fluentd-config
configMap:
name: fluentd-for-k8s-component
defaultMode: 420
- name: fluentd-pos
hostPath:
path: /app/fluentd/pos
type: DirectoryOrCreate
- name: var-log-containers
hostPath:
path: /var/log/containers
type: DirectoryOrCreate
- name: var-log-pods
hostPath:
path: /var/log/pods
type: DirectoryOrCreate
- name: var-log-real
hostPath:
path: /var/lib/docker/containers
type: DirectoryOrCreate
hostNetwork: true
tolerations:
- operator: Exists
effect: NoSchedule
- operator: Exists
effect: NoExecute
- key: CriticalAddonsOnly
operator: Exists