在 Kubernetes 中开发应用程序的要求

今天我计划谈谈如何编写应用程序以及您的应用程序在 Kubernetes 中良好运行的要求是什么。 这样应用程序就不会出现任何令人头疼的问题,这样您就不必围绕它发明和构建任何“缺陷”——一切都按照 Kubernetes 本身的预期方式运行。

本次讲座是“Kubernetes 上的 Slurm 夜校” 您可以查看夜校公开理论讲座 在 YouTube 上,分组到播放列表中。 对于那些喜欢文字而不是视频的人,我们准备了这篇文章。

我的名字是 Pavel Selivanov,目前我是 Mail.ru Cloud Solutions 的首席 DevOps 工程师,我们制作云,我们制作管理 kubernetes 等等。 我现在的任务包括协助开发、推出这些云、推出我们编写的应用程序以及直接开发我们为用户提供的工具。

在 Kubernetes 中开发应用程序的要求

我想过去大概三年来我一直在从事 DevOps。 但是,原则上,我从事 DevOps 所做的事情可能已经有五年了。 在此之前,我主要从事管理工作。 我很久以前就开始使用 Kubernetes——自从我开始使用它以来大概已经过去了四年了。

总的来说,我是在 Kubernetes 1.3 版本(可能是 1.2 版本)时开始的——当时它还处于起步阶段。 现在它已不再处于起步阶段 - 很明显,市场对希望能够做 Kubernetes 的工程师有巨大的需求。 而企业对这样的人的需求是非常高的。 所以,事实上,就出现了这个讲座。

如果按照我要讲的内容的计划来讲的话,看起来是这样的,括号里写着(TL;DR)——“太长了;太长了”。 不要阅读”。 我今天的演讲将包含无穷无尽的清单。

在 Kubernetes 中开发应用程序的要求

事实上,我本人并不喜欢这样的演示,但这是一个这样的主题,当我准备这个演示时,我根本没有真正弄清楚如何以不同的方式组织这些信息。

因为总的来说,这些信息是“ctrl+c、ctrl+v”,除其他外,来自我们的 Wiki 中的 DevOps 部分,我们在其中为开发人员编写了要求:“伙计们,以便我们在以下位置启动您的应用程序: Kubernetes,它应该是这样的。”

这就是为什么演示文稿的清单如此之大。 对不起。 我会尽力讲述尽可能多的内容,以便尽可能不无聊。

我们现在要看什么:

  • 首先是日志(应用程序日志?),在 Kubernetes 中如何处理它们,如何处理它们,它们应该是什么;
  • 如何处理 Kubernetes 中的配置,为 Kubernetes 配置应用程序的最佳和最差方法是什么;
  • 让我们谈谈可访问性检查的一般含义以及它们应该是什么样子;
  • 我们来谈谈什么是优雅关闭;
  • 我们再谈谈资源;
  • 让我们再次讨论一下数据存储的话题;
  • 最后我会告诉你这个神秘的云原生应用程序是什么。 Cloudnativeness,作为该术语的形容词。

日志

我建议从日志开始 - 这些日志需要被推送到 Kubernetes 中的位置。 现在您已经在 Kubernetes 中启动了一个应用程序。 根据经典,以前的应用程序总是将日志写入文件中的某个位置。 不良应用程序将日志写入启动该应用程序的开发人员主目录中的文件中。 好的应用程序将日志写入某个位置的文件 /var/log.

在 Kubernetes 中开发应用程序的要求

因此,进一步,优秀的管理员在他们的基础设施中配置了一些东西,这些日志可以轮换 - 相同的 rsyslog,它查看这些日志,当它们发生问题时,有很多日志,它创建备份副本,将日志放在那里,删除旧文件,超过一周、六个月等等。 理论上,我们应该做出规定,以便仅仅因为应用程序写入日志,生产服务器(战斗服务器?)上的空间就不会耗尽。 因此,整个生产并没有因为原木而停止。

当我们进入 Kubernetes 世界并在那里运行相同的东西时,您首先要注意的是,人们在将日志写入文件时会继续写入它们。

事实证明,如果我们谈论 Kubernetes,从 docker 容器中写入日志的正确位置就是将它们从应用程序写入所谓的 Stdout/Stderr,即操作系统的标准输出流,标准错误输出。 这是原则上在 Docker、特别是在 Kubernetes 中放置日志的最正确、最简单、最合乎逻辑的方式。 因为如果您的应用程序将日志写入 Stdout/Stderr,则由 Docker 和 Kubernetes 插件决定如何处理这些日志。 Docker 默认情况下会以 JSON 格式构建其特殊文件。

那么问题来了,你接下来要如何处理这些日志呢? 最简单的方法很明确,我们有能力做到 kubectl logs 并查看这些“pod”的日志。 但是,这可能不是一个很好的选择 - 需要对日志执行其他操作。

现在,既然我们谈到了日志这个话题,那么我们就同时谈谈日志应该是什么样子吧。 也就是说,这并不直接适用于 Kubernetes,但是当我们开始考虑如何处理日志时,最好也考虑一下这一点。

我们需要某种工具,以友好的方式,将我们的码头工人放入其文件中的这些日志并将它们发送到某个地方。 总的来说,我们通常会在 Kubernetes 中以 DaemonSet 的形式启动某种代理——日志收集器,它只是告诉 Docker 收集的日志位于哪里。 这个收集代理只是简单地获取它们,甚至可能以某种方式一路解析它们,或许用一些额外的元信息来丰富它们,并最终将它们发送到某个地方存储。 那里已经有可能出现变化。 最常见的可能是 Elasticsearch,您可以在其中存储日志,并且可以方便地从那里检索它们。 然后,使用请求,例如使用 Kibana,基于它们构建图表,基于它们构建警报,等等。

