Kubernetes Loki 日志收集系统部署文档 (读写分离模式 + Ceph S3 + Higress 日志分离)

本文档将指导你在自建的 Kubernetes 集群中,针对每天 TB 级别的海量日志场景,使用 Helm 部署 Loki 的读写分离模式(Simple Scalable)和 Promtail 日志收集系统。

其核心特点为:

  1. 高吞吐与高可用:采用读写分离架构(Write/Read/Backend),可水平扩展,轻松应对每天 1TB 以上的日志写入和并行查询。
  2. 对象存储:完全依赖 Ceph 提供的 S3 对象存储(RGW)来存储索引和日志块,摆脱本地磁盘容量和并发写入的限制。
  3. 日志分离:文档支持通过多租户隔离 (Tenant)分类标签 (Label) 两种方式,将 Higress Ingress 日志与其他业务日志进行物理流隔离或逻辑分类,便于独立查询和告警。

1. 环境准备与 Ceph S3 (RGW) 配置

Loki 的读写分离模式强依赖对象存储。既然你已经部署了 Rook-Ceph,我们需要使用 Ceph 的 RADOS Gateway (RGW) 来提供 S3 服务,并为 Loki 创建专属的 Bucket 和账号。

1.1 确认并获取 Ceph S3 Endpoint

如果你的 Rook-Ceph 已经部署了对象存储(CephObjectStore),可以通过以下命令查看内部访问地址:

# 获取 Rook-Ceph 命名空间下的 svc 列表
kubectl get svc -n rook-ceph | grep rgw

根据你的环境输出,Endpoint 为:
http://rook-ceph-rgw-my-store.rook-ceph.svc.cluster.local:8080

(注:如果尚未部署 CephObjectStore,你需要先应用包含 CephObjectStore 的 YAML 文件来启动 RGW 服务。)

1.2 创建安全保留策略的 StorageClass (Retain)

为了防止未来误删 Loki 的 ObjectBucketClaim (OBC) 导致 Ceph 底层的日志数据被一并销毁,我们强烈建议创建一个 reclaimPolicy: Retain(保留策略)的 StorageClass。

首先,确认你的 CephObjectStore 的名称:

kubectl get CephObjectStore -n rook-ceph

(假设输出的 NAME 为 my-store)

创建 sc-retain-bucket.yaml 文件:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rook-ceph-retain-bucket
provisioner: rook-ceph.ceph.rook.io/bucket # 必须匹配 Rook-Ceph 的 provisioner
reclaimPolicy: Retain # 关键配置:删除 OBC 时保留底层 Bucket 和数据
parameters:
  objectStoreName: my-store # 替换为你的 CephObjectStore 名称
  objectStoreNamespace: rook-ceph

应用该配置:

kubectl apply -f sc-retain-bucket.yaml

1.3 创建 Loki 专属 Bucket 与凭证 (使用 OBC)

现在,我们可以使用刚刚创建的安全 StorageClass 来为 Loki 自动配置专属的 Bucket 和独立的 AK/SK 凭证。

创建一个专门的 namespace,并提交 OBC 请求 loki-obc.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: loki-stack
---
apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
metadata:
  name: loki-data-claim
  namespace: loki-stack
spec:
  bucketName: loki-data # 明确指定具体的 Bucket 名称,不使用随机后缀
  storageClassName: rook-ceph-retain-bucket # 使用我们刚创建的安全 SC

应用该配置:

kubectl apply -f loki-obc.yaml

获取自动生成的专属配置:
Rook-Ceph 会在 loki-stack 命名空间下自动创建一个 ConfigMap(包含 Bucket 名称和 Endpoint)和一个 Secret(包含专属的 AK/SK)。

# 1. 获取专属 Access Key (AK)
kubectl get secret loki-data-claim -n loki-stack -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 --decode && echo

# 2. 获取专属 Secret Key (SK)
kubectl get secret loki-data-claim -n loki-stack -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 --decode && echo

至此,你已经获得了 Loki 所需的 Endpoint、专属 AK 以及专属 SK。Bucket 名称为你指定的 loki-data。请妥善保存这些值,稍后将它们填入 Loki 的配置中。

