概要:
简述 Loki 相关组件原理,并演示两种使用 Loki 监控Pod日志的部署方式:使用 yaml 资源清单部署 和 使用 Helm 部署
集群环境
IP | Hostname | 用途 |
---|---|---|
10.20.1.139 | k8s-master01 | Master节点,NFS Server |
10.20.1.140 | k8s-node01 | Node节点, NFS Client |
10.20.1.141 | k8s-node02 | Node节点, NFS Client |
10.20.1.142 | k8s-node03 | 安装NFS,Harbor, NFS Client |
一、Loki 简介
是一个水平可扩展,高可用性,多租户的日志聚合系统,Loki 是基于仅索引有关日志元数据的想法而构建的:标签(就像 Prometheus 标签一样)。日志数据本身被压缩然后并存储在对象存储(例如 S3 或 GCS)的块中,甚至存储在本地文件系统上,轻量级的索引和高度压缩的块简化了操作,并显著降低了 Loki 的成本,Loki 更适合中小团队。由于 Loki 使用和 Prometheus 类似的标签概念,所以如果你熟悉 Prometheus 那么将很容易上手,也可以直接和 Grafana 集成,只需要添加 Loki 数据源就可以开始查询日志数据了。
Loki 还提供了一个专门用于日志查询的 LogQL
查询语句,类似于 PromQL
,通过 LogQL 我们可以很容易查询到需要的日志,也可以很轻松获取监控指标。Loki 还能够将 LogQL 查询直接转换为 Prometheus 指标。此外 Loki 允许我们定义有关 LogQL 指标的报警,并可以将它们和 Alertmanager 进行对接。
Loki技术栈中使用了以下组件。
Promtail
用来将容器日志发送到 Loki 或者 Grafana 服务上的日志收集工具,该工具主要包括发现采集目标以及给日志流添加上 Label 标签 然后发送给 Loki,Promtail 的服务发现是基于 Prometheus 的服务发现机制实现的。
相当于 EFK 中的 Filebeat/Fluentd ,用于采集日志并将其发送给 Loki 。
Loki
受 Prometheus 启发的可以水平扩展、高可用以及支持多租户的日志聚合系统,使用了和 Prometheus 相同的服务发现机制,将标签添加到日志流中而不是构建全文索引,从 Promtail 接收到的日志和应用的 metrics 指标就具有相同的标签集,不仅提供了更好的日志和指标之间的上下文切换,还避免了对日志进行全文索引。
相当于 EFK 中的 ElasticSearch ,用于存储日志和处理查询。
Grafana
一个用于监控和可视化观测的开源平台,支持非常丰富的数据源,在 Loki 技术栈中它专门用来展示来自 Prometheus 和 Loki 等数据源的时间序列数据,可进行查询、可视化、报警等操作,可以用于创建、探索和共享数据 Dashboard,鼓励数据驱动。
相当于 EFK 中的 Kibana ,用于 UI 的展示。
Loki的工作原理如下图所示:
Loki 针对本地运行(或小规模运行)和水平扩展进行了优化,Loki 带有单一进程模式,可在一个进程中运行所有必需的微服务。单进程模式非常适合测试 Loki 或以小规模运行。为了实现水平可伸缩性,可以将 Loki 的服务拆分为单独的组件,从而使它们彼此独立地扩展。每个组件都产生一个用于内部请求的 gRPC 服务器和一个用于外部 API 请求的 HTTP 服务,所有组件都带有 HTTP 服务器,但是大多数只暴露就绪接口、运行状况和指标端点。
1. Loki 组件
Loki 运行哪个组件取决于命令行中的 -target
标志或 Loki 的配置文件中的 target:<string>
配置。 当 target 的值为 all
时,Loki 将在单进程中运行其所有组件。这称为 单进程
或 单体模式
。 使用 Helm 安装 Loki 时,单体模式是默认部署方式。
当 target 未设置为 all(即被设置为 querier
、ingester
、query-frontend
或 distributor
),则可以说 Loki 在 水平伸缩
或 微服务模式
下运行。
Loki 的每个组件,例如 ingester
和 distributors
都使用 Loki 配置中定义的 gRPC 监听端口通过 gRPC 相互通信。当以单体模式运行组件时,仍然是这样的,尽管每个组件都以相同的进程运行,但它们仍将通过本地网络相互连接进行组件之间的通信。
单体模式非常适合于本地开发、小规模等场景,单体模式可以通过多个进程进行扩展,但有以下限制:
- 当运行带有多个副本的单体模式时,当前无法使用本地索引和本地存储,因为每个副本必须能够访问相同的存储后端,并且本地存储对于并发访问并不安全。
- 各个组件无法独立缩放,因此读取组件的数量不能超过写入组件的数量。
1.1 Distributor
distributor
服务负责处理客户端写入的日志,它本质上是日志数据写入路径中的第一站,一旦 distributor
收到日志数据,会将其拆分为多个批次,然后并行发送给多个 ingester
。distributor
通过 gRPC 与 ingester
通信,它们都是无状态的,所以可以根据需要扩大或缩小规模。
Hashing
distributor
将一致性 Hash和可配置的复制因子结合使用,以确定 ingester
服务的哪些实例应该接收指定的数据流。
流是一组与租户和唯一标签集关联的日志,使用租户 ID 和标签集对流进行 hash 处理,然后使用哈希查询要发送流的 ingester
。
存储在 Consul/Etcd 中的哈希环被用来实现一致性哈希,所有的 ingester
都会使用自己拥有的一组 Token 注册到哈希环中,每个 Token 是一个随机的无符号 32 位数字,与一组 Token 一起,ingester
将其状态注册到哈希环中,状态 JOINING
和 ACTIVE
都可以接收写请求,而 ACTIVE
和 LEAVING
的 ingester
可以接收读请求。在进行哈希查询时,distributor
只使用处于请求的适当状态的 ingester 的 Token。
为了进行哈希查找,distributor
找到最小合适的 Token,其值大于日志流的哈希值,当复制因子大于 1 时,属于不同 ingester
的下一个后续 Token(在环中顺时针方向)也将被包括在结果中。
这种哈希配置的效果是,一个 ingester
拥有的每个 Token 都负责一个范围的哈希值,如果有三个值为 0、25 和 50 的 Token,那么 3 的哈希值将被给予拥有 25 这个 Token 的 ingester
,拥有 25 这个 Token 的 ingester
负责1-25
的哈希值范围。
1.2 Ingester
ingester
负责接收 distributor
发送过来的日志数据,存储日志的索引数据以及内容数据。此外 ingester
会验证摄取的日志行是否按照时间戳递增的顺序接收的(即每条日志的时间戳都比前面的日志晚一些),当 ingester
收到不符合这个顺序的日志时,该日志行会被拒绝并返回一个错误。
如果传入的行与之前收到的行完全匹配(与之前的时间戳和日志文本都匹配),传入的行将被视为完全重复并被忽略。
如果传入的行与前一行的时间戳相同,但内容不同,则接受该日志行,表示同一时间戳有两个不同的日志行是可能的。
来自每个唯一标签集的日志在内存中被建立成 chunks(块)
,然后可以根据配置的时间间隔刷新到支持的后端存储。在下列情况下,块被压缩并标记为只读:
- 当前块容量已满(该值可配置)
- 过了太长时间没有更新当前块的内容
- 刷新了
每当一个数据块被压缩并标记为只读时,一个可写的数据块就会取代它。如果一个 ingester
进程崩溃或突然退出,所有尚未刷新的数据都会丢失,Loki 通常配置为多个副本来降低这种风险。
当向持久存储刷新时,该块将根据其租户、标签和内容进行哈希处理,这意味着具有相同数据副本的多个 ingester
实例不会将相同的数据两次写入备份存储中,但如果对其中一个副本的写入失败,则会在备份存储中创建多个不同的块对象。(当写入因子大于1时,比如:3, 那么日志数据将会由3个不同的 Ingester 共同处理。每个日志都会计算出一个唯一hash值,同一个日志在不同的Ingester中 hash 值是相同的,正常情况下只会往持久存储中(入S3,minio)写入一次,如果某个Ingester 写入失败了,可能导入数据被重复写入持久存储中。)
WAL
上面我们提到了 ingester
将数据临时存储在内存中,如果发生了崩溃,可能会导致数据丢失,而 WAL
就可以帮助我们来提高这方面的可靠性。
在计算机领域,WAL(Write-ahead logging,预写式日志)是数据库系统提供原子性和持久化的一系列技术。
在使用 WAL 的系统中,所有的修改都先被写入到日志中,然后再被应用到系统状态中。通常包含 redo 和 undo 两部分信息。为什么需要使用 WAL,然后包含 redo 和 undo 信息呢?举个例子,如果一个系统直接将变更应用到系统状态中,那么在机器断电重启之后系统需要知道操作是成功了,还是只有部分成功或者是失败了(为了恢复状态)。如果使用了 WAL,那么在重启之后系统可以通过比较日志和系统状态来决定是继续完成操作还是撤销操作。
redo log
称为重做日志,每当有操作时,在数据变更之前将操作写入 redo log
,这样当发生断电之类的情况时系统可以在重启后继续操作。undo log
称为撤销日志,当一些变更执行到一半无法完成时,可以根据撤销日志恢复到变更之前的状态。
Loki 中的 WAL 记录了传入的数据,并将其存储在本地文件系统中,以保证在进程崩溃的情况下持久保存已确认的数据。重新启动后,Loki 将重放日志中的所有数据,然后将自身注册,准备进行后续写操作。这使得 Loki 能够保持在内存中缓冲数据的性能和成本优势,以及持久性优势(一旦写被确认,它就不会丢失数据)。
Loki 通过校验日志的 Hash 值,以及在 WAL 中创建检查点,确保重放时数据不会重复。
1.3 Querier
Querier
接收日志数据查询、聚合统计请求,使用 LogQL 查询语言处理查询,从 ingester
和长期存储中获取日志。
查询器查询所有 ingester
的内存数据,然后再到后端存储运行相同的查询。由于复制因子,查询器有可能会收到重复的数据。为了解决这个问题,查询器在内部对具有相同纳秒时间戳、标签集和日志信息的数据进行重复数据删除。
1.4 Query Frontend
Query Frontend
查询前端是一个可选的服务,可以用来加速读取路径。当查询前端就位时,将传入的查询请求定向到查询前端,而不是 querier
, 为了执行实际的查询,群集中仍需要 querier
服务。
查询前端在内部执行一些查询调整,并在内部队列中保存查询。querier
作为 workers 从队列中提取作业,执行它们,并将它们返回到查询前端进行汇总。querier
需要配置查询前端地址,以便允许它们连接到查询前端。
查询前端是无状态的,然而,由于内部队列的工作方式,建议运行几个查询前台的副本,以获得公平调度的好处,在大多数情况下,两个副本应该足够了。
队列
查询前端的排队机制用于:
- 确保可能导致
querier
出现内存不足(OOM)错误的查询在失败时被重试。这样管理员就可以为查询提供稍低的内存,或者并行运行更多的小型查询,这有助于降低总成本。 - 通过使用先进先出队列(FIFO)将多个大型请求分配到所有
querier
上,以防止在单个querier
中进行多个大型请求。 - 通过在租户之间公平调度查询。
分割
查询前端将较大的查询分割成多个较小的查询,在下游 querier
上并行执行这些查询,并将结果再次拼接起来。这可以防止大型查询在单个查询器中造成内存不足的问题,并有助于更快地执行这些查询。
缓存
查询前端支持缓存查询结果,并在后续查询中重复使用。如果缓存的结果不完整,查询前端会计算所需的子查询,并在下游 querier
上并行执行这些子查询。查询前端可以选择将查询与其 step
参数对齐,以提高查询结果的可缓存性。
举例
假设你用 Grafana 查询 Loki 中的日志数据,系统中有 Query Frontend 和多个 Querier:
- 客户端发送查询:
- 你在 Grafana 输入查询 {app=”frontend”} |~ “error”,请求发送到 Query Frontend。
- Query Frontend 处理:
- Query Frontend 接收请求,可能将查询拆分成多个子查询(比如按时间范围分成 3 段)。
- 这些子查询被放入内部队列,等待处理。
- Querier 执行查询:
- 集群中有 3 个 Querier(Q1、Q2、Q3),它们连接到 Query Frontend,从队列中领取子查询任务。
- Q1 取第一个子查询,Q2 取第二个,Q3 取第三个,分别去 ingester 或 S3 查询日志数据。
- 结果汇总:
- Querier 完成查询后,将结果返回给 Query Frontend。
- Query Frontend 合并 3 个子查询的结果,形成完整的日志列表,返回给 Grafana。
- 多个副本:
- 如果有 2 个 Query Frontend 副本,客户端请求可能随机分配到其中一个。
- Querier 同时连接到两个副本的队列,动态领取任务,确保负载均衡。
1.5 读取路径
日志读取路径的流程如下所示:
- 查询器收到一个对数据的 HTTP 请求。
- 查询器将查询传递给所有
ingester
。 ingester
收到读取请求,并返回与查询相匹配的数据。- 如果没有
ingester
返回数据,查询器会从后端存储加载数据,并对其运行查询。 - 查询器对所有收到的数据进行迭代和重复计算,通过 HTTP 连接返回最后一组数据。
举个例子
假设你在 Grafana 中查询 {app=”frontend”} |~ “error”,时间范围是过去 6 小时:
- 客户端发送请求:
- Grafana 通过 HTTP 发送查询到 Querier。
- Querier 分发查询:
- Querier 将查询 {app=”frontend”} |~ “error” 广播给集群中的 3 个 Ingester(I1、I2、I3)。
- Ingester 处理:
- I1 发现自己有部分匹配的日志(比如过去 2 小时的数据),返回这些日志。
- I2 和 I3 可能也有部分数据(由于复制因子),返回相同的或不同的日志。
- 后端存储查询:
- 如果查询的 6 小时范围超出了 Ingester 的内存存储(比如 Ingester 只存 4 小时数据),Querier 会从 S3 加载更老的块(4-6 小时的数据),并执行查询。
- 合并和去重:
- Querier 收集 I1、I2、I3 和 S3 返回的数据。
- 如果 I1 和 I2 返回了相同的日志(由于复制),Querier 通过哈希值或时间戳去重。
- Querier 按时间排序所有日志,生成最终结果。
- 返回结果:
- Querier 通过 HTTP 返回整理好的日志列表,Grafana 显示这些日志。
1.6 写入路径
整体的日志写入路径如下所示:
distributor
收到一个 HTTP 请求,以存储流的数据。- 每个流都使用哈希环进行哈希操作。
distributor
将每个流发送到合适的ingester
和他们的副本(基于配置的复制因子)。- 每个
ingester
将为日志流数据创建一个块或附加到一个现有的块上。每个租户和每个标签集的块是唯一的。
2. Promtail
promtail 是 loki 架构中最常用的采集器, 相当于 EFK 中的 filebeat/fluentd 。
Promtail 是 Loki 官方支持的日志采集端,在需要采集日志的节点上运行采集代理,再统一发送到 Loki 进行处理。除了使用 Promtail,社区还有很多采集日志的组件,比如 fluentd、fluent bit、logstash 等,也都支持发送到 Loki。
但是 Promtail 是运行 Kubernetes 时的首选客户端,因为你可以将其配置为自动从 Promtail 运行的同一节点上运行的 Pod 中抓取日志。Promtail 和 Prometheus 在 Kubernetes 中一起运行,还可以实现非常强大的调试功能,如果 Prometheus 和 Promtail 使用相同的标签,用户还可以使用 Grafana 根据标签集在指标和日志之间切换。
它的主要工作流程:
- 使用 fsnotify 监听指定目录下(例如:/var/log/*.log)的文件创建与删除
- 对每个活跃的日志文件起一个 goroutine 进行类似 tail -f 的读取,读取到的内容发送给 channel
- 有一个单独的 goroutine 会读取 channel 中的日志行,分批并附加上标签后推送给 Loki
二、使用YAML部署 Loki
这次部署的 loki 整体架构如下,
- loki 使用
StatefulSet
的方式运行, - Promtail 以
DaemonSet
的方式运行在 k8s 集群的每个节点 - Grafana 以 Deploment 方式运行,通过 Ingress 暴露访问端口
1. Promtail部署
1.1 Promtail 部署文件
资源清单:loki-promtail.yaml
# 1.命名空间
apiVersion: v1
kind: Namespace
metadata:
name: logging
# 2.ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: loki-promtail
labels:
app: promtail
namespace: logging
# 3.集群角色
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: promtail-clusterrole
namespace: logging
labels:
app: promtail
rules:
- apiGroups: [""] # 核心组
resources:
- nodes
- nodes/proxy
- services
- endpoints
- pods
verbs: ["get", "watch", "list"]
# 4.集群角色绑定
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: promtail-clusterrolebinding
labels:
app: promtail
namespace: logging
subjects:
- name: loki-promtail
kind: ServiceAccount
namespace: logging
roleRef:
name: promtail-clusterrole
kind: ClusterRole
apiGroup: rbac.authorization.k8s.io
# 5.ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: loki-promtail
namespace: logging
labels:
app: promtail
data:
promtail.yaml: |
client: # 定义 Promtail 如何与 Loki 服务器通信,发送收集到的日志
backoff_config: # 配置当请求失败时的重试策略
max_period: 1s # 重试间隔的最大时间为 1 秒
max_retries: 20 # 最多重试 20 次
min_period: 500ms # 重试间隔的最小时间为 500 毫秒
batchsize: 10240 # 每次发送到 Loki 的日志批次最大大小为 10240 字节(约 10KB)
batchwait: 2s # 即使批次大小未达到 batchsize,Promtail 最多等待 2 秒后也会发送日志。这是为了避免延迟过高
external_labels: {} # 为所有发送到 Loki 的日志添加静态标签,这里为空(可以手动添加标签,如 env: prod)
timeout: 15s # 等待 Loki 服务器响应的最大时间为 15 秒,超时后请求失败并触发重试
positions:
filename: /run/promtail/positions.yaml # Promtail 使用一个文件来记录它读取日志文件的位置(类似于书签),以避免重复读取或遗漏日志
server:
http_listen_port: 3101 # 配置 Promtail 自身的 HTTP 服务,用于暴露监控指标或调试
target_config:
sync_period: 10s # 控制 Promtail 如何定期同步其发现目标(如 Kubernetes Pods)的元数据。
scrape_configs: # 定义 Promtail 如何发现和收集 Kubernetes 集群中的日志。scrape_configs 包含多个任务(job),每个任务针对不同类型的 Pod 进行日志收集
- job_name: kubernetes-pods-name # 收集由 name 标签定义的 Pod 日志
pipeline_stages:
- docker: {} # 指定日志格式为 Docker 格式(JSON 格式的容器日志)。Promtail 会解析 Docker 容器日志,提取时间戳和日志内容
kubernetes_sd_configs:
- role: pod # Promtail 通过 Kubernetes 服务发现(Service Discovery)查找 Pod,收集它们的日志
relabel_configs:
- source_labels:
- __meta_kubernetes_pod_label_name # 从 Pod 的标签中提取 name 标签的值,设置为 __service__ 标签
target_label: __service__ # Kubernetes Pod 可能有标签 name=my-app,Promtail 将其值 my-app 保存为 __service__,用于标识服务
- source_labels:
- __meta_kubernetes_pod_node_name # 将 Pod 所在节点的名称设置为 __host__ 标签
target_label: __host__ # 记录 Pod 运行在哪个 Kubernetes 节点上(比如 node-1),便于日志追踪
- action: drop # 如果 __service__ 标签为空(即 Pod 没有 name 标签),丢弃该 Pod,不收集其日志
regex: ''
source_labels:
- __service__
- action: labelmap # 将所有以 __meta_kubernetes_pod_label_ 开头的元数据标签(如 __meta_kubernetes_pod_label_app)映射为普通标签
regex: __meta_kubernetes_pod_label_(.+)
- action: replace # 将命名空间(namespace)和 __service__ 组合成 job 标签,格式为 namespace/service
replacement: $1
separator: /
source_labels:
- __meta_kubernetes_namespace # 如果命名空间是 default,__service__ 是 my-app,则 job 标签为 default/my-app
- __service__
target_label: job
- action: replace # 将 Pod 的命名空间设置为 namespace 标签
source_labels: # 如果 Pod 在 default 命名空间,日志会带上 namespace=default 标签
- __meta_kubernetes_namespace
target_label: namespace
- action: replace # 将 Pod 的名称设置为 pod 标签
source_labels: # 如果 Pod 名为 my-app-1234,日志会带上 pod=my-app-1234 标签
- __meta_kubernetes_pod_name
target_label: pod
- action: replace # 将容器的名称设置为 container 标签
source_labels: # 如果容器名为 app-container,日志会带上 container=app-container 标签
- __meta_kubernetes_pod_container_name
target_label: container
- replacement: /var/log/pods/*$1/*.log # 生成日志文件的路径,告诉 Promtail 从哪里读取日志,路径格式为 /var/log/pods/*<pod_uid>/<container_name>.log
separator: /
source_labels:
- __meta_kubernetes_pod_uid # Pod 的唯一 ID
- __meta_kubernetes_pod_container_name # 容器名称
target_label: __path__ # 例如,Pod UID 是 abc-123,容器名为 app-container,日志路径为 /var/log/pods/*abc-123/app-container.log
- job_name: kubernetes-pods-app # 收集由 app 标签定义的 Pod 日志
pipeline_stages:
- docker: {}
kubernetes_sd_configs:
- role: pod
relabel_configs:
- action: drop
regex: .+
source_labels:
- __meta_kubernetes_pod_label_name
- source_labels:
- __meta_kubernetes_pod_label_app
target_label: __service__
- source_labels:
- __meta_kubernetes_pod_node_name
target_label: __host__
- action: drop
regex: ''
source_labels:
- __service__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- action: replace
replacement: $1
separator: /
source_labels:
- __meta_kubernetes_namespace
- __service__
target_label: job
- 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
- replacement: /var/log/pods/*$1/*.log
separator: /
source_labels:
- __meta_kubernetes_pod_uid
- __meta_kubernetes_pod_container_name
target_label: __path__
- job_name: kubernetes-pods-direct-controllers # 收集由直接控制器(如 Deployment)管理的 Pod 日志
pipeline_stages:
- docker: {}
kubernetes_sd_configs:
- role: pod
relabel_configs:
- action: drop # 首先检查 __meta_kubernetes_pod_label_name(name 标签)
regex: .+ # 如果 name 标签存在(匹配正则 .+,即非空),执行 drop 动作,丢弃该 Pod
separator: ''
source_labels:
- __meta_kubernetes_pod_label_name
- __meta_kubernetes_pod_label_app
- action: drop
regex: '[0-9a-z-.]+-[0-9a-f]{8,10}'
source_labels:
- __meta_kubernetes_pod_controller_name
- source_labels:
- __meta_kubernetes_pod_controller_name
target_label: __service__
- source_labels:
- __meta_kubernetes_pod_node_name
target_label: __host__
- action: drop
regex: ''
source_labels:
- __service__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- action: replace
replacement: $1
separator: /
source_labels:
- __meta_kubernetes_namespace
- __service__
target_label: job
- 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
- replacement: /var/log/pods/*$1/*.log
separator: /
source_labels:
- __meta_kubernetes_pod_uid
- __meta_kubernetes_pod_container_name
target_label: __path__
- job_name: kubernetes-pods-indirect-controller # 收集由间接控制器(如 ReplicaSet)管理的 Pod 日志
pipeline_stages:
- docker: {}
kubernetes_sd_configs:
- role: pod
relabel_configs:
- action: drop # 如果 Pod 有 name 或 app 标签,触发 drop 动作,丢弃该 Pod
regex: .+
separator: ''
source_labels:
- __meta_kubernetes_pod_label_name
- __meta_kubernetes_pod_label_app
- action: keep # 如果 Pod 由 ReplicaSet 控制器管理(控制器名称匹配 xxx-12345678 模式),触发 keep 动作,保留该 Pod
regex: '[0-9a-z-.]+-[0-9a-f]{8,10}'
source_labels:
- __meta_kubernetes_pod_controller_name
- action: replace
regex: '([0-9a-z-.]+)-[0-9a-f]{8,10}'
source_labels:
- __meta_kubernetes_pod_controller_name
target_label: __service__
- source_labels:
- __meta_kubernetes_pod_node_name
target_label: __host__
- action: drop
regex: ''
source_labels:
- __service__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- action: replace
replacement: $1
separator: /
source_labels:
- __meta_kubernetes_namespace
- __service__
target_label: job
- 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
- replacement: /var/log/pods/*$1/*.log
separator: /
source_labels:
- __meta_kubernetes_pod_uid
- __meta_kubernetes_pod_container_name
target_label: __path__
- job_name: kubernetes-pods-static # 收集静态配置的 Pod 日志(基于特定的注解)
pipeline_stages:
- docker: {}
kubernetes_sd_configs:
- role: pod
relabel_configs:
- action: drop
regex: ''
source_labels:
- __meta_kubernetes_pod_annotation_kubernetes_io_config_mirror
- action: replace
source_labels:
- __meta_kubernetes_pod_label_component
target_label: __service__
- source_labels:
- __meta_kubernetes_pod_node_name
target_label: __host__
- action: drop
regex: ''
source_labels:
- __service__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- action: replace
replacement: $1
separator: /
source_labels:
- __meta_kubernetes_namespace
- __service__
target_label: job
- 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
- replacement: /var/log/pods/*$1/*.log
separator: /
source_labels:
- __meta_kubernetes_pod_annotation_kubernetes_io_config_mirror
- __meta_kubernetes_pod_container_name
target_label: __path__
# 6.DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: loki-promtail
namespace: logging
labels:
app: promtail
spec:
selector:
matchLabels:
app: promtail
updateStrategy: # 更新策略
rollingUpdate:
maxUnavailable: 1 # 滚动更新时最多一个pod不可用,即同一时刻只允许一个节点上的Pod不可用
type: RollingUpdate # 滚动更新
template:
metadata:
labels:
app: promtail
spec:
serviceAccountName: loki-promtail # 绑定的SA
containers:
- name: promtail
image: grafana/promtail:2.9.2
imagePullPolicy: IfNotPresent
args:
- -config.file=/etc/promtail/promtail.yaml # 指定 Promtail 的配置文件路径,位于容器内的 /etc/promtail/promtail.yaml(由 ConfigMap 提供)
- -client.url=http://loki:3100/loki/api/v1/push # 指定 Loki 服务器的地址,Promtail 将日志发送到 http://loki:3100/loki/api/v1/push
env:
- name: HOSTNAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.nodeName # 从 Kubernetes Pod 的元数据中获取节点名称(如 k8s-node01)
volumeMounts:
- mountPath: /etc/promtail # 挂载 config 卷,包含 Promtail 配置文件(promtail.yaml)
name: config
- mountPath: /run/promtail # 挂载 run 卷,存储 Promtail 的位置文件(positions.yaml),用于记录日志读取偏移量
name: run
- mountPath: /data/docker/containers # 挂载 docker 卷,包含 Docker 容器的日志文件(路径需要根据容器运行时调整,如果使用 containerd 可忽略)
name: docker
readOnly: true # 挂载卷只读
- mountPath: /var/log/pods # 挂载 pods 卷,包含 Kubernetes Pod 的日志文件(标准路径)
name: pods
readOnly: true # 挂载卷只读
ports:
- containerPort: 3101 #Promtail 容器在 3101 端口提供 HTTP 服务,与 promtail.yaml 中的 server.http_listen_port: 3101 一致
name: http
protocol: TCP
securityContext: # 定义容器的安全上下文,控制运行权限和文件系统访问
readOnlyRootFilesystem: true # 容器根文件系统为只读,增强安全性,防止意外修改。Promtail 需要 root 权限访问节点上的日志文件(如 /var/log/pods)
runAsGroup: 0 # 容器以 root 用户(UID 0)运行
runAsUser: 0 # 容器以 root 组(GID 0)运行
readinessProbe: # 定义就绪探针,检查 Promtail 容器是否准备好提供服务
httpGet:
path: /ready # 访问 /ready 端点
port: http # 使用命名为 http 的端口(3101)
scheme: HTTP
failureThreshold: 5 # 连续 5 次探测失败后,标记容器为未就绪
initialDelaySeconds: 10 # 容器启动后等待 10 秒开始第一次探测
periodSeconds: 10 # 每 10 秒探测一次
successThreshold: 1 # 一次探测成功即标记为就绪
timeoutSeconds: 1 # 每次探测的超时时间为 1 秒
tolerations:
- operator: Exists # 允许 Pod 调度到带有任何污点(taint)的节点上
volumes: # 定义 Pod 使用的卷,供容器挂载
- name: config
configMap:
defaultMode: 420 # 文件权限为 0644(即 rw-r--r--),适合配置文件(420为十进制,0644是Linux系统中使用的八进制)
name: loki-promtail # 使用名为 loki-promtail 的 ConfigMap(即之前提供的配置)
- name: run
hostPath:
path: /run/promtail # 挂载节点上的 /run/promtail 目录
type: "" # 默认类型,目录不存在时不会自动创建
- name: docker
hostPath:
path: /data/docker/containers # 挂载节点上的 Docker 容器日志目录
- name: pods
hostPath:
path: /var/log/pods # 挂载节点上的 Kubernetes Pod 日志目录
执行资源清单
# 执行资源清单
$ kubectl apply -f loki-promtail.yaml
查看资源
# 查看命名空间
$ kubectl get ns
NAME STATUS AGE
default Active 13d
harbor Active 7d4h
kube-node-lease Active 13d
kube-public Active 13d
kube-system Active 13d
logging Active 2d4h
# 查看 ServiceAccount
$ kubectl get sa -n logging
NAME SECRETS AGE
default 0 2d4h
loki-promtail 0 2d4h
# 查看集群角色
$ kubectl get ClusterRole | grep promtail
promtail-clusterrole
# 查看集群角色绑定
$ kubectl get ClusterRoleBinding | grep promtail
promtail-clusterrolebinding ClusterRole/promtail-clusterrole 2d4h
# 查看 ConfigMap
$ kubectl get cm -n logging
NAME DATA AGE
kube-root-ca.crt 1 2d4h
loki-promtail 1 2d1h
# 查看 DaemonSet
$ kubectl get ds -n logging
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
loki-promtail 4 4 4 4 4 <none> 27m
# 查看Pod
$ kubectl get pods -n logging
NAME READY STATUS RESTARTS AGE
loki-promtail-lsqsg 1/1 Running 0 27m
loki-promtail-r7vjh 1/1 Running 0 27m
loki-promtail-szd8m 1/1 Running 0 27m
loki-promtail-whffb 1/1 Running 0 27m
1.2 scrape_configs 配置详解
scrape_configs
配置了 Promtail 如何使用指定的发现方法从一系列目标中抓取日志,类似于 Prometheus 中的抓取配置。
任务名称,用于在 Promtail 中识别该抓取配置的名称。
job_name: <string>
描述如何对目标日志进行结构化
[pipeline_stages: <pipeline_stages>]
如何从 jounal 抓取日志
[journal: <journal_config>]
如何从 syslog 抓取日志
[syslog: <syslog_config>]
如何通过 Loki push API 接收日志 (例如从其他 Promtail 或 Docker Logging Driver 中获取的数据)
[loki_push_api: <loki_push_api_config>]
描述了如何 relabel 目标
relabel_configs:
- [<relabel_config>]
抓取日志静态目标配置
static_configs:
- [<static_config>]
包含要抓取的目标文件
file_sd_configs:
- [<file_sd_configs>]
基于kubernetes的自动发现配置
kubernetes_sd_configs:
- [<kubernetes_sd_config>]
relabel_configs
Relabeling
是一个强大的工具,可以在日志被抓取之前动态地重写其标签集。每个抓取配置可以配置多个 relabeling
步骤,按照它们在配置文件中出现的顺序应用于每个目标的标签集。和 Prometheus 中的 Relabel 操作也非常类似。
在 relabeling
之后,如果 instance
标签在 relabeling 的时候没有被设置,则默认设置为 __address__
的值。__param_<name>
标签被设置为第一个传递的 URL 参数 <name>
的值。
在 relabeling
阶段,以 __meta_
为前缀的额外标签也是可用的,它们是由提供目标的服务发现机制设置的,不同的机制之间有所不同。
在目标 relabeling
完成后,以 __
开头的标签将从标签集中删除。
如果一个 relabeling
操作只需要临时存储一个标签值(作为后续重新标注步骤的输入),则可以使用 __tmp
标签名称前缀。
从现有标签中选择 values 值的源标签
它们的内容使用配置的分隔符连接起来,并与配置的正则表达式相匹配,以进行替换、保留和删除操作。
[ source_labels: '[' <labelname> [, ...] ']' ]
连接源标签值之间的分隔符
[ separator: <string> | default = ; ]
在一个 replace 替换操作后结果值被写入的标签
它对替换动作是强制性的,Regex 捕获组是可用的。
[ target_label: <labelname> ]
正则表达式,提取的值与之匹配
[ regex: <regex> | default = (.*) ]
[ modulus: <uint64> ]
Replacement 值:如果正则表达式匹配,则对其进行 regex 替换
[ replacement: <string> | default = $1 ]
根据正则匹配结果执行的动作
[ action: <relabel_action> | default = replace ]
<regex>
是任何有效的 RE2
正则表达式,它是 replace
、keep
、drop
、labelmap
、labeldrop
和 labelkeep
操作的必要条件。
<relabel_action>
决定了要采取的 relabeling
动作:
replace
:将正则表达式与连接的source_labels
匹配,然后设置target_label
为replacement
,用 replacement 中的匹配组引用(${1}、${2}…)替换其值,如果正则表达式不匹配,则不会进行替换。keep
:删除那些 regex 与source_labels
不匹配的目标。drop
:删除与 regex 相匹配的source_labels
目标。hashmod
:将target_label
设置为source_labels
的哈希值的模。labelmap
:将正则表达式与所有标签名称匹配,然后将匹配的标签值复制到由replacement
给出的标签名中,replacement 中的匹配组引用(${1}, ${2}, …)由其值代替。labeldrop
:将正则表达式与所有标签名称匹配,任何匹配的标签都将从标签集中删除。labelkeep
:将正则表达式与所有标签名称匹配,任何不匹配的标签将被从标签集中删除。
关于 Promotail 配置更加详细的介绍,见:https://www.qikqiak.com/k3s/logging/loki/promtail/
2. Loki 部署
2.1 安装NFS
安装 NFS,配置存储卷自动分配 PV,用于持久化 Harbor 存储的镜像。
这里选用 k8s-master (10.20.1.139) 作为 nfs 服务端,其它节点作为 nfs 客户端
2.1.1 安装 NFS 服务
master节点、node01、node02、node03 节点都需要安装执行
$ yum install -y nfs-utils rpcbind
2.1.2 创建共享目录
仅在 nfs 服务端 (master节点) 执行
# 创建 共享目录
$ mkdir -p /root/data/loki/
# 目录提权
chmod 777 /root/data/loki/
# 变更用户组
chown nobody /root/data/loki/
2.1.3 编辑共享目录读写配置
仅在 nfs 服务端 (master节点) 执行
$ vim /etc/exports
/root/data 10.20.1.0/24(rw,fsid=0,no_root_squash)
/root/data/loki 10.20.1.0/24(rw,no_root_squash,no_all_squash,no_subtree_check,sync)
这里使用的是 NFS4 服务,上面的配置表示 10.20.1.0/24 网段的 ip 都可以与 nfs 主服务器共享 /root/data/harbor
目录内容
2.1.4 启动NFS服务
集群内所有节点都需要操作
# 启动服务
$ systemctl start rpcbind
$ systemctl restart nfs-server.service
# 设置开机自启
$ systemctl enable rpcbind
$ systemctl enable nfs-server.service
2.1.5 测试 NFS 目录挂载
# NFS 客户端执行:创建目录
$ mkdir /data/test1
# NFS 客户端执行:挂载目录到NFS服务端
$ mount -t nfs4 10.20.1.139:/loki /data/test1
# NFS 客户端执行:查看挂载结果
$ mount | grep /data/test1
10.20.1.139:/loki on /data/test1 type nfs4 (rw,relatime,vers=4.2,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=10.20.1.142,local_lock=none,addr=10.20.1.139)
# NFS 客户端执行:写入数据
$ echo "this is client" > /data/test1/a.txt
# NFS 服务端执行:查看数据, 结论:客户端数据已写入服务端挂载目录
$ cat /root/data/loki/a.txt
this is client
# NFS 服务端执行:写入数据
$ echo "This is Server" >> /root/data/loki/a.txt
# NFS 客户端执行:查看数据, 结论:服务端数据已写入客户端挂载目录
$ cat /data/test1/a.txt
this is client
This is Server
取消挂载
# 取消挂载
umount /data/test1
# 如果取消挂载出现报错,例如:
$ umount /data/test1
umount.nfs4: /data/test1: device is busy
# 查看目录占用进程
$ fuser -m /data/test1
/data/test1: 32679c
# kill 进程
$ kill -9 32679
# 方式二:强制卸载
umount -l /data/test1
2.2 创建 StorageClass
promtail 抓取到的日志,推送给 Loki 存储,这里将 Loki 的数据通过 StorageClass 动态创建PV,将数据存储起来。
资源清单:loki-storage.yaml
# 1.命名空间
apiVersion: v1
kind: Namespace
metadata:
name: loki-storage
# 2.存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
namespace: loki-storage
name: loki-storage
# provisioner: nfs-provisioner
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 指定动态配置器,NFS 子目录外部配置器
parameters:
pathPattern: ${.PVC.namespace}/${.PVC.name} # 动态生成的 NFS 路径,格式为 <PVC 命名空间>/<PVC 名称>,例如 loki-storageclass/test-claim。
archiveOnDelete: "true" ##删除 pv,pv内容是否备份
# 3.NFS 动态存储配置器,用于自动为 PVC 创建 PV
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: loki-storage
name: nfs-client-provisioner # NFS 动态存储配置器,用于自动为 PVC 创建 PV
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner # 指定使用的 ServiceAccountName
containers:
- name: nfs-client-provisioner
image: k8s.dockerproxy.com/sig-storage/nfs-subdir-external-provisioner:v4.0.2
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client-root # 挂载的卷名称,与 volumes 部分定义的卷对应
mountPath: /persistentvolumes # 将 NFS 卷挂载到容器内的 /persistentvolumes 路径,供容器读写 NFS 共享数据。
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner # 指定配置器名称,与 StorageClass 保持一致
- name: NFS_SERVER
value: 10.20.1.139 # NFS 服务器地址
- name: NFS_PATH
value: /root/data/loki # NFS 共享路径
volumes:
- name: nfs-client-root # 定义一个名为 nfs-client-root 的 NFS 卷,连接到 NFS 服务器的指定地址和路径
nfs:
server: 10.20.1.139 # NFS 服务器地址
path: /root/data/loki # NFS 共享路径
nodeSelector: # 指定Pod运行的节点
kubernetes.io/hostname: k8s-node01
# nodeName: k8s-node01 # 指定 Pod 运行在 k8s-node02 节点上
# 4.服务账户
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner # SA 的名称
namespace: loki-storage
# 5.集群角色
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
# 6.集群角色绑定
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount # 绑定类型 ServiceAccount
name: nfs-client-provisioner # ServiceAccount 的名称
namespace: loki-storage
roleRef:
kind: ClusterRole # 绑定的角色类型
name: nfs-client-provisioner-runner # 集群角色名称 nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
# 角色
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner # 角色的名称,表明它与 NFS 客户端存储提供者的领导者选举(leader election)机制相关。
namespace: loki-storage
rules:
- apiGroups: [""] # 空字符串表示核心 API 组(core API group),包含 Kubernetes 的基本资源,如 endpoints。
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# SA角色绑定
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-leader-locking-nfs-client-provisioner
namespace: loki-storage
subjects:
- kind: ServiceAccount # 绑定资源类型为 ServiceAccount
name: nfs-client-provisioner # 绑定的ServiceAccount 名称
namespace: loki-storage
roleRef:
kind: Role # 绑定角色(loki-storage名称空间的角色)
apiGroup: rbac.authorization.k8s.io
name: leader-locking-nfs-client-provisioner # 角色的名称
执行资源清单
# 创建 StorageClass
$ kubectl apply -f loki-storage.yaml
# 查看 StorageClass
$ kubectl get StorageClass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
loki-storage k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 16m
nfs-client k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 109m
# 查看 Pod
$ kubectl get pods -n loki-storage -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-79f97f7689-w2mtj 1/1 Running 0 15m 192.168.85.236 k8s-node01 <none> <none>
2.3 部署 Loki
Loki在接收日志后会对日志数据进行一定的加工整理,因为存储的数据为有状态的,安装时候推荐使用StatefulSet。
资源清单:loki.yaml
# 1.命名空间
apiVersion: v1
kind: Namespace
metadata:
name: logging
# 2.ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: loki
namespace: logging
# 3.Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: loki
namespace: logging
rules:
- apiGroups:
- extensions # 指定 API 组为 extensions,这是 PodSecurityPolicy 在 Kubernetes 早期版本(v1.21 之前)使用的 API 组,v1.25 后完全移除
resourceNames:
- loki
resources: # 指定权限针对的资源类型为 podsecuritypolicies(PSP)
- podsecuritypolicies # PSP 是一种 Kubernetes 资源,用于控制 Pod 的安全策略,例如是否允许以 root 运行、挂载主机路径等。在 v1.29 中,podsecuritypolicies 资源不存在,规则无效
verbs:
- use # 指定允许的操作是 use,表示主体可以应用指定的 PSP(loki)到 Pod
# 4. RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: loki
namespace: logging
roleRef:
name: loki
apiGroup: rbac.authorization.k8s.io
kind: Role
subjects:
- name: loki
kind: ServiceAccount
# 5. ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: loki
namespace: logging
labels:
app: loki
data:
loki.yaml: | # Loki 的核心配置文件,控制认证、日志存储、索引、块存储、保留策略等
# 通过 X-Scope-OrgID Header 启用身份验证,如果为 true,该 Header 必须存在。
# 如果为 false,OrgID 将始终设置为 "fake"。(用于多租户隔离)
auth_enabled: false
ingester:
chunk_idle_period: 3m # 如果一个块在 3 分钟内没有新日志写入,且未达到最大大小,Loki 会将其刷新到存储
chunk_block_size: 65535 # 每个块的最大大小(字节),这里约为 64KB
chunk_retain_period: 1m # 块刷新到存储后,在内存中保留 1 分钟,允许查询最近的日志
max_transfer_retries: 0 # 当 ingester 退出时,尝试将块转移到其他 ingester 的次数(0 表示不转移,直接刷新到存储)
lifecycler: # 配置 ingester 的生命周期管理,决定如何注册和发现其他 ingester
ring:
kvstore:
store: inmemory # 使用内存作为环存储(其他选项如 consul、etcd)
replication_factor: 1 # 写入和读取的ingesters数量,至少为1(为了冗余和弹性,默认情况下为3)
wal: # 配置日志预写
enabled: true # 启用预写日志(Write-Ahead Log, WAL),在崩溃恢复时确保数据不丢失
dir: /data/wal # WAL 文件存储路径
limits_config: # 设置日志写入的限制规则
enforce_metric_name: false # 不强制要求日志流具有指标名称(metric name)
reject_old_samples: true # 拒绝时间戳早于当前时间的旧日志
reject_old_samples_max_age: 8h # 拒绝时间戳早于当前时间 8 小时的日志
schema_config: # 配置从特定时间段开始应该使用哪些索引模式
configs: # 定义 Loki 的索引模式和存储配置,指定从某个日期开始的存储方式
- from: 2025-07-15 # 指定此配置从 2023-12-05 开始生效
store: boltdb-shipper # 索引存储使用 boltdb-shipper,一种高效的键值存储,适合分布式环境
object_store: filesystem # 日志块(chunks)存储在本地文件系统,支持 S3、GCS 等,filesystem 适合单节点或测试环境
schema: v11 # 使用 Loki 的 v11 索引模式(Loki 的版本化架构)
index: # 配置如何更新和存储索引
prefix: index_ # 索引表的名称前缀(如 index_2025_07_15 )
period: 24h # 索引表的时间周期为 24 小时(每天生成新表)
server:
http_listen_port: 3100 # 配置 Loki 的 HTTP 服务端口,Promtail 将日志发送到 http://loki:3100/loki/api/v1/push(与之前的 Promtail 配置一致)
storage_config: # 为索引和块配置一个或多个存储
boltdb_shipper:
active_index_directory: /data/loki/boltdb-shipper-active # 活动索引存储路径
cache_location: /data/loki/boltdb-shipper-cache # 索引缓存路径
cache_ttl: 24h # 缓存有效期为 24 小时
shared_store: filesystem # 索引使用文件系统存储
filesystem:
directory: /data/loki/chunks # 日志块存储路径
chunk_store_config: # 配置日志块的缓存和查询行为
max_look_back_period: 0s # 限制查询数据的时间,默认是禁用的,这个值应该小于或等于table_manager.retention_period中的值
table_manager: # 管理索引表的保留和删除
retention_deletes_enabled: true # 启用索引表和日志的删除
retention_period: 48h # 日志和索引保留 48 小时,超过的会被删除,保留期必须是索引周期(schema_config.index.period: 24h)的倍数
compactor: # 配置 Loki 的压缩器(compactor),用于压缩和清理索引
working_directory: /data/loki/boltdb-shipper-compactor # 压缩器工作目录
shared_store: filesystem # 压缩器使用文件系统存储,工作目录需持久化存储,避免数据丢失
# ruler: # 配置 Loki 的告警规则(ruler),用于基于日志触发告警。如果需要告警功能,需取消注释并确保 Alertmanager 服务可用
# storage: # rules规则存储
# type: local # 主要支持本地存储(local)和对象文件系统(azure, gcs, s3, swift)
# local:
# directory: /etc/loki/rules # 告警文件存放目录
# rule_path: /data/loki/rules-temp # rules临时规则文件存储路径
# flush_period: 1m # 规则刷新间隔为 1 分钟。
# alertmanager_url: http://alertmanager-main.monitoring.svc:9093 # alertmanager地址
# external_url: http://alertmanager.od.com # 外部访问 alertmanager
# ring:
# kvstore:
# store: inmemory # 规则环存储使用内存
# enable_api: true # 启用规则管理 API
# enable_alertmanager_v2: true # 支持 Alertmanager v2 协议
# 6.Lock Pod
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: loki
namespace: logging
labels:
app: loki
spec:
podManagementPolicy: OrderedReady # 指定 Pod 的管理策略为 OrderedReady:Pod 按顺序(0, 1, 2...)创建和删除,等待前一个 Pod 就绪后再创建下一个
replicas: 1
selector:
matchLabels:
app: loki
serviceName: loki
updateStrategy:
type: RollingUpdate # 使用滚动更新策略,逐步替换旧 Pod
template:
metadata:
labels:
app: loki
spec:
serviceAccountName: loki # 指定 Pod 使用 loki 服务账号(之前定义的 ServiceAccount)
securityContext: # 定义 Pod 级别的安全上下文,控制文件系统权限和用户/组 ID
fsGroup: 10001 # Pod 挂载的卷(如 /data)将归属组 ID 10001
runAsGroup: 10001 # 容器进程以组 ID 10001 运行,确保挂载的卷(如 PVC)归属组 10001,Loki 进程可以访问
runAsNonRoot: true # 强制容器以非 root 用户运行,增强安全性
runAsUser: 10001 # 容器进程以用户 ID 10001 运行
initContainers: # 定义初始化容器,在主容器启动前调整存储路径的权限
- name: fix-permissions # 初始化容器名称
image: busybox:1.37.0 # 使用 busybox 镜像,适合执行简单命令
securityContext:
privileged: true # 以特权模式运行,允许修改文件系统权限
runAsGroup: 0 # 以 root 用户运行
runAsNonRoot: false
runAsUser: 0
command: # 创建 /data/loki 目录,将 /data 及其子目录的拥有者改为 UID 10001 和 GID 10001,列出 /data 目录内容,验证权限
- sh
- -c
- >-
id;
mkdir -p /data/loki;
chown 10001:10001 /data -R;
ls -la /data/
volumeMounts:
- mountPath: /data # 挂载 storage 卷到 /data,与主容器共享
name: storage
containers:
- name: loki
image: grafana/loki:2.9.2
imagePullPolicy: IfNotPresent
args:
- -config.file=/etc/loki/config/loki.yaml # 指定配置文件路径
volumeMounts:
- name: config
mountPath: /etc/loki/config/loki.yaml # 挂载 ConfigMap loki 的 loki.yaml 到 /etc/loki/config/loki.yaml
subPath: loki.yaml # subPath: loki.yaml 表示只挂载 ConfigMap 中 loki.yaml 键对应的文件内容到 mountPath 指定的路径(/etc/loki/config/loki.yaml)
- name: storage
mountPath: "/data" # 挂载 PVC 到 /data,用于存储 WAL、索引和块
ports:
- name: http-metrics
containerPort: 3100 # Loki 监听 3100 端口(HTTP),用于接收日志和监控指标
protocol: TCP
livenessProbe: # 存活探测
httpGet:
path: /ready # 检查 /ready 端点,确认 Loki 是否健康/就绪
port: http-metrics
scheme: HTTP
initialDelaySeconds: 45 # 启动后 45 秒开始探测
timeoutSeconds: 1 # 每次探测超时 1 秒
periodSeconds: 10 # 每 10 秒探测一次
successThreshold: 1 # 1 次成功即健康
failureThreshold: 3 # 3 次失败标记不健康
readinessProbe: # 就绪探测
httpGet:
path: /ready
port: http-metrics
scheme: HTTP
initialDelaySeconds: 45
timeoutSeconds: 1
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
securityContext:
readOnlyRootFilesystem: true # 容器根文件系统为只读,增强安全性。
terminationGracePeriodSeconds: 4800 # Pod 终止时的宽限期为 4800 秒(80 分钟)
volumes:
- name: config # 定义卷,将 ConfigMap loki 挂载到容器
configMap:
defaultMode: 0640 # 文件权限为 rw-r-----(所有者读写,组可读)。0640 前面有0,因此被解析为八进制数
name: loki # 引用名为 loki ConfigMap
volumeClaimTemplates:
- metadata:
name: storage
labels:
app: loki
spec:
storageClassName: "loki-storage" # 注意修改 storageClass 名称
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "10Gi"
# 7.Loki Service
apiVersion: v1
kind: Service
metadata:
name: loki
namespace: logging
labels:
app: loki
spec:
type: ClusterIP
ports:
- port: 3100
protocol: TCP
name: http
targetPort: http-metrics
selector:
app: loki
执行资源清单
# 执行资源清单
$ kubectl apply -f loki.yaml
# ConfigMap
$ kubectl get configMap -n logging
NAME DATA AGE
kube-root-ca.crt 1 55m
loki 1 54m
loki-promtail 1 55m
# StatefulSet
$ kubectl get StatefulSet -n logging
NAME READY AGE
loki 1/1 55m
# Pod
$ kubectl get pod -n logging
NAME READY STATUS RESTARTS AGE
loki-0 1/1 Running 0 55m
loki-promtail-cbllm 1/1 Running 0 56m
loki-promtail-h7pz5 1/1 Running 0 56m
loki-promtail-rrwtf 1/1 Running 0 56m
loki-promtail-vs4df 1/1 Running 0 56m
# Service
$ kubectl get svc -n logging
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
loki ClusterIP 10.106.23.220 <none> 3100/TCP 55m
3. 部署 Grafana
部署 Grafana, 展示推送到 Loki 的日志数据
3.1 Grafana 资源清单
资源清单: loki-grafana.yaml
# 1.声明 PVC ,使用 StorageClass 动态创建PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: grafana-pvc
namespace: logging
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: loki-storage # 使用 StorageClass 动态创建PV
volumeMode: Filesystem
# 2.创建Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: grafana
name: grafana
namespace: logging
spec:
replicas: 1
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
securityContext:
fsGroup: 472
supplementalGroups:
- 0
containers:
- name: grafana
image: grafana/grafana:8.3.5
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
name: http-grafana
protocol: TCP
resources:
requests:
cpu: 500m
memory: 1024Mi
limits:
cpu: 1000m
memory: 2048Mi
volumeMounts:
- mountPath: /var/lib/grafana
name: grafana-pv
volumes: # 挂载容器卷
- name: grafana-pv
persistentVolumeClaim:
claimName: grafana-pvc # 使用声明的PVC,通过 StorageClass 动态创建PV
nodeSelector: # 指定Pod运行的节点
kubernetes.io/hostname: k8s-node02
# 3.创建Service
apiVersion: v1
kind: Service
metadata:
name: grafana
namespace: logging
spec:
ports:
- port: 3000
protocol: TCP
targetPort: http-grafana
nodePort: 30339
selector:
app: grafana
type: NodePort
# 4.创建 Ingress
apiVersion: networking.k8s.io/v1 # 指定 API 版本
kind: Ingress
metadata:
name: grafana-ui
namespace: logging
labels:
k8s-app: grafana
spec:
ingressClassName: nginx # 指定此 Ingress 资源由名称为 nginx 的 IngressClass 处理
rules:
- host: loki.grafana.com # 指定此规则适用于请求的 HTTP 主机头(Host Header)为 loki.grafana.com 的流量。客户端必须通过该域名访问
http:
paths:
- path: / # 指定匹配的 URL 路径为 /,即根路径,表示匹配所有以 / 开头的请求
pathType: Prefix # 定义路径匹配的类型为 Prefix,表示匹配以指定路径(/) 开头的所有请求
backend:
service:
name: grafana # 指定后端服务的名称为 nginx-svc.(必须存在于同一命名空间,或通过 <namespace>/<service-name> 跨命名空间引用)
port:
number: 3000 # 指定目标 Service 的端口为 80
执行资源清单
# 执行资源清单
$ kubectl apply -f loki-grafana.yaml
# 查看 Ingress
$ kubectl get ingress -n logging
NAME CLASS HOSTS ADDRESS PORTS AGE
grafana-ui nginx loki.grafana.com 80 10s
# 查看 Serivce
$ kubectl get svc -n logging
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana NodePort 10.104.180.186 <none> 3000:30339/TCP 6m13s
loki ClusterIP 10.106.23.220 <none> 3100/TCP 148m
# 查看 PVC
$ kubectl get pvc -n logging
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
grafana-pvc Bound pvc-649c73ed-2885-4924-a219-ee58a8ab0166 5Gi RWO loki-storage <unset> 6m28s
storage-loki-0 Bound pvc-819fb39b-1ad9-48ff-8dfe-fafa62885af7 10Gi RWO loki-storage <unset> 149m
# 查看 PV
$ kubectl get pv -n logging
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-649c73ed-2885-4924-a219-ee58a8ab0166 5Gi RWO Delete Bound logging/grafana-pvc loki-storage <unset> 6m36s
pvc-819fb39b-1ad9-48ff-8dfe-fafa62885af7 10Gi RWO Delete Bound logging/storage-loki-0 loki-storage <unset> 149m
# 查看 Pod
$ kubectl get pods -n logging
NAME READY STATUS RESTARTS AGE
grafana-599d67bdbb-m6zjz 1/1 Running 0 6m54s
loki-0 1/1 Running 0 149m
loki-promtail-cbllm 1/1 Running 0 150m
loki-promtail-h7pz5 1/1 Running 0 150m
loki-promtail-rrwtf 1/1 Running 0 150m
loki-promtail-vs4df 1/1 Running 0 150m
3.2 配置 Host 域名映射
在浏览器所在主机编辑 /etc/hosts
10.20.1.140 loki.grafana.com
保存后,使用浏览器访问 Grafana,地址:https://loki.grafana.com/
4. Grafana 基础使用
4.1 登录 Grafana
默认用户名:admin
默认密码:admin
Grafana 首页
4.2 添加 Loki 作为数据源
添加数据源
选择 Loki
Loki数据源设置
4.3 查看日志
至此,基于资源清单 yaml 部署 Loki、Promtail、Grafana 就完成了。
三、使用 Helm 部署 Loki
使用 Helm 部署 Loki 需要保持网络通畅,如果你的服务器无法连接外网,那就需要提前将loki的helm安装包下载到服务器,并且提前将需要的镜像导入到服务器中。
1. 部署 StorageClass
使用 StorageClass 自动创建 PV,管理文件存储。
资源清单:loki-storage.yaml
# 1.命名空间
apiVersion: v1
kind: Namespace
metadata:
name: loki-storage
# 2.存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
namespace: loki-storage
name: loki-storage
# provisioner: nfs-provisioner
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 指定动态配置器,NFS 子目录外部配置器
parameters:
pathPattern: ${.PVC.namespace}/${.PVC.name} # 动态生成的 NFS 路径,格式为 <PVC 命名空间>/<PVC 名称>,例如 loki-storageclass/test-claim。
archiveOnDelete: "true" ##删除 pv,pv内容是否备份
# 3.NFS 动态存储配置器,用于自动为 PVC 创建 PV
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: loki-storage
name: nfs-client-provisioner # NFS 动态存储配置器,用于自动为 PVC 创建 PV
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner # 指定使用的 ServiceAccountName
containers:
- name: nfs-client-provisioner
image: k8s.dockerproxy.com/sig-storage/nfs-subdir-external-provisioner:v4.0.2
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client-root # 挂载的卷名称,与 volumes 部分定义的卷对应
mountPath: /persistentvolumes # 将 NFS 卷挂载到容器内的 /persistentvolumes 路径,供容器读写 NFS 共享数据。
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner # 指定配置器名称,与 StorageClass 保持一致
- name: NFS_SERVER
value: 10.20.1.139 # NFS 服务器地址
- name: NFS_PATH
value: /root/data/loki # NFS 共享路径
volumes:
- name: nfs-client-root # 定义一个名为 nfs-client-root 的 NFS 卷,连接到 NFS 服务器的指定地址和路径
nfs:
server: 10.20.1.139 # NFS 服务器地址
path: /root/data/loki # NFS 共享路径
nodeSelector: # 指定Pod运行的节点
kubernetes.io/hostname: k8s-node01
# nodeName: k8s-node01 # 指定 Pod 运行在 k8s-node02 节点上
# 4.服务账户
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner # SA 的名称
namespace: loki-storage
# 5.集群角色
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
# 6.集群角色绑定
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount # 绑定类型 ServiceAccount
name: nfs-client-provisioner # ServiceAccount 的名称
namespace: loki-storage
roleRef:
kind: ClusterRole # 绑定的角色类型
name: nfs-client-provisioner-runner # 集群角色名称 nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
# 角色
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner # 角色的名称,表明它与 NFS 客户端存储提供者的领导者选举(leader election)机制相关。
namespace: loki-storage
rules:
- apiGroups: [""] # 空字符串表示核心 API 组(core API group),包含 Kubernetes 的基本资源,如 endpoints。
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# SA角色绑定
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-leader-locking-nfs-client-provisioner
namespace: loki-storage
subjects:
- kind: ServiceAccount # 绑定资源类型为 ServiceAccount
name: nfs-client-provisioner # 绑定的ServiceAccount 名称
namespace: loki-storage
roleRef:
kind: Role # 绑定角色(loki-storage名称空间的角色)
apiGroup: rbac.authorization.k8s.io
name: leader-locking-nfs-client-provisioner # 角色的名称
执行资源清单
# 执行
$ kubectl apply -f loki-storage.yaml
# 查看Pod
$ kubectl get pods -n loki-storage
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-79f97f7689-h5rrl 1/1 Running 0 20h
2. 下载 Helm Chat 包
# 添加grafana仓库
$ helm repo add grafana https://grafana.github.io/helm-charts
# 更新仓库
$ helm repo update
# 拉取 Chart 包
$ helm pull grafana/loki-stack --version=2.9.11
# 查看下载的 chat 包
$ ll
-rw-r--r-- 1 root root 132807 Jul 16 15:09 loki-stack-2.9.11.tgz
# 如果上面的命令由于网络问题无法下载 Chat 包, 就手动下载,然后上传到服务器上
# helm包下载地址 https://github.com/grafana/helm-charts/releases/download/loki-stack-2.9.11/loki-stack-2.9.11.tgz
解压 Chart 包
# 解压
$ tar -zxvf loki-stack-2.9.11.tgz
# 查看解压目录
$ ls
loki-stack loki-stack-2.9.11.tgz
3. 编辑 values.yaml
vim loki-stack/values.yaml
内容如下:
loki:
enabled: true
isDefault: true
url: http://{{(include "loki.serviceName" .)}}:{{ .Values.loki.service.port }}
readinessProbe:
httpGet:
path: /ready
port: http-metrics
initialDelaySeconds: 45
livenessProbe:
httpGet:
path: /ready
port: http-metrics
initialDelaySeconds: 45
datasource:
jsonData: "{}"
uid: ""
persistence: # 添加存储设置
enabled: true
accessModes:
- ReadWriteOnce
size: 10Gi
storageClassName: loki-storage # 使用 loki-storage 自动创建PV
promtail:
enabled: true
config:
logLevel: info
serverPort: 3101
clients:
- url: http://{{ .Release.Name }}:3100/loki/api/v1/push
defaultVolumes: # 定时 Promtail Pod 使用的卷
- name: run
hostPath:
path: /run/promtail
- name: containers
hostPath:
path: /data/docker/containers
- name: pods
hostPath:
path: /var/log/pods
defaultVolumeMounts: # 定义容器内的挂载点,将上述卷挂载到 Promtail 容器
- name: run
mountPath: /run/promtail
- name: containers
mountPath: /data/docker/containers
readOnly: true
- name: pods
mountPath: /var/log/pods
readOnly: true
grafana:
enabled: true # 启用部署Grafana
persistence:
enabled: true
accessModes:
- ReadWriteOnce
size: 5Gi
storageClassName: loki-storage # 使用 loki-storage 自动创建PV
# 显式禁用不需要的子 Chart
test_pod:
enabled: false
filebeat:
enabled: false
fluent-bit:
enabled: false
prometheus:
enabled: false
logstash:
enabled: false
如上,修改了 loki、promtail、grafana ,使用 helm 部署时会依次安装这3个组件,并且关闭其它不需要的模块
4. 修改 grafana 密码
编辑 grafana 目录下的 values.yaml
文件
$ vim loki-stack/charts/grafana/values.yaml
# 编辑下面的内容,将 admin 用户的密码也设置成 admin
# Administrator credentials when not using an existing secret (see below)
adminUser: admin
adminPassword: admin
5. 使用Helm部署Loki
# 进入 loki 解压目录
$ cd loki-stack
# 查看文件夹内容
$ ll
total 20
-rw-r--r-- 1 root root 374 Jul 16 16:50 Chart.yaml
-rw-r--r-- 1 root root 2027 Jul 16 16:50 README.md
drwxr-xr-x 9 root root 117 Jul 16 16:50 charts
-rw-r--r-- 1 root root 729 Jul 16 16:50 requirements.lock
-rw-r--r-- 1 root root 867 Jul 16 16:50 requirements.yaml
drwxr-xr-x 3 root root 80 Jul 16 16:50 templates
-rw-r--r-- 1 root root 1455 Jul 16 16:50 values.yaml
# 执行 helm 命令,安装 loki
$ helm install loki -n loki --create-namespace -f values.yaml .
NAME: loki
LAST DEPLOYED: Wed Jul 16 17:00:37 2025
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.
# 补充命令:更新部署
helm upgrade loki -n loki -f values.yaml .
查看部署资源
$ helm list -n loki
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
loki loki 1 2025-07-16 17:15:50.136762865 +0800 CST deployed loki-stack-2.9.11 v2.6.1
$ kubectl get pods -n loki
NAME READY STATUS RESTARTS AGE
loki-0 1/1 Running 0 3m38s
loki-grafana-8869df8b7-ggnnh 1/1 Running 0 3m38s
loki-promtail-5gvgd 1/1 Running 0 3m38s
loki-promtail-bb5l9 1/1 Running 0 3m38s
loki-promtail-gbv8t 1/1 Running 0 3m38s
loki-promtail-mrw6n 1/1 Running 0 3m38s
$ kubectl get svc -n loki
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
loki ClusterIP 10.97.142.238 <none> 3100/TCP 76s
loki-grafana ClusterIP 10.111.244.51 <none> 80/TCP 76s
loki-headless ClusterIP None <none> 3100/TCP 76s
loki-memberlist ClusterIP None <none> 7946/TCP 76s
$ kubectl get statefulset -n loki
NAME READY AGE
loki 1/1 91s
$ kubectl get deployment -n loki
NAME READY UP-TO-DATE AVAILABLE AGE
loki-grafana 1/1 1 1 99s
$ kubectl get cm -n loki
NAME DATA AGE
kube-root-ca.crt 1 16m
loki-grafana 1 107s
6. 部署 Ingress
用于浏览器访问 Grafana
资源清单:loki-ingress.yaml
apiVersion: networking.k8s.io/v1 # 指定 API 版本
kind: Ingress
metadata:
name: grafana-ui
namespace: loki
labels:
k8s-app: grafana
spec:
ingressClassName: nginx # 指定此 Ingress 资源由名称为 nginx 的 IngressClass 处理
rules:
- host: loki.grafana.com # 指定此规则适用于请求的 HTTP 主机头(Host Header)为 loki.grafana.com 的流量。客户端必须通过该域名访问
http:
paths:
- path: / # 指定匹配的 URL 路径为 /,即根路径,表示匹配所有以 / 开头的请求
pathType: Prefix # 定义路径匹配的类型为 Prefix,表示匹配以指定路径(/) 开头的所有请求
backend:
service:
name: loki-grafana # 指定后端服务的名称为 nginx-svc.(必须存在于同一命名空间,或通过 <namespace>/<service-name> 跨命名空间引用)
port:
number: 80 # 指定目标 Service 的端口为 80
执行资源清单
$ kubectl apply -f loki-ingress.yaml
修改 HOSTS 文件
添加域名映射
10.20.1.140 loki.grafana.com
补充:如果没有设置 grafana 的初始密码,grafana 在创建时会默认生成密码
# 查看有哪些 Secret
$ kubectl get secret -n loki
NAME TYPE DATA AGE
loki Opaque 1 13m
loki-grafana Opaque 3 13m
loki-promtail Opaque 1 13m
sh.helm.release.v1.loki.v1 helm.sh/release.v1 1 13m
# 查看默认用户名
$ kubectl get secret -n loki loki-grafana -o jsonpath="{.data.admin-user}" | base64 --decode
admin
# 查看默认密码
$ $ kubectl get secret -n loki loki-grafana -o jsonpath="{.data.admin-password}" | base64 --decode
UnBUY0EbcPuaLrHnBybhYuZOSFb5rmDvNpXQn2vr
7. 添加 Loki 数据源
登录 Grafana
添加数据源
选择 Loki 作为数据源
编辑数据源
保存数据源配置
选择数据源,搜索查看日志
选择标签,点击查询
至此,关于如何使用 Helm 部署 Loki 就介绍完成了
参考链接:
https://grafana.org.cn/docs/loki/latest/get-started/overview/
https://www.cnblogs.com/starsray/p/17549842.html
https://www.boysec.cn/boy/632ed78c.html
https://www.qikqiak.com/k3s/logging/loki/overview/
https://blog.frognew.com/tags/loki.html
https://blog.csdn.net/sj1163739403/article/details/142638504
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 george_95@126.com