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
|
日志的需求
- 系统各组件的日志 : eg: kubelet、kube-proxy,docker 等日志
- 容器化运行的应用程序自身日志 : eg: Nginx 的运行日志
- k8s 内部各种 Event : eg: kubctl create pod 后,kubectl describe pod *** 的pod 的 Event信息
日志系统
Kubernetes 的日志系统在设计的时候,必须得独立于节点和 Pod 的生命周期,且保证日志数据可以实时采集到服务端,即完全独立于 Kubernetes 系统,使用自己的后端存储和查询工具
日志收集方式
一般有如下 四种方案来做日志收集:

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

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

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

- 通过一个
sidecar 容器,直接把应用的日志文件发送到远程存储里面去。也就是相当于把方案二里的 logging agent,放在了应用 Pod 里。(应用还可以直接把日志输出到固定的文件里而不是 stdout,logging-agent 还可以使用 fluentd,后端存储还可以是 ElasticSearch。只不过, fluentd 的输入源,变成了应用的日志文件。一般来说,我们会把 fluentd 的输入源配置保存在一个 ConfigMap 里)
sidecar 指的就是我们可以在一个 Pod 中,启动一个辅助容器,来完成一些独立于主进程(主容器)之外的工作。
sidecar 与 fluentd 方案示例
Pod 只有一个容器,它会把日志输出到容器里的 /var/log/1.log 和 2.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: {}
|
因为非 stdout 和 stderr, kubectl logs 命令是看不到应用的任何日志的。所以方案二不能使用。
使用方案三 : Pod 添加两个 sidecar 容器,分别将上述两个日志文件里的内容重新以 stdout 和 stderr 的方式输出出来 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 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.log 和 2.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
|
下面用到的 es.yaml / fluent-bit.yaml 是从上面那两个 helm chart 渲染出来再改的,用 helm template 就能得到同样的起点:
1 2 3 4 5 6
| helm repo add elastic https://helm.elastic.co helm repo add fluent https://fluent.github.io/helm-charts
helm template es elastic/elasticsearch -n elasticsearch > es.yaml helm template fluent-bit fluent/fluent-bit -n elasticsearch > fluent-bit.yaml
|
开启了 prometheus 拉取, 先装的 es, ns 为 elasticsearch 方便管理。
使用






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

环境信息
使用的 hadoop 完全分布式集群:
1 2 3
| 192.168.2.241 hadoop01 192.168.2.242 hadoop02 192.168.2.243 hadoop03
|
所有组件均已完成安装(各组件的单独部署过程见 ELK 组件部署),以收集 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


