正文共:1923字 预计阅读时间:5分钟
很多时候我们把Loki部署成一个单体应用,这样能够让我们快速的将它在开发、测试环境中应用起来。不过最终大家都还是逃不过真香定律,这个时候大家就在琢磨运维的灵魂三问了,这东西怎么部署到生产环境?高可用稳定吗?分布式怎么样?今天小白起个引子, 在Loki分布式部署上面给大家带来思考。
Loki主要组件
在分布式部署之前,小白还是有必要简单介绍下Loki的几个核心组件。
- Distributor
Distributor
是Loki日志写入的最前端,当它收到日志时会验证其正确性,之后会将日志切成块(chunk)后,转给Ingester
负责存储。
- Ingester
Ingester
主要负责将收到的日志数据写入到后端存储,如DynamoDB,S3,Cassandra等),同时它还会将日志信息发送给Querier
组件。
- Querier
Querier
主要负责从Ingester
和后端存储里面提取日志,并用LogQL查询语言处理后返回给客户端
- Query Frontend
Query frontend
主要提供查询API,它可以将一条大的查询请求拆分成多条让Querier
并行查询,并汇总后返回。它是一个可选的部署组件,通常我们部署它用来防止大型查询在单个查询器中引起内存不足的问题。
Loki的这些组件主要构成系统内的两条数据路径如下图,红色的为数据写入路径,绿色的为数据查询路径。
了解
Cortex
的同学可能看到这个图发现比较眼熟。没错,根据Loki的开发者描述,Loki的分布式架构正是从Cortex代码里面获取的。毕竟都是一个公司的产品,有个成熟的架构不用新造轮子?
Loki分布式架构
一致性哈希的应用是整个Loki实现分布式部署的关键部分,
正如和Cortex一样,Loki的Distributor组件会将日志的租户ID
或者Label集
进行哈希处理,并决定将处理后日志发送给哈希环上的某一个Ingester处理。Ingester内维护了一个生命周期管理器
,它会利用consul的token将自己的状态注册到哈希环中,其中状态包括PENDING
,JOINING
,ACTIVE
,LEAVING
或UNHEALTHY
JOINING
状态的ingester可能会处理日志写入的请求LEAVING
状态的ingester可能仍会处理日志的查询请求
为了保证查询最终的一致性,Loki在响应客户端数据之前,Distributor必须要等待一半以上的Ingester服务成功响应后才会返回。
现在我们再来看看Loki要满足分布式所需要具备的条件:
- 日志块文件需要持久化的对象文件存储,
推荐Ceph S3
- 日志索引需要一个可以水平扩展的NoSQL数据库,
推荐Cassandra
- 需要一个Consul的集群来保存Loki主要组件状态
- 需要一个统一网关,用于Loki日志写入和查询的负载均衡
那么我们得到的Loki的整体架构就会像下面这样:
对了,如果你看过小白之前的文章《巧用缓存加速Loki日志查询》,如果我们希望在Loki分布式部署架构里面还要引入缓存的话,那Loki的架构就变成这样
怎么样,感觉是不是有点复杂了?不过这个图画着画着怎么感觉和Cortex一模一样。算了,大佬们负责设计,我们小白能用好就不错了?
Loki组件配置
Loki的模块部署主要通过在启动参数中调整--target
参数来控制,默认情况下我们启动loki时没有指定该参数,那他就会以全量all_in_one的方式运行。
distributor_config
定义了distributor的全局配置,主要维护了保存在consul或者etcd的一致性哈希环访问地址和ingesters心跳的信息。
ring:
kvstore:
#存储方式,支持consul、etcd或者memroy,三者选其一
store: <string>
#访问路径
[prefix: <string> | default = "collectors/"]
#kv存储的配置
[consul: <consul_config>]
[host: <string> | default = "localhost:8500"]
[acl_token: <string>]
[http_client_timeout: <duration> | default = 20s]
[consistent_reads: <boolean> | default = true]
[etcd: <etcd_config>]
[endpoints: <list of string> | default = []]
[dial_timeout: <duration> | default = 10s]
[max_retries: <int> | default = 10]
[memberlist: <memberlist_config>]
[...略过...]
#与ingesters的心跳超时时间
[heartbeat_timeout: <duration> | default = 1m]
Loki自身利用gossip协议可以在内存里面实现了一致性hash,不过member的配置比较复杂,小白还是建议大家采用consul或者etcd来作为哈希环的存储后端
ingester_config
里面定义生命周期管理器和日志存储的相关配置,这部分除生命周期管理器需调整外,大部分可以使用默认配置。
lifecycler:
ring:
kvstore:
# 参照istributor_config配置即可
[store: <string> | default = "consul"]
[prefix: <string> | default = "collectors/"]
[consul: <consul_config>]
[etcd: <etcd_config>]
[memberlist: <memberlist_config>]
[heartbeat_timeout: <duration> | default = 1m]
[replication_factor: <int> | default = 3]
# 注册在哈希环上的token数,可以理解为虚拟节点
[num_tokens: <int> | default = 128]
[heartbeat_period: <duration> | default = 5s]
# 从服务的网卡上读取IP地址
interface_names:
- [<string> ... | default = ["eth0", "en0"]]
query_frontend_config
这部分定义查询前端的配置
frontend:
# 压缩http的响应包
[compress_responses: <boolean> | default = false]
# 定义下游的querier服务地址
[downstream_url: <string> | default = ""]
query_frontend拆分查询请求是通过queryrange.split_queriers_by_interval
决定的。如果将它设置为1h,query_frontend会将一天的查询分解为24个一小时的查询,将其分发给querier,然后在返回后聚合日志。
这在生产环境中非常有用,小白强烈建议开启。
query_frontend对于缓存的应用,当前支持Prometheus指标,对于日志的缓存,Loki1.6版本还不支持,后续可以期待下。
gateway
是一个外部自定义的nginx负载均衡器,主要是将Loki的日志写入和查询请求做路由和负载均衡,它的核心配置可以参照如下:
server {
listen 3100;
location = / {
proxy_pass http://querier:3100/ready;
}
location = /api/prom/push {
proxy_pass http://distributor:3100$request_uri;
}
location = /api/prom/tail {
proxy_pass http://querier:3100$request_uri;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~ /api/prom/.* {
proxy_pass http://querier-frontend:3100$request_uri;
}
location = /loki/api/v1/push {
proxy_pass http://distributor:3100$request_uri;
}
location = /loki/api/v1/tail {
proxy_pass http://querier:3100$request_uri;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~ /loki/api/.* {
proxy_pass http://querier-frontend:3100$request_uri;
}
}
关于Loki的分布式总结小白就介绍到这里,《听说,你的Loki还是单体?(下篇)》我将更新利用Helm的方式部署Loki集群,敬请期待。