日志收集

Kubernetes 组件默认日志位置

kubernetes : /var/log/kubernetes
etcd : /var/log/messages

容器 : /var/log/containers/ -> /var/log/pods/ # 指向了这个位置

1
2
3
4
5
6
7
8
[root@k8s01 ~]# ll /var/log/kubernetes
total 86936
-rw-r--r--. 1 root root 9468454 Mar 23 15:09 kubelet.k8s01.root.log.ERROR.20210315-140844.24735
-rw-r--r--. 1 root root 59335295 Mar 23 15:09 kubelet.k8s01.root.log.INFO.20210315-140843.24735
-rw-r--r--. 1 root root 20200286 Mar 23 15:09 kubelet.k8s01.root.log.WARNING.20210315-140844.24735
lrwxrwxrwx. 1 root root 51 Mar 15 14:08 kubelet.ERROR -> kubelet.k8s01.root.log.ERROR.20210315-140844.24735
lrwxrwxrwx. 1 root root 50 Mar 15 14:08 kubelet.INFO -> kubelet.k8s01.root.log.INFO.20210315-140843.24735
lrwxrwxrwx. 1 root root 53 Mar 15 14:08 kubelet.WARNING -> kubelet.k8s01.root.log.WARNING.20210315-140844.24735

日志的需求

  1. 系统各组件的日志 : eg: kubelet、kube-proxy,docker 等日志
  2. 容器化运行的应用程序自身日志 : eg: Nginx 的运行日志
  3. k8s 内部各种 Event : eg: kubctl create pod 后,kubectl describe pod *** 的pod 的 Event信息

日志系统

Kubernetes 的日志系统在设计的时候,必须得独立于节点和 Pod 的生命周期,且保证日志数据可以实时采集到服务端,即完全独立于 Kubernetes 系统,使用自己的后端存储和查询工具

日志收集方式

一般有如下 四种方案来做日志收集:

log4

  1. 直接在应用程序中将日志信息推送到采集后端; (不推荐, 耦合严重。本身已有完善日志处理系统的公司可以采用)

log1

  1. 在节点上运行一个 Agent 来采集节点级别的日志; (使用 daemonset,比较常用,但只适合 容器内应用日志是标准输出场景,即应用把日志 输出的 stdout 和 stderr)

log2
3.当容器的日志只能输出到某些文件里的时候,我们可以通过一个 sidecar 容器把这些日志文件重新输出到 sidecarstdoutstderr 上,这样就能够继续使用第一种方案了; (Sidecar 容器读取日志文件,并重定向到自己的标准输出。会增加额外开销,cpu 内存,磁盘空间等,大规模集群,这些开销不可忽视,所以非特殊情况,一般推荐二和四)

log3

  1. 通过一个 sidecar 容器,直接把应用的日志文件发送到远程存储里面去。也就是相当于把方案二里的 logging agent,放在了应用 Pod 里。(应用还可以直接把日志输出到固定的文件里而不是 stdout,logging-agent 还可以使用 fluentd,后端存储还可以是 ElasticSearch。只不过, fluentd 的输入源,变成了应用的日志文件。一般来说,我们会把 fluentd 的输入源配置保存在一个 ConfigMap 里)

sidecar 指的就是我们可以在一个 Pod 中,启动一个辅助容器,来完成一些独立于主进程(主容器)之外的工作。

sidecar 与 fluentd 方案示例

Pod 只有一个容器,它会把日志输出到容器里的 /var/log/1.log2.log 这两个文件里

log_to_file.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
volumes:
- name: varlog
emptyDir: {}

因为非 stdoutstderr, kubectl logs 命令是看不到应用的任何日志的。所以方案二不能使用。
使用方案三 : Pod 添加两个 sidecar 容器,分别将上述两个日志文件里的内容重新以 stdoutstderr 的方式输出出来 tail -f

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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-log-1 # 此容器将主容器的 /var/log/1.log 通过 tail 输出到 stdout 和 stderr
image: busybox
args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log-2
image: busybox
args: [/bin/sh, -c, 'tail -n+1 -f /var/log/2.log']
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}

