原创 吴就业 198 0 2024-03-26
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://wujiuye.com/article/eb6e2ee612a34c01b5a269537d2ebbd7
作者:吴就业
链接:https://wujiuye.com/article/eb6e2ee612a34c01b5a269537d2ebbd7
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
本篇内容包括:
我们通过kubectl logs
命令可以查看某个pod输出到标准输出的日志(stdout),甚至还可以查看指定容器的日志。正常理解,我们将日志输出到标准输出是不存文件的,那么为什么还可以获取到标准输出的日志呢?其实我们输出到标准输出的日志也是会被存到某个文件的。
参考文献:https://loggie-io.github.io/docs/user-guide/use-in-kubernetes/general-usage/
如果我们的k8s集群使用的容器运行时的是dockershim,那么正常情况下我们可以在宿主机的/var/lib/docker/containers
路径中找到对应容器的stdout的日志,默认为/var/lib/docker/containers/{containerId}/{containerId}-json.log
。通过读取这个文件就能获取到容器的标准输出(stdout)。
但是,怎么知道这个容器是属于哪个Pod的呢?
我们还需要知道pod的namespace和name,才能获取关联的Deployment。
如果容器运行时是标准的CRI,那么kubelet会在宿主机/var/log/pods/
目录下生成/var/log/pods/<namespace>_<pod_name>_<pod_id>/<container_name>/<num>.log
日志文件。而如果运行时是dockershim,这个文件就会是一个软链文件,会链接到/var/lib/docker/containers/{containerId}/{containerId}-json.log
。所以我们可以直接读取宿主机下的/var/log/pods/<namespace>_<pod_name>_<pod_id>/<container_name>/<num>.log
文件。
DaemonSet可以给Pod挂载/var/log/pods/
这个路径,然后遍历**/*.log
文件,通过log文件的父级目录能够获取文件所属的Pod的namespace和pod的名称,通过log文件名能够获取容器名。
我们可以做个实验验证:写个DaemonSet,并给Pod挂载/var/log/pods/
宿主机目录,然后运行/bin/sh
(或 /bin/bash
)进入容器终端,查看/var/log/pods/
目录。
附DaemonSet:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: curl-daemonset
spec:
selector:
matchLabels:
app: curl-daemonset
template:
metadata:
labels:
app: curl-daemonset
spec:
containers:
- name: curl-container
image: curlimages/curl:latest
command: ["sleep", "infinity"]
volumeMounts:
- name: pod-logs
mountPath: /var/log/pods/
volumes:
- name: pod-logs
hostPath:
path: /var/log/pods/
进入Pod的/var/log/pods
目录执行ls -la
命令输出如下:
使用tree命令查看输出如下:
Grafana Agent是一个开源的Agent项目,集成了指标、日志、链接追踪的收集上报能力,支持将指标上报到Prometheus或Grafana Mimir,支持将日志收集到Grafana Loki,支持将tracing收集到Grafana Tempo。
Grafana Agent的配置项基本兼容Prometheus的配置。很多Grafana Agent没给出的配置项解析,需要我们去查看Prometheus的配置项文档:https://prometheus.io/docs/prometheus/2.45/configuration/configuration/。
关于配置Grafana Agent如何收集日志并将其发送到Grafana Loki,详细的可以阅读官方文档。官方文档给了很多例子,唯独没给出收集K8s的Pod标准输出的例子。这篇文章的目的是降低上手门槛,通过给出收集K8s的Pod标准输出的配置案例,并解释为什么这样配置,让读者能够快速上手。
收集k8s的Pod标准输出我们需要借助DaemonSet,在每个Node上部署一个Grafana Agent Pod,这个Pod用来收集日志。
部署Grafana Agent的DaemonSet如下:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: grafana-agent
spec:
selector:
matchLabels:
app: grafana-agent
template:
metadata:
labels:
app: grafana-agent
spec:
serviceAccountName: grafana-agent
containers:
- name: grafana-agent
image: grafana/agent:latest
command:
- grafana-agent
- -config.file=/etc/agent/agent.yaml
env:
- name: HOSTNAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: config-volume
mountPath: /etc/agent
- name: pod-logs-volume
mountPath: /var/log/pods
- name: store-otherdata-volume
mountPath: /tmp
volumes:
- name: config-volume
configMap:
name: grafana-agent-config
- name: pod-logs-volume
hostPath:
path: /var/log/pods
- name: store-otherdata-volume
hostPath:
path: /tmp
关于挂盘部分:
/etc/agent
目录,然后grafana-agent-config有个key是agent.yaml
,所以最终会在Pod生成/etc/agent/agent.yaml
这个配置文件。所以command需要指定这个配置文件:-config.file=/etc/agent/agent.yaml
。/var/log/pods
目录到Pod,挂载的目录也是/var/log/pods
,让Pod能够读取到Node上的日志文件。关于环境变量:
由于grafana agent的日志收集模块需要读取一些k8s资源,因此还需要配置rbac赋予权限。
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: grafana-agent
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: grafana-agent
rules:
- apiGroups:
- ""
resources:
- nodes
- nodes/proxy
- nodes/metrics
- services
- endpoints
- pods
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: grafana-agent
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: grafana-agent
subjects:
- kind: ServiceAccount
name: grafana-agent
namespace: default
最后就是配置文件了。
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-agent-config
data:
agent.yaml: |
server:
log_level: debug ## 这个是配置grafana agent本身的日记输出级别,测试验证阶段我们设置为debug
logs:
configs:
- name: default
clients:
- url: https://<username>:<password>@logs-prod-006.grafana.net/loki/api/v1/push
positions:
filename: /tmp/logs/positions.yaml
scrape_configs:
- job_name: kubernetes-pods
pipeline_stages:
- cri: {}
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels:
- __meta_kubernetes_pod_node_name
target_label: __host__
- action: replace
source_labels:
- __meta_kubernetes_namespace
target_label: namespace
- action: replace
source_labels:
- __meta_kubernetes_pod_name
target_label: pod
- action: replace
source_labels:
- __meta_kubernetes_pod_container_name
target_label: container
- action: replace
source_labels:
- __meta_kubernetes_pod_host_ip
target_label: host_ip
- action: replace
target_label: __path__
source_labels:
- __meta_kubernetes_pod_uid
- __meta_kubernetes_pod_container_name
separator: /
replacement: "/var/log/pods/*$1/*.log"
配置项说明:
clients
:配置接收grafana agent上传日志的服务器,可以有多个,但通常情况下只会配置一个。
url
:上传日志的接口,这里由于我们使用的是Grafana Cloud的Grafana Loki,所以填的是Loki的接口,并且由于Loki接口使用base_auth,所以我们在url上加上了用户名密码。https://<用户名>:<密码>@logs-prod-006.grafana.net/loki/api/v1/push
。positions.filename
:指定position文件,这个文件是grafana agent用于记录每个日志文件当前已经收集到哪个位置的。
scrape_configs
:如何使用指定的发现方法从一系列目标中抓取日志。
kubernetes_sd_configs
:使用k8s的服务发现,- role: pod
为发现Node上的所有Pod,这些Pod就是抓取日志的目标。pipeline_stages
:如果我们k8s集群使用的是标准CRI(如containerd),而不是dockershim,那么这里配置为- cri: {}
,否则配置为- docker: {}
,区别是标准输出的格式不同。
- dockershim的日志格式:
{"log":"level=info ts=2019-04-30T02:12:41.844179Z caller=filetargetmanager.go:180 msg=\"Adding target\"\n","stream":"stderr","time":"2019-04-30T02:12:41.8443515Z"}
- cri的日志格式:
2019-01-01T01:00:00.000000001Z stderr P some log message
relabel_configs配置的解释:
其中最主要的是这个配置:
- action: replace
target_label: __path__
source_labels:
- __meta_kubernetes_pod_uid
- __meta_kubernetes_pod_container_name
separator: /
replacement: "/var/log/pods/*$1/*.log"
通过替换__path__
标签来指定日志文件的路径。separator: /
定义使用/
分隔符将__meta_kubernetes_pod_uid
和__meta_kubernetes_pod_container_name
标签的值连接起来。/var/log/pods/*$1/*.log
中的$1会被{__meta_kubernetes_pod_uid}/{__meta_kubernetes_pod_container_name}
替换。所以是匹配/var/log/pods
目录下的*{__meta_kubernetes_pod_uid}/{__meta_kubernetes_pod_container_name}/*.log
日记文件。
其它是自定义标签,例如这个:
- action: replace
source_labels:
- __meta_kubernetes_pod_host_ip
target_label: host_ip
意思是我们自定义了host_ip这个标签,将grafana agent提供的元标签__meta_kubernetes_pod_host_ip
输出为目标标签host_ip
。
grafana-agent提供的这些元标签,只是提供值给我们使用,并不会把这些元标签都上报。例如,如果我们不配置__meta_kubernetes_pod_host_ip
的目标标签为host_ip
,收集的日记并不存在__meta_kubernetes_pod_host_ip
这个标签。
将日志上传到Grafana Cloud,其实我们只需要修改配置文件中log_configs模块的clients.url
即可。这个url填什么呢?
假如你已经注册了Grafana Cloud,有一个Grafana Cloud的账号。
现在可以进入My Account页面,然后找到Loki产品,点击Details按钮。
进入产品实例页面后,找到Sending Logs
。
我们可以直接拿到url,然后其中的<Your Grafana.com API Token>
,可点击Generate now
按钮生成一个Token,这个Token就是密码了。
最后,如何查看收集的日志?
在Loki产品实例页面,找到Using Grafana with Logs
。
这里可以看到我们的数据源名称。这里是grafanacloud-wujiuye-logs
。
回到My Account页面,然乎找到Grafana产品,点击Launch按钮进入Grafana控制台。
点击explore菜单,数据源选择grafanacloud-wujiuye-logs
。然后就可以查看收集的日志了。
声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。
通常指标和日志收集这两者是一起的,可观测即离不开指标,也离不开日记。当两者都需要的时候,就没必要部署两个DaemonSet了。本篇将两者结合成一个完整的案例,大家可以直接拿去部署使用。
本篇是作者在云原生PaaS平台项目中实战可观测能力做的技术调研,将关键技术知识点讲透,涉及:如何获取Pod的cpu和内存指标、使用Grafana Agent收集指标、上传到Prometheus。
不禁感叹,k8s这个底座设计的太牛了,我们不仅可以通过CRD + 控制器做扩展,还可以自定义APIService去做扩展。k8s,牛啊!
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。