1.4 安装基础工具

  1. 安装 Helm:确保集群管理节点已安装 Helm v3。
  2. 添加 Grafana Helm 仓库
    helm repo add grafana https://grafana.github.io/helm-charts
    helm repo update
    

2. 部署 Loki (Simple Scalable 读写分离模式)

对于 TB 级日志,我们必须使用读写分离架构。这会将 Loki 拆分为三种角色的 Pod:

  • Write: 负责接收日志并写入 S3(可横向扩容应对写入高峰)。
  • Read: 负责从 S3 读取数据响应查询请求(支持分布式拆分查询)。
  • Backend: 负责后台任务(如压缩、过期数据清理等)。

2.1 创建 Loki 配置文件

创建 values-loki-scalable.yaml 文件,请务必将 S3 的连接信息替换为你实际的 Ceph RGW 配置

deploymentMode: SimpleScalable

loki:
  # 是否开启多租户 (开启后需配置 X-Scope-OrgID Header,关闭则混租)
  auth_enabled: true
  
  commonConfig:
    replication_factor: 1 # 因为我们将 write/read 副本数降为了 1,这里必须同步设置为 1,否则会导致 500 报错
  
  # 配置统一的对象存储 (指向 Ceph S3)
  storage:
    bucketNames:
      chunks: loki-data  # 替换为你的 Bucket 名称
      ruler: loki-data
      admin: loki-data
    type: s3
    s3:
      endpoint: http://rook-ceph-rgw-my-store.rook-ceph.svc.cluster.local:8080 # 替换为实际的 Ceph RGW 地址 (注意必须保留 http:// 前缀)
      region: default # Ceph 默认通常是 default 或者 us-east-1
      accessKeyId: 3JY8F130F2VDZLK6GTMQ       # 替换为你的 AK
      secretAccessKey: O0p7yBLp8Vrc6rnizaA1rWSthLL0BSR5zpq1hAQQ   # 替换为你的 SK
      s3ForcePathStyle: true                     # Ceph S3 必须开启路径模式
      insecure: true                             # 如果没有配置 https 则设为 true

  # 存储 Schema 配置 (使用 TSDB 引擎)
  schemaConfig:
    configs:
      - from: "2024-01-01" # 表示这个存储格式规则从这个日期开始生效。可以填一个早于当前的任意日期。
        store: tsdb
        object_store: s3
        schema: v13
        index:
          prefix: index_
          period: 24h

  # 日志保留策略 (Retention) - 例如保留 30 天
  compactor:
    working_directory: /var/loki/compactor # Compactor 的本地工作目录,用于存放压缩、合并、删除过程中的临时文件和状态数据
    retention_enabled: true # 是否启用日志保留清理功能;开启后才会按 retention_period 自动删除过期日志
    retention_delete_delay: 2h # 过期日志被标记后延迟多久再真正删除,给索引同步和误操作回滚预留缓冲时间
    retention_delete_worker_count: 150 # 执行过期日志删除任务的并发 worker 数量,值越大清理越快,但对后端存储压力也越大
    delete_request_store: s3 # 开启保留策略后必须配置此项,指定用于存放"删除请求记录"的后端存储

  limits_config:
    retention_period: 30d                     # 全局日志保留时间
    ingestion_rate_mb: 1024                   # 全局每秒允许写入的日志量 (MB/s)
    ingestion_burst_size_mb: 1024             # 允许的全局瞬间突发写入量 (MB)
    per_stream_rate_limit: 1024MB             # 单个日志流每秒允许的写入量
    per_stream_rate_limit_burst: 1024MB       # 单个日志流允许的瞬间突发写入量

# 禁用单体模式
singleBinary:
  replicas: 0

# --- 读写分离组件配置 ---

# 资源消耗优化 (关闭不必要的附加组件)
# 如果集群内存/CPU 资源紧张,强烈建议将以下组件关闭。
chunksCache:
  enabled: false # 关闭用于缓存日志块(Chunks)的 Memcached 集群,节约大量内存
resultsCache:
  enabled: false # 关闭用于缓存查询结果的 Memcached 集群,节约大量内存
