Loki 是一款易于水平扩展、高可用的日志聚合系统。
对比其他日志聚合系统,Loki 具有的优势:
- 不对日志进行全文索引,通过存储压缩的非结构化日志并且仅索引元数据,Loki 的操作更简单且运行成本更低。
- 使用与 Prometheus 相同的标签对日志流进行索引和分组。
- 与 Kubernetes 完美结合,存储 Pod 日志,Pod 标签等元数据会自动抓取并建立索引。
环境准备
- Kubernetes 1.22
- Helm 3.3
- S3 对象存储 Bucket、访问密钥及授权
我们使用 Grafana 官方提供的 Helm Charts 部署一套 Loki 日志系统,包括以下组件:
- 微服务架构的 Loki 后端服务
- Promtail 日志采集客户端
- Grafana 可视化前端工具
对象存储
兼容 AWS S3 API 的对象存储都可以,例如:
- AWS S3
- 腾讯云 COS
- 阿里云 OSS
- MinIO
Loki 与存储之间需要有大带宽,不推荐跨云跨区域部署。
需要具有以下权限的访问密钥:
- ListBucket
- PutObject
- GetObject
- DeleteObject
不同云平台授权策略有差异,如果遇到权限不足,请改成整个 Bucket 完整读写权限。
获取 Helm 仓库
代码语言:shell复制## 添加加速镜像仓库
helm repo add grafana "https://helm-charts.itboon.top/grafana" --force-update
helm repo update grafana
## 添加官方仓库
# helm repo add grafana "https://grafana.github.io/helm-charts" --force-update
# helm repo update grafana
加速镜像仓库 完全从官方克隆
Helm 部署
Loki Values
代码语言:yaml复制## values-loki.yaml
fullnameOverride: loki
rbac:
namespaced: true
loki:
auth_enabled: false
image:
# registry: docker.io
# repository: grafana/loki
tag: 2.9.6
readinessProbe:
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 1
## loki.config
commonConfig:
## set to 1, otherwise more replicas are needed to connect to grafana
replication_factor: 1
storage:
bucketNames:
chunks: Your_Loki_Bucket
ruler: Your_Loki_Bucket
admin: Your_Loki_Bucket
type: s3
s3:
## s3 access, AWS S3 或者兼容 S3 API 的对象存储都可以
endpoint: cos.ap-guangzhou.myzijiebao.com
accessKeyId: Your_Access_Key_ID
secretAccessKey: Your_Secret_Access_Key
compactor:
working_directory: /var/loki/retention
shared_store: s3
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150
ingester:
max_transfer_retries: 0
# max_chunk_age: 2h # default = 2h
# chunk_idle_period: 30m # default = 30m
# chunk_target_size: 1572864
chunk_encoding: zstd
autoforget_unhealthy: true
storage_config:
tsdb_shipper:
active_index_directory: /var/loki/tsdb-index
cache_location: /var/loki/tsdb-cache
cache_ttl: 72h
shared_store: s3
schemaConfig:
configs:
- from: 2024-01-01
store: tsdb
object_store: s3
schema: v12
index:
prefix: loki_tsdb_index_
period: 24h
limits_config:
ingestion_rate_strategy: local # default = "global"
# retention_period: 240h
ingestion_rate_mb: 20 # default = 4
ingestion_burst_size_mb: 100 # default = 6
per_stream_rate_limit: 6MB # default = 3MB
per_stream_rate_limit_burst: 50MB # default = 15MB
max_concurrent_tail_requests: 20
max_cache_freshness_per_query: 10m
max_query_length: 72h
max_line_size: 256kb
max_line_size_truncate: true
## [max_streams_matchers_per_query: <int> | default = 1000]
## [max_query_bytes_read: <int> | default = 0B]
shard_streams:
enabled: true
desired_rate: 2097152 #2MiB
server:
grpc_server_max_recv_msg_size: 32000100 # default = 4194304
grpc_server_max_send_msg_size: 32000100 # default = 4194304
test:
enabled: false
monitoring:
lokiCanary:
enabled: false
dashboards:
enabled: false
selfMonitoring:
enabled: false
grafanaAgent:
installOperator: false
serviceMonitor:
enabled: false
#sidecar:
# resources:
# limits:
# cpu: 200m
# memory: 500Mi
# requests:
# cpu: 10m
# memory: 50Mi
gateway:
replicas: 1
#resources:
# limits:
# cpu: 1
# memory: 2Gi
# requests:
# cpu: 50m
# memory: 200Mi
#service:
# type: LoadBalancer
backend:
replicas: 1
persistence:
size: 20Gi
enableStatefulSetAutoDeletePVC: false
#storageClass: null
#resources:
# limits:
# cpu: 2
# memory: 8Gi
# requests:
# cpu: 100m
# memory: 500Mi
write:
replicas: 1
persistence:
size: 20Gi
#storageClass: null
#resources:
# limits:
# cpu: 2
# memory: 8Gi
# requests:
# cpu: 500m
# memory: 4Gi
read:
replicas: 3
resources:
limits:
cpu: 1
memory: 2Gi
requests:
cpu: 50m
memory: 100Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 15
targetCPUUtilizationPercentage: 90
部署 Loki
- 将上面的 Values 保存到
values-loki.yaml
文件。 - 正确修改 loki.storage 相关字段:
endpoint
,bucketnames
,access_key_id
,secret_access_key
代码语言:shell复制正确的 endpoint 字段不含 Bucket 名称
## 部署 Loki
helm upgrade --install loki
--namespace loki
--create-namespace
-f values-loki.yaml
--version 5.47.1
grafana/loki
Promtail Values
代码语言:yaml复制## values-promtail.yaml
image:
tag: 2.9.6
#resources:
# limits:
# cpu: 2
# memory: 2Gi
# requests:
# cpu: 100m
# memory: 512Mi
config:
clients:
- url: http://loki-gateway/loki/api/v1/push
snippets:
extraLimitsConfig: |
max_line_size: 256kb
max_line_size_truncate: true
readline_rate_enabled: true
readline_rate: 110000 # default = 10000
readline_burst: 810000 # default = 10000
pipelineStages:
- cri: {}
# - match:
# ## 过滤某些 namespace
# selector: '{namespace=~"Your_Namespace"}'
# action: drop
# - match:
# ## 过滤某些 container
# selector: '{container=~"Your_Container_Name"}'
# action: drop
- json:
expressions:
log: log
- output:
source: log
- multiline:
## 跨行合并
firstline: '^S'
max_wait_time: 3s
max_lines: 2000
#- match:
# ## 匹配标签的日志将进行额外管道处理
# selector: '{container=~"java.*"}'
# stages:
# - multiline:
# firstline: '^[A-Z]{3,9} ['
# max_wait_time: 3s
# max_lines: 2000
# - regex:
# expression: '^(?P<level>[A-Z]{3,9}) [.*'
# #source: log
# - labels:
# level:
部署 Promtail
将上面的 Values 保存到 values-promtail.yaml
文件。
## 部署 Promtail
helm upgrade --install promtail
--namespace loki
--create-namespace
-f values-promtail.yaml
--version 6.15.5
grafana/promtail
Grafana Values
代码语言:yaml复制## values-grafana.yaml
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Loki
type: loki
url: http://loki-gateway
access: proxy
jsonData:
maxLines: 500
# ingress:
# enabled: true
# ingressClassName: nginx
# annotations: {}
# # kubernetes.io/ingress.class: nginx
# # kubernetes.io/tls-acme: "true"
# hosts:
# - grafana.example.com
resources:
limits:
cpu: 100m
memory: 1Gi
requests:
cpu: 20m
memory: 128Mi
部署 Grafana
将上面的 Values 保存到 values-grafana.yaml
文件。
## 部署 Grafana
helm upgrade --install grafana
--namespace loki
--create-namespace
-f values-grafana.yaml
--version 7.3.7
grafana/grafana
获取 Grafana 初始 admin 密码:
代码语言:shell复制## 获取 Grafana 初始 admin 密码
kubectl get secret -n loki grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
部署优化
- 数据压缩
- 标签和索引
- Querier HPA 扩容
上面的部署案例已经在数据压缩、索引、HPA 等方面进行优化了。
数据压缩
Loki 默认使用 gzip 压缩算法,但是 gzip 解压速度比较慢,推荐使用 zstd 压缩,两者的比较请参考 从 gzip 切换到 zstd
代码语言:yaml复制loki:
ingester:
chunk_encoding: zstd
TSDB 索引
Loki v2.8 引入的 TSDB 比旧版本的索引方案具有更优的性能,请参考 官方文档
代码语言:yaml复制loki:
storage_config:
tsdb_shipper:
增加吞吐量
代码语言:yaml复制loki:
limits_config:
ingestion_rate_strategy: local # default = "global"
# retention_period: 240h
ingestion_rate_mb: 20 # default = 4
ingestion_burst_size_mb: 100 # default = 6
per_stream_rate_limit: 6MB # default = 3MB
per_stream_rate_limit_burst: 50MB # default = 15MB
max_concurrent_tail_requests: 20
max_cache_freshness_per_query: 10m
max_query_length: 72h
max_line_size: 256kb
max_line_size_truncate: true
## [max_streams_matchers_per_query: <int> | default = 1000]
## [max_query_bytes_read: <int> | default = 0B]
shard_streams:
enabled: true
desired_rate: 2097152 #2MiB
server:
grpc_server_max_recv_msg_size: 32000100 # default = 4194304
grpc_server_max_send_msg_size: 32000100 # default = 4194304
自定义标签
假如日志格式是这样的:
代码语言:INFO复制[INFO] Reloading
[INFO] plugin/reload: Running configuration SHA512 = 637429c29e6628b9c013e5f8bd5211d4564943dd9f2dd6eeb506a5a2993177f61da856d85e9ed3c8020ebde2b4abbc232f057d9ba5d9df8247d706893feb8754
[INFO] Reloading complete
[ERROR] plugin/errors: 2 cm-cn-central-00001.albatross.10086.cn. HTTPS: read tcp 10.4.63.250:47580->100.100.2.136:53: i/o timeout
在部署 Promtail 的时候可以提取日志级别作为标签:
代码语言:yaml复制- match:
## 匹配容器名以 java 开头的容器
selector: '{container=~"java.*"}'
stages:
- multiline:
## 跨行合并
firstline: '^[[A-Z]{3,9}] '
max_wait_time: 3s
max_lines: 2000
- regex:
expression: '^[(?P<level>[A-Z]{3,9})] '
#source: log
- labels:
level:
上面这个案例会提取每条日志从行首开始中括号里的大写字母并打上 level 标签,查询条件加上 level 标签可以显著提升查询效率。
自定义标签需要根据日志格式和内容进行设置,这里仅仅演示一下实现方法。
不要使用动态标签,关于动态标签并没有固定的标准。假如一个 Pod 一小时日志量有 100 万条,某个标签会产生超过 1 万个值,那这个标签肯定就属于动态标签。标签需要把日志分类打散,但是不能太散。
性能瓶颈
查询的语句的标签和时间区段决定了 Loki 需要从存储中取出并处理的数据量。如果这个数据量在 10GB 以下,那问题不大。如果达到百GB级别,那就存在性能问题了。
假如有一个 pod coredns-79f4544dbb-8ck2b
, 一天产生超过 100GB 的日志(压缩前 100GB),我们一次性查询一天的日志数据,从中找出符合条件的内容。
- 使用 pod 标签
{pod="coredns-79f4544dbb-8ck2b"}
查询一天的日志,Loki 一次处理 100GB 日志会比较慢。 - 假如增加了 level 标签,INFO 90GB、WARN 9GB、ERROR 1GB,单独查询 ERROR 级别的日志
{pod="coredns-79f4544dbb-8ck2b",level="ERROR}
就很轻松了。
解决的办法就是合理设置标签,比如日志级别、功能模块、事件类型等。
查询语句优化
查询的语句的标签和时间区段决定了 Loki 需要处理的数据量,标签越具体查询效率越高,以下列举一些查询语句及评分:
- 25 分:
{namespace="kube-system"} |= "timeout"
时间区段最近 24 小时 - 50 分:
{namespace="kube-system",pod=~"coredns-.*"} |= "timeout"
时间区段最近 24 小时 - 60 分:
{pod="coredns-79f4544dbb-8ck2b"} |= "timeout"
时间区段最近 24 小时 - 70 分:
{pod="coredns-79f4544dbb-8ck2b"} |= "timeout"
时间区段最近 1 小时 - 90 分:
{pod="coredns-79f4544dbb-8ck2b",level="ERROR} |= "timeout"
时间区段最近 1 小时
网络带宽和存储IO
如果日志量比较大,建议 Kubernetes 节点选择 10G 以上的网络带宽,因为 Querier 可以分布到多个节点,所以节点网络带宽的压力可以稀释,带宽瓶颈会转移到后端存储。如果云平台对象存储的性能不能满足需求,可以考虑裸机部署 MinIO 存储。
查询性能存在瓶颈并不能掩盖 Loki 独特的优势,实际上微服务架构中的 Loki 异常坚固,哪怕多个大型查询并发造成堵塞,Loki 依然可以稳定地摄入新数据。
课后作业
- 给日志增加自定义标签,提升查询效率。
- 在 Loki 微服务架构中,查询日志时 Querier 需要消耗大量 CPU 和网络带宽,但它并不需要稳定的机器。可以把 querier 调度到廉价的 spot 节点,并尽可能均匀地分布到多台节点上,用较低的成本大幅度提升查询速度。
- 加快 Querier HPA 扩容速度。
后续章节会完善上述内容,敬请期待。