大家好! 我叫帕维尔·阿加列茨基(Pavel Agaletsky)。 我在开发Lamoda交付系统的团队中担任团队负责人。 在2018年,我在HighLoad ++大会上发表了讲话,今天我想介绍我的报告笔录。
我的主题致力于公司在不同环境中部署系统和服务的经验。 从我们的史前时代开始,当我们deploili在传统的虚拟服务器的所有系统,从游牧到deployu在Kubernetes逐步过渡结束。 我将告诉您为什么这样做,以及在此过程中遇到了什么问题。
在VM上部署应用程序
首先,三年前,公司的所有系统和服务都部署在普通的虚拟服务器上。 从技术上讲,它是经过组织的,以便使用jenkins使用自动构建工具来放置和汇编我们系统的所有代码。 通过Ansible,他将我们的版本控制系统推广到虚拟服务器。 此外,我们公司中的每个系统都至少部署在2台服务器上:其中一台在头上,第二台在尾上。 这两个系统在所有设置,电源,配置等方面都完全相同。 它们之间的唯一区别是,头接收用户流量,而尾巴从未接收用户流量。
为什么这样做?
当我们部署应用程序的新版本时,我们希望提供无缝部署的可能性,即不会给用户带来明显的后果。 之所以能够实现这一目标,是因为使用了Ansible的下一个汇编发行版被推出了。 在那里,参与部署的人员可以检查并确保一切都很好:所有指标,部分和应用程序都可以工作; 启动必要的脚本。 只有在他们确信一切都OK之后,流量才被切换。 他开始去以前那条尾巴的服务器。 而以前的那个,则没有用户访问量,而上面装有我们应用程序的先前版本。
因此,对于用户而言,它是无缝的。 由于切换是同时进行的,因为它只是平衡器切换。 通过简单地将平衡器切换回原来的版本非常容易。 我们甚至可以在用户访问量之前验证应用程序的生产能力,这非常方便。
我们在这一切中看到了什么优势?
- 首先,它的工作非常简单。 每个人都了解此部署方案的工作原理,因为大多数人都曾经部署到普通虚拟服务器。
- 这是相当可靠的 ,因为部署技术很简单,已经过数千家公司的测试。 以这种方式部署了数百万台服务器。 很难破坏一些东西。
- 最后,我们可以获得原子部署 。 用户同时进行的部署,而没有在旧版本和新版本之间切换的明显阶段。
但是在此我们也看到了一些缺点:
- 除了生产环境,开发环境外,还有其他环境。 例如,qa和预生产。 当时,我们有许多服务器和大约60项服务。 因此, 每个服务都必须维护与其相关的虚拟机版本 。 此外,如果要更新库或安装新的依赖项,则需要在所有环境中执行此操作。 还需要将要部署应用程序的下一个新版本的时间与devops进行必要的环境设置的时间同步。 在这种情况下,很容易陷入一种情况,即我们的环境在连续的所有环境中一次都会稍有不同。 例如,在质量检查环境中,将存在某些版本的库,而在生产环境中将存在某些版本,这将导致问题。
- 难以更新应用程序的依赖关系 。 它不取决于您,而是取决于另一个团队。 即从支持服务器的devops命令开始。 您必须为他们设置适当的任务,并提供您要做什么的描述。
- 那时,我们还希望将我们拥有的大型大型整体分成单独的小型服务,因为我们知道会有越来越多的整体。 到那时,我们已经有100多个,每个新服务都必须创建一个单独的新虚拟机,并且还需要对其进行服务和部署。 此外,您不需要一辆车,但至少需要两辆。 为此,质量检查环境仍在添加中。 这会引起问题,并使创建和启动新系统更加困难,昂贵且耗时。
因此,我们决定在Docker容器中从普通虚拟机的部署切换到应用程序的部署会更加方便。 如果您有docker,则需要一个可以在集群中运行该应用程序的系统,因为您不能仅仅提升容器。 通常,您要跟踪抬起多少个容器,以便它们自动抬起。 因此,我们必须选择一个控制系统。
我们考虑了很长一段时间可以采取的措施。 事实是,那时那里的普通虚拟服务器部署堆栈有些过时了,因为那里没有最新版本的操作系统。 在某些时候,甚至FreeBSD都站在那儿,维护起来不太方便。 我们了解到,您需要尽快迁移到docker。 我们的开发人员根据他们在不同解决方案上的现有经验,选择了Nomad之类的系统。
切换到游牧
Nomad是HashiCorp的产品。 他们还因其他决定而闻名:
Consul是用于服务发现的工具。
Terraform是一个服务器管理系统,允许您通过称为基础结构即代码的配置来配置它们。
Vagrant允许您通过特定的配置文件在本地或云中部署虚拟机。
那时的Nomad似乎是一个相当简单的解决方案,您可以快速切换到该解决方案,而无需更改整个基础架构。 另外,它很容易掌握。 因此,我们选择它作为容器的过滤系统。
将系统完全部署到Nomad需要什么?
- 首先,您需要应用程序的docker映像 。 您需要构建它并将其放入docker映像存储中。 在我们的例子中,这是人工的-这种系统允许您将各种类型的各种人工工具推入其中。 它可以存储档案,docker映像,PHP composer软件包,NPM软件包等。
- 您还需要一个配置文件 ,告诉Nomad您想要部署什么,在哪里部署以及部署多少。
当我们谈论Nomad时,它使用HCL语言作为信息文件格式,代表
HashiCorp配置语言 。 这是Yaml的超集,可让您用Nomad来描述您的服务。

