适用于 k8s 的生产就绪映像

这个故事是关于我们如何在生产环境中使用容器,特别是 Kubernetes。 本文致力于从容器中收集指标和日志,以及构建镜像。

适用于 k8s 的生产就绪映像

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

我们代表负责为我们的开发人员收集和运行代码的平台的团队。 特别是,我们负责收集、存储和报告来自应用程序的指标、日志和事件。 目前,我们在生产环境中运行大约三千个 Docker 容器,维护我们的 50 TB 大数据存储,并提供围绕我们的基础设施构建的架构解决方案:Kubernetes、Rancher 和各种公共云提供商。 

我们的动力

什么在燃烧? 没有人能回答。 壁炉在哪里? 这很难理解。 什么时候起火的? 你可以发现,但不是马上就能发现。 

适用于 k8s 的生产就绪映像

为什么有些集装箱能站着,而另一些却倒塌了? 哪个容器是罪魁祸首? 毕竟,容器的外部是一样的,但里面却各有各的 Neo。

适用于 k8s 的生产就绪映像

我们的开发人员都是有能力的人。 他们提供良好的服务,为公司带来利润。 但是,当带有应用程序的容器误入歧途时,就会出现故障。 一个容器消耗过多的 CPU,另一个容器消耗网络,第三个容器消耗 I/O 操作,第四个容器完全不清楚它对套接字做了什么。 一切都倒塌了,船也沉没了。 

代理商

为了了解内部发生的情况,我们决定将代理直接放置在容器中。

适用于 k8s 的生产就绪映像

这些代理是限制程序,使容器保持在不会互相破坏的状态。 代理是标准化的,这允许使用标准化的方法来服务容器。 

在我们的例子中,代理必须提供标准格式的日志,并进行标记和限制。 它们还应该为我们提供可从业务应用程序角度扩展的标准化指标。

代理也指用于操作和维护的实用程序,可以在支持不同镜像的不同编排系统(Debian、Alpine、Centos 等)中工作。

最后,代理必须支持包含 Docker 文件的简单 CI/CD。 否则,船就会散架,因为集装箱将开始沿着“弯曲”的铁轨运输。

构建过程和目标图像设备

为了保持一切标准化和可管理性,需要遵循某种标准构建流程。 因此,我们决定按容器收集容器——这就是递归。

适用于 k8s 的生产就绪映像

这里的容器由实心轮廓表示。 与此同时,他们决定在其中放入分发包,让“生活不再像覆盆子”。 为什么这样做,我们将在下面解释。
 
结果是一个构建工具——一个引用特定发行版本和特定脚本版本的特定于版本的容器。

我们如何使用它? 我们有一个包含容器的 Docker Hub。 我们将其镜像到系统内部以摆脱外部依赖。 结果是一个标记为黄色的容器。 我们创建一个模板来将我们需要的所有发行版和脚本安装到容器中。 之后,我们组装一个随时可用的镜像:开发人员将代码和一些自己的特殊依赖项放入其中。 

这种方法有什么好处呢? 

  • 首先,构建工具的完整版本控制——构建容器、脚本和分发版本。 
  • 其次,我们实现了标准化:我们以相同的方式创建模板、中间图像和即用图像。 
  • 第三,容器给我们带来了可移植性。 今天我们使用 Gitlab,明天我们将切换到 TeamCity 或 Jenkins,我们将能够以相同的方式运行容器。 
  • 第四,最小化依赖性。 我们将分发包放入容器中并非巧合,因为这样我们就可以避免每次都从互联网上下载它们。 
  • 第五,构建速度提高了 - 图像本地副本的存在使您可以避免浪费时间下载,因为有本地图像。 

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

我们的构建过程如何运作

适用于 k8s 的生产就绪映像

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

标题添加了相应图像的分布。 页脚在里面安装我们的服务,配置工作负载的启动,日志记录和其他代理,替换入口点等。 

适用于 k8s 的生产就绪映像

