将应用程序部署到 VM、Nomad 和 Kubernetes

大家好! 我的名字是帕维尔·阿加列茨基。 我在开发 Lamoda 交付系统的团队中担任团队领导。 2018 年,我在 HighLoad++ 会议上发表了演讲,今天我想展示一下我的报告的文字记录。

我的主题致力于我们公司在不同环境中部署系统和服务的经验。 从史前时代开始,我们将所有系统部署到普通的虚拟服务器中,最后从Nomad逐渐过渡到部署在Kubernetes中。 我会告诉你我们为什么这样做以及我们在这个过程中遇到了什么问题。

将应用程序部署到虚拟机

首先,3年前,公司所有的系统和服务都部署在常规虚拟服务器上。 从技术上讲,它的组织方式是使用自动组装工具(使用 jenkins)存储和组装我们系统的所有代码。 使用 Ansible,它从我们的版本控制系统推广到虚拟服务器。 而且,我们公司的每个系统都至少部署在两台服务器上:一台在机头,一台在机尾。 这两个系统在所有设置、电源、配置等方面都完全相同。 它们之间唯一的区别是 head 接收用户流量,而 tail 不接收用户流量。

它做了什么?

当我们部署应用程序的新版本时,我们希望确保无缝部署,也就是说,不会给用户带来明显的后果。 这是因为使用 Ansible 的下一个编译版本已推出尾部。 在那里,参与部署的人员可以检查并确保一切正常:所有指标、部分和应用程序都正常工作; 启动必要的脚本。 只有在他们确信一切正常后,交通才被切换。 它开始前往之前处于尾部的服务器。 之前的头部仍然没有用户流量,但仍然保留着我们应用程序的前一版本。

所以对于用户来说这是无缝的。 因为切换是瞬时的,因为它只是切换平衡器。 您只需将平衡器切换回即可轻松回滚到以前的版本。 我们还可以在应用程序收到用户流量之前验证其是否能够投入生产,这非常方便。

我们在这一切中看到了哪些优势?

  1. 首先,够了 它就是有效的。 每个人都明白这样的部署方案是如何工作的,因为大多数人都曾经部署过常规的虚拟服务器。
  2. 这就够了 可靠,由于部署技术简单,经过数千家公司的测试。 数以百万计的服务器都是这样部署的。 打破东西很难。
  3. 最后我们可以得到 原子部署。 用户同时进行部署,在旧版本和新版本之间没有明显的切换阶段。

但我们也看到了这一切的一些缺点:

  1. 除了生产环境、开发环境之外,还有其他环境。 例如,质量保证和预生产。 当时我们有很多服务器,大概有60个服务。 为此,有必要 对于每项服务,维护其最新版本 虚拟机。 此外,如果您想更新库或安装新的依赖项,则需要在所有环境中执行此操作。 您还需要将部署应用程序的下一个新版本的时间与 devops 执行必要的环境设置的时间同步。 在这种情况下,很容易陷入这样一种情况,即我们的环境在所有环境中都会有所不同。 例如,在 QA 环境中会有一些版本的库,而在生产环境中会有不同的库,这会导致问题。
  2. 更新依赖项困难 你的申请。 这不取决于你,而是取决于其他团队。 即,来自维护服务器的 DevOps 团队。 您必须给他们适当的任务并描述您想要做什么。
  3. 当时,我们还想将我们拥有的大型整体划分为单独的小型服务,因为我们知道它们会越来越多。 当时我们已经有100多个了,每一个新的服务,都需要单独创建一个新的虚拟机,也需要维护和部署。 另外,你不需要一辆车,而是至少两辆车。 除此之外还有 QA 环境。 这会导致问题并使您构建和运行新系统变得更加困难。 复杂、昂贵且漫长的过程。

因此,我们决定从部署常规虚拟机转向在 Docker 容器中部署应用程序会更方便。 如果您有 docker,则需要一个可以在集群中运行应用程序的系统,因为您不能只创建一个容器。 通常您想要跟踪有多少集装箱被提升,以便它们自动提升。 因此,我们需要选择一个控制系统。

我们考虑了很长时间我们可以选择哪一个。 事实是,当时常规虚拟服务器上的部署堆栈有些过时,因为它们没有最新版本的操作系统。 在某个时候,甚至出现了 FreeBSD,但支持起来并不是很方便。 我们知道我们需要尽快迁移到 docker。 我们的开发人员根据不同解决方案的现有经验,选择了像 Nomad 这样的系统。