它允许您说出要部署多少个容器,以及在部署过程中从哪个映像向它们传输各种参数。 因此,您输入此Nomad文件,并根据该文件启动生产中的容器。
在我们的案例中,我们意识到仅为每个服务编写完全相同,完全相同的HLC文件不会很方便,因为有许多服务,有时您需要更新它们。 碰巧一种服务不是部署在一个实例中,而是部署在最不同的实例中。 例如,我们在生产中拥有的系统之一在生产中具有100多个实例。 它们是从同一映像启动的,但是配置设置和配置文件不同。
因此,我们决定为我们方便地将所有用于部署的配置文件存储在一个公共存储库中。 因此,它们变得可观察:它们易于维护,并且可以查看我们拥有哪些系统。 如有必要,也很容易更新或更改某些内容。 添加新系统也不难-只需在新目录中输入配置文件即可。 它的内部是文件:service.hcl,其中包含对我们的服务的描述,以及一些允许在生产环境中部署的服务进行配置的环境文件。

但是,我们的某些系统不是一次部署在产品中,而是一次部署在多个产品中。 因此,我们决定不以纯格式存储配置,而是以模板格式存储配置对我们来说是方便的。 作为模板语言,我们选择了
jinja 2 。 以这种格式,我们既存储服务本身的配置,也存储其所需的环境文件。
另外,我们在存储库中放置了一个用于所有项目的通用脚本部署,这使您可以在期望的环境中在期望的目标中在生产中启动和部署服务。 如果我们将HCL-config转换为模板,则以前是常规Nomad配置的HCL文件在这种情况下看起来有些不同。

也就是说,我们用变量插入替换了配置文件中的一些变量,这些插入来自环境文件或其他来源。 另外,我们能够动态地收集HL文件,也就是说,我们不仅可以使用通常的变量插入。 由于jinja支持循环和条件,因此您还可以在其中创建配置文件,具体取决于您在何处正确部署应用程序。
例如,您想要在预生产和生产中部署服务。 假设在预生产中您不想运行Crown脚本,而只想在单独的域中查看该服务以确保其正常运行。 对于任何部署服务的人,该过程看起来都非常简单和透明。 运行deploy.sh文件,指定要部署的服务以及在哪个目标中就足够了。 例如,您想要将某个系统部署到俄罗斯,白俄罗斯或哈萨克斯坦。 为此,只需更改参数之一,即可获得正确的配置文件。
当Nomad服务已经部署在您的群集中时,它看起来像这样。