我想再次重复一遍,最重要的想法是,在 Docker 内部,特别是在 Kubernetes 内部,将日志存储在文件中是一个非常糟糕的主意。

因为首先,很难将容器内的日志放入文件中。 您必须首先进入容器,在那里执行,然后查看日志。 下一点是,如果文件中有日志,那么容器通常具有极简环境,并且不存在正常处理日志所需的实用程序。 埋葬它们,查看它们,在文本编辑器中打开它们。 下一刻,当我们将日志记录在容器内的文件中时,如果删除该容器,您知道,日志将随之消失。 因此,容器的任何重新启动都意味着不再有日志。 再次,糟糕的选择。

最后一点是,在容器内通常有您的应用程序,仅此而已 - 它通常是唯一正在运行的进程。 根本没有谈论任何会随日志轮转文件的过程。 一旦日志开始写入文件,这意味着,对不起,我们将开始失去生产服务器。 因为,首先,它们很难找到,没有人跟踪它们,而且没有人控制它们 - 因此,文件会无休止地增长,直到服务器上的空间耗尽。 因此,我再次强调,将 Docker(尤其是 Kubernetes)登录到文件是一个坏主意。

下一点,在这里我想再次讨论这一点 - 由于我们正在讨论日志主题,因此最好讨论一下日志应该是什么样子,以便于使用它们。 正如我所说,该主题与 Kubernetes 没有直接关系,但与 DevOps 主题密切相关。 关于开发文化和这两个不同部门(Dev 和 Ops)之间的友谊的话题,让每个人都感到舒服。

这意味着理想情况下,今天日志应该以 JSON 格式编写。 如果您自己有一些难以理解的应用程序,因为您插入了某种打印或类似的内容,所以它以难以理解的格式写入日志,那么是时候谷歌搜索某种框架,某种允​​许您实现正常日志记录的包装器; 在那里启用 JSON 中的日志记录参数,因为 JSON 是一种简单的格式,解析它很简单。

如果您的 JSON 无法按照某些标准工作,没有人知道是什么,那么至少以可解析的格式写入日志。 相反,在这里,值得考虑的事实是,例如,如果您正在运行一堆容器或仅使用 nginx 运行进程,并且每个容器都有自己的日志记录设置,那么您可能会觉得非常不方便解析它们。 因为对于每个新的 nginx 实例,您都需要编写自己的解析器,因为它们写入日志的方式不同。 同样,可能值得考虑的是确保所有这些 nginx 实例具有相同的日志配置并绝对统一地写入所有日志。 这同样适用于所有应用程序。

最后,我还想火上浇油,理想情况下应该避免多行格式日志。 事情是这样的,如果您曾经使用过日志收集器,那么您很可能已经看到了他们向您承诺的内容,即他们可以处理多行日志,知道如何收集它们,等等。 事实上,在我看来,现在没有一个收集器能够正常、完整、无错误地收集多行日志。 以人性化的方式,方便且无差错。

在 Kubernetes 中开发应用程序的要求

但堆栈跟踪始终是多行日志以及如何避免它们。 这里的问题是,日志是一个事件的记录,而stactrace实际上并不是日志。 如果我们收集日志并将它们放在 Elasticsearch 中的某个位置,然后从中绘制图表,在您的站点上构建一些用户活动报告,那么当您获得堆栈跟踪时,这意味着发生了意外的情况。您的应用程序中出现了未处理的情况。 自动将堆栈跟踪上传到可以跟踪它们的系统中是有意义的。

这是专门用于处理堆栈跟踪的软件(同一个 Sentry)。 它可以立即创建自动化任务,将其分配给某人,在 stacttraces 发生时发出警报,按一种类型对这些 stacttraces 进行分组,等等。 原则上,当我们谈论日志时谈论 stactrace 并没有多大意义,因为它们毕竟是具有不同用途的不同事物。

布局

接下来我们讨论 Kubernetes 中的配置:如何使用它以及应该如何配置 Kubernetes 内的应用程序。 一般来说,我通常说Docker不是关于容器的。 每个人都知道 Docker 是关于容器的,即使是那些没有太多使用过 Docker 的人也是如此。 我再说一遍,Docker 与容器无关。

在我看来,Docker 是关于标准的。 几乎所有事情都有标准:构建应用程序的标准、安装应用程序的标准。

在 Kubernetes 中开发应用程序的要求

这个东西——我们以前用过它,随着容器的出现它变得特别流行——这个东西叫做ENV(环境)变量,也就是你操作系统中的环境变量。 这通常是配置应用程序的理想方法,因为如果您有 JAVA、Python、Go、Perl(上帝保佑)的应用程序,并且它们都可以读取数据库主机、数据库用户、数据库密码变量,那么它是理想的。 您以相同的方式在数据库计划中配置了四种不同语言的应用程序。 没有更多不同的配置。

一切都可以使用 ENV 变量进行配置。 当我们谈论 Kubernetes 时,有一种很好的方法可以在 Deployment 中声明 ENV 变量。 因此,如果我们谈论的是秘密数据,那么我们可以立即将 ENV 变量中的秘密数据(数据库的密码等)推送到秘密中,创建秘密集群并在 Deployment 中的 ENV 描述中指出我们不是直接声明该变量的值,以及该数据库密码变量的值将从密钥中读取。 这是标准的 Kubernetes 行为。 这是配置您的应用程序的最理想的选项。 仅在代码级别,这同样适用于开发人员。 如果您是 DevOps,您可以问:“伙计们,请教您的应用程序读取环境变量。 我们都会幸福的。”

如果公司中的每个人都读取相同的命名环境变量,那就太好了。 这样就不会出现有些人在等待 postgres 数据库、其他人正在等待数据库名称、其他人正在等待其他东西、其他人正在等待某种 dbn 的情况,因此,相应地,存在统一性。

