日志在一个应用程序的生命周期中扮演着几个角色。它们帮助调试问题--有些给我们分析数据(如访问日志)--并帮助提供一个应用程序在特定时刻的状态概览。这需要不同的访问和可视化模式,而且,由于每种结果所涉及的不同行为,我们往往最终使用不同的工具。比如说。
- 对于实时跟踪日志,我们使用Loki。
- 对于复杂的可视化,我们更喜欢Elasticsearch,以及
- 归档日志,我们更喜欢对象存储(如Amazon S3)。
然而,在有些情况下,我们需要回顾过去,并且只将日志归档为gzipped文本文件。当然,一定有更好的方法来运行分析。那么解决方案是什么呢:在这些工具之间传输日志!在这篇博客中,我们将讨论一个简单的方案,如何将日志从S3桶转移到Elasticsearch。
这里描述的功能将包含在即将发布的One Eye 0.4.0版本中。
从哪里开始? 🔗︎
最明显的解决方案是编写小型脚本来获取、转换和摄取日志。然而,这些脚本很少可以重复使用,而且需要作者付出相当大的努力。
尝试编写脚本 🔗︎
假设我们在S3桶中已经有了一堆压缩的日志。我们可以使用aws-cli 来浏览和下载这些档案。在我们得到原始日志之后,我们可以用一个简单的curl 命令将它们摄入Elasticsearch。让我们来看看这在实践中是如何运作的吧!
列出一个桶中的特定键 🔗︎
aws s3 ls s3://my-bucket --recursive | grep 'terms'
从S3取回日志 🔗︎
aws s3 cp s3://my-bucket/my-key - | gzip -d > raw.logs
向elasticsearch发送批量索引请求 🔗︎
curl -XPOST elasticsearch:port/my_index/_bulk -d
{"index":{}}
{"stream":"stderr","logtag":"F","message":"I0730 09:31:05.661702 1 trace.go:116] Trace[736852026]: \"GuaranteedUpdate etcd3\" type:*v1.Endpoints ...}}
{"index":{}}
{"stream":"stderr","logtag":"F","message":"some other log ...}}
注意:Elasticsearch的批量请求需要一个以换行符分隔的JSON!
这种方法的唯一问题是,你需要从原始日志中创建一个Elasticsearch有效载荷。这听起来很简单,但要做到这一点,你需要添加元数据,如索引,等等。这仍然是一个可行的方法,但现在让我们跳过这部分。
优点。
缺点。
- 过滤日志的潜力有限
- 将日志转换为Elasticsearch的批量请求格式
- 使其可重复使用需要相当大的努力
使用AWS Lambda 🔗︎
最后一个例子其实更像是一个临时性的解决方案。有更多标准化的方法来处理这个问题。其中一个更复杂的方法是使用AWS Lambda。我们不会深入挖掘这种解决方案,因为已经有大量关于它的优秀文章,你可以在这里找到。
优点。
缺点。
- 限于亚马逊
- 使其可重复使用是一个相当大的努力
用Athena改善过滤功能 🔗︎
你可能知道,有一个专门用于在Amazon S3桶上运行查询的服务,叫做Amazon Athena。Athena可以成为帮助过滤你的日志的一个伟大的工具:它允许你用一个简单的SQL查询来查询S3对象存储。
亚马逊也有一个独立的服务来处理ETL(提取转换负载)工作。这是我们所描述的问题的一般化版本。你可以在官方文档中阅读更多关于它的内容。
一目了然 🔗︎
正如你所看到的,有几种方法可以在云中完成这个工作。它们中的大多数都是随用随付的服务,深深地融入了云供应商的产品中。这意味着,在内部或不同的环境中使用它们,是一个真正的挑战。One Eye是一个商业产品,为Kubernetes监控提供开箱即用的解决方案,以你已经喜欢使用的开源软件为中心。根据我们的用户需求,我们确定了以下关键功能。
- 处理向对象存储摄取日志和从对象存储重新加载日志的问题
- 基本的时间和元数据过滤(集群、命名空间、吊舱、容器)。
- 易于使用的用户界面
- 自动完成从零到仪表盘的整个过程
让我们看一个例子 🔗︎
第一步,我们还没有触及,就是把日志保存到对象存储中。有一个定义明确的格式来摄取这些日志是极其重要的。原因是我们不希望随便使用任何索引来映射我们存储的日志,以及存储在哪里。在这种情况下,我们要完全依靠对象键路径来存储元数据。
Logging Operator的S3输出 🔗︎
One Eye部署了Logging Operator,它负责处理日志运输。
如果你对Logging Operator不熟悉,请阅读我们的介绍博文。
不谈细节,Logging Operator配置了一个fluentd S3输出。你可以在插件中定义s3_object_key_format 作为一个参数。这种格式可以包含您日志中的任何元数据。我们的参考格式如下。
${clustername}/%Y/%m/%d/${$.kubernetes.namespace_name}/${$.kubernetes.pod_name}/${$.kubernetes.container_name}/%H:%M_%{index}.%{file_extension}
上面的例子翻译成这样的内容。
one-eye-cluster/2020/07/30/kube-system/kube-apiserver-kind-control-plane/kube-apiserver/09:00_0.gz
这种密钥格式仍然是人类可读的,但也提供了通过密钥过滤的机会,通过上面采用的术语。存储在块中的日志是经过gzip压缩的。块的大小取决于S3输出的timekey 设置,gzip格式支持流式数据处理。
One Eye Log Restoration 🔗︎
让我们来看看UI中的过滤功能,这比较容易理解。你首先要选择的是你要检索的日志的时间范围。
在接下来的步骤中,有几个选项。如果你已经知道你要加载日志的表达式,使用Advanced 选项,然后写出你的查询。如果你需要帮助建立你的表达式,我们建议你使用Simple 表达式生成器。
UI会根据你选择的时间范围,列出对象模式。这有助于缩小查询的范围。如果你不想缩小范围,使用any 选项。你也可以使用Go regexp来缩小你的搜索范围。
一旦你完成了你的查询表达式,你会看到一个摘要页,上面有你的选择。我们建议你审查两次,因为搜刮有许多对象的巨大范围可能需要大量的时间。
最后一步是提交工作。然后你会得到一个状态栏,告诉你搜刮你的归档日志的工作是如何进行的。
工作完成后,你可以导航到Kibana仪表盘来探索你的预热日志。
日志恢复的深度 🔗︎
这看起来很简单,不是吗?嗯,在引擎盖下,它有点儿复杂。让我们来看看日志恢复子系统的架构。
- S3输出将日志保存到给定的S3桶中,并有一个预先定义的对象键,它以这样的方式结构数据,使日志恢复能够进行过滤。
- 通过定义集群的唯一名称,可以将多个集群的日志保存到同一个S3桶中,这意味着跨集群的日志也可以从同一个桶中检索。然而,一个作业目前只限于一个集群。
One Eye UI为启动/管理/定制日志恢复工作提供了一个开箱即用的解决方案。One Eye Backend将日志恢复工作作为Kubernetes工作启动,并轮询Prometheus端点以监控其进度。它还启动Elasticsearch和Kibana的目标实例。Log-restoration job将数据传输到新初始化的Elasticsearch实例,并公开Prometheus端点以进行刮擦。
筛选 🔗︎
你可以根据关键属性设置各种过滤器来定制你要恢复的日志子集。一个过滤器由多个criteria 。一个标准是一个过滤器表达式,包含命名空间、荚和容器。每个属性都表示为一个RE2正则表达式。空的过滤器和标准被解释为.* 。
修复工作的API表示。
{
"cluster": "one-eye-cluster",
"from": "2020-08-03T16:27:51.69398+00:00",
"to": "2020-08-14T16:27:51.693982+00:00",
"filters": [
{
"namespaces": [
".*"
],
"pods": [
".*-kb-.*",
"elastic-operator.*"
],
"containers": [
""
]
},
{
"namespaces": [],
"pods": [],
"containers": [
"fluent.*"
]
}
]
}
我们知道这乍一看有点复杂,这就是为什么我们在用户界面中加入了表达式生成器。通过API,你可以将工作自动化,也可以将其整合到CICD系统中。
Log-restoration job 🔗︎
我们设计这个系统是为了尽可能地利用Kubernetes的天然优势。恢复工作实际上是一个Kubernetes工作。该作业暴露了Prometheus指标,这些指标都符合One Eye生态系统的要求。日志恢复是一个独立的二进制文件,它的实现方式是使用一个模块链来处理日志。这种方式的代码是可重复使用的,容易维护,也容易扩展。
我们所做的,符合UNIX的理念:"做一件事,并把它做好",所以这项工作由链上的几个模块组成。这样一来,这些模块与数据相对独立,可以在多种情况下使用。
-
S3 downloader:从Amazon S3对象存储中下载所有对象,并将数据作为流转发。 -
gzip extractor:将流式数据从gzip中解压。 -
uniform logs:将有效载荷转换为通用的日志格式,这可以确保此后使用的模块将收到一个标准化的格式。 -
dedot filter:记录可能有限制的键,比如Elasticsearch如何解释点作为层次结构的分隔符;这个模块只是用下划线替换点。 -
NDJSON formatter:将日志行转换为NDJSON格式。(Elasticsearch批量输入需要) -
Bulk buffer:缓冲模块,接收NDJSON行,只有在超过目标缓冲区大小(或缓冲区被刷新)时才转发数据。 -
elasticsearch bulker:调用Elasticsearch Bulk API端点。 -
Reader:模块链,从一组对象键中生成统一的日志格式。 -
Writer:将统一的日志传输到一个端点的模块链。让我们假设,你想创建一个
Amazon S3到一个Sumologiclog-restoration job。在这种情况下,实现只需要在一个Writer链中编写几个模块。然而,单个组件--如dedot filter--可以被重复使用。只有格式化器和传输器组件需要被实现。 -
meta stream:所有的模块都能够将元数据报告到一个流中,这个流在日志恢复过程中被处理。这个元数据包含日志、Prometheus指标和进度报告,但有可能扩展到包含更多内容。
该作业的第一个版本是一个单线程的应用程序,将数据从reader 转移到writer 组件。在未来的版本中,我们将添加一个异步传输层来处理多个readers 和writers ,这将提高高流量性能。
下一步是什么? 🔗︎
第一个版本的日志修复只支持S3和Elasticsearch作为后端。我们在可行的地方使用了标准的Go绑定。
虽然日志修复组件是相当新的,但我们已经有了几个关于如何扩展它的想法。
- 异步读写器链
- 支持其他对象存储,如GCS、Azure Storage
- 支持其他端点,如fluentd、Kafka等。