首先,您需要在外部使用一些平衡器,以平衡所有用户流量。 他将与Consul合作,并从他那里找出在哪个节点上的哪个IP地址上对应于特定域名的特定服务。 领事的服务来自Nomad本身。 由于这些是同一公司的产品,因此它们之间具有良好的联系。 我们可以说Nomad可以在Consul内部注册其中启动的所有服务。
外部平衡器找到向其发送流量所必需的服务后,便会将其重定向到适当的容器或与您的应用程序对应的多个容器。 自然,还必须考虑安全性。 即使所有服务都在容器中的同一虚拟机上运行,这通常要求禁止任何服务自由访问任何其他服务。 我们通过细分实现了这一目标。 每个服务都在其自己的虚拟网络中启动,该网络上规定了路由规则以及允许/拒绝访问其他系统和服务的规则。 它们既可以位于该群集内部,也可以位于群集外部。 例如,如果要阻止服务连接到特定数据库,可以通过在网络级别进行分段来实现。 也就是说,即使是由于错误,您也不会意外地从测试环境连接到生产基地。
人力资源方面的过渡成本使我们付出了什么?
大约5-6个月占领的所有公司在游牧转移。 我们改用了无需服务的服务,但是步伐相当快。 每个团队都必须创建自己的服务容器。
我们采用了这样一种方法,即每个团队自己负责他们系统的docker映像。 Devops还提供了部署所需的常规基础结构,即对集群本身的支持,对CI系统的支持等等。 那时我们有60多个系统移至Nomad,结果大约有2000个容器。
Devops负责与部署以及服务器相关的所有事物的整体基础结构。 每个开发团队依次负责其特定系统的容器实现,因为是团队知道特定容器中通常需要什么。
放弃游牧民族的原因
通过切换到同时使用Nomad和docker进行部署,我们获得了哪些优势?
- 我们为所有环境提供了相同的条件 。 在开发公司的QA环境,预生产,生产中,使用相同的容器映像,并且具有相同的依存关系。 因此,您几乎没有机会证明产品与您先前在本地或在测试环境中进行测试的结果有所不同。
- 我们还发现添加新服务非常容易 。 从部署的角度来看,任何新系统的启动都非常简单。 转到存储配置的存储库,在其中添加系统的下一个配置就足够了,您就可以准备就绪。 您可以在生产环境中部署系统,而无需devop的额外努力。
- 原来 ,一个公共存储库中的所有配置文件 都受到监视 。 那时,当我们使用虚拟服务器部署系统时,我们使用了Ansible,其中的配置位于同一存储库中。 但是,对于大多数开发人员而言,使用起来会有些困难。 在这里,为了部署服务而需要添加的配置和代码量已大大减少。 另外,对于devops,修复或更改它非常容易。 例如,在过渡情况下,在新版本的Nomad上,它们可以获取并大规模更新位于同一位置的所有运行文件。
但是我们也面临几个缺点:
事实证明,就Nomad而言,我们
无法实现无缝部署 。 当将集装箱滚动出不同的条件时,可能证明它正在运行,而Nomad认为它是准备接受交通的集装箱。 即使在其中的应用程序无法启动之前,也发生了这种情况。 因此,该系统在短时间内开始产生500个错误,因为流量开始流向尚未准备好接收的容器。
我们遇到了一些
错误 。 最重要的错误是,如果您有许多系统和容器,Nomad不能很好地接受大型群集。 当您要将Nomad群集中包含的一台服务器投入使用时,该群集很有可能会感觉不太好并且会崩溃。 例如,部分容器可能会掉落而不会上升-如果您的所有生产系统都位于Nomad管理的群集中,那么对于您而言这将非常昂贵。
因此,我们决定考虑下一步去哪里。 那时,我们对要实现的目标有了更好的了解。 即:我们想要可靠性,比Nomad提供的功能更多,以及更成熟,更稳定的系统。
在这方面,我们选择Kubernetes作为启动集群的最流行平台。 特别是在我们的集装箱的尺寸和数量很大的情况下。 为此,Kubernetes似乎是我们所看到的最合适的系统。
去Kubernetes
我将讨论Kubernetes的基本概念以及它们与Nomad的区别。