test:
  enabled: true # 开启 Helm test 框架,允许使用 helm test 命令来验证 Loki 集群是否正常工作
lokiCanary:
  enabled: true # 开启 Canary 守护进程,它会持续向 Loki 写入和查询模拟日志,用于自我监控和告警

# 写入节点配置 (根据日志量调整副本数,可按需扩容)
write:
  replicas: 1
  persistence:
    # 写入节点需要少量本地存储用于 WAL (预写日志),避免宕机丢失缓存的日志
    size: 10Gi
    storageClass: "rook-ceph-block"

# 读取节点配置 (根据查询并发量调整,支持横向扩容)
read:
  replicas: 1
  extraArgs:
    # 修复开启多租户后,read 节点找不到 scheduler 导致 empty ring 的问题
    - '-legacy-read-mode=true'

# 后端节点配置 (处理定时任务,通常 1 个或 2 个即可)
backend:
  replicas: 1
  persistence:
    # Backend 需要本地存储用于 Compactor 的临时工作空间
    size: 10Gi
    storageClass: "rook-ceph-block"

2.2 执行 Loki 部署

创建一个独立的 namespace 并部署:

# 首次安装或常规升级
helm upgrade --install loki grafana/loki \
  --namespace loki-stack --create-namespace \
  -f values-loki-scalable.yaml

# 遇到结构冲突报错时,使用强制重置模式升级
# helm upgrade --install loki grafana/loki \
#   --namespace loki-stack --create-namespace \
#   --reset-values \
#   -f values-loki-scalable.yaml

💡 排错提示

  • 如果执行常规升级时出现 coalesce.go 警告,或提示 You have more than zero replicas configured for scalable targets... 错误,说明 Helm 在合并旧版本配置时发生了结构冲突(例如在 SimpleScalable 模式下误开了 Distributed 组件)。此时请使用带有 --reset-values 参数的命令重新执行即可解决。
  • Empty Ring 报错:如果你发现 loki-read 节点一直无法 Ready,且日志中报 number of schedulers is 0empty ring,这通常是因为开启了多租户(auth_enabled: true)且使用了新版查询架构导致的内部路由问题。请在 read 组件的配置中添加 extraArgs: ['-legacy-read-mode=true'] 来降级查询模式即可解决。

验证 Loki Pod 和 Ceph PVC 状态:

kubectl get pods -n loki-stack -l app.kubernetes.io/name=loki
kubectl get pvc -n loki-stack
# 确保 PVC 状态为 Bound,说明 Ceph 已成功分配存储卷

3. 部署 Promtail 并实现 Higress 日志分离

Promtail 以 DaemonSet 的方式运行在每个节点上。由于当前集群的 Ingress 网关使用的是 Higress,因此这里将 Higress Gateway 日志与其他业务日志分离,并使用 Promtail 的 pipeline_stagesmatch 机制实现。

分离逻辑

  • 识别 Higress Gateway Pod 日志,这里以 app="higress-gateway" 为匹配条件。
  • 给 Higress 日志打上独立租户或分类标签,便于单独查询 Ingress 访问日志与网关日志。
  • 给非 Higress 的其他业务日志打上 app 类租户或分类标签。

3.1 创建 Promtail 配置文件

创建 values-promtail.yaml 文件:

config:
  clients:
    # 指向上面部署的 Loki 统一网关服务
    # 读写分离模式下,所有请求都必须打给 loki-gateway
    - url: http://loki-gateway.loki-stack.svc.cluster.local:80/loki/api/v1/push
  
  snippets:
    pipelineStages:
      # 1. 容器运行时日志基础解析 (docker/cri)
      - cri: {}
      
      # 2. 匹配 Higress Gateway 日志
      - match:
          selector: '{app="higress-gateway"}'
          stages:
            # 方案 A: 设置多租户 (与 loki.auth_enabled: true 配合使用,默认推荐)
            - tenant:
                value: "tenant-higress"
            # 方案 B: 添加分类标签 (若 loki.auth_enabled 为 false,则取消下方注释,查询时用 {log_category="higress"})
            # - static_labels:
            #     log_category: "higress"

      # 3. 匹配非 Higress 的其他所有业务日志
      - match:
          selector: '{app!="higress-gateway"}'
          stages:
            # 方案 A: 设置多租户
            - tenant:
                value: "tenant-app"
            # 方案 B: 添加分类标签
            # - static_labels:
            #     log_category: "application"