当您有太多环境变量以至于您只需打开 Deployment 时就会出现问题 - 并且有 XNUMX 行环境变量。 在这种情况下,你的环境变量已经超出了你的范围——你不再需要折磨自己了。 在这种情况下,开始使用配置是有意义的。 也就是说,训练您的应用程序使用配置。

唯一的问题是配置不是你想象的那样。 Config.pi 不是一个使用起来很方便的配置。 或者以您自己的格式进行一些配置,或者有天赋 - 这也不是我所说的配置。

我所说的是可接​​受格式的配置,也就是说,迄今为止最流行的标准是 .yaml 标准。 很清楚如何读取它,它是人类可读的,很清楚如何从应用程序中读取它。

因此,除了 YAML 之外,您还可以使用 JSON,从那里读取应用程序配置方面,解析与 YAML 一样方便。 人们的阅读显然更加不方便。 您可以尝试 a la ini 格式。 从人类的角度来看,它读起来很方便,但自动处理它可能会很不方便,因为如果你想生成自己的配置,ini 格式可能已经不方便生成了。

但无论如何,无论你选择什么格式,关键是从 Kubernetes 的角度来看,它非常方便。 您可以将整个配置放入 Kubernetes 的 ConfigMap 中。 然后获取此 configmap 并要求将其安装在 pod 内的某个特定目录中,您的应用程序将从该 configmap 中读取配置,就好像它只是一个文件一样。 事实上,当您的应用程序中有很多配置选项时,最好这样做。 或者只是某种复杂的结构,有嵌套。

如果您有一个 configmap,那么您可以很好地教导您的应用程序,例如,自动跟踪挂载 configmap 的文件中的更改,并在配置更改时自动重新加载您的应用程序。 这通常是一个理想的选择。

再说一次,我已经讨论过这一点 - 秘密信息不在 configmap 中,秘密信息不在变量中,秘密信息不在秘密中。 从那里,将这些秘密信息与外交联系起来。 通常我们将 Kubernetes 对象、部署、配置映射、服务的所有描述存储在 git 中。 因此,将数据库的密码放在 git 中,即使它是您在公司内部拥有的 git,也是一个坏主意。 因为,至少,git 会记住所有内容,并且简单地从那里删除密码并不那么容易。

健康检查

下一点是健康检查这个东西。 一般来说,健康检查只是检查您的应用程序是否正常工作。 同时,我们最常谈论的是某些Web应用程序,相应地,从健康检查的角度来看(最好不要在这里翻译和进一步翻译),这将是一些特殊的URL,它们将其处理为标准,他们通常这样做 /health.

因此,当访问此 URL 时,我们的应用程序会说“是的,好吧,我一切都很好,200”或“不,我一切都不好,大约 500”。 因此,如果我们的应用程序不是http,不是web应用程序,我们现在谈论的是某种守护进程,我们可以弄清楚如何进行健康检查。 也就是说,没有必要,如果应用程序不是 http,那么一切都可以在没有健康检查的情况下运行,并且不能以任何方式完成。 您可以定期更新文件中的一些信息,您可以为您的守护进程提供一些特殊的命令,例如, daemon status,它会说“是的,一切都很好,守护进程正在工作,它还活着。”

它是做什么用的? 第一个也是最明显的事情可能是为什么需要进行健康检查 - 了解应用程序是否正在运行。 我的意思是,这很愚蠢,当它现在启动时,看起来它正在工作,所以你可以确定它正在工作。 事实证明,应用程序正在运行,容器正在运行,实例正在工作,一切都很好 - 然后用户已经切断了所有技术支持的电话号码,并说“你在做什么......,你睡着了,什么都没有。”

健康检查就是从用户的角度来看它是否有效的一种方式。 方法之一。 让我们这样说吧。 从Kubernetes的角度来看,这也是理解应用程序何时启动的一种方式,因为我们理解容器启动、创建和启动的时间与应用程序直接在这个容器中启动的时间是有区别的。 因为如果我们使用一些普通的 java 应用程序并尝试在扩展坞中启动它,那么四十秒,甚至一分钟,甚至十分钟,它都可以正常启动。 在这种情况下,你至少可以敲它的端口,它不会在那里应答,也就是说,它还没有准备好接收流量。

再次,借助健康检查,借助我们正在转向这里的事实,我们可以在 Kubernetes 中了解到,不仅容器已经在应用程序中升起,而且应用程序本身也已启动,它已经响应了健康检查,这意味着我们可以在那里发送流量。

在 Kubernetes 中开发应用程序的要求

我现在所说的是 Kubernetes 中的就绪/活跃测试;相应地,我们的就绪测试负责平衡应用程序的可用性。 也就是说,如果在应用程序中执行就绪测试,则一切正常,客户端流量将流向应用程序。 如果未执行就绪测试,则应用程序根本不参与,该特定实例不参与平衡,它会从平衡中删除,客户端流量不会流动。 因此,需要在 Kubernetes 内进行 Liveness 测试,以便在应用程序卡住时可以重新启动。 如果活性测试对于 Kubernetes 中声明的应用程序不起作用,那么该应用程序不仅会从平衡中删除,还会重新启动。

这里我想提一下重要的一点:从实际的角度来看,就绪测试通常比活性测试更常用,也更经常需要。 也就是说,简单地不假思索地声明就绪性和活跃性测试,因为 Kubernetes 可以做到这一点,并且让我们使用它可以做的一切,这并不是一个好主意。 我会解释原因。 因为测试中的第二点是,最好在健康检查中检查基础服务。 这意味着,如果您有一个 Web 应用程序提供一些信息,那么它自然必须从某个地方获取这些信息。 例如,在数据库中。 嗯,它将进入此 REST API 的信息保存到同一个数据库中。 然后,相应地,如果您的健康检查响应简单,就像联系的slashhealth,应用程序会说“200,好的,一切都很好”,同时您的应用程序的数据库无法访问,并且健康检查应用程序会说“200,好的,一切都很好” ” - 这是一个糟糕的健康检查。 这不应该是这样的。