方案四

fluentd-from-config.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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>

声明一个 Fluentd 容器作为 sidecar,专门负责将应用生成的 1.log2.log 转发到 ElasticSearch 当中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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

这个 sidecar 容器很可能会消耗较多的资源,甚至拖垮应用容器。并且,由于日志还是没有输出到 stdout 上,所以你通过 kubectl logs 是看不到任何日志输出的。

推荐方案

使用 EFK 进行日志收集和管理,通过 Fluentd 将日志导入到 Elasticsearch 中,然后通过 Kibana 查看所有的日志。

使用 EFLK 收集容器内文件日志: Elasticsearch + Filebeat + Logstash + Kibana

EFK 部署(基于 Helm)

简介

项目地址:

1
2
3
https://github.com/bitnami/charts/tree/main/bitnami/elasticsearch

https://github.com/fluent/helm-charts/tree/main/charts/fluent-bit # fluent-bit

基于 helm 制作的 yaml 文件放于以下路径:

1
2
3
https://raw.githubusercontent.com/itswl/shellbackup/main/k8s_config/EFK/es.yaml

https://raw.githubusercontent.com/itswl/shellbackup/main/k8s_config/EFK/fluent-bit.yaml

开启了 prometheus 拉取, 先装的 es, ns 为 elasticsearch 方便管理。

使用

EFK
EFK
EFK
EFK
EFK
EFK

EFLK 部署(Filebeat + Kafka + Logstash + Elasticsearch + Kibana)

EFLK

环境信息

使用的 hadoop 完全分布式集群:

1
2
3
192.168.2.241 hadoop01 
192.168.2.242 hadoop02
192.168.2.243 hadoop03

所有组件均已完成安装(参考前文), 以收集 nginx 日志为例。

Filebeat 配置

/opt/bigdata/filebeat/current/filebeat-nginx-to-kafka.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/nginx/*.log
fields:
log_topic: nginxlogs
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: false
setup.template.settings:
index.number_of_shards: 1
name: "hadoop01"
setup.kibana:
output.kafka:
enabled: true
hosts: ["hadoop01:9092", "hadoop02:9092", "hadoop03:9092"]
version: "0.10"
topic: '%{[fields][log_topic]}'
codec.format.string: '%{[message]}'
partition.round_robin:
reachable_only: true
worker: 2
required_acks: 1
compression: gzip
max_message_bytes: 10000000
processors:
- drop_fields:
fields: ["input", "host", "agent.type", "agent.ephemeral_id", "agent.id", "agent.version", "ecs"]
logging.level: info

启动

1
2
cd /opt/bigdata/filebeat/current/
nohup ./filebeat -e -c filebeat-nginx-to-kafka.yml &

Logstash 配置

/opt/bigdata/logstash/current/kafka_nginx_into_es.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
input {
kafka {
bootstrap_servers => "hadoop01:9092,hadoop02:9092,hadoop03:9092"
topics => ["nginxlogs"]
add_field => { "[@metadata][myid]" => "nginxlogs" }
}
}
filter {
if [@metadata][myid] == "nginxlogs" {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:localtime}\|\~\|%{IPORHOST:clientip}\|\~\|(%{GREEDYDATA:http_user_agent})\|\~\|(%{DATA:http_referer})\|\~\|%{GREEDYDATA:media_id}\|\~\|%{GREEDYDATA:nginx_id}" }
}
date {
match => ["localtime", "yyyy-MM-dd'T'HH:mm:ssZZ"]
target => "@timestamp"
}

mutate {
remove_field => "@version"
remove_field => "message"
remove_field => "localtime"
}
}
}
output {
if [@metadata][myid] == "nginxlogs" {
elasticsearch {
hosts => ["hadoop01:9200","hadoop02:9200","hadoop03:9200"]
index => "nginxlogs-%{+YYYY.MM.dd}"
}
}
}

启动

1
2
cd /opt/bigdata/logstash/current/
nohup bin/logstash -f kafka_nginx_into_es.conf &

验证

使用 kibana

创建index1
创建index2
discover