首页 / 资讯中心 / 文章详情

Helm部署Loki到Kubernetes:日志聚合系统实战指南

Helm部署Loki到Kubernetes:日志聚合系统实战指南 ★ FEATURED ARTICLE
1. 为什么选了 Loki 而不是 ELK做运维或者后端开发的同学应该都有过这种经历线上服务突然告警接口报错第一反应就是去看日志。如果服务就三五台机器直接 ssh 上去grep一下就行。但到了 Kubernetes 环境里Pod 随时可能被调度到不同节点容器一重启日志就没了再靠登机器翻日志这招基本等于盲人摸象。所以日志聚合系统成了 K8s 集群的刚需。提到日志聚合大部分人首先想到的是 ELKElasticsearch Logstash Kibana或者 EFKElasticsearch Filebeat Kibana。这套组合确实成熟功能强大但有个问题Elasticsearch 太重了。它本身是搜索引擎需要维护索引、分片、副本对内存和磁盘的要求都很高。我见过一个每天日志量不算大的业务ES 集群三台节点每台配了 32G 内存结果堆内存经常打满G1 GC 频繁触发运维同学天天盯着监控面板调参数。如果只是做日志检索这成本确实有点冤。Loki 的思路完全不同。它不建全文索引而是从日志中提取标签比如 Pod 名、namespace、容器名只对标签建索引日志内容本身以压缩块的形式存进对象存储。查询的时候先用标签过滤出候选日志再做内容匹配。这个设计意味着它的内存开销远低于 ES存储成本也更低尤其适合那种日志很多、但真正需要全文检索的场景不多的业务。这次选 Helm 来部署 Loki也是因为 Helm 本身就是 Kubernetes 生态里的包管理工具一条命令装完整个 chart后续升级、回滚都方便。对比直接用 YAML 文件一个个kubectl applyHelm 的价值在生产环境尤其明显——你不可能把几十个 YAML 文件扔给同事然后说你自己排顺序执行吧。2. 部署前的准备工作2.1 环境要求与版本选择先确认一下基础环境。我用的是 Kubernetes 1.24 版本Helm 3.10 以上版本。这里有一个坑要先说清楚Helm 2 和 Helm 3 的架构差异很大Helm 2 依赖 Tiller 服务端组件Helm 3 完全去掉了 Tiller如果你之前用的是 Helm 2升级到 Helm 3 后旧 release 是没法直接平滑迁移的。建议新项目一律用 Helm 3。Loki 从 2.x 开始官方推荐直接用 Helm 仓库里的loki-stack或者拆分成loki、promtail、grafana分别安装。到了 Loki 3.x官方又把采集端从 Promtail 逐步往 Alloy 迁移不过 Promtail 依然可用。这篇文章我以 Loki 3.x Promtail 为例部署方式相对经典资料也最多遇到问题好排查。安装 Helm 本身很简单# 下载 Helm 3.10 版本官方 GitHub release 页面 wget https://get.helm.sh/helm-v3.10.0-linux-amd64.tar.gz tar -zxvf helm-v3.10.0-linux-amd64.tar.gz sudo mv linux-amd64/helm /usr/local/bin/helm # 验证版本 helm version如果你不想手动下载也可以直接用官方脚本安装curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 chmod 700 get_helm.sh ./get_helm.sh2.2 存储方案选择Loki 支持多种存储后端MinIO、AWS S3、阿里云 OSS、腾讯云 COS甚至本地磁盘。生产环境我强烈建议用对象存储因为 Loki 把日志数据压缩后存成块文件对象存储天然适合这种海量小文件的读写场景。如果只是测试环境可以用 MinIO 在集群内部搭一个。这里有个设计细节值得注意Loki 的索引index和日志块chunk在 3.x 版本之前可以分离存储3.x 之后默认使用单一对象存储Single Store模式也就是索引和块都放在同一个对象存储桶里通过不同的目录前缀区分。这个变化大幅简化了运维——以前要分别管理索引存储和块存储的配置现在只要配一个桶就行。3. Loki 的核心架构与部署逻辑3.1 读写分离架构Loki 在微服务部署模式下包含多个组件各自职责不同Distributor接收日志写入请求做合法性校验、速率限制把日志流按标签哈希分布到对应的 Ingester。Ingester负责接收日志并写入本地内存达到阈值chunk 大小或时间后 flush 到对象存储。它还负责接收查询请求查询近期未 flush 的内存数据。Querier处理查询请求从 Ingester 和对象存储中获取日志合并结果返回。Query Frontend查询前端接收用户查询请求做分片、缓存、并行分发到后端 Querier结果聚合后返回。Compactor后台任务负责合并对象存储中的小 chunk、清理过期数据。分布式部署模式性能好但组件多、运维复杂。对于中小规模的集群每天日志量不超过几百 GB我推荐单二进制模式Single Binary或者简单可扩展模式Simple Scalable。Loki 的 Helm chart 提供了对应的架构配置singleBinary一个 Pod 跑全部组件适合测试环境。simpleScalable把组件拆成读写分离的 Deployment StatefulSet适合生产环境。我在生产环境用的就是mode: simpleScalable它把组件划分为read组件Querier、Query Frontendwrite组件Ingesterbackend组件Distributor、Compactor、Index Gateway每个组件可以独立扩缩容比单二进制模式更稳比微服务模式更省心。3.2 Helm Chart 的选型Loki 官方在 ArtifactHUB 上的 Helm 仓库地址是helm repo add grafana https://grafana.github.io/helm-charts helm repo update搜索可用的 charthelm search repo loki输出结果示例NAME CHART VERSION APP VERSION DESCRIPTION grafana/loki 6.16.0 3.4.2 Helm chart for Grafana Loki in simple, scalable mode grafana/loki-stack 2.10.2 2.9.10 Loki: like Prometheus, but for logs. grafana/promtail 6.16.0 3.4.2 Helm chart for Promtail注意loki-stack这个 chart 已经很久不更新了上次更新在 2.x 时代如果你用最新版 Loki不建议再装loki-stack因为它的默认镜像版本可能不匹配。我这边选择直接使用grafana/lokichart然后单独部署 Promtail 和 Grafana。这样的好处是各个组件独立升级不会因为 chart 捆绑版本导致互相牵制。4. 实操Helm 部署 Loki 全流程4.1 创建命名空间与配置 values.yaml先建一个独立命名空间方便隔离和管理kubectl create namespace loki然后写 values.yaml。这个文件是部署的核心所有个性化配置都集中在这里。我先给出一份生产可用的配置# values-loki.yaml mode: simpleScalable loki: auth_enabled: false commonConfig: replication_factor: 1 ring_instance_addr: 127.0.0.1 storage: s3: endpoint: minio.loki.svc.cluster.local:9000 access_key_id: lokiadmin secret_access_key: lokiadmin123 insecure: true bucketnames: loki-data s3forcepathstyle: true schemaConfig: configs: - from: 2024-01-01 store: tsdb object_store: s3 schema: v13 index: prefix: loki_index_ period: 24h storage: type: s3 s3: endpoint: minio.loki.svc.cluster.local:9000 access_key_id: lokiadmin secret_access_key: lokiadmin123 insecure: true bucketnames: loki-data s3forcepathstyle: true limits_config: retention_period: 168h allow_structured_metadata: true volume_enabled: true deploymentStrategy: RollingUpdate read: replicas: 2 write: replicas: 2 backend: replicas: 2 monitoring: selfMonitoring: enabled: true grafanaAgent: installOperator: false lokiCanary: enabled: false ingester: persistence: enabled: true size: 10Gi storageClass: standard singleBinary: replicas: 0这份配置有几个关键点我逐一解释复制因子replication_factor我设置为 1意味着每条日志只在一个 Ingester 上保存一份。如果你的集群节点数充足、日志重要性高可以设为 3代价是存储和内存占用翻两倍。我这边是中小规模业务1 副本够用省下的资源给业务更重要。schemaConfig这里指定了索引存储使用 TSDB 格式schema 版本 v13索引周期 24h。TSDB 是 Loki 从 2.8 开始支持的索引格式相比之前的 boltdb-shipper 更高效查询性能更好。注意from字段不要随便改它表示这个 schema 配置从哪个时间点开始生效。retention_period日志保留时间这里设置了 168 小时7 天。说是保留其实是自动过期清理的时间线。Loki 的 Compactor 组件会定期扫描超过保留期限的日志块并删除。如果你不设置这个值默认是不清理的存储会无限增长。ingester.persistenceIngester 的本地磁盘用于存储 WALWrite-Ahead Logging和 flush 前的临时块。如果 Ingester Pod 重启了数据可以从 WAL 恢复避免丢日志。生产环境这个必须开持久化否则 Pod 一重启内存里的日志就没了。4.2 部署 MinIO 作为对象存储如果你的环境里没有现成的 S3 兼容对象存储最简单的方式是在 K8s 里装一个 MinIOhelm repo add minio https://operator.min.io/ helm install minio minio/tenant --namespace loki -f - EOF apiVersion: minio.min.io/v2 kind: Tenant metadata: name: minio namespace: loki spec: pools: - servers: 4 volumesPerServer: 1 volumeClaimTemplate: metadata: name: data spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi configuration: name: minio credentials: secret: name: minio-secret EOF这个调用过程会创建 MinIO Operator然后通过 Tenant 自定义资源拉起一个 4 节点的 MinIO 集群。装完 MinIO 后创建 access keykubectl apply -f - EOF apiVersion: v1 kind: Secret metadata: name: minio-secret namespace: loki type: Opaque stringData: accesskey: lokiadmin secretkey: lokiadmin123 EOF4.3 执行安装与验证配置写好后执行安装helm install loki grafana/loki -n loki -f values-loki.yaml安装过程会创建大量资源等待 2-3 分钟让 Pod 全部就绪kubectl get pods -n loki -w当所有 Pod 状态都变成 Running 或 Completed 之后验证部署结果helm list -n loki kubectl get svc -n loki看到类似输出就说明安装成功NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE loki-backend ClusterIP 10.43.145.120 none 3100/TCP 5m loki-gateway ClusterIP 10.43.25.11 none 80/TCP 5m loki-read ClusterIP 10.43.89.16 none 3100/TCP 5m loki-write ClusterIP 10.43.132.9 none 3100/TCP 5mloki-gateway是统一入口读写查询都走这个 Service默认端口 80 转发到后端的 3100。4.4 部署 Promtail 采集日志Loki 本身只负责存和查采集日志要靠 Promtail。Promtail 的部署同样用 Helmhelm install promtail grafana/promtail -n loki --set loki.serviceNameloki-gateway --set loki.servicePort80这里的关键是让 Promtail 知道往哪里发日志。loki.serviceName指向 gateway Serviceloki.servicePort指向 gateway 的 80 端口。Promtail 默认配置会采集/var/log和容器标准输出日志。如果你是更高的版本或者想用 AlloyGrafana 新一代采集器安装方式也类似但配置格式不同。这篇文章先用 Promtail 演示后续有机会再单独聊 Alloy。4.5 接入 Grafana 展示日志系统没有可视化面板等于白搭。Grafana 安装helm install grafana grafana/grafana -n loki --set persistence.enabledtrue --set persistence.size10Gi获取 Grafana 登录密码kubectl get secret --namespace loki grafana -o jsonpath{.data.admin-password} | base64 --decode登录 Grafana 后在左侧菜单找到Data Sources - Add data source选择 Loki。URL 填http://loki-gateway或者直接用集群内地址http://loki-gateway.loki.svc.cluster.local然后保存测试。如果测试连接提示 HTTP 400 之类的错误大概率是 Loki 的auth_enabled设成了 true默认值而我们安装时已经将其设为 false所以应该能正常连通。测试成功后到Explore页面选择 Loki 数据源然后用 LogQL 查询日志。5. LogQL 查询玩转日志检索日志系统部署好了最重要的事情是会用。Loki 的查询语言叫 LogQL语法接近 PromQL但针对日志做了扩展。5.1 基础查询语法最简单的查询直接选标签{namespaceproduction}这条语句返回所有namespace为production的日志流。复杂一点可以加多个条件{namespaceproduction, pod~api-server.*} | ERROR| ERROR的含义是筛选日志内容包含 ERROR 的行。LogQL 还支持|~ 正则和!~ 排除正则用起来跟 grep 一个思路{appnginx} |~ status5\\d{2}匹配 nginx 日志状态码为 5xx 的行。5.2 指标聚合LogQL 不只可以查日志文本还可以做指标计算。统计过去 5 分钟 ERROR 日志数量sum(count_over_time({namespaceproduction} | ERROR [5m]))统计各 Pod 的日志量排行topk(10, sum by (pod) (count_over_time({namespaceproduction} [1h])))这个功能大大扩展了 Loki 的适用场景——不只是查日志还可以做实时监控告警。比如我搭建过一个简单的告警规则rate({apppayment} |~ timeout [5m]) 0.1一旦超时日志速率超过每秒 0.1 条就报警效果意外地好。6. 常见问题与踩坑实录6.1 Ingester 内存持续高涨这是 Loki 部署后最常见的性能问题。Ingester 默认会把日志存在内存里达到 chunk 大小或时间阈值才 flush 到对象存储。如果写入速率大内存自然涨得快。缓解方案调低chunk_target_size默认 1.5MB可以降到 1MB 或更低。这样 chunk 更快写满并 flush内存压力降低。调低max_chunk_age默认 2h可以改为 1h让日志在内存中停留时间变短。增加 Ingester 副本数分散写入负载。loki: limits_config: chunk_target_size: 1048576 max_chunk_age: 1h6.2 Promtail 采集不到日志部署 Promtail 后发现 Grafana 里查不到日志。排查路径看 Promtail Pod 日志是否报错kubectl logs -n loki promtail-xxxx --tail50检查 Promtail 配置里的采集路径是否正确/var/log/**/*.log和/var/lib/docker/containers/**/*.log确认 K8s 集群的容器运行时。如果是 containerd日志路径是/var/log/pods/如果是 docker则是/var/lib/docker/containers/。Promtail 的 Helm chart 默认会把节点的日志目录挂载进容器但假如你的运行时路径特殊要做调整。还有一个容易遗漏的点Promtail 需要 RBAC 权限才能读取 Pod 元数据和日志文件。Helm chart 默认会创建 ServiceAccount 和 ClusterRole但如果你是从旧版本升级注意检查 RBAC 规则是否完整。6.3 查询超时或返回 429当查询跨度很大比如查 7 天的数据时Loki 前端会拆分查询为多个子查询并发执行。如果子查询数量超过max_query_parallelism的限制会返回 429。此时可以检查 Query Frontend 的缓存有没有生效Redis 或内存缓存。增大max_query_parallelism但要注意 Querier 的资源占用。优化查询语句缩小时间范围或者用粒度更粗的聚合查询减少数据量。6.4 数据存储桶不自动创建S3 兼容的存储一般需要提前创建好 bucket。如果你按我上面的配置部署但 MinIO 里没有预先创建loki-data桶Loki 启动时会报错。Loki 支持开启自动创建 bucket 的配置loki: storage: s3: s3forcepathstyle: true chunks: skip_ingestion: false同时还依赖存储后端的权限MinIO 默认允许s3:CreateBucket但 AWS S3 往往需要额外加策略。建议一开始就把 bucket 建好省得后面排查。6.5 Helm 升级导致数据不兼容Loki 版本升级时schema 版本可能发生变化。如果直接helm upgrade可能导致新版本无法读取旧数据。最稳妥的做法升级前备份重要配置values.yaml。先看官方升级文档确认从当前版本到目标版本是否需要中间版本过渡。在测试环境先演练一遍升级流程。升级时配合schemaConfig中的from字段添加新的 schema 配置保留旧 schema 的兼容读取能力。打个比方你从 v3.0 升到 v3.4如果官方说需要先到 v3.2那就别硬跳。日志数据丢了没法从备份恢复因为 Loki 的存储格式是内部定义的。7. 资源调优建议7.1 合理规划副本数以每天 100GB 日志量为例大概每秒 1.2MB 写入建议组件副本数单副本资源说明write (Ingester)34C8G根据复制因子调整1 副本可减至 2 副本read (Querier Frontend)24C8G查询量大可扩至 4 副本backend22C4GCompactor 和 Distributor 不需要太多资源MinIO42C4G 20GB对象存储节点我踩过的坑是 write 组件副本数设少了。当时日志量临时暴涨Ingester 处理不过来直接 reject 写入导致日志丢失。后来把 Ingester 从 2 副本扩到 3 副本问题立刻缓解。7.2 监控自身健康状态Loki 的 Helm chart 可以开启监控配置在值文件中设置monitoring: selfMonitoring: enabled: true logs: enabled: true lokiCanary: enabled: trueLoki canary 是一个持续写日志、持续查日志的小工具它能检测写入链路是否完整。如果 canary 日志没查到说明某个环节出了问题这个比人肉盯监控要灵敏得多。建议生产环境一定打开。8. 日志系统部署后的运营心得Loki 部署成功只是第一步后续真正难的是让它稳定运行并真正为人所用。日志系统本质上是一个基础设施它的用户是开发、运维、SRE 甚至业务人员。如果你只是部署完丢给团队用而不提供查询指南大部分人会因为不知道怎么查日志而放弃最终回到登录机器看日志的老路。我在团队里做了几件事效果不错制定日志规范要求各服务在输出日志时统一 JSON 格式包含 trace_id、request_id 等字段。这样在 Loki 里可以通过 trace_id 快速串联一次请求的完整链路。配置常用 Dashboard把每个服务的 ERROR 日志量、HTTP 状态码分布、延迟情况做成 Grafana Dashboard让团队一眼看到系统健康状况。建立日志告警结合 LogQL 的指标能力针对 ERROR 日志速率、超时日志数量设置 Prometheus 告警规则。有一点需要提醒Loki 不是搜索引擎。如果你拿着 Google 的习惯去搜日志发现它不支持分词、不支持模糊匹配如*error*、不支持反向索引你可能会觉得它很弱。但 Loki 的定位就是 低成本、高可用、够用就好——它把成本压到 PK 级同时让你能用标签把查询快速缩小到可接受的范围。理解了这个设计哲学你才会真正用好它。Helm 部署本身只是流程问题文档都有真正拉开差距的是对系统的理解和运维经验的积累。比如 Ingester 的内存模型、Chunk 的 flush 时机、对象存储的性能调优这些不踩几次坑是真的记不住。我这套方案跑了一年多每天日志量大约 200GB对象存储一个月成本比之前用 ES 低了将近 80%查询一般都能在几秒内返回。对我来说这就够了。
阅读完成 · 觉得有帮助?
咨询建站