我们想了很久要不要装一个supervisor。 最后,我们决定我们需要他。 我们选择了S6。 Supervisor 提供容器管理:允许您在主进程崩溃时连接到它,并提供容器的手动管理而无需重新创建它。 日志和指标是在容器内运行的进程。 它们还需要以某种方式受到控制,我们在主管的帮助下做到这一点。 最后,S6 负责内务处理、信号处理和其他任务。

由于我们使用不同的编排系统,容器在构建和运行之后,必须了解自己处于什么环境,并根据情况采取行动。 例如:
这使我们能够构建一个映像并在不同的编排系统中运行它,并且将在考虑到该编排系统的具体情况的情况下启动它。

 适用于 k8s 的生产就绪映像

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

适用于 k8s 的生产就绪映像

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

如果我们查看“pod”(以下称为 Kubernetes pod)的规范,我们会发现事件容器是在 pod 中执行的,pod 具有单独的收集器容器,用于执行收集指标和日志的功能。 我们可以使用 Kubernetes 的功能:在单个进程和/或网络空间中的一个 Pod 中运行容器。 实际上介绍您的代理并执行一些功能。 如果在 Docker 中启动相同的容器,它将接收所有相同的功能作为输出,即它将能够传递日志和指标,因为代理将在内部启动。 

指标和日志

交付指标和日志是一项复杂的任务。 她的决定有几个方面。
基础设施是为了执行有效负载而创建的,而不是为了大量交付日志。 也就是说,必须以最少的容器资源需求来执行此过程。 我们努力帮助我们的开发人员:“获取 Docker Hub 容器,运行它,然后我们就可以交付日志。” 

第二个方面是限制日志量。 如果多个容器中的日志量激增(应用程序循环输出堆栈跟踪),CPU、通信通道和日志处理系统的负载就会增加,这会影响主机的运行主机上的整个容器和其他容器,有时这会导致主机“崩溃”。 

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

最后一个方面是最大限度地减少资源消耗。

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

适用于 k8s 的生产就绪映像

假设开发人员部署了一个工作负载,并且 Kubernetes 收到了创建 pod 的请求。 此时,会自动为每个 pod 创建一个名为 Collector 的容器(我们使用mutation webhook)。 收藏家是我们的代理人。 一开始,该容器将自身配置为与 Prometheus 和日志收集系统配合使用。

  • 为此,它使用 Pod 注释,并根据其内容创建 Prometheus 端点; 
  • 根据Pod规格和具体容器设置,决定如何投递日志。

我们通过 Docker API 收集日志:开发人员只需将它们放入 stdout 或 stderr 中,Collector 就会对其进行整理。 日志以块的形式收集,并有一定的延迟,以防止可能的主机过载。 

指标是跨容器中的工作负载实例(进程)收集的。 所有内容都被标记为:命名空间、下等,然后转换为 Prometheus 格式 - 并可用于收集(日志除外)。 我们还将日志、指标和事件发送到 Kafka 以及其他:

  • 日志可在 Graylog 中获取(用于可视化分析);
  • 日志、指标、事件被发送到 Clickhouse 进行长期存储。

AWS 中的一切工作方式完全相同,只是我们使用 Cloudwatch 将 Graylog 替换为 Kafka。 我们将日志发送到那里,一切都变得非常方便:立即清楚它们属于哪个集群和容器。 Google Stackdriver 也是如此。 也就是说,我们的方案既可以在本地使用 Kafka,也可以在云端运行。 

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

适用于 k8s 的生产就绪映像

相同的进程在容器内执行,它们是使用 S6 编排的。 所有相同的进程都在同一个容器内运行。

其结果是,

我们创建了一个用于构建和启动图像的完整解决方案,并提供了收集和交付日志和指标的选项:

  • 我们开发了一种标准化的图像组装方法,并在此基础上开发了 CI 模板;
  • 数据收集代理是我们的 Telegraf 扩展。 我们在生产中对它们进行了很好的测试;
  • 我们使用mutation webhook来实现在pod中带有代理的容器; 
  • 融入Kubernetes/Rancher生态系统;
  • 我们可以在不同的编排系统中执行相同的容器并得到我们期望的结果;
  • 创建了完全动态的容器管理配置。 

共同作者: 伊利亚·普鲁德尼科夫

来源: habr.com

添加评论