切换到游牧者

Nomad 是 HashiCorp 的产品。 他们还因其其他解决方案而闻名:

将应用程序部署到 VM、Nomad 和 Kubernetes

“领事” 是一个服务发现工具。

“地形” - 一个用于管理服务器的系统,允许您通过配置来配置它们,即所谓的基础设施即代码。

“流浪汉” 允许您通过特定的配置文件在本地或云端部署虚拟机。

当时的 Nomad 似乎是一个相当简单的解决方案,可以快速切换而无需更改整个基础设施。 此外,它非常容易学习。 这就是我们选择它作为容器过滤系统的原因。

将系统部署到 Nomad 需要什么?

  1. 首先你需要 码头工人形象 你的申请。 您需要构建它并将其放置在 docker 镜像存储库中。 在我们的例子中,这是一个工件 - 一个允许您将不同类型的各种工件推入其中的系统。 它可以存储档案、docker 镜像、composer PHP 包、NPM 包等。
  2. 还需要 配置文件,这将告诉 Nomad 您要部署什么、在哪里以及要部署多少数量。

当我们谈论Nomad时,它使用HCL语言作为其信息文件格式,它代表 HashiCorp 配置语言。 这是 Yaml 的超集,允许您用 Nomad 术语描述您的服务。

将应用程序部署到 VM、Nomad 和 Kubernetes

它允许您指定要部署多少个容器,以及在部署期间从哪些映像将各种参数传递给它们。 因此,您将此文件提供给 Nomad,它会根据该文件将容器启动到生产环境中。

在我们的例子中,我们意识到简单地为每个服务编写完全相同的 HCL 文件并不是很方便,因为有很多服务,有时您想要更新它们。 有时一项服务不是部署在一个实例中,而是部署在多个不同的实例中。 例如,我们生产中的系统之一有超过 100 个生产实例。 它们从相同的映像运行,但配置设置和配置文件不同。

因此,我们决定将所有配置文件存储在一个公共存储库中以方便部署。 这样它们就可见了:它们很容易维护,我们可以看到我们拥有什么系统。 如有必要,更新或更改某些内容也很容易。 添加新系统也不难 - 您只需在新目录中创建一个配置文件即可。 其中包含以下文件:service.hcl,其中包含我们服务的描述,以及一些允许配置在生产中部署的服务的环境文件。

将应用程序部署到 VM、Nomad 和 Kubernetes

然而,我们的一些系统在生产中部署时不是一个副本,而是同时部署多个副本。 因此,我们决定不以纯粹的形式存储配置,而是以模板化的形式存储配置,这对我们来说会很方便。 我们选择了 金贾2。 在这种格式中,我们存储服务本身的配置及其所需的环境文件。

此外,我们还在存储库中放置了所有项目通用的部署脚本,它允许您启动服务并将其部署到生产、所需的环境、所需的目标中。 在我们将 HCL 配置转换为模板的情况下,之前是常规 Nomad 配置的 HCL 文件在这种情况下开始看起来有点不同。

将应用程序部署到 VM、Nomad 和 Kubernetes

也就是说,我们用从 env 文件或其他来源获取的变量插入替换了一些配置位置变量。 此外,我们有机会动态收集HCL文件,也就是说,我们不仅可以使用普通的变量插入。 由于 jinja 支持循环和条件,因此您还可以在那里创建配置文件,这些文件会根据您部署应用程序的具体位置而变化。

例如,您想要将服务部署到预生产和生产中。 假设在预生产中您不想运行 cron 脚本,而只想查看单独域上的服务以确保其正常运行。 对于部署该服务的任何人来说,该过程看起来都非常简单和透明。 您所需要做的就是执行deploy.sh 文件,指定要部署哪个服务以及部署到哪个目标。 例如,您想将某个系统部署到俄罗斯、白俄罗斯或哈萨克斯坦。 为此,只需更改其中一个参数,您将获得正确的配置文件。

当 Nomad 服务已经部署到您的集群时,它看起来像这样。

将应用程序部署到 VM、Nomad 和 Kubernetes

首先,您需要某种外部平衡器,它将接收所有用户流量。 它将与 Consul 一起工作,并从中找出与特定域名相对应的特定服务所在的位置、节点、IP 地址。 Consul 中的服务来自 Nomad 本身。 由于这些是同一家公司的产品,因此彼此之间有很大的相关性。 可以说,Nomad 开箱即用,可以在 Consul 中注册其中启动的所有服务。