也就是说,当收到请求时,您的应用程序 /health,它不只是响应“200,好的”,它首先进入数据库,尝试连接到它,在那里做一些非常基本的事情,比如选择一个,只是检查数据库中是否有一个连接数据库,并且可以查询数据库。 如果这一切都成功,那么答案就是“200,好的”。 如果不成功,则表示出现错误,数据库不可用。

因此,在这方面,我再次回到就绪/活跃测试 - 为什么你很可能需要就绪测试,但活跃测试是有问题的。 因为如果你完全按照我刚才说的描述健康检查,那么结果会发现它在实例部分不可用в или со всех instance例如,在数据库中。 当您声明准备就绪测试时,我们的运行状况检查开始失败,因此无法访问数据库的所有应用程序,它们只是从平衡状态中关闭,实际上“挂起”处于被忽略的状态,并等待其数据库工作。

如果我们已经声明了活性测试,那么想象一下,我们的数据库已经损坏,并且在 Kubernetes 中,由于活性测试失败,所有内容的一半都开始重新启动。 这意味着您需要重新启动。 这根本不是你想要的,我什至在实践中也有过亲身经历。 我们有一个用 JS 编写的聊天应用程序,并输入到 Mongo 数据库中。 问题是,在我开始使用 Kubernetes 时,我们根据 Kubernetes 可以做到的原则描述了测试的就绪性和活跃性,所以我们将使用它。 因此,在某个时候 Mongo 变得有点“迟钝”,样本开始失败。 因此,根据降雨测试,豆荚开始“死亡”。

正如你所理解的,当他们被“杀死”时,这是一个聊天,也就是说,上面挂着很多来自客户端的连接。 它们也会被“杀死” - 不,不是客户端,只是连接 - 不是全部同时被杀死,并且由于它们不是同时被杀死,有的早,有的晚,所以它们不会同时启动时间。 加上标准随机,我们无法以毫秒精度预测应用程序每次的启动时间,因此他们一次只执行一个实例。 一个信息点上升,被添加到平衡中,所有客户端都到那里,它无法承受这样的负载,因为它是孤独的,而且,粗略地说,有十几个在那里工作,然后它就下降了。 下一个站起来,所有的重担都压在他身上,他也倒下了。 好吧,这些瀑布还在继续倾泻而下。 最后,这个问题是如何解决的——我们只需严格停止该应用程序的用户流量,让所有实例上升,然后立即启动所有用户流量,以便它已经分布在所有十个实例中。

如果不是宣布了这个活性测试,这将迫使一切重新启动,应用程序本来可以很好地处理它。 但是平衡的一切对我们来说都是禁用的,因为数据库无法访问并且所有用户都“掉线”了。 然后,当这个数据库变得可用时,一切都包含在平衡中,但应用程序不需要重新启动,也不需要为此浪费时间和资源。 他们都已经在这里,准备好交通,所以交通刚刚开放,一切都很好 - 应用程序已就位,一切都继续工作。

因此,就绪性和活性测试是不同的,而且,理论上你可以做不同的健康检查,例如一种类型的半径,一种类型的生活,并检查不同的东西。 在准备测试期间,检查您的后端。 例如,在活性测试中,您不会从活性测试通常只是应用程序响应的角度进行检查(如果它能够响应)。

因为总的来说,活性测试是我们“陷入困境”的时候。 无限循环已开始或发生其他情况 - 并且不再处理更多请求。 因此,将它们分开并在其中实现不同的逻辑是有意义的。

关于进行测试和健康检查时需要回答的问题。 这真的很痛苦。 熟悉这一点的人可能会笑 - 但说真的,我一生中见过在 200% 的情况下回答“XNUMX”的服务。 也就是说,谁是成功者。 但与此同时,他们在回复正文中写下了“这样那样的错误”。

也就是说,响应状态出现在您面前 - 一切都成功。 但同时,您必须解析正文,因为正文会说“抱歉,请求以错误结束”,而这就是现实。 我在现实生活中看到过这个。

因此,有些人觉得它不好笑,而另一些人觉得它非常痛苦,但仍然值得遵守一个简单的规则。 在健康检查中,原则上在使用 Web 应用程序时也是如此。

如果一切顺利,则回复第二百个答案。 原则上,任何百分之二的答案都适合你。 如果您非常熟悉 ragsy 并且知道某些响应状态与其他响应状态不同,请使用适当的状态进行回答:204、5、10、15 等等。 如果不太好,那就“二零零”。 如果一切都不顺利并且健康检查没有响应,则回答任意百分之五。 同样,如果您了解如何响应,以及不同的响应状态之间有何不同。 如果您不明白,那么如果出现问题,您可以选择 502 来响应健康检查。

这是另外一点,我想回一点关于检查底层服务。 例如,如果您开始检查应用程序背后的所有底层服务 - 一般而言的所有内容。 从微服务架构的角度来看,我们有一个“低耦合”的概念——也就是说,当你的服务彼此之间的依赖程度最低时。 如果其中一个失败,所有其他没有此功能的其他将继续工作。 有些功能根本不起作用。 因此,如果你将所有的健康检查相互联系起来,那么你最终会在基础设施中出现一个东西掉落,并且因为它掉落,所有服务的所有健康检查也开始失败 - 并且一般来说有更多的基础设施可供使用整个微服务架构No. 那里一切都变黑了。