# 容忍所有的污点,确保 Promtail 能在所有节点(包括 Master 节点)上收集日志
tolerations:
  - operator: Exists

3.2 执行 Promtail 部署

helm upgrade --install promtail grafana/promtail \
  --namespace loki-stack --create-namespace \
  -f values-promtail.yaml

检查 Promtail 运行状态:

kubectl get pods -n loki-stack -l app.kubernetes.io/name=promtail
# 应该在集群的每个节点上都有一个处于 Running 状态的 Pod

4. 验证与 Grafana 集成

4.1 在 Grafana 中添加数据源

根据你在第 3 节中选择的方案,配置数据源的方法有所不同:

方案 A: 多租户隔离 (auth_enabled: true)

  1. 登录到你的 Grafana 面板。
  2. 进入 Connections -> Data Sources -> Add new data source,选择 Loki
  3. 在 HTTP URL 中填写 Loki Gateway 的内部服务地址:
    http://loki-gateway.loki-stack.svc.cluster.local:80
  4. HTTP headers 部分,点击 Add header,填写:
    • Header: X-Scope-OrgID
    • Value: tenant-higress
  5. 将这个数据源命名为 Loki-Higress
  6. 点击 Save & test,如果提示成功则说明连接正常。
  7. 重复以上步骤,再添加一个名为 Loki-App 的数据源,并将 X-Scope-OrgID 的值设置为 tenant-app

方案 B: 标签分类 (auth_enabled: false)

  1. 登录到你的 Grafana 面板。
  2. 进入 Connections -> Data Sources -> Add new data source,选择 Loki
  3. 在 HTTP URL 中填写:http://loki-gateway.loki-stack.svc.cluster.local:80
  4. 不需要配置任何 Header,直接将数据源命名为 Loki
  5. 点击 Save & test 测试连接即可。

💡 获取 Loki 地址方法:你可以通过执行 kubectl get svc -n loki-stack | grep gateway 确认服务名称和端口。Kubernetes 集群内部服务地址的标准格式为 http://<服务名>.<命名空间>.svc.cluster.local:<端口>

4.2 查询与验证日志分离

进入 Grafana 的 Explore 页面。

如果你使用的是多租户方案 (auth_enabled: true)

  1. 在顶部的数据源下拉菜单中,选择 Loki-Higress
  2. 输入查询 {app="higress-gateway"} 即可查询 Higress 租户下的日志。
  3. 切换为 Loki-App 数据源后,不能只输入 {app!="higress-gateway"},因为 LogQL 要求查询至少包含一个非空的正向匹配条件。建议使用 {namespace=~".+", app!="higress-gateway"}{job=~".+", app!="higress-gateway"} 查询其他业务容器日志。

如果你使用的是分类标签方案 (auth_enabled: false)

  1. 只需要一个配置了 Loki 内部地址的普通数据源即可(无需 Header)。
  2. 在 Log browser 中输入以下 LogQL 查询 Higress 日志:
    {log_category="higress"}
    
  3. 查询其他业务日志:
    {log_category="application"}
    