一旦您的前端负载均衡器知道将流量发送到哪个服务,它就会将其转发到与您的应用程序匹配的适当容器或多个容器。 当然,安全方面的考虑也是必要的。 尽管所有服务都在容器中的同一虚拟机上运行,​​但这通常需要阻止任何服务对任何其他服务的自由访问。 我们通过细分实现了这一点。 每个服务都在自己的虚拟网络中启动,在该虚拟网络上规定了路由规则以及允许/拒绝访问其他系统和服务的规则。 它们可能位于该星团内部,也可能位于该星团外部。 例如,如果您想阻止某个服务连接到特定数据库,可以通过网络级分段来完成。 也就是说,即使是错误的,您也不会意外地从测试环境连接到生产数据库。

就人力资源而言,转型花费了我们多少?

整个公司向 Nomad 的过渡大约需要 5-6 个月的时间。 我们在逐个服务的基础上移动,但速度相当快。 每个团队都必须为服务创建自己的容器。

我们采用了这样的做法,每个团队独立负责自己系统的docker镜像。 DevOps提供了部署所需的通用基础设施,即对集群本身的支持、对CI系统的支持等等。 当时,我们有60多个系统迁移到Nomad,总计约2个容器。

DevOps 负责与部署和服务器相关的一切的通用基础设施。 每个开发团队又负责为其特定系统实现容器,因为该团队知道特定容器中通常需要什么。

放弃 Nomad 的原因

通过切换到使用 Nomad 和 docker 等进行部署,我们获得了哪些优势?

  1. 我们 提供同等条件 适用于所有环境。 在开发、QA 环境、预生产、生产中,使用相同的容器镜像,具有相同的依赖关系。 因此,您几乎不可能最终在生产中得到的结果不是您之前在本地或测试环境中测试的结果。
  2. 我们还发现,这样就足够了 轻松添加新服务。 从部署的角度来看,任何新系统的启动都非常简单。 只需转到存储配置的存储库,为您的系统添加另一个配置,就可以了。 您可以将系统部署到生产环境,而无需开发人员进行任何额外的工作。
  3. 所有 配置文件 在一个公共存储库中 原来正在审核中。 当我们使用虚拟服务器部署系统时,我们使用 Ansible,其中配置位于同一存储库中。 然而,对于大多数开发人员来说,这有点难以使用。 这里,您需要添加的部署服务的配置和代码量已经变得小得多。 另外,开发人员很容易修复或更改它。 例如,在过渡到 Nomad 新版本的情况下,他们可以获取并批量更新位于同一位置的所有操作文件。

但我们也遇到了几个缺点:

原来我们 无法实现无缝部署 以游牧者为例。 当在不同条件下推出容器时,它可能正在运行,Nomad 将其视为准备接收流量的容器。 这发生在其中的应用程序有机会启动之前。 因此,系统开始在短时间内产生 500 个错误,因为流量开始流向尚未准备好接受它的容器。

我们遇到了一些 在沼泽地。 最重要的错误是,如果您有许多系统和容器,Nomad 不能很好地处理大型集群。 当你想取出Nomad集群中包含的一台服务器进行维护时,很有可能集群会感觉不太好并且会崩溃。 例如,某些容器可能会下降而不上升 - 如果您的所有生产系统都位于由 Nomad 管理的集群中,这将使您付出很大的代价。

所以我们决定考虑下一步该去哪里。 那时,我们更加清楚我们想要实现的目标。 即:我们想要可靠性,比Nomad提供的功能多一点,以及更成熟、更稳定的系统。

在这方面,我们选择了 Kubernetes 作为最流行的集群启动平台。 特别是考虑到我们集装箱的尺寸和数量足够大。 出于这样的目的,Kubernetes 似乎是我们可以考虑的最合适的系统。

过渡到 Kubernetes

我将向您介绍一些 Kubernetes 的基本概念以及它们与 Nomad 的不同之处。

将应用程序部署到 VM、Nomad 和 Kubernetes

首先,Kubernetes中最基本的概念是pod的概念。 是一组始终一起运行的一个或多个容器。 而且它们总是严格地在一台虚拟机上工作。 它们可以通过不同端口上的 IP 127.0.0.1 相互访问。