因此,我想再次重复一遍,您需要检查底层服务,如果没有这些服务,您的应用程序在百分之一百的情况下无法完成其工作。 也就是说,如果您有一个 REST API,用户可以通过它保存到数据库或从数据库检索,那么在没有数据库的情况下,您就无法保证与您的用户一起工作,这是合乎逻辑的。

但是,如果您的用户在您将其从数据库中取出时,还从另一个后端丰富了一些其他元数据,您在向前端发送响应之前输入这些元数据 - 并且该后端不可用,这意味着您给了您的没有任何元数据部分的答案。

接下来,我们在启动应用程序时还遇到一个令人痛苦的问题。

事实上,这不仅适用于 Kubernetes;碰巧的是,某种大规模开发、特别是 DevOps 的文化与 Kubernetes 几乎同时开始传播。 因此,总的来说,您需要在没有 Kubernetes 的情况下优雅地关闭应用程序。 甚至在 Kubernetes 之前,人们就这样做了,但随着 Kubernetes 的出现,我们开始集体讨论它。

优雅关机

一般来说,什么是优雅关机以及为什么需要它? 这是关于当你的应用程序由于某种原因崩溃时,你需要做的 app stop - 或者,例如,您收到来自操作系统的信号,您的应用程序必须理解它并对其采取措施。 当然,最糟糕的情况是当您的应用程序收到 SIGTERM 并且类似于“SIGTERM,让我们坚持下去,工作,什么都不做”。 这是一个彻头彻尾的糟糕选择。

在 Kubernetes 中开发应用程序的要求

一个几乎同样糟糕的选择是,当您的应用程序收到 SIGTERM 并且就像“他们说 segterm,这意味着我们即将结束,我还没有看到,我不知道任何用户请求,我不知道什么样的”我现在正在处理的请求,他们说 SIGTERM,这意味着我们即将结束” 这也是一个糟糕的选择。

哪个选项好? 第一点是考虑操作的完成情况。 一个好的选择是您的服务器仍然考虑收到 SIGTERM 时它会做什么。

SIGTERM是一个软关闭,它是专门设计的,它可以在代码级别拦截,它可以处理,说现在,等等,我们先完成我们手头的工作,然后我们就退出。

从 Kubernetes 的角度来看,这就是它的样子。 当我们对 Kubernetes 集群中运行的 pod 说“请停止,走开”,或者我们重新启动,或者 Kubernetes 重新创建 pod 时发生更新时,Kubernetes 会向 pod 发送相同的 SIGTERM 消息,等待还有,这个就是他等待的时间,也是配置的,文凭里有一个特殊的参数,叫做Graceful ShutdownTimeout。 正如你所理解的,它不是无缘无故的,我们现在谈论它也不是无缘无故的。

在那里,我们可以具体地说,从向应用程序发送 SIGTERM 到我们了解到应用程序似乎因某些原因而疯狂或“卡住”并且不会结束,我们需要等待多长时间 - 我们需要发送SIGKILL给它,即努力完成它的工作。 也就是说,相应地,我们有某种守护进程在运行,它处理操作。 我们知道,平均而言,守护进程所执行的操作一次持续时间不会超过 30 秒。 因此,当 SIGTERM 到达时,我们知道我们的守护进程最多可以在 SIGTERM 之后 30 秒完成。 例如,为了以防万一,我们将其写为 45 秒,并表示 SIGTERM。 之后我们等待 45 秒。 按理说,在这段时间里,恶魔应该已经完成​​了它的工作,结束了自己。 但如果突然不能,则意味着它很可能被卡住了——它不再正常处理我们的请求。 实际上,在 45 秒内,您就可以安全地锁定他。

事实上,这里甚至可以考虑两个方面。 首先,请了解,如果您收到请求,您就开始以某种方式处理该请求,并且没有向用户做出响应,但您收到了 SIGTERM。 对其进行完善并向用户提供答案是有意义的。 这是这方面的第一点。 这里的第二点是,如果您编写自己的应用程序,通常会以这样的方式构建架构:您收到应用程序的请求,然后开始一些工作,开始从某个地方下载文件,下载数据库等等。-那。 一般来说,你的用户,你的请求会挂起半个小时,等待你回答他——然后,很可能,你需要在架构上工作。 也就是说,只要考虑到常识,如果您的操作很短,那么忽略 SIGTERM 并修改它是有意义的。 如果您的操作很长,那么在这种情况下忽略 SIGTERM 是没有意义的。 重新设计架构以避免如此长的操作是有意义的。 这样用户就不会只是闲逛和等待。 我不知道,在那里做某种 websocket,做反向挂钩,你的服务器已经发送到客户端,其他什么,但不要强迫用户挂起半个小时,只是等待会话,直到你回答他。 因为无法预测它可能在哪里破裂。

当您的应用程序终止时,您应该提供一些适当的退出代码。 也就是说,如果您的应用程序被要求关闭、停止,并且它能够正常自行停止,那么您不需要返回某种退出代码 1,5,255 等。 我确信,任何不是零代码的东西,至少在 Linux 系统中,都被认为是不成功的。 也就是说,在这种情况下,您的应用程序被认为以错误结束。 因此,以友好的方式,如果您的应用程序完成且没有错误,您可以在输出中说 0。 如果您的应用程序由于某种原因失败,您会在输出中显示非 0。 您可以使用这些信息。

还有最后一个选择。 当您的用户发送请求并在您处理该请求时挂起半个小时时,情况会很糟糕。 但总的来说,我还想谈谈从客户角度来看什么是值得的。 如果您有移动应用程序、前端等,那并不重要。 有必要考虑到,一般来说,用户的会话可能会被终止,任何事情都可能发生。 例如,请求可能被发送,处理不足并且没有返回响应。 您的前端或移动应用程序(一般来说任何前端,让我们这样说)应该考虑到这一点。 如果你使用 websocket,这通常是我经历过的最痛苦的事情。

