NGD日志方案
1. 背景
1.1 介绍
在任何分布式系统中,都需要合适的分布式日志收集方案协助系统运维。在Kubernetes平台下,服务都以容器的方式运行,对日志收集方案的需求更为迫切,众多同种应用的服务在高可用要求下需要同时运行多个副本。为了追踪同一个用户的行为,可能需要同时查看多个容器的日志,在没有日志收集的场景下,不仅需要在Kubernetes平台下查看所有相关Pod的日志,还需要按照时间顺序关联日志上下文,这无疑阻碍了问题的查找和分析。
在NGD服务场景中,涉及到的基础组件和业务服务众多,当服务出现故障后,查看众多的Pod日志十分耗费时间和精力,这就需要一个统一的平台查看日志,Kubernetes Dashboard提供可视化日志查看功能,但其使用非常有限,只能查看每个Pod中容器的日志,无法关联多副本服务的日志上下文。故需要一种分布式日志收集方案,简化日志查看流程和故障问题分析与定位。
除了常规的容器日志,NGD也有业务日志收集的需求,后期可能基于业务日志做数据分析,NGD暂时没有提供合适的分布式日志收集方案实现此目标。不同于容器日志,业务日志数据对高可用要求相对较高,为业务功能提供数据支持。
1.2 现状
目前,NGD的日志有两种策略,对容器的Console日志由Docker Log Driver处理,此类日志并没有持久化保证,NGD的业务日志使用hostPath方式持久化到硬盘。
对于厂内故障问题分析,RD、QA和OP可以通过查看基础组件和业务Pod日志,但其步骤也比较繁琐;对于私有化项目服务故障,往往不允许登录机器收集到日志,即使可以登录到机器,现场操作人员可能也不知所措,获取不到可用的日志数据。
因此,日志收集对NGD服务的运维来说至关重要,不仅可以统一管理日志数据,简化日志数据查看,也用于保留服务故障日志,有利于私有化场景问题定位和分析。
2. 方案调研
2.1 收集方式
调研目前主流日志收集方式,基本以两种实现方式为主,一种是以节点Agent方式运行的日志收集器,另一种是作为应用容器的Sidecar容器形式的日志收集器。两种方式的主要优缺点如下所述:
- Node Log Agent方式:
优点:- 资源消耗低
- 配置方式简单
- 只适合单租户
- 日志需要存在到宿主机中
- Sidecar容器方式:
优点:- 可以收集到应用的所有日志
- 不需要将日志暴露在节点级别中
- 能够在容器层面控制日志
- 支持多租户
- 适合较低存储量的日志信息,不适合大量日志收集。
- IO效率降低,大概两倍的IO资源消耗。
- 收集和配置相对复杂
针对两种收集方式,考虑到目前需要的日志收集方案是收集所有容器日志和相关业务日志,并且也暂时不存在多租户的场景,基于以下几点,选择以Node Log Agent的形式收集所有服务日志:
- 基于Sidecar容器日志收集方案实现和配置相对比较复杂,需要侵入现有的业务Chart或通过MutatingAdmissionWebhook的方式实现;Node Log Agent的收集方式对业务Chart无侵入,独立于应用服务。
- 需要收集的日志数据量较大,并且数据相对增量也较大,基于Sidecar容器方式将增加应用Pod资源消耗,对应用服务影响较大;Node Log Agent方式以独立Pod形式运行,相对可控,可处理大数据量日志数据。
- 基于Sidecar容器方式非常适合于多租户场景,但是NGD暂时没有多租户日志收集场景的需求
综上所述,使用Node Log Agent的收集方式是最佳的选择,并且不影响NGD部署资源结构,支持日志收集系统功能的可插拔,独立于NGD版本迭代。
2.2 可选方案
调研目前主流的分布式日志收集方案,围绕通用日志收集方案,不考虑特定平台的日志方案,主要包括两种Log-Stack,其一是总所周知的Elastic-Stack,其二是云原生场景下的日志新贵Loki-Stack。
Elastic-Stack的日志收集方案主要包含的组件有:Filebeat、FluentD、Logstash、Elasticsearch和Kibana,其中Filebeat/FluentD作为日志收集器,Logstash作为日志处理模块,Elasticsearch为日志数据提供持久化和检索能力,Kibana作为日志展示平台,提供可视化能力。
Loki-Stack的日志收集方案主要包含的组件有:Promtail、Filebeat、Fluent-bit、Logstash、Loki和Grafana,其中Promtail/Logstash可作为日志收集和处理模块,Loki为日志数据提供持久化、检索和过滤能力,Grafana作为日志数据展示平台,提供可视化能力,功能与Kibana类似。
下面分别介绍两种Log-Stack的主要优缺点和各自适应的应用场景:
Elastic-Stack日志方案:
优点:
- 应用场景广泛,经历过大量实践案例验证的通用日志解决方案,稳定可靠,提供丰富的功能。
- Elasticsearch存储的日志数据可满足额外的业务功能开发。
缺点:
- 方案规模复杂和臃肿,对资源要求相对较高
- 日志数据存储在Elasticsearch中,存储的json对象key和value都需要建立索引,为了支持日志内容检索,需要对日志内容进行分词并建立全文索引,可能导致索引数据占用空间大于日志数据,在面对大量日志数据场景下, 对存储资源要求较高。
- 大量日志数据场景下,检索日志数据消耗资源较大。
Loki-Stack日志方案:
优点:
- 支持云原生容器平台,特别是在Kubernetes场景下, 简化容器日志收集,提供近实时查看功能。
- 提供日志标签元数据的方式提供检索能力,可无缝与Prometheus监控系统配合,提供监控系统发现问题,日志系统分析和定位问题的统一方案。
- Loki的日志检索方式LogQL借鉴Prometheus的PromQL,提供基于标签的方式查看日志数据流。标签的添加与Prometheus指标数据相同。
- Loki通过提供日志元数据检索和日志内容过滤功能实现日志查看,相较于Elasticserach极大地降低了存储索引数据和检索的资源消耗。
- Loki提供多种日志压缩算法,存储压缩后的日志内容极大的降低了存储资源消耗,未压缩的日志数据大概是压缩后的5到10倍多。由于只需要为日志元数据建立索引,索引数据资源消耗远远小于日志数据。
- 提供灵活的可扩展性,索引存储可以对接其他NoSQL,日志数据可以对接对象存储系统,存放日志数据。
- Loki和Grafana社区活跃,功能新增和优化响应速度快
缺点:
- 功能较为单一,但满足日常日志查看需求。
- 基于存储的日志数据实现业务需求较为复杂。
通过上述两种方案的对比,可以看到针对容器日志数据,采用Loki-Stack方案更加适合,因为Kubenetes环境中容器日志的数据量很大,如果采用Elastic-Stack日志方案,将消耗较大的系统资源,并且大量日志数据文件和索引文件也会消耗较大的存储空间,对IO性能要求较高。相较之下,Loki-Stack非常适合作为此类日志数据的处理,低容量的日志存储和高性能的日志检索和过滤为日志分析提供便利。
针对业务分析日志数据的需求, Elastic-Stack更加满足需求,Elasticsearch作为常用的数据检索和持久化组件,方便业务侧查询和处理日志数据,Loki-Stack目前针对业务侧日志数据分析提供的能力十分有限,可能无法满足业务日志数据分析需求。
综合上述分布式日志收集方案,考虑到NGD服务的现状和日志需求,容器日志的全量收集和业务日志数据分析能力都需要提供。围绕稳定性、CPU/Memory/IO资源消耗、存储容量、日志查询效率和业务功能需求等几个方面综合考虑,推荐两种日志收集方案都使用,两种日志方案分别针对的场景如下:
- Elastic-Stack:负责具有业务需求的日志数据收集,提供复杂的业务日志数据处理和分析能力,业务日志的数据量可控,此类日志数据后期可接入NGD的统计ES集群。对日志收集服务和业务日志数据存储的高可用要求高。
- Loki-Stack:负责容器全量日志的收集,提供常用的日志处理能力,用于日志查询、服务异常问题分析和定位,并提供较长日志保留时间。对日志收集服务和日志数据存储的高可用要求较低。
提供两种日志收集方案在NGD场景下也是合理的,NGD服务和依赖的组件较多,容器日志数据量较大,占用日志数据量的90%以上,业务日志提供业务分析能力,隔离两种日志数据有利于业务服务稳定,更新容器日志收集配置不影响业务日志收集。
考虑到同时部署两套分布式日志收集方案,可能会有提高部署复杂度和资源消耗的顾虑,不过借助Kubernetes+Helm的部署方式,并不会额外增加部署的复杂性。与此同时,使用两种方案的资源消耗甚至比单独使用Elastic-Stack要低,正如前文所述,日志数据量的90%以上为服务运行日志,此类数据采用Loki-Stack处理对CPU、Memory、IO和存储资源的消耗更低。
3. 方案实现
针对NGD的业务日志数据,使用分布式日志收集框架ELK进行数据收集和处理。
针对Pod中容器的运行日志,使用PLG进行数据收集和处理。
下面分别介绍NGD针对不同场景的日志收集方案:
3.1 Filebeat + Logstash + Elasticsearch + Kibana
ELK的日志收集方案使用FIlebeat+Logstash+Elasticsearch+Kibana四个组件,其中各组件的功能如下:
- Filebeat:轻量级的日志转发工具,根据给定的输入模式监控特定日志文件,收集日志数据发送到各种类型的后端。
- Logstash:具备实时流水线功能的数据收集引擎,用于聚合来自多个数据源的数据、转换数据和分发数据到不同的后端。
- Elasticsearch:作为分布式搜索和分析引擎,提供近实时地数据检索和分析,负责日志数据的持久化和快速检索能力。
- Kibana:提供数据的可视化功能,支持用户基于角色的授权功能。
NGD的业务日志数据统一使用ELK日志收集方案,Filebeat配置中添加待监听的日志文件匹配模式,将收集到的日志数据统一发送到Logstash中。Logstash中通过新增pipline配置处理接收到的数据,Input配置数据源,filter配置数据转换和处理逻辑,output配置数据分发Elasticsearch集群。Elasticsearch接收日志索引请求,将日志数据写入es集群中。Kibana提供日志的展示和近实时查看。
<待补充>
3.2 Promtail + Loki + Grafana
PLG的日志收集方案使用Promtail+Loki+Grafana三个组件,其中各组件的功能如下:
- Promtail:与Filebeat功能类似,负责日志文件的监听、日志数据的转换处理和发送。由于天生适配Kubernetes,可通过请求apiserver获取数据源的元数据信息。
- Loki:包含Distributor、Ingester、Querier和Query-Frontend等组件,负责日志数据的缓存、压缩、存储、索引和持久化,提供高性能的日志查询功能,使用LogQL(与PromQL类似)查询语法,支持日志筛选和过滤。
- Grafana:提供日志数据可视化能力,支持日志数据实时查看,并提供各种日志数据指标展示。
PLG日志方案适合收集容器日志和业务日志,其用于查看日志数据,帮助分析和定位服务问题。通过配置Promtail收集所有容器日志,另外也可以添加监听业务日志相关的配置,实现业务日志数据的收集和查看。Promtail通过自定义的Pipeline处理收集的数据,包括数据转换和过滤,并为数据添加自定义标签,作为日志数据的元信息用于索引操作。通过调用Loki的Push API将最终的数据分发到Loki,Loki的Distributor负责收集日志数据并进行压缩,将数据传送到Ingester进行持久化处理。Querier提供Loki的查询API和LogQL查询日志数据和日志指标。通过在Grafana中配置Loki数据源,便可使用Grafana展示查询出的日志数据和日志指标。
PLG中Loki支持两种部署方式:单实例和微服务。Loki的可执行文件中支持微服务场景下的所有组件,即所有模块打包在一个可执行文件中, 通过指定不同的 target配置将Loki运行在特定的功能下。使用单实例部署时,单个实例使用所有模块,使用本地存储作为index和chunks数据存储系统,无需任何而外依赖服务,但是不适合高可用场景;微服务模式可支持水平扩展,但是index需要使用NoSQL服务,chunks数据需要使用对象存储服务,配置日志数据副本数以保持高可用。针对目前NGD日志使用场景,对日志数据的高可用并非强需求,并且在尽可能保持日志方案轻量化的前提下,优先选择单实例部署方式。
在Kubernetes平台下,优先考虑使用Helm部署Loki-Stack,PLG方案中Promtail和Loki需要使用新的Chart部署,Grafana和可选的Prometheus可以使用现有的服务,因此Loki-Stack至少需要整合Promtail和Loki的集成化部署,Loki-Stack的Chart实现方式可以查看ngd-devops代码库。下面针对NGD的日志收集场景配置Promtail和Loki:
1、Promtail配置
日志数据挂载:Promtail使用DaemonSet的方式以容器方式运行收集宿主机日志,因此需要将待收集的日志路径挂载到容器中,目前涉及到的日志路径主要有如下两类:
- 容器日志:默认路径为/var/log/pods,考虑到根目录容量问题,一般都会软链到特定目录中,目前OP提供的部署配置使用的路径是/data/lib/docker/containers,两个路径都需要挂载。
- 业务日志:根据NGD Chart配置的日志根目录挂载日志路径,默认为/data/logs
日志抓取配置: 两种日志数据使用不同的日志抓取配置,容器日志统一使用相同的配置为日志数据添加指定标签,业务日志需要自定义抓取任务配置,根据业务日志抽取指定标签。
-
容器日志:使用统一的抓取任务收集容器日志
- job_name: kubernetes-pods-general pipeline_stages: - docker: {} kubernetes_sd_configs: - role: pod relabel_configs: - action: replace source_labels: - __meta_kubernetes_pod_node_name target_label: host - 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__
添加4种标签用于日志数据索引:host、namespace、pod和container,在LogQL中可使用这4标签查看数据。__path__用于指定监听日志路径。
- 业务日志:使用静态抓取配置,根据业务服务的日志类型分别进行如下配置:
-
backend服务access日志配置
- job_name: static-ngd-backend-access static_configs: - targets: - localhost labels: job: ngd-backend-access __path__: /data/logs/backend/*/access/*.access.log pipeline_stages: - match: selector: '{job="ngd-backend-access"}' stages: - regex: source: filename expression: '(?:backend)/(?P<env>.*)/access/(?P<pod>.*)\.access\.log' - labels: env: pod:
为backend的access日志添加"job: ngd-backend-accesse"的标签,并通过__path__指定监听的业务日志路径。
-
core服务access日志配置
- job_name: static-ngd-core-access static_configs: - targets: - localhost labels: job: ngd-core-access __path__: /data/logs/core/*/access-*-[a-z0-9][a-z0-9][a-z0-9][a-z0-9][a-z0-9].log pipeline_stages: - match: selector: '{job="ngd-core-access"}' stages: - regex: source: filename expression: '(?:core)/(?P<env>.*)/access-(?P<pod>.*)\.log' - labels: env: pod:
为core服务的access日志添加“job: ngd-core-access"标签,__path__指定监听的access日志文件路径模式。由于日志滚动策略,需要排除掉滚动的日志文件。
-
nlu服务access日志配置
- job_name: static-ngd-nlu-access static_configs: - targets: - localhost labels: job: ngd-nlu-access __path__: /data/logs/ngd_nlu_platform/*/access/*.access.log pipeline_stages: - match: selector: '{job="ngd-nlu-access"}' stages: - regex: source: filename expression: '(?:ngd_nlu_platform)/(?P<env>.*)/access/(?P<pod>.*)\.access\.log' - labels: env: pod:
为nlu服务的access日志数据添加"job: ngd-nlu-access"标签,__path__指定监听的access日志文件路径模式。
-
core服务的conversation日志配置
- job_name: static-ngd-conversation static_configs: - targets: - localhost labels: job: ngd-core-conversation __path__: /data/logs/core/*/conversation-*.log pipeline_stages: - match: selector: '{job="ngd-core-conversation"}' stages: - regex: source: filename expression: '(?:core)/(?P<env>.*)/conversation-(kafka-log-|)(?P<pod>.*)\.log' - labels: env: pod:
为core服务的会话日志数据添加"job: ngd-core-conversation"标签,__path__指定监听的conversation日志文件路径模式。
-
vector-search服务的日志配置
- job_name: static-ngd-vector-search static_configs: - targets: - localhost labels: job: ngd-vector-search __path__: /data/logs/vector_search/*/*.log pipeline_stages: - match: selector: '{job="ngd-vector-search"}' stages: - regex: source: filename expression: '(?:vector_search)/(?P<env>.*)/(?P<type>access|error)-(?P<pod>.*)\.log' - labels: env: pod: type:
收集vector-serach服务的访问日志和服务日志,为日志数据添加"job: ngd-vector-search"标签,__path__指定日志文件路径模式,type标签区分日志类型。
-
deep-learning-platform服务的日志配置
- job_name: static-ngd-deep-learning-platform static_configs: - targets: - localhost labels: job: ngd-deep-learning-platform __path__: /data/logs/deep-learning-platform/*.log pipeline_stages: - match: selector: '{job="ngd-deep-learning-platform"}' stages: - regex: source: filename expression: '(?:deep-learning-platform)/(?P<type>access|error)\.log' - labels: type:
收集deep-learning-platform服务的访问日志和服务日志,为日志数据添加"job: ngd-deep-learning-platform"标签,__path__指定日志文件路径模式,type标签区分日志类型。
-
2、Loki配置
Loki使用单实例模式运行,相关的配置信息如下:
target: all
auth_enabled: false
ingester:
chunk_idle_period: 2h
chunk_block_size: 262144
chunk_target_size: 1536000
chunk_retain_period: 30s
max_transfer_retries: 0
max_chunk_age: 2h
lifecycler:
ring:
kvstore:
store: inmemory
replication_factor: 1
limits_config:
enforce_metric_name: false
reject_old_samples: true
reject_old_samples_max_age: 168h
schema_config:
configs:
- from: 2020-01-01
store: boltdb
object_store: filesystem
schema: v11
index:
prefix: index_
period: 168h
row_shards: 16
server:
http_listen_port: 3100
storage_config:
boltdb:
directory: /data/loki/index
filesystem:
directory: /data/loki/chunks
chunk_store_config:
max_look_back_period: 840h
table_manager:
retention_deletes_enabled: true
retention_period: 840h
creation_grace_period: 10m
其中较为重要的配置如下:
-
target: 指定服务运行的功能,all表示运行所有模块
-
storage_config: 指定index和chunks存储的类型和路径
-
server.http_listen_port: 指定Loki的http端口
-
chunk_store_config.max_look_back_period: 指定Loki可查询的最长时间范围
-
table_manager: 配置日志的保留策略,设置的retention_period必须为storage period的整数倍
4. 日志操作
4.1 Log-Stack部署
Elastic-Stack和Loki-Stack的Chart统一使用依赖子Chart模式整合,其原因可参考NGD标准化Chart。
两种日志方案的集成Chart都在内部维护各服务组件调用关系,降低部署和配置难度,新增收集日志仅需要添加各自的日志收集配置即可,无需关注部署细节。
4.1.1 ELK部署
ELK-Stack Chart:http://icode.baidu.com/repos/baidu/icpd/ngd-devops/tree/feature-6.2:charts/middleware/log-stack/elk-stack (使用NGD对应分支版本)
无用户名/密码版本ELK部署:
# Helm 2 helm install -f values.yaml --name=elk-stack --namespace=elk ./elk-stack |
支持用户名/密码版本ELK部署:
# Helm 2 helm install -f values-security.yaml --name=elk-stack --namespace=elk ./elk-stack |
4.1.2 PLG部署
Loki-Stack Chart: http://icode.baidu.com/repos/baidu/icpd/ngd-devops/tree/feature-6.2:charts/middleware/log-stack/loki-stack(使用NGD对应分支版本)
Loki-Stack的部署仅需要开启Promtail和Loki组件的部署开关,Grafana使用集群中现有服务。
Loki-Stack部署:
# Helm 2 helm install -f values.yaml --name=loki-stack --namespace=loki ./loki-stack |
部署完成后,需要在Grafana中配置Loki数据源,配置流程如下:
1、 访问集群中的Grafana地址,默认对外访问端口为8396,选择“Configuration” -> "Data Sources" -> "Add data source" -> "Loki"。
2、填写Name和HTTP-URL,关闭TLS验证并保存,需要注意的是,若Loki与Grafana不在同一Namespace下,则Loki的地址需要添加Namespace信息,格式为:http://<loki-service-name>.<namespace>:3100
3、通过Explore验证功能,选择Log Labels查看服务的日志。如下图所示,Grafana展示Loki中存储的日志数据
4.2 常用操作
4.2.1 ELK相关操作
<待补充>
4.2.2 PLG相关操作
Loki的LogQL可支持多种多样的日志查询需求,在展示日志查询样例之前,首先介绍一下LogQL的基本用法。
LogQL根据两种规则查询日志数据,分别是日志流选择器和过滤表达式:
-
Log Stream Selector:根据日志数据的标签选择对应的数据流,支持如下四种规则:
- =: 精确匹配
- !=: 不匹配
- =~: 正则匹配
- !~: 正则匹配取反
-
Filter Expression:根据筛选出的日志流过滤日志内容,也支持如下四种规则:
- |=: 日志包含指定字符串
- !=: 日志不包含指定字符串
- |~: 日志匹配指定的正则表达式
- !~: 日志不匹配指定的正则表达式
上面日志流选择器和过滤表达式都能同时指定多个条件,多个条件间是AND的关系。
下面通过样例分别展示NGD常用场景下的日志查询的LogQL,查询的日志时间范围统一在时间范围筛选框中选择。
场景1:查看backend的Pod中backend容器的日志
{pod= "ngd-ngdee-v60-backend-offline-65f88b6dfc-gcvph" ,container= "backend" } |
场景2:查看backend-offline服务所有Pod的错误日志
{pod=~ "ngd-ngdee-v63-backend-offline-.+" ,namespace= "ngdee-v63" }|= "ERROR" |
场景3:查看backend-offline服务所有Pod的access日志文件中agentId为“601c75f9-9544-48ba-bb86-369691e9a4b8“,返回状态码非200的所有请求
{job= "ngd-backend-access" ,pod=~ "ngd-ngdee-v63-backend-offline-.+" }|= "601c75f9-9544-48ba-bb86-369691e9a4b8" != "200" |
Loki除了支持日志查看外,还支持日志指标数据的查看。下面以几种常见的几种运维场景介绍日志指标数据的查看
场景1:查看指定namespace下所有服务的Console日志在1分钟内产生的条数
sum(count_over_time({namespace= "ngdee-v63" }[1m])) |
场景2:查看指定namespace下产生日志数据最多的前5个Pod
topk( 5 , sum(bytes_rate({namespace= "ngdee-v63" }[1m])) by (pod)) |
sum(rate({job= "ngd-backend-access" ,pod=~ "ngd-ngdee-v63-backend-offline-.+" }!= "/api/v2/healthcheck" != "/actuator/prometheus" [10m])) |
5. FAQ
5.1 ELK
1. 当文件处于Filebeat的harvester读取中,文件被删除或者重命名后会丢失未剩余未读取的数据吗?
使用Filebeat进行日志收集时,每个文件都会启动一个harvester,harvester负责文件的打开和关闭,也就是说,只要harvester仍在读取中,那么该文件的描述符就仍处于打开状态。所以,当文件被删除或者重命名后, Filebeat仍然能够读取到该文件的剩余内容, 直到harvest运行退出。
2. FIlebeat保证at-least-once发送的限制有哪些?
当日志文件滚动快于Filebeat处理时,将会出现数据丢失的情况。
5.2 PLG
1. 如何解决Promtail日志出现报错:error="filetarget.fsnotify.NewWatcher: too many open files"?
出现上述错误是因为操作系统参数设置问题,由于Promtail针对每个监听的文件都开启一个Watcher,当监听的文件数量过多时,可能由于fs.inotify.max_user_watches参数过小无法创建新的Watcher导致。
解决办法:将fs.inotify.max_user_watches = 8192追加到/etc/sysctl.conf文件中,执行sysctl -p /etc/sysctl.conf,重启Promtail服务。
2. 为什么grafana镜像建议使用7.0.0版本或以上版本?
部分业务输出的console日志包含颜色渲染,在低版本的grafana中,展示的日志无法渲染出颜色,颜色配置会以ascii字符显示,影响日志查看体验。另外,由于非Live模式下日志从上到下由新到旧展示,与正常日志查看习惯相悖,Grafana官方计划在7.2版本支持日志查看顺序选择,故待7.2版本release后将替换使用7.2版本。
6. 参考
- https://alexmarquardt.com/2018/11/05/security-tls-ssl-pki-authentication-in-elasticsearch/
- https://www.elastic.co/guide/en/elasticsearch/reference/current/certutil.html
- https://www.elastic.co/guide/en/elasticsearch/reference/current/security-settings.html
- https://www.elastic.co/guide/en/beats/filebeat/current/elasticsearch-output.html
- https://grafana.com/docs/loki/latest/overview/comparisons/
- https://www.infracloud.io/blogs/logging-in-kubernetes-efk-vs-plg-stack/
- https://grafana.com/docs/loki/latest/logql/