适用于k8s的生产就绪图像

这个故事是关于我们如何在杂货店环境中使用容器,尤其是在Kubernetes下。本文致力于从容器中收集度量和日志,以及构建图像。

图片

我们来自金融科技公司Exness,该公司为B2B和B2C开发在线交易服务和金融科技产品。我们的研发部门有许多不同的团队,开发部门有100多名员工。

我们代表负责由开发人员收集和运行代码的平台的团队。特别是,我们负责从应用程序收集,存储和提供指标,日志和事件。目前,我们在产品环境中操作约3000个Docker容器,支持我们的50 TB大数据存储,并提供围绕我们的基础架构构建的架构解决方案:Kubernetes,Rancher和各种公共云提供商。 

我们的动力


什么在燃烧?没有人可以回答。壁炉在哪里?很难理解。它什么时候着火的?您可以找到,但不能立即发现。 



为什么有些容器站立而另一些容器掉下来?应归咎于哪个容器?确实,外部的容器是相同的,但是每个内部都有自己的Neo。



我们的开发人员是有文化的家伙。他们提供优质服务,使公司获利。但是,当带有应用程序的容器随机出现时,就会出现问题。一个容器消耗太多的CPU,另一个消耗网络,第三个消耗I / O操作,第四个通常不清楚它对套接字的作用。这一切都掉了,船沉没了。 

代理商


为了了解内部情况,我们决定将代理商直接放入容器中。



这些代理是包含程序,可将容器保持在不会相互破坏的状态。代理是标准化的,这允许采用标准化的方法来处理容器。 

在我们的情况下,代理必须以标准格式提供日志,并标记并带有小跑标记。它们还应向我们提供可在业务应用程序方面扩展的标准化指标。

代理还意味着用于操作和维护的实用程序,能够在不同的编排系统中工作,并支持不同的映像(Debian,Alpine,Centos等)。

最后,代理必须支持包含Docker文件的简单CI / CD。否则,船将解体,因为集装箱将开始在“弯曲”的轨道上运输。

组装过程和目标设备映像


为了使所有内容标准化和易于管理,您必须遵守一些标准的组装过程。因此,我们决定按容器收集容器-这样的递归。



在这里,容器用实心轮廓表示。同时,他们决定将发行版放入其中,以使“生活看起来不会像覆盆子”。为什么这样做,我们将在下面描述。
 
结果是构建工具-某个版本的容器,该容器引用发行版的某些版本和脚本的某些版本。

我们怎么用它?我们有一个容器所在的Docker Hub。我们将其镜像到系统内部,以摆脱外部依赖性。生成的容器标记为黄色。我们创建一个模板,以将所需的所有发行版和脚本安装在容器中。之后,我们收集一个准备就绪的图像:开发人员将代码和一些特殊的依赖项放入其中。 

为什么这种方法好呢? 

  • 首先,对构建工具进行完全版本控制-生成容器,脚本和发行版。 
  • 其次,我们已经实现了标准化:以同样的方式,我们可以创建中间的,准备用于操作映像的模板。 
  • 第三,容器为我们提供了可移植性。今天我们使用Gitlab,明天我们将切换到TeamCity或Jenkins,并以相同的方式启动容器。 
  • 第四,最小化依赖性。将分发放入容器中并非偶然,因为这使我们不必每次都从Internet下载它们。 
  • 第五,组装速度提高了-图像的本地副本的可用性使您不必浪费时间下载,因为存在本地图像。 

换句话说,我们已经实现了受控且灵活的组装过程。我们使用相同的工具来构建具有完整版本控制的任何容器。 

我们的构建程序如何工作




用一个命令启动该程序集,该过程在映像中执行(以红色突出显示)。开发人员有一个Docker文件(以黄色突出显示),我们通过用值替换变量来呈现它。在此过程中,我们添加页眉和页脚-这些是我们的代理。 

页眉添加来自相应图像的分布。页脚将我们的服务安装在内部,配置工作负载的启动,日志记录和其他代理,替换入口点等。 



我们考虑了很长时间是否要设置主管。最后,他们决定我们需要他。选择S6。主管提供对容器的控制:它可以让您在主流程下降的情况下与其连接,并提供对容器的手动控制,而无需重新创建。日志和指标是在容器内运行的进程。还需要以某种方式控制他们,我们在主管的帮助下做到这一点。最后,S6负责内部管理,信号处理和其他任务。