当一些常规聊天的开发人员不知道这一点时,事实证明,websocket 可能会崩溃。 对于他们来说,当代理发生问题时,我们只需更改配置,然后它就会重新加载。 当然,在这种情况下,所有长期会话都会被破坏。 开发人员跑过来对我们说:“伙计们,你们在做什么,我们所有客户的聊天都中断了!” 我们告诉他们:“你在做什么? 您的客户端无法重新连接吗? 他们说:“不,我们需要不让会议被撕裂。” 总之,这其实是无稽之谈。 需要考虑客户端。 特别是,正如我所说,对于 Websocket 等长期会话,它可能会中断,并且在用户不注意的情况下,您需要能够重新安装此类会话。 然后一切就都很完美了。

资源

其实,我在这里只是给大家讲一个正经的故事。 再次来自现实生活。 我听过的关于资源的最恶心的事情。

在这种情况下,资源是指您可以对 Kubernetes 集群中的 Pod 施加的某种请求和限制。 我从开发人员那里听到的最有趣的事情......我之前工作地点的一位开发同事曾经说过:“我的应用程序无法在集群中启动。” 我发现它没有启动,但要么它不适合资源,要么他们设置了非常小的限制。 简而言之,应用程序因资源而无法启动。 我说:“不会因为资源而启动,你决定需要多少并设置一个足够的值。” 他说:“什么样的资源?” 我开始向他解释 Kubernetes、请求限制以及需要设置的等等。 那人听了五分钟,点点头说道:“我来这里是作为一名开发人员,我不想了解任何资源。 我来这里是为了写代码,仅此而已。” 这是可悲的。 从开发人员的角度来看,这是一个非常可悲的概念。 特别是在现代世界,可以说,是进步的 devops。

为什么需要资源? Kubernetes 中有两种类型的资源。 有些称为请求,有些称为限制。 通过资源我们会明白,基本上总是只有两个基本限制。 即 Kubernetes 中运行的容器的 CPU 时间限制和 RAM 限制。

限制对应用程序中资源的使用方式设置了上限。 也就是说,相应地,如果您将 1GB RAM 设置为限制,那么您的应用程序将无法使用超过 1GB 的 RAM。 如果他突然想要并尝试这样做,那么一个名为 oomkiller 的进程(即内存不足)将会出现并杀死您的应用程序 - 也就是说,它只会重新启动。 应用程序不会根据CPU重新启动。 就 CPU 而言,如果应用程序尝试使用大量 CPU,超过限制中指定的值,则只会严格选择 CPU。 这不会导致重新启动。 这就是极限——这就是上限。

并且有一个请求。 请求是 Kubernetes 如何了解 Kubernetes 集群中的节点如何填充应用程序的方式。 也就是说,请求是应用程序的一种提交。 它说明了我想要使用的内容:“我希望您为我保留这么多的 CPU 和内存。” 如此简单的比喻。 如果我们有一个节点总共有 8 个 CPU(我不知道)怎么办? 一个 Pod 到达那里,其请求为 1 个 CPU,这意味着该节点还剩 7 个 CPU。 也就是说,一旦有 8 个 Pod 到达该节点,每个 Pod 的请求都有 1 个 CPU,那么从 Kubernetes 的角度来看,该节点就好像 CPU 已经耗尽,并且无法再接收更多有请求的 Pod。在此节点上启动。 如果所有节点都耗尽了 CPU,那么 Kubernetes 将开始提示集群中没有合适的节点来运行您的 Pod,因为 CPU 已耗尽。

为什么需要请求,为什么没有请求,我认为没有必要在 Kubernetes 中启动任何东西? 让我们想象一个假设的情况。 你在没有请求的情况下启动应用程序,Kubernetes 不知道你有多少内容,你可以将其推送到哪些节点。 好吧,他推、推、推到节点上。 在某个时候,您将开始获得应用程序的流量。 其中一个应用程序突然开始使用资源,直至达到其根据限制所具有的限制。 原来附近还有另一个应用程序,它也需要资源。 该节点实际上开始耗尽物理资源,例如 OP。 该节点实际上开始耗尽物理资源,例如随机存取存储器(RAM)。 当节点断电时,首先 docker 将停止响应,然后是 Cubelet,然后是操作系统。 他们只会失去意识,一切都将不再对你有用。 也就是说,这将导致您的节点卡住,您需要重新启动它。 总之,情况不太好。

而且当你有请求的时候,限制相差不是很大,至少不会比限制或者请求多很多倍,那么你就可以跨Kubernetes集群的节点有这样正常、合理的应用填充。 同时,Kubernetes 大约知道它把多少东西放在哪里,在哪里使用了多少东西。 也就是说,只是这样一个时刻。 理解它很重要。 控制这一点很重要。

数据存储

我们的下一点是关于数据存储的。 如何处理它们?一般而言,如何处理 Kubernetes 中的持久性?

我再次认为,在我们的范围内 夜校,有一个关于 Kubernetes 中数据库的话题。 在我看来,我什至大致知道你的同事在被问到“是否可以在 Kubernetes 中运行数据库?”时告诉你的内容。 出于某种原因,在我看来,你的同事应该告诉你,如果你问是否可以在 Kubernetes 中运行数据库的问题,那么这是不可能的。

这里的逻辑很简单。 以防万一,我将再次解释一下,如果您是一个非常酷的人,可以构建一个相当容错的分布式网络存储系统,了解如何将数据库适合这种情况,容器中的云原生应该如何工作一般在数据库中。 您很可能对如何运行它没有任何疑问。 如果你有这样的问题,并且你想确保这一切都展开并在生产中直立到死并且永远不会掉落,那么这种情况就不会发生。 这种做法肯定会搬起石头砸自己的脚。 所以最好不要这样做。