首先,Kubernetes中最基本的概念是pod的概念。
容器是一组始终运行在一起的一个或多个容器。 而且它们似乎始终严格在同一虚拟机上工作。 它们可以通过IP 127.0.0.1在不同端口上相互访问。
假设您有一个包含nginx和php-fpm(经典电路)的PHP应用程序。 您很可能希望nginx和php-fpm容器始终在一起。 Kubernetes通过将它们描述为一个通用吊舱来做到这一点。 这正是我们在Nomad的帮助下无法获得的。
第二个概念是
部署 。 事实是,豆荚本身是短暂的事物,它开始并消失。 无论您是要先杀死所有以前的容器,然后立即启动新版本,还是要逐步推出它们-这正是部署负责的概念。 它描述了如何部署您的Pod,有多少个Pod以及如何更新它们。
第三个概念是
服务 。 您的服务实际上是您的系统,它接收一些流量,然后将其定向到与您的服务相对应的一个或多个Pod。 也就是说,它允许您说必须将具有此类名称的此类服务的所有传入流量发送到这些特定的Pod。 同时,它为您提供流量平衡。 也就是说,您可以运行应用程序的两个Pod,与此服务相关的Pod之间将平衡所有传入流量。
第四个基本概念是
Ingress 。 这是在Kubernetes集群中运行的服务。 它充当外部负载平衡器,可以处理所有请求。 由于使用了API,Kubernetes Ingress可以确定将这些请求发送到何处。 而且他非常灵活地做到这一点。 您可以说,对此主机的所有请求和此类URL均已发送到该服务。 然后,我们将这些请求发送到该主机,并发送到另一个服务的另一个URL。
从开发应用程序的人的角度来看,最酷的事情是您可以自己管理所有应用程序。 设置了Ingress配置后,您可以将所有访问此类API的流量发送到单独的已注册容器,例如Go。 但是,将来自同一域但到达不同URL的流量发送到用PHP编写的容器中,在该容器中有很多逻辑,但它们的速度不是很快。
如果将所有这些概念与Nomad进行比较,那么我们可以说前三个概念都是Service。 游牧民族本身的最后一个概念不见了。 我们使用了外部平衡器:它可以是haproxy,nginx,nginx +等。 在多维数据集的情况下,您无需单独介绍此附加概念。 但是,如果您查看内部的Ingress,则它是nginx,haproxy或traefik,就像内置在Kubernetes中一样。
我所描述的所有概念本质上都是Kubernetes集群中存在的资源。 为了在多维数据集中描述它们,使用了yaml格式,比Nomad的HCl文件更易读和熟悉。 但是在结构上,它们在例如pod的情况下描述的是同一件事。 他们说-我想在某处和某处部署这样的豆荚,并以这样的图像和数量来部署。