假设您有一个由 nginx 和 php-fpm(经典方案)组成的 PHP 应用程序。 最有可能的是,您希望始终将 nginx 和 php-fpm 容器保持在一起。 Kubernetes 允许您通过将它们描述为一个通用 Pod 来实现这一目标。 这正是 Nomad 无法做到的。

第二个概念是 部署。 事实上,pod 本身是一个短暂的东西;它会启动并消失。 你想先杀掉所有以前的容器,然后立即推出新版本,还是想逐步推出它们?这就是部署概念负责的过程。 它描述了如何部署 Pod、数量以及如何更新它们。

第三个概念是 服务。 你的服务实际上就是你的系统,它接收一些流量,然后将其转发到与你的服务对应的一个或多个 Pod。 也就是说,它允许您说具有此类名称的此类服务的所有传入流量都必须发送到这些特定的 Pod。 同时它为您提供流量平衡。 也就是说,您可以启动应用程序的两个 Pod,并且所有传入流量将在与该服务相关的 Pod 之间均匀平衡。

第四个基本概念是 入口。 这是一个运行在 Kubernetes 集群上的服务。 它充当接管所有请求的外部负载平衡器。 使用 Kubernetes API,Ingress 可以确定这些请求应发送到何处。 而且,他这样做非常灵活。 您可以说,对此主机和此类 URL 的所有请求都发送到此服务。 这些到达该主机和另一个 URL 的请求将被发送到另一个服务。

从开发应用程序的人的角度来看,最酷的事情是您能够自己管理这一切。 通过设置 Ingress 配置,您可以将所有到达某个 API 的流量发送到用 Go 等语言编写的单独容器。 但是,这些流量,来自同一个域,但不同的 URL,应该发送到用 PHP 编写的容器,其中有很多逻辑,但速度不是很快。

如果我们将所有这些概念与Nomad进行比较,我们可以说前三个概念都是服务。 Nomad 本身并不存在最后一个概念。 我们使用了一个外部平衡器:它可以是 haproxy、nginx、nginx+ 等。 对于立方体,您不需要单独引入这个附加概念。 然而,如果你看看 Ingress 的内部,它要么是 nginx、haproxy,要么是 traefik,但有点内置于 Kubernetes 中。

事实上,我描述的所有概念都是 Kubernetes 集群中存在的资源。 为了在多维数据集中描述它们,使用了 yaml 格式,在 Nomad 的情况下,它比 HCL 文件更具可读性和熟悉性。 但从结构上来说,它们描述的是相同的东西,例如 pod。 他们说 - 我想在那里部署这样那样的吊舱,带有这样那样的图像,以这样那样的数量。

将应用程序部署到 VM、Nomad 和 Kubernetes

此外,我们意识到我们不想手动创建每个单独的资源:部署、服务、Ingress 等。 相反,我们希望在部署过程中用 Kubernetes 来描述我们的每个系统,这样我们就不必按照正确的顺序手动重新创建所有必要的资源依赖项。 Helm 被选为允许我们做到这一点的系统。

Helm 中的基本概念

头盔是 包管理器 对于 Kubernetes。 它与编程语言中的包管理器的工作方式非常相似。 它们允许您以以下形式存储由部署 nginx、部署 php-fpm、Ingress 配置、configmaps(这是一个允许您为系统设置 env 和其他参数的实体)组成的服务:称为图表。 与此同时,头盔 运行在 Kubernetes 之上。 也就是说,这不是某种孤立的系统,而只是立方体内启动的另一项服务。 您可以通过控制台命令通过其 API 与其进行交互。 它的便利性和美妙之处在于,即使 helm 损坏或者您将其从集群中删除,您的服务也不会消失,因为 helm 本质上只用于启动系统。 Kubernetes 本身负责服务的性能和状态。

我们还意识到 模板化之前我们被迫自己将 jinja 引入到我们的配置中,这是 helm 的主要功能之一。 你为系统创建的所有配置都以模板的形式存储在 helm 中,有点类似于 jinja,但实际上,使用 Go 语言的模板,helm 是用 Go 语言编写的,就像 Kubernetes 一样。

Helm 为我们添加了更多概念。

图表 - 这是对您的服务的描述。 在其他包管理器中,它被称为包、捆绑包或类似的名称。 这里称为图表。

价值观 是您想要用来从模板构建配置的变量。