例如,我们应该如何处理应用程序想要存储的数据、用户上传的一些图片、应用程序在运行期间(启动时)生成的一些内容? 在 Kubernetes 中如何处理它们?

一般来说,理想情况下,是的,当然,Kubernetes 设计得非常好,通常最初是为无状态应用程序构思的。 也就是说,对于那些根本不存储信息的应用程序。 这是理想的。

但是,当然,理想的选择并不总是存在。 所以呢? 第一个也是最简单的一点是采用某种 S3,但不是自制的,也不清楚它是如何工作的,而是来自某个提供商。 一个好的、正常的提供商 - 并教您的应用程序使用 S3。 也就是说,当您的用户想要上传文件时,请说“请在此处将其上传到 S3”。 当他想要接收时,请说:“这是 S3 的链接,从这里获取。” 这是理想的。

如果突然由于某种原因这个理想的选项不适合,您有一个应用程序不是您编写的,您没有开发的,或者它是某种可怕的遗产,它不能使用 S3 协议,但必须使用本地目录本地文件夹。 采取或多或少简单的方式,部署 Kubernetes。 也就是说,在我看来,立即隔离 Ceph 来执行一些最小的任务是一个坏主意。 当然,因为 Ceph 很好而且很时尚。 但是,如果您并不真正了解自己在做什么,那么一旦您将某些内容放在 Ceph 上,您就可以非常轻松且再也无法将其从那里取出。 因为,如您所知,Ceph 在其集群中以二进制形式存储数据,而不是以简单文件的形式。 因此,如果 Ceph 集群突然崩溃,那么您很有可能再也无法从那里获取数据。

我们将开设有关 Ceph 的课程,您可以 熟悉该计划并提交申请.

因此,最好做一些简单的事情,比如 NFS 服务器。 Kubernetes 可以与它​​们一起工作,您可以在 NFS 服务器下挂载一个目录 - 您的应用程序就像本地目录一样。 同时,自然地,您需要再次了解,您需要对 NFS 执行某些操作,您需要了解有时它可能会变得无法访问,并考虑在这种情况下您将做什么的问题。 也许它应该备份在一台单独的机器上的某个地方。

我接下来要讲的是如果你的应用程序在运行过程中生成了一些文件该怎么办。 例如,当它启动时,它会生成一些静态文件,该文件基于应用程序仅在启动时收到的一些信息。 多么美好的一刻啊。 如果此类数据不多,那么您根本不必费心,只需为自己安装此应用程序即可工作。 这里唯一的问题是什么,看。 很多时候,各种遗留系统,比如WordPress等等,尤其是修改了某种巧妙的插件,聪明的PHP开发人员,他们往往知道如何制作,以便为自己生成某种文件。 因此,一个生成一个文件,第二个生成第二个文件。 他们是不同的。 平衡发生在客户端的 Kubernetes 集群中只是偶然的。 因此,事实证明他们不知道如何一起工作。 一个提供一种信息,另一个向用户提供另一种信息。 这是你应该避免的事情。 也就是说,在 Kubernetes 中,您启动的所有内容都保证能够在多个实例中工作。 因为 Kubernetes 是一个移动的东西。 因此,他可以随时移动任何东西,而无需询问任何人。 因此,你需要依靠这一点。 在一个实例中启动的所有内容迟早都会失败。 您的预订越多越好。 但我再说一遍,如果你有几个这样的文件,那么你可以把它们放在你的下面,它们的重量很小。 如果它们的数量较多,您可能不应该将它们推入容器内。

我建议,Kubernetes中有这么美妙的东西,你可以使用volume。 特别是,有一个空目录类型的卷。 也就是说,只是 Kubernetes 会自动在你启动的服务器上的服务目录中创建一个目录。 他会把它给你,以便你可以使用它。 只有一点很重要。 也就是说,您的数据不会存储在容器内,而是存储在您运行的主机上。 此外,Kubernetes可以在正常配置下控制此类空目录,并且能够控制它们的最大大小并且不允许超过它。 唯一的一点是,在空目录中写入的内容在 pod 重新启动期间不会丢失。 也就是说,如果你的 pod 错误地跌落并再次升起,空目录中的信息将不会去任何地方。 他可以在新的开始时再次使用它——这很好。 如果你的 Pod 离开某个地方,那么他自然会在没有数据的情况下离开。 也就是说,一旦使用空目录启动的节点中的 pod 消失,空目录就会被删除。

空目录还有什么好处? 例如,它可以用作缓存。 让我们想象一下,我们的应用程序动态生成一些东西,将其提供给用户,并持续很长时间。 因此,应用程序例如生成并交给用户,同时将其存储在某个地方,以便下次用户来获取相同的东西时,立即生成给它会更快。 可以要求 Kubernetes 在内存中创建空目录。 因此,就磁盘访问速度而言,您的缓存通常可以以闪电般的速度运行。 也就是说,你在内存中有一个空目录,在操作系统中它存储在内存中,但是对于你,对于 pod 内的用户来说,它看起来只是一个本地目录。 您不需要该应用程序来专门教授任何魔法。 您只需直接将文件放入目录中,但实际上是在操作系统的内存中。 这对于 Kubernetes 来说也是一个非常方便的功能。

Minio有什么问题? Minio 的主要问题是,为了让这个东西工作,它需要在某个地方运行,并且必须有某种文件系统,即存储。 在这里我们遇到了与 Ceph 相同的问题。 也就是说,Minio 必须将其文件存储在某个地方。 它只是文件的 HTTP 接口。 而且,功能上明显比亚马逊的S3差。 此前,它无法正确授权用户。 现在,据我所知,它已经可以创建具有不同授权的存储桶,但在我看来,主要问题至少是底层存储系统。