此外,我们意识到我们不希望自己动手创建每个单独的资源:部署,服务,Ingress等。 相反,我们希望在部署期间以Kubernetes的形式描述每个已部署的系统,这样我们就不必以正确的顺序手动重新创建所有必要的资源依赖项。 选择了Helm作为允许我们执行此操作的系统。
舵手的关键概念
Helm是Kubernetes的
软件包经理 。 这与程序包管理器以编程语言工作的方式非常相似。 它们允许您以所谓的图表形式存储由部署nginx,部署php-fpm,用于Ingress的配置,配置映射(这是允许您为系统设置环境和其他参数的实体)组成的服务。 同时,Helm
在Kubernetes之上运行 。 就是说,这不是一种可以搁置的系统,而只是在多维数据集内运行的另一种服务。 您可以通过控制台命令通过其API与之交互。 它的便利和魅力在于,即使掌舵中断或将其从集群中删除,您的服务也不会消失,因为掌舵本质上仅用于启动系统。 Kubernetes本身负责正常运行时间和服务状态。
我们还意识到,标准化(以前必须通过在配置中引入jinja来独立完成)是helm的主要功能之一。您为系统创建的所有配置都以类似于jinja的模板形式存储在头盔中,但实际上,使用的是编写头盔的Go语言模板(例如Kubernetes)。Helm向我们添加了更多其他概念。图表是对服务的描述。其他程序包管理器将其称为程序包,捆绑包或类似的东西。这里称为图表。值是您要用来从模板构建配置的变量。放行。每次使用helm部署的服务收到该发行版的增量版本。 Helm会记住上一服务配置,上一发布前一年的服务配置,等等。因此,如果需要回滚,只需运行helm callback命令,并向其指示该发行版的先前版本。即使在回滚时,存储库中的相应配置不可用,Helm仍会记住该配置,并将系统回滚到先前发行版中的状态。在使用头盔的情况下,Kubernetes的常规配置也将转换为模板,在其中可以使用变量,函数,应用条件运算符。因此,您可以根据环境收集服务的配置。
实际上,我们决定与Nomad有所不同。如果在同一存储库中的Nomad中存储了部署配置和部署服务所需的n变量,那么我们决定将它们分为两个单独的存储库。仅部署所需的n变量存储在部署存储库中,而配置或图表存储在helm存储库中。
它给了我们什么?尽管我们没有在配置文件中存储任何真正敏感的数据。例如,数据库密码。它们是作为秘密存储在Kubernetes中的,但尽管如此,仍有一些我们不想让所有人连续访问的东西。因此,对部署存储库的访问受到更多限制,并且掌舵存储库仅包含该服务的描述。因此,可以安全地接触更大范围的人群。由于我们不仅拥有生产环境,还拥有其他环境,因此,通过这种分离,我们不仅可以在生产环境中,而且可以在例如QA环境中重复使用舵图来部署服务。即使使用Minikube在本地部署它们,在本地运行Kubernetes也是如此。在每个存储库中,我们将每个服务的目录分开。也就是说,每个目录中都有与相应图表相关的模板,这些模板描述了需要部署以启动我们的系统的资源。在部署存储库中,我们仅保留了enve。在这种情况下,我们没有使用使用jinja的模板,因为helm本身提供了开箱即用的模板-这是其主要功能之一。我们保留了部署脚本deploy.sh,该脚本简化并标准化了使用头盔进行部署的启动。因此,对于任何想要部署的人,部署界面看起来与通过Nomad进行部署的情况完全相同。相同的deploy.sh,服务的名称以及要将其部署到的位置。这会导致头盔从内部开始。反过来,他从模板收集配置,将必要的value-files替换为模板,然后进行部署,并将其放入Kubernetes中。结论
Kubernetes服务看起来比Nomad更复杂。
这是出站流量进入Ingress的地方。这只是前端控制器,它接收所有请求,然后将它们发送到与请求数据相对应的服务。它基于配置定义它们,这些是掌控应用程序的一部分,并且由开发人员独立设置。该服务将请求发送到其Pod(即特定的容器),以平衡属于该服务的所有容器之间的传入流量。好吧,当然,不要忘记,我们不应该在网络级别上脱离安全性。因此,Kubernetes集群基于标签来进行分段。所有服务都具有某些标记,群集内部或外部附加了对某些外部/内部资源的服务访问权。当完成过渡时,我们看到Kubernetes具有Nomad的所有功能,这是我们以前使用的,并且还添加了许多新功能。可以通过插件,甚至可以通过自定义资源类型进行扩展。也就是说,您不仅有机会使用开箱即用的Kubernetes内容,还可以创建自己的资源和服务来读取您的资源。这提供了用于扩展系统的其他选项,而无需重新安装Kubernetes且无需进行更改。Prometheus就是一个例子,它在我们的Kubernetes集群中运行。为了使他开始从特定服务中收集指标,我们需要在服务描述中添加其他资源类型,即所谓的服务监视器。由于Prometheus可以读取,因此在Kubernetes中启动,这是一种自定义类型的资源,它会自动开始从新系统中收集指标。这很方便。我们在Kubernetes进行的首次部署是2018年3月。在此期间,我们从未遇到过任何问题。它运行稳定,没有重大错误。此外,我们可以进一步扩展它。今天,我们拥有足够的机会,我们非常喜欢Kubernetes的发展步伐。目前,在Kubernetes中有3,000多个容器。群集需要几个节点。同时,它易于维护,稳定且受到严格控制。