💡 排查查询不到 Higress 日志的问题
如果查不到数据,通常是因为你的 Higress Pod 的标签与 Promtail 配置中的选择器不匹配。

  1. 文档中 Promtail 的 match 规则是 selector: '{app="higress-gateway"}'。但请注意,在 Promtail 的管道处理 (pipeline_stages) 中,match 选择器使用的是经过 Promtail relabel 处理后最终保留的标签。(查看最终标签的方法:执行 kubectl port-forward -n loki-stack daemonset/promtail --address 0.0.0.0 3101:3101,然后在浏览器访问 http://<节点IP>:3101/targets,在页面中即可看到各 Pod 目标被 Promtail 最终保留的实际标签)。
  2. 如果你在 Promtail 的 target 页面发现最终生成的标签不匹配,你需要将 match 规则修改为匹配现有的标签。
  3. 修改 values-promtail.yaml 中的 selector 并重新执行 helm upgrade
  4. 触发日志产生:如果没有日志产生,查询结果也会为空。你可以通过访问你的 Ingress 域名或直接请求 Higress 网关来产生访问日志,例如:curl -I http://<你的Ingress节点IP或域名>curl -k -I https://<你的Ingress域名或节点IP>,然后再去 Grafana 重新查询。
  5. 确认 Higress 自身是否打印了日志:你可以直接通过 kubectl 查看 Higress Gateway 的原生日志,确认它是否记录了刚才的请求:kubectl logs -n higress-system -l app=higress-gateway --tail=10。如果这里也没有日志,说明是 Higress 日志输出配置本身需要进一步确认。
  6. 排查 Promtail 采集问题:如果 Higress 本身有日志,但在 Grafana 中查不到,说明 Promtail 没有成功采集或发送。请执行 kubectl logs -n loki-stack -l app.kubernetes.io/name=promtail --tail=50 查看 Promtail 的运行日志,检查是否有连接 Loki Gateway 失败的报错(如 401 认证失败、DNS 解析失败等)或是读取节点容器日志目录权限不足的问题。
  7. 处理 Loki 写入限流 (429 错误):如果在 Promtail 日志中发现 server returned HTTP status 429 Too Many Requests (429): ingestion rate limit exceeded,说明你的日志产生量超过了 Loki 默认的写入限制(默认通常是 4MB/s)。你需要修改 values-loki-scalable.yaml,在 loki.limits_config 下增加或调大 ingestion_rate_mbingestion_burst_size_mb (例如设置为 1024 即 1GB/s),然后执行 helm upgrade 应用新配置。
  8. 查看 Promtail 采集目标状态:如果以上都没问题,可以通过端口转发访问 Promtail 的内置 Web 页面,查看它到底抓取了哪些目标以及打上了什么标签:kubectl port-forward -n loki-stack daemonset/promtail --address 0.0.0.0 3101:3101,然后在浏览器访问 http://<任意节点IP>:3101/targets 确认 Higress 的 Pod 是否在列,以及它的标签是什么。

5. 维护与建议 (进阶)

  1. Write/Read 节点本地盘扩容:如果日后发现 Write 节点的 WAL (预写日志) 空间不足(如 10Gi 不够),由于它们挂载的是 Rook-Ceph 的块存储,可以直接编辑对应的 PVC 修改大小实现在线扩容。
    • 判断标准:执行 kubectl exec -it <pod-name> -n loki-stack -- df -h /var/loki,若使用率超过 85% 或日志中出现 no space left on device 报错,即为空间不足。
    • 扩容命令示例 (以扩容至 20Gi 为例):
      # 1. 确认 PVC 名称
      kubectl get pvc -n loki-stack | grep loki-write
      
      # 2. 修改存储大小 (在线生效)
      kubectl patch pvc data-loki-write-0 -n loki-stack -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
      
      # 3. 验证扩容结果
      kubectl exec -it loki-write-0 -n loki-stack -- df -h /var/loki
      
  2. 多租户隔离:我们已经通过 Promtail 的 tenant 机制和 Loki 的 auth_enabled: true 实现了多租户分离(已在本次部署中完成)。如果未来有新的业务线接入,只需在 Promtail 配置中增加相应的 match 规则,并为其分配新的 tenant_id,然后在 Grafana 中添加对应的带 X-Scope-OrgID Header 的数据源即可实现硬隔离。
  3. 数据清理监控:文档中我们已经配置了 Compactor(保留 30 天日志),请确保 Backend 节点正常运行,否则过期数据将不会被清理,会导致 S3 存储容量持续增长。

6. 进阶场景:使用 Sidecar 模式收集容器内文件日志 (支持 Java 多行异常)

文档前述部署的 DaemonSet 模式 Promtail 默认只收集容器的标准输出 (stdout/stderr)。如果业务应用将日志写到了容器内部的文件中(例如 /app/logs/app.log),我们需要使用 Sidecar(边车)模式 进行收集。

为了避免为每个业务硬编码标签,以下方案结合了 Kubernetes Downward API 自动注入 Pod 标签,并配置了 multiline 阶段来完美合并 Java 的多行异常堆栈。

6.1 创建通用的 Promtail ConfigMap

该 ConfigMap 可以被集群中所有需要 Sidecar 的业务复用。它通过读取环境变量动态生成日志标签。

步骤 1:创建配置文件 promtail-sidecar-config.yaml

将以下内容保存为 promtail-sidecar-config.yaml 文件:

apiVersion: v1
kind: ConfigMap
metadata:
  name: promtail-sidecar-config-universal
  namespace: default
data:
  promtail.yaml: |
    server:
      http_listen_port: 9080
      grpc_listen_port: 0
    
    clients:
      # 指向 Loki Gateway 地址。
      # 注意:如果 Loki 开启了多租户 (auth_enabled: true),这里必须配置 tenant_id。
      # 建议为 Sidecar 收集的日志配置独立的 tenant_id,以便与 DaemonSet 收集的容器标准输出隔离。
      - url: http://loki-gateway.loki-stack.svc.cluster.local:80/loki/api/v1/push
        tenant_id: "tenant-sidecar-app" 
    
    positions:
      filename: /tmp/positions.yaml
      
    scrape_configs:
      - job_name: sidecar-local-logs
        static_configs:
          - targets:
              - localhost
            labels:
              log_source: file
              # 指定共享目录下的日志文件路径
              __path__: /shared-logs/*.log
              
        pipeline_stages:
          # --- Java 多行异常合并 ---
          - multiline:
              # 匹配新日志的开头(例如以 2023-10-01 或 2023/10/01 开头)
              firstline: '^\d{4}[-/]\d{2}[-/]\d{2}'
              max_wait_time: 3s
              max_lines: 500
              
          # --- 通过环境变量展开写入静态标签 ---
          - static_labels:
              # 启动时自动将 ${XXX} 替换为真实的 Pod 环境变量值
              pod: "${POD_NAME}"
              namespace: "${POD_NAMESPACE}"
              app: "${APP_LABEL}"

步骤 2:应用 ConfigMap 到集群

执行以下命令,将配置部署到 Kubernetes 中:

kubectl apply -f promtail-sidecar-config.yaml

6.2 业务 Deployment 注入 Sidecar 示例

业务方只需在原有的 Deployment 中加入一个共享的 emptyDir 卷,并增加 promtail-sidecar 容器即可。

步骤 1:创建部署文件 java-multiline-demo.yaml

将以下内容保存为 java-multiline-demo.yaml 文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: java-multiline-demo
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: java-demo-app
  template:
    metadata:
      labels:
        app: java-demo-app 
    spec:
      volumes:
        - name: shared-logs
          emptyDir: {}
        - name: promtail-config
          configMap:
            name: promtail-sidecar-config-universal
            
      containers:
        # 1. 主业务容器
        - name: main-app
          image: busybox:latest
          command: ["/bin/sh", "-c"]
          args:
            - |
              mkdir -p /app/logs
              while true; do 
                echo "$(date +'%Y-%m-%d %H:%M:%S') INFO  com.example.MyApp - Starting application..." >> /app/logs/app.log
                sleep 2
                echo "$(date +'%Y-%m-%d %H:%M:%S') ERROR com.example.MyApp - An unexpected error occurred:" >> /app/logs/app.log
                echo "java.lang.NullPointerException" >> /app/logs/app.log
                echo "        at com.example.MyApp.process(MyApp.java:42)" >> /app/logs/app.log
                sleep 5
              done
          volumeMounts:
            # 将共享卷挂载到业务代码写日志的目录
            - name: shared-logs
              mountPath: /app/logs 
              
        # 2. Promtail Sidecar 容器
        - name: promtail-sidecar
          image: grafana/promtail:2.9.2
          args:
            - -config.file=/etc/promtail/promtail.yaml
            # 核心参数:允许解析配置文件中的 ${ENV_VAR}
            - -config.expand-env=true 
          env:
            # 通过 Downward API 自动注入元数据
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: APP_LABEL
              valueFrom:
                fieldRef:
                  fieldPath: metadata.labels['app']
          volumeMounts:
            # 必须和 ConfigMap 里的 __path__ 匹配
            - name: shared-logs
              mountPath: /shared-logs
            - name: promtail-config
              mountPath: /etc/promtail

步骤 2:执行部署并验证

执行以下命令,启动带有 Sidecar 的业务 Pod:

kubectl apply -f java-multiline-demo.yaml

6.3 在 Grafana 中配置 Sidecar 专用数据源

由于我们在 ConfigMap 中为 Sidecar 指定了独立的租户 (tenant-sidecar-app),我们需要在 Grafana 中创建一个专门的数据源来查询这些容器内文件日志,以便与直接从容器终端获取的日志进行隔离。

配置步骤:

  1. 登录 Grafana 面板,进入 Connections -> Data Sources -> Add new data source,选择 Loki
  2. 在 HTTP URL 中填写 Loki Gateway 地址:http://loki-gateway.loki-stack.svc.cluster.local:80
  3. HTTP headers 部分,点击 Add header,填写:
    • Header: X-Scope-OrgID
    • Value: tenant-sidecar-app (必须与 Promtail ConfigMap 中的 tenant_id 保持一致)
  4. 将该数据源命名为 Loki-Sidecar-File
  5. 点击 Save & test 测试连接。

查询验证:
在 Grafana 的 Explore 页面中选择 Loki-Sidecar-File 数据源。由于我们注入了动态标签并合并了 Java 多行异常,你可以使用以下查询语句精准定位:

  • 查询特定应用的日志:{app="java-demo-app", log_source="file"}
  • 验证 Java 异常是否被正确合并:你会看到完整的包含多行 at com.example... 的堆栈信息作为一条完整的日志条目展示,而不再是支离破碎的单行。

7. 配置日志告警 (使用 Grafana Alerting 推荐方案)

由于 Loki 内部的 Ruler 在读写分离 (SimpleScalable) 模式下配置较为繁琐,且不利于频繁动态修改规则。在生产环境中,强烈建议直接使用 Grafana Alerting 模块来进行日志告警。

Grafana Alerting 的优势:

  • 纯图形化操作:无需编写 YAML、无需重启 Pod,即可动态添加、修改、测试告警规则。
  • 与大盘无缝集成:可以直接在查询日志的面板上创建告警。
  • 多渠道分发:原生支持推送到现有的 Alertmanager,或者直接推送到钉钉、企业微信、邮件、飞书等。

7.1 步骤一:配置联系人 (Contact Points)

如果你的集群中已经部署了 kube-prometheus-stack,可以将 Grafana 的告警直接路由给 Alertmanager,由 Alertmanager 统一管理发送逻辑。

  1. 登录 Grafana。
  2. 左侧导航栏进入 Alerting -> Notification configuration,然后选择顶部的 Contact points 选项卡。
  3. 点击 + New contact point
  4. Name: 输入 Prometheus-Alertmanager
  5. Integration: 选择 Alertmanager
  6. URL: 输入 http://prometheus-stack-kube-prom-alertmanager.monitoring.svc.cluster.local:9093
  7. 点击 Save contact point

(注:如果你不想经过 Alertmanager,也可以在这里直接选择 DingDing/Webhook 等原生集成。)

7.2 步骤二:配置通知策略 (Notification Policies)

  1. 左侧导航栏进入 Alerting -> Notification configuration,然后选择顶部的 Notification policies 选项卡。
  2. 找到 Default policy,点击右侧的 … More -> Edit(如果直接编辑默认策略,则不需要填写匹配标签)。
  3. (如果你选择点击 + Add route 创建新路由)
    • 你需要在 Matching labels 中添加至少一个匹配条件(例如:Label 填 severity,Operator 选 =~,Value 填 warning|critical,表示只处理这两个级别的告警)。
  4. Contact point 设置为刚刚创建的 Prometheus-Alertmanager
  5. 点击保存 (Save policyAdd route)。这样触发规则的告警后,就会发送给 Alertmanager。

7.3 步骤三:创建日志告警规则 (Alert Rules)

  1. 左侧导航栏进入 Alerting -> Alert rules
  2. 点击 + New alert rule
  3. 设置查询条件 (Define query and alert condition)
    • 选择数据源为你的 Loki 数据源(如 Loki-HigressLoki-App)。

    • 编写 LogQL 查询(使用 count_over_time 函数计算频率)。

    • 示例:监控 Higress 网关 1 分钟内的 error 数量(忽略大小写)

      count_over_time({app="higress-gateway"} |~ "(?i)error" [1m])
      

      (注:确保查询面板右上角的类型选择为 Code 模式)

      进阶:按域名分组统计
      如果想在告警里知道是哪个域名报错了,需要先用 | json 提取出 authority 标签,然后再用 sum by 进行分组计算。
      由于网关日志中可能混有非 JSON 格式的启动日志或内部日志,为了避免解析报错导致告警失败,必须加上 | __error__="" 来过滤掉解析失败的行。
      前提:你的 Higress 日志输出格式必须是 JSON,且包含类似 authorityhost 的字段。

      sum by (authority) (
        count_over_time({app="higress-gateway"} | json | __error__="" |~ "(?i)error" [1m])
      )
      
    • 配置阈值 (Alert condition)

      • Alert condition 区域,将条件设置为 WHEN QUERY IS ABOVE 0
      • 这样只要上面 LogQL 计算出的 error 数量大于 0,就会触发 Firing 状态。
  4. 设置告警文件夹与标签 (Add folder and labels)
    • Folder: 选择一个文件夹(如 Loki Alerts)来存放这个告警规则,如果没有可以点击 + New folder 新建。
    • Labels: 点击 + Add labels 可以为这条告警添加自定义标签(例如 Label 填 severity,Value 填 warning)。这些标签主要用于在 Alertmanager 中进行更细粒度的路由分发或静默处理。
  5. 设置告警行为 (Set evaluation behavior)
    • Evaluation group and interval: 选择现有的组,或者点击 + New evaluation group 新建一个组并设置评估频率(例如 1m)。
    • Pending period: 选择 None(一有 error 立即触发)或者 1m(持续 1 分钟都满足条件才触发)。
    • Keep firing for: 保持默认 None (0s) 即可。
  6. 配置通知路由 (Configure notifications)
    • Contact point: 选择你之前创建的 Prometheus-Alertmanager。这样就可以直接在这个规则里指定通知渠道,覆盖默认策略。
  7. 设置告警信息 (Configure notification message)
    • Summary (optional): 填写简短的告警摘要。例如:域名 {{ $labels.authority }} 出现 Error 日志
    • Description (optional): 填写详细的告警描述。例如:在过去1分钟内,域名 {{ $labels.authority }} 的访问日志中检测到了 error 关键字。
      • 注意避坑:在 Grafana 告警规则的 Annotation(如 Description 和 Summary)中,不支持使用 {{ .StartsAt }} 变量。因为在计算和评估模板内容时,告警才刚刚被触发,还没有 “StartsAt”(开始时间)属性。
      • 如果你在模板中写入了不支持的变量(如 {{ .StartsAt }}),会导致 Grafana 模板引擎解析报错,为了容错,它会直接放弃渲染并把带有变量的原始字符串发出去(即你看到的原文)。
      • 解决方法:只需把 {{ .StartsAt }} 从配置中删掉即可恢复 {{ $labels.authority }} 的正常渲染。实际上,Alertmanager 发送的邮件或通知内容中,本身就会自动带上 StartsAt (告警触发时间) 和相关的 Labels 列表,不需要在描述中画蛇添足。
    • (可选) Runbook URL: 如果你有内部的故障排查文档,可以填在这里。
    • (可选) 点击 + Add custom annotation 可以添加额外的注释信息。
  8. 点击左下角的 Save 按钮保存规则。

至此,你已经成功配置了基于 Grafana Alerting 的日志告警机制!以后所有的规则修改都可以直接在这个界面完成,立即生效。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