由于我们使用不同的编排系统,因此在组装和启动后,容器必须了解其所处的环境并根据情况采取行动。例如:
这使我们能够收集一个图像并将其在不同的业务流程系统中启动,并且将在考虑到该业务流程系统的细节的情况下启动它。

 

对于同一个容器,我们在Docker和Kubernetes中获得了不同的进程树:



有效负载在S6主管下执行。注意收集器和事件-这些是我们负责日志和指标的代理。 Kubernetes没有它们,但是Docker有它们。为什么? 

如果您查看“炉床”的规范(以下称为Kubernetes pod),我们将看到事件容器是在炉床中执行的,其中有一个单独的收集器容器,该容器执行收集度量和日志的功能。我们可以使用Kubernetes的功能:在一个炉膛中,在单个进程和/或网络空间中运行容器。实际介绍您的代理并执行一些功能。而且,如果在Docker中启动了相同的容器,则它将在输出端接收所有相同的功能,也就是说,由于代理将在内部启动,因此它将能够传递日志和指标。 

指标和日志


指标和日志的交付是一项艰巨的任务。她的决定有几个方面。
创建基础结构是为了满足有效负载,而不是大量交付日志。即,应该在对容器资源的最低要求下执行该过程。我们努力为开发人员提供帮助:“拿起Docker Hub容器,启动它,然后我们就可以交付日志了。” 

第二方面是原木量的限制。如果在几个容器中出现日志量激增的情况(应用程序在循环中显示堆栈跟踪),则CPU,通信通道,日志处理系统上的负载会增加,这会影响主机整体以及主机上其他容器的运行,有时会导致主机的“秋天”。 

第三方面-您需要开箱即用地支持尽可能多的指标收集方法。从读取文件和轮询Prometheus端点到使用特定的应用程序协议。

最后一个方面-您需要最大程度地减少资源消耗。

我们选择了一个名为Telegraf的开源Go解决方案。这是一个通用连接器,它支持140多种类型的输入通道(输入插件)和30种类型的输出(输出插件)。我们完成了它,现在我们将以Kubernetes为例说明它是如何使用的。 



假设开发人员部署了负载,并且Kubernetes收到了创建炉床的请求。此时,将为每个吊舱自动创建一个名为Collector的容器(我们使用突变Webhook)。收藏家是我们的代理。首先,此容器将自身配置为与Prometheus和日志收集系统一起使用。

  • 为此,他使用炉膛的注释,并根据其内容创建Prometheus的终点; 
  • 根据炉床的规格和容器的特定设置,它决定如何传送日志。

我们通过Docker API收集日志:对于开发人员而言,将它们放入stdout或stderr中就足够了,然后Collector会找出来。为了防止可能的主机拥塞,日志会按一定的延迟按组块收集。 

在容器中的工作负载实例(流程)上收集度量。一切都标记为:名称空间,等等,然后转换为Prometheus格式-并可以用于收集(日志除外)。此外,我们还将日志,指标和事件发送到Kafka,并进一步发送:

  • 日志可从Graylog获得(用于可视化分析);
  • 日志,指标,事件将发送到Clickhouse进行长期存储。

同样,一切都在AWS中运行,只有我们用Cloudwatch替换了Kafka的Graylog。我们将日志发送到那里,一切都变得非常方便:群集和容器属于谁,这是立即清楚的。Google Stackdriver也是如此。也就是说,我们的方案既可以在Kafka上进行本地部署,也可以在云中运行。 

如果我们没有带吊舱的Kubernetes,则该方案会稍微复杂一些,但它的工作原理相同。



在容器内部执行相同的过程,使用S6对其进行编排。所有相同的进程都在同一容器中运行。

最终


我们已经创建了一个完整的解决方案,用于组装和启动映像以进行操作,并提供了用于收集和传递日志和指标的选项:

  • 在此基础上开发了标准化的图像组装方法,并开发了CI模板;
  • 数据收集代理是我们对Telegraf的扩展。我们在生产中运行良好;
  • 我们使用变异Webhook来实现在Pod中带有代理的容器; 
  • 集成到Kubernetes / Rancher生态系统中;
  • 我们可以在不同的业务流程系统中执行相同的容器,并获得我们期望的结果;
  • 创建了一个完全动态的容器管理配置。 

合著者:伊利亚·普鲁德尼科夫(Ilya Prudnikov)

All Articles