内存中的空目录如何影响限制? 不会以任何方式影响限制。 它位于主机的内存中,而不是容器的内存中。 也就是说,您的容器不会将内存中的空目录视为其占用内存的一部分。 楼主看到这个了因此,是的,从 kubernetes 的角度来看,当您开始使用它时,最好了解您将部分内存分配给空目录。 因此,要了解内存耗尽不仅是因为应用程序,而且还因为有人写入这些空目录。

云原生

最后一个子主题是 Cloudnative 是什么。 为什么需要它? 云原生等。

也就是说,那些能够在现代云基础设施中运行的应用程序。 但事实上,Cloudnative 还有另一个这样的方面。 这不仅是一个考虑到现代云基础设施的所有要求的应用程序,而且还知道如何与这种现代云基础设施一起工作,利用它在这些云中工作的优点和缺点。 不要只是过度地在云中工作,而是要充分利用在云中工作的好处。

在 Kubernetes 中开发应用程序的要求

我们以 Kubernetes 为例。 您的应用程序正在 Kubernetes 中运行。 您的应用程序,或者更确切地说,您的应用程序的管理员,始终可以创建服务帐户。 即 Kubernetes 本身在其服务器中的一个授权帐户。 在那里添加一些我们需要的权利。 您可以从应用程序内访问 Kubernetes。 这样你能做什么呢? 例如,从应用程序接收有关其他应用程序、其他类似实例所在位置的数据,并以某种方式聚集在 Kubernetes 之上(如果有这样的需求)。

再说一次,我们最近确实遇到了一个案例。 我们有一名监控队列的控制器。 当这个队列中出现一些新任务时,它会转到 Kubernetes,并在 Kubernetes 内部创建一个新的 pod。 为该 pod 提供一些新任务,并在此 pod 的框架内,该 pod 执行该任务,向控制器本身发送响应,然后控制器使用该信息执行某些操作。 例如,它添加了一个数据库。 也就是说,这又是我们的应用程序在 Kubernetes 中运行的一个优点。 我们可以使用内置的 Kubernetes 功能本身来以某种方式扩展并使我们的应用程序的功能更加方便。 也就是说,不要隐藏有关如何启动应用程序、如何启动工作线程的某种魔法。 在 Kubernetes 中,如果应用程序是用 Python 编写的,则只需在应用程序中发送请求即可。

如果我们超越 Kubernetes,这同样适用。 我们的 Kubernetes 在某个地方运行 - 如果它在某种云中,那就太好了。 同样,我相信我们可以使用,甚至应该使用我们运行所在的云本身的功能。 从云为我们提供的基本东西开始。 平衡,即我们可以创建云平衡器并使用它们。 这是我们可以利用的直接优势。 因为云平衡首先愚蠢地免除了我们对其工作方式和配置方式的责任。 而且它非常方便,因为常规 Kubernetes 可以与云集成。

缩放也是如此。 常规 Kubernetes 可以与云提供商集成。 知道如何理解,如果集群用完节点,即节点空间已经用完,那么您需要添加 - Kubernetes 本身会向您的集群添加新节点,并开始在它们上启动 pod。 也就是说,当你的负载到来时,炉床的数量就开始增加。 当集群中的节点耗尽这些 pod 时,Kubernetes 会启动新节点,因此 pod 的数量仍然可以增加。 而且非常方便。 这是动态扩展集群的直接机会。 不是很快,从某种意义上说,它不是一秒钟,更像是一分钟才能添加新节点。

但根据我的经验,这又是我见过的最酷的事情。 当 Cloudnative 集群根据一天中的时间进行扩展时。 这是一个供后台人员使用的后端服务。 也就是说,他们早上 9 点上班,开始登录系统,相应地,所有运行的 Cloudnative 集群开始膨胀,启动新的 Pod,以便每个上班的人都可以使用该应用程序。 当他们晚上 8 点或 6 点下班时,Kubernetes 集群注意到没有人再使用该应用程序并开始收缩。 保证节省高达 30% 的费用。 当时它在亚马逊行得通;当时俄罗斯没有人能做得这么好。

我直接告诉你,节省了 30%,仅仅是因为我们使用 Kubernetes 并利用了云的功能。 现在这可以在俄罗斯完成。 当然,我不会向任何人做广告,但我们只能说,有些提供商可以做到这一点,通过按钮直接提供开箱即用的服务。

我还想提请大家注意最后一点。 为了使您的应用程序、您的基础设施成为 Cloudnative,最终开始采用称为“基础设施即代码”的方法是有意义的。也就是说,这意味着您的应用程序,或者更确切地说,您的基础设施,需要以与代码 以代码的形式描述您的应用程序、您的业务逻辑。 并将其作为代码使用,即测试它、推出它、将其存储在 git 中、对其应用 CICD。

首先,这正是让您始终控制基础设施、始终了解其状态的原因。 其次,避免手工操作导致错误。 第三,当您不断需要执行相同的手动任务时,避免所谓的人员流动。 第四,它可以让您在发生故障时更快地恢复。 在俄罗斯,每当我谈到这个问题时,总会有很多人说:“是的,很清楚,但你有办法,总之,没有必要解决任何问题。” 但这是真的。 如果您的基础设施中出现问题,那么从 Cloudnative 方法的角度以及基础设施即代码的角度来看,与其修复它,不如前往服务器,找出损坏的部分并修复它,这会更容易删除服务器并重新创建它。 我会让这一切恢复原状。

所有这些问题都在以下位置进行了更详细的讨论: Kubernetes 视频课程:初级、基础、超级。 通过点击链接,您可以熟悉该计划和条件。 方便的是,每天在家或工作学习1-2小时就可以掌握Kubernetes。

来源: habr.com

添加评论