本章主要讲解在 Kubernetes 集群中如何通过不同的技术栈收集容器的日志,包括程序直接输出到控制台日志、自定义文件日志等。
一、有哪些日志需要收集
为了更加方便的处理异常,日志的收集与分析极为重要,在学习日志收集之前,需要知道在集群内有哪些日志需要收集,在这里简单的总结一些比较重要的需要收集的日志:
- 服务器系统日志
- Kubernetes 组件日志
- 应用程序日志
除了上面列出的日志外,可能还存在其他很多需要采集的日志,比如网关的日志、服务之间调用链的日志等。
二、日志采集工具有哪些
1.ELK 和 EFK
在传统的架构中,比较成熟且流行的日志收集平台是 ELK(Elasticsearch+Logstash+Kibana)其中 Logstash 负责采集日志,并输出给 Elasticsearch,之后用 Kibana 进行展示。
我们都知道,几乎所有的服务都可以进行容器化,ELK 技术栈同样可以部署到 Kubernetes 集群中,也可以进行相关的日志收集,但是由于 Logstash 比较消耗系统资源,并且配置稍微有点复杂,因此Kubernetes官方提出了 EFK(Elasticsearch+fluentd+Kibana)的解决方案,相对于 ELK 中的Logstash,Fluentd 采用一锅端的形式,可以直接将某些日志文件中的内容存储至 Elasticsearch,然后通过Kibana进行展示。
但是 Fluentd 也有缺陷,比如,它只能收集控制台日志(程序直接输出到控制台的日志),不能收集非控制台的日志,所以很难满足生产环境的需求,因为大部分情况下,没有遵循云原生理念开发的程序,往往会输出很多日志文件,这些容器内的日志无法采集,除非在每个 Pod 内添加一个 sidecar,将日志文件的内容进行 tail -f 转换成控制台日志,但这也是非常麻烦的。
另一个问题是,大部分公司内都有很成熟的 ELK 平台,如果再搭建一个 EFK 平台,属于重复,当然用来存储日志的 Elasticsearch 集群不建议搭建在 Kubernetes 集群中,因为会非常浪费 Kubernetes 集群的资源,所以大部分情况下通过 Fluentd 采集日志输出到外部的 Elasticsearch 集群中。
综上所述,Fluentd 功能有限,Logstash 太重,所以需要一个中和的工具进行日志的收集工作,此时就可以采用一个轻量化的收集工具:Filebeat。
2.Filebeat
在早期的 ELK 架构中,日志收集均以 Logstash 为主,Logstash 负责收集和解析日志,对内存、CPU、IO资源的消耗比较高,但是 Filebeat 所占系统的 CPU 和内存几乎可以忽略不计。
由于 Filebeat 本身是比较轻量级的日志采集工具,因此 Filebeat 经常被用于以 sidecar 的形式配置在 Pod 中,用来采集容器内冲虚输出的自定义日志文件。当然,Filebeat 同样可以采用 Daemonset 的形式部署在 Kubernetes 集群中,用于采集系统日志和程序控制台输出的日志。
Fluentd 和 Logstash 可以将采集的日志输出到 Elasticsearch 集群,Filebeat 同样可以将日志直接存储到 Elasticsearch 中,但是为了更好的分析日志或者减轻 Elasticsearch 的压力,一般都是将志先输出到 Kafka,再由 Logstash 进行简单的处理,最后输出到 Elasticsearch 中。
3.新贵 Loki
上述讲的无论是 ELK、EFK还是 Filebeat,都需要用到Elasticsearch 来存储数据,Elasticsearch本身就像一座大山,维护难度和资源使用都是偏高的。对于很多公司而言,热别是创新公司,可能并不想大费周章的去搭建一个 ELK、EFK 或者其他重量级的日志平台,刚开始的人力投入可能是大于收益的,所以就需要一个更轻量级的日志收集平台。
为了解决上述的问题和难点,一个基于 Kubernetes 平台的原生日志收集平台 Loki stack 应运而生,所以一经推出,就受到了用户的青睐。
Loki 是 Grafana Labs 开源的一个支持水平扩展、高可用、多租户的日志聚合系统。
可以看到,Loki 主要包含如下组件:
- Loki:主服务器,负责日志的存储和査询,参考了 prometheus 服务发现机制,将标签添加到日志流,而不是想其他平台一样进行全文索引。
- Promtai1:负责收集日志并将其发送给 Loki,主要用于发现采集目标以及添加对应 Label,最终发送给 Loki。
- Grafana:用来展示或查询相关日志,可以在页面査询指定标签 Pod 的日志。
和其他工具相比,虽然 Loki 不如其他技术栈功能灵活,但是 Loki 不对日志进行全文索引,仅索弓相关日志的元数据,所以 Loki 操作起来更简单、更省成本。而且 Loki 是基于 Kubernetes 进行设计的,可以很方便的部署在 Kubernetes 上,并且对集群的 Pod 进行日志采集,采集时会将 Kubernetes 集群中的一些元数据自动添加到日志中,让技术人员可以根据命名空间、标签字段进行日志的过滤,可以很快的定位到相关日志。
通过上述的了解,我们知道了在 Kubernetes 集群中,日志收集可以选择的技术栈有很多,不只局限于上述提到的。对于平台的选择,没有最好,只有最合适,比如公司内已经有了很成熟的 ELK 平台,那么我们可以直接采用 Fluentd 或 Filebeat,然后将 Kubernetes 的日志输出到已存在的 Elasticsearch集群中即可。如果公司并没有成熟的平台支持,又不想耗费很大的精力和成本去建立一个庞大的系统,那么 Loki stack 将会是一个很好的选择。
三、使用 EFK 收集控制台日志
首先我们使用 EFK 收集 Kubernetes 集群中的日志,本次实验讲解的是在 Kubernetes 集群中启动一个 Elasticsearch 集群,如果企业已经有了 Elasticsearch 集群,可以直接将日志输出到已有的Elasticsearch 集群中。
1.部署Elasticsearch+Fluentd+Kibana
(1)下载需要的部署文档
[root@k8s-master ~]# get clone htps://github.com/dotbalo/k8s.git
备注:
本案例已经下载好,可以直接使用
(2)创建 EFK 所用的命名空间
[root@master ~]# cd efk-7.10.2/
[root@master efk-7.10.2]# ls
create-logging-namespace.yaml fluentd-es-configmap.yaml kibana-service.yaml
es-service.yaml fluentd-es-ds.yaml nginx-service.yaml
es-statefulset.yaml kafka
filebeat kibana-deployment.yaml
[root@master efk-7.10.2]# ku create -f create-logging-namespace.yaml
namespace/logging created
(3)创建 Elasticsearch 群集(已有该平台可以不创建)
[root@master efk-7.10.2]# ku create -f es-service.yaml
service/elasticsearch-logging created
备注:
为 es 集群创建服务,以便为 Fluentd 提供数据传入的端口
[root@master efk-7.10.2]# ku get svc -n logging
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
elasticsearch-logging ClusterIP None <none> 9200/TCP,9300/TCP 76s
备注:
- 9208 端口:用于所有通过 HTTP 协议进行的 API 调用。包括搜索、聚合、监控、以及其他任何使用HTTP 协议的请求。所有的客户端库都会使用该端口与 Elasticsearch 进行交互。
- 9308 端口:是一个自定义的二进制协议,用于集群中各节点之间的通信。用于诸如集群变更、主节点选举、节点加入/离开、分片分配等事项。
[root@master efk-7.10.2]# ku create -f es-statefulset.yaml
serviceaccount/elasticsearch-logging created
clusterrole.rbac.authorization.k8s.io/elasticsearch-logging created
clusterrolebinding.rbac.authorization.k8s.io/elasticsearch-logging created
statefulset.apps/elasticsearch-logging created
备注:
创建 es 集群
[root@master efk-7.10.2]# ku get pod -n logging
NAME READY STATUS RESTARTS AGE
elasticsearch-logging-0 0/1 Pending 0 7m12s
(4)创建Kibana(已有该平台可以不创建)
[root@master efk-7.10.2]# ku create -f kibana-deployment.yaml -f kibana-service.yaml
deployment.apps/kibana-logging created
service/kibana-logging created
(5)修改 Fluentd 的部署文件
由于在 Kubernetes 集群中,可能不需要对所有的机器都采集日志,因此可以更改 Fluentd 的部署文
件。添加一个 Nodeselector,只部署至需要采集日志的主机即可。
[root@master efk-7.10.2]# grep "nodeSelector" fluentd-es-ds.yaml -A 3
nodeSelector:
fluentd: "true"
volumes:
- name: varlog
(6)为需要采集日志的服务器设置标签
[root@master efk-7.10.2]# ku label node node1 fluentd=true
node/node1 labeled
[root@master efk-7.10.2]# ku label node node2 fluentd=true
node/node2 labeled
[root@master efk-7.10.2]# ku label node master fluentd=true
node/master labeled
[root@master efk-7.10.2]# ku get node -l fluentd=true --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master Ready control-plane,master 14d v1.23.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,fluentd=true,kubernetes.io/arch=amd64,kubernetes.io/hostname=master,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
node1 Ready <none> 14d v1.23.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,fluentd=true,kubernetes.io/arch=amd64,kubernetes.io/hostname=node1,kubernetes.io/os=linux
node2 Ready <none> 14d v1.23.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,fluentd=true,kubernetes.io/arch=amd64,kubernetes.io/hostname=node2,kubernetes.io/os=linux
(7)创建 Fluentd
[root@master efk-7.10.2]# ku create -f fluentd-es-ds.yaml -f fluentd-es-configmap.yaml
serviceaccount/fluentd-es created
clusterrole.rbac.authorization.k8s.io/fluentd-es created
clusterrolebinding.rbac.authorization.k8s.io/fluentd-es created
daemonset.apps/fluentd-es-v3.1.1 created
configmap/fluentd-es-config-v0.2.1 created
fluentd 的 ConfigMap 有一个字段需要注意,在 fluentd-es-configmap.yaml 文件的最后有一个output.conf:
host elasticsearch-logging
port 9200
此处的配置将收集到得数据输出到指定的 Elasticsearch 集群中,由于创建 Elasticsearch 集群时会自动创建一个名为 elasticsearch-logging 的 Service,因此默认 Fluentd 会将数据输出到前面创建的 Elasticsearch 集群中。如果企业已经存在一个成熟的 ELK 平台,可以创建一个同名的 service 指向该集群,然后就能输出到现有的 Elasticsearch 集群中。
例如:
apiVersion: v1
kind: Service
metadata:
name:elasticsearch-logging
namespace:logging
spec:
type:ExternalName
externalName:www.es.com
备注:
externalName:www.es.com
指向了外部 es 主机的地址或域名
备注:
<match **>
。。。
host elasticsearch-logging
port 9200
logstash format true
。。。
</match>
host elasticsearch-logging:指定 fluentd 获取的日志要发往的主机
port 9200:目标es 主机的端口号
logstash format true: 指定是否使用常规的 index 命名格式,(logstash-%v.%m.%d),默认为 false
2.Kibana 的使用
(1)确认创建的 Pod 已经成功启动
[root@master efk-7.10.2]# ku get pod -n logging
NAME READY STATUS RESTARTS AGE
elasticsearch-logging-0 0/1 Pending 0 25m
fluentd-es-v3.1.1-5bn6f 0/1 CrashLoopBackOff 5 (48s ago) 6m48s
fluentd-es-v3.1.1-p4vdn 0/1 CrashLoopBackOff 5 (48s ago) 6m48s
kibana-logging-7bf48fb7b4-k6cq9 0/1 CrashLoopBackOff 9 (2m25s ago) 17m
(2)查看kibana 暴露的端口
[root@master efk-7.10.2]# ku get svc -n logging
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
elasticsearch-logging ClusterIP None <none> 9200/TCP,9300/TCP 27m
kibana-logging NodePort 10.97.250.231 <none> 5601:32726/TCP 17m
(3)访问 kibana
使用任意一个部署了 kube-proxy 服务的节点的 IP+32734 端口即可访问 kibana。
http://192.168.10.101:32734/kibana
3.创建一个Pod,进行日志采集
(1)编写nginx部署文件
[root@master efk-7.10.2]# vim nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: mynginx
namespace: default
labels:
app: mynginx
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: mynginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mynginx-deployment
namespace: default
labels:
app: mynginx
spec:
replicas: 2
selector:
matchLabels:
app: mynginx
template:
metadata:
labels:
app: mynginx
spec:
containers:
- name: mynginx
image: nginx:1.15.2
ports:
- name: http
containerPort: 80
protocol: TCP
(2)部署该Deployment
[root@master efk-7.10.2]# ku create -f nginx-service.yaml
service/mynginx created
deployment.apps/mynginx-deployment created
(3)查看pod状态
(4)查看暴露的端口
[root@master efk-7.10.2]# ku get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 14d
mynginx LoadBalancer 10.105.213.83 <pending> 80:32710/TCP 29s
(5)客户端访问测试
[root@master efk-7.10.2]# curl 192.168.10.101:32710
(6)到kibana上查看
四、使用Filebeat收集自定义文件日志
基于云原生 12 要素开发的程序,一般会将日志直接输出到控制台,并非是指定一个文件存储日志,这一点和传统架构还是有区别的。但是公司内的程序并非都是基于云原生要素开发的,比如一些年代已久的程序,这类程序如果部署至 Kubernetes 集群中,就需要考虑如何将输出至本地文件的日志采集到Elasticsearch。
之前我们了解到 Filebeat 是一个非常轻量级的日志收集工具,可以将其和应用程序部署至一个 Pod中,通过 Volume 进行日志文件的共享,之后 Filebeat 就可以采集里面的数据,并推送到日志平台。
为了减轻 Elasticsearch 的压力。本案例将引入 Kafka 消息队列来缓存日志数据,之后通过Logstash 简单处理一下日志,最后再输出到 Elasticsearch 集群。这种架构在生产环境中也是常用的架构,因为日志数量可能比较多,同时也需要对其进行加工。当然 Fluentd 也可以将日志输出到 Kafka。
1.安装 helm 客户端(如果已有此工具,可跳过该步骤)
(1)下载安装包
[root@k8s-master ~]#wget https://get.helm.sh/helm-v3.6.2-linux-amd64.tar.gz
(2)解压
[root@k8s-master ~]# tar zxvf helm-v3.6.2-linux-amd64.tar.gz
(3)安装
[root@k8s-master ~]# mv linux-amd64/helm /usr/local/bin/
(4)查看版本
[root@master ~]# helm version
version.BuildInfo{Version:"v3.9.4", GitCommit:"dbc6d8e20fe1d58d50e6ed30f09a04a77e4c68db", GitTreeState:"clean", GoVersion:"go1.17.13"}
2.创建卡Kafka和 Logstash
(1)部署zookeeper
[root@k8s-master ~l# cd efk-7.10.2/filebeat
[root@k8s-master filebeat]# helm install zookeeper zookeeper/ -n logging
(2)查看zookeeper 状态
[root@k8s-master filebeat]# kubectl get pod-n logging -l app.kubernetes.io/name=zookeeper
备注:
-l:指定标签
等一会,部署好显示状态为 Running
(3)部署 kafka
[root@master filebeat]# helm install kafka kafka/ -n logging
(4)查看 kafka 状态
[root@k8s-master filebeat]# kubectl get pod -n logging -l app.kubernetes.io/component=kafka
备注:
等一会,部署好显示状态为 Running
(5)创建 logstash 服务
[root@master filebeat]# ku create -f logstash-service.yaml -f logstash-cm.yaml -f logstash.yaml -n logging
service/logstash-service created
configmap/logstash-configmap created
deployment.apps/logstash-deployment created
3.注入Filebeat sidecar
(1)创建一个模拟程序
[root@master filebeat]# ku create -f filebeat-cm.yaml -f app-filebeat.yaml -n logging
configmap/filebeatconf created
service/mynginx created
deployment.apps/app2 created
(2)查看pod状态
[root@master filebeat]# ku get pod -n logging
NAME READY STATUS RESTARTS AGE
app2-666cd8b58f-pdrtp 2/2 Running 0 33s
elasticsearch-logging-0 1/1 Running 1 (28m ago) 47m
fluentd-es-v3.1.1-5bn6f 1/1 Running 16 (28m ago) 70m
fluentd-es-v3.1.1-p4vdn 1/1 Running 16 (27m ago) 70m
kafka-0 1/1 Running 0 23m
kibana-logging-7bf48fb7b4-k6cq9 1/1 Running 21 (28m ago) 80m
logstash-deployment-6485f5d5b5-cpndc 1/1 Running 0 14m
zookeeper-0 1/1 Running 0 25m
[root@master filebeat]# ku get svc -n logging
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
elasticsearch-logging ClusterIP None <none> 9200/TCP,9300/TCP 90m
kafka ClusterIP 10.97.94.74 <none> 9092/TCP 23m
kafka-headless ClusterIP None <none> 9092/TCP,9093/TCP 23m
kibana-logging NodePort 10.97.250.231 <none> 5601:32726/TCP 80m
logstash-service ClusterIP 10.101.139.254 <none> 5044/TCP 14m
mynginx LoadBalancer 10.98.250.245 <pending> 80:30022/TCP 43s
zookeeper ClusterIP 10.97.171.172 <none> 2181/TCP,2888/TCP,3888/TCP 25m
zookeeper-headless ClusterIP None <none> 2181/TCP,2888/TCP,3888/TCP 25m
[root@master filebeat]# ku get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15d
mynginx LoadBalancer 10.105.213.83 <pending> 80:32710/TCP 58m
注意:这个 mynginx的 svc 是创建在了 default 的命名空间的。
(3)查看日志中的内容
[root@k8s-master filebeat]# kubectl exec app-ddf4b6db9-thtbs -n logging
-- tail/opt/date.log
五、Loki
1.安装 Loki stack(如果已安装,忽略此步骤)
[root@k8s-master ~]# tar zxvf helm-v3.6.2-linux-amd64.tar.gz
[root@k8s-master ~]# cp linux-amd64/helm /usr/bin
2.添加安装源并下载 1oki-stack(如已有离线包,忽略此步骤)
[root@k8s-master ~]# helm repo add grafana https://grafana.github.io/helm-charts
[root@k8s-master ~]# helm repo update
备注:
其他安装源
helm repo add loki https://grafana.github.io/loki/charts && helm repo update
[root@k8s-master ~]# helm repo list
[root@k8s-master ~l# helm pull grafana/loki-stack --version 2.8.3
3.创建 Loki 命名空间
[root@k8s-master ~#kubectl create ns loki
namespace/loki created
4. 创建 Loki stack
[root@k8s-master ~]# tar xf loki-stack-2.8.3.tgz
[root@master loki]# helm upgrade --install loki loki-stack --set grafana.enabled=true --set grafana.service.type=NodePort -n loki
Release "loki" does not exist. Installing it now.
W0903 11:51:46.761749 48521 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W0903 11:51:46.765865 48521 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W0903 11:51:46.769012 48521 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W0903 11:51:46.937041 48521 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W0903 11:51:46.937366 48521 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W0903 11:51:46.937414 48521 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
NAME: loki
LAST DEPLOYED: Tue Sep 3 11:51:45 2024
NAMESPACE: loki
STATUS: deployed
REVISION: 1
NOTES:
The Loki stack has been deployed to your cluster. Loki can now be added as a datasource in Grafana.
See http://docs.grafana.org/features/datasources/loki/ for more detail.
5.查看Pod状态
[root@master loki]# ku get pod -n loki
NAME READY STATUS RESTARTS AGE
loki-0 0/1 Running 0 3m24s
loki-grafana-fd49f9d86-jgspv 1/2 Running 0 3m24s
loki-promtail-b7jl2 1/1 Running 0 3m24s
loki-promtail-h7jjt 1/1 Running 0 3m24s
loki-promtail-szgd9 1/1 Running 0 3m24s
6.查看暴露的端口
[root@master loki]# ku get svc -n loki
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
loki ClusterIP 10.104.80.31 <none> 3100/TCP 115s
loki-grafana NodePort 10.100.211.151 <none> 80:32393/TCP 115s
loki-headless ClusterIP None <none> 3100/TCP 115s
loki-memberlist ClusterIP None <none> 7946/TCP 115s
7.查看grafana的密码
[root@master loki]# ku get secret loki-grafana --namespace loki -o jsonpath="{.data.admin-password}" | base64 --decode && echo
mDrZEfY8CSZwjMpbaKziqaCzUUZRt8Ku23hnFOCC
修改密码
[root@master loki]# ku exec -it loki-grafana-fd49f9d86-jgspv -n loki -c grafana grafana-cli admin reset-admin-password admin
备注:
账号为 admin
8.登录
9.创建一个 Pod,进行日志采集(还是前面的 pod)
(1)编写 nginx 部署文件
[root@master loki]# vim nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: mynginx
namespace: default
labels:
app: mynginx
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: mynginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mynginx-deployment
namespace: default
labels:
app: mynginx
spec:
replicas: 2
selector:
matchLabels:
app: mynginx
template:
metadata:
labels:
app: mynginx
spec:
containers:
- name: mynginx
image: nginx:1.15.2
ports:
- name: http
containerPort: 80
protocol: TCP
(2)部署该Dployment
[root@master loki]# ku create -f nginx-service.yaml
(3)查看 pod 状态
[root@master loki]# ku get pod
NAME READY STATUS RESTARTS AGE
mynginx-deployment-668d5454cb-9lwgx 1/1 Running 2 (25m ago) 118m
mynginx-deployment-668d5454cb-bdg8v 1/1 Running 1 (89m ago) 118m
(4)查看暴露的端口
[root@master loki]# ku get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15d
mynginx LoadBalancer 10.105.213.83 <pending> 80:32710/TCP 111m
(5)客户端访问测试
[root@master loki]# curl 192.168.10.101:32710
(6)在 Loki 中查看此 Pod 的日志
{namespace="default",pod="mynginx-deployment-668d5454cb-lldng"}
{namespace="kube-system"}
{namespace="kube-system",pod="calico-node-gwtqw"}