发布。 每次使用 helm 部署的服务都会收到版本的增量版本。 Helm 会记住上一个版本、之前的版本等中的服务配置。 因此,如果需要回滚,只需运行 helm 回调命令,将其指向之前的发行版本即可。 即使您的存储库中的相应配置在回滚时不可用,helm 仍然会记住它是什么,并将您的系统回滚到上一个版本中的状态。

当我们使用 helm 时,Kubernetes 的常规配置也会变成模板,在其中可以使用变量、函数和应用条件语句。 这样您就可以根据环境收集您的服务配置。

将应用程序部署到 VM、Nomad 和 Kubernetes

在实践中,我们决定采取与 Nomad 稍有不同的做法。 如果在 Nomad 中,部署我们的服务所需的部署配置和 n 变量都存储在一个存储库中,那么我们决定将它们划分为两个单独的存储库。 “deploy”存储库仅存储部署所需的 n 个变量,“helm”存储库存储配置或图表。

将应用程序部署到 VM、Nomad 和 Kubernetes

这给我们带来了什么?

尽管事实上我们不在配置文件本身中存储任何真正敏感的数据。 例如,数据库的密码。 它们作为秘密存储在 Kubernetes 中,但尽管如此,我们仍然不希望让每个人都可以访问某些内容。 因此,对“deploy”存储库的访问受到更多限制,而“helm”存储库仅包含服务的描述。 因此,它可以被更广泛的人安全地访问。

由于我们不仅有生产环境,还有其他环境,由于这种分离,我们可以重用 Helm Charts,不仅将服务部署到生产环境,还可以部署到 QA 环境等。 甚至可以使用以下方式在本地部署它们 迷你酷 - 这是本地运行 Kubernetes 的东西。

在每个存储库中,我们为每个服务划分了不同的目录。 也就是说,每个目录内都有与相应图表相关的模板,并描述启动系统所需部署的资源。 我们在“deploy”存储库中只留下了 envs。 在本例中,我们没有使用 jinja 进行模板化,因为 helm 本身提供了开箱即用的模板化 - 这是它的主要功能之一。

我们留下了一个部署脚本——deploy.sh,它简化并标准化了使用 helm 进行部署的启动。 因此,对于任何想要部署的人来说,部署界面看起来与通过 Nomad 部署时完全相同。 相同的deploy.sh、您的服务的名称以及您要部署它的位置。 这会导致 helm 在内部启动。 反过来,它从模板中收集配置,将必要的值文件插入其中,然后部署它们,将它们启动到 Kubernetes 中。

发现

Kubernetes 服务似乎比 Nomad 更复杂。

将应用程序部署到 VM、Nomad 和 Kubernetes

这里传出流量到达 Ingress。 这只是前端控制器,它接管所有请求,并随后将它们发送到请求数据对应的服务。 它根据配置来确定它们,这些配置是 helm 中应用程序描述的一部分以及开发人员自己设置的。 该服务将请求发送到其 pod(即特定容器),平衡属于该服务的所有容器之间的传入流量。 当然,我们不应该忘记,我们不应该偏离网络级别的安全性。 因此,在 Kubernetes 集群中,分段是基于标记的。 所有服务都有特定的标签,这些标签与服务对集群内部或外部的某些外部/内部资源的访问权限相关联。

当我们进行过渡时,我们看到 Kubernetes 拥有我们之前使用的 Nomad 的所有功能,并且还添加了很多新东西。 它可以通过插件进行扩展,实际上可以通过自定义资源类型进行扩展。 也就是说,您不仅有机会使用 Kubernetes 附带的开箱即用的东西,而且有机会创建自己的资源和服务来读取您的资源。 这为您提供了扩展系统的更多选项,而无需重新安装 Kubernetes,也无需进行修改。

Prometheus 就是此类使用的一个示例,它在我们的 Kubernetes 集群内运行。 为了让它开始从特定服务收集指标,我们需要在服务描述中添加一种额外类型的资源,即所谓的服务监视器。 Prometheus 由于在 Kubernetes 中启动时可以读取自定义资源类型,因此会自动开始从新系统收集指标。 相当方便。

我们对 Kubernetes 进行的首次部署是在 2018 年 3000 月。 在此期间我们从未遇到过任何问题。 它工作相当稳定,没有明显的错误。 此外,我们还可以进一步扩展它。 今天我们已经拥有了足够多的功能,而且我们非常喜欢 Kubernetes 的发展速度。 目前,Kubernetes 中有超过 XNUMX 个容器。 集群占用多个Node。 同时,它耐用、稳定、可控性强。

来源: habr.com

添加评论