从池请求到释放。 报告Yandex.Taxi

服务的发布周期中有一个关键时期-从准备新版本到用户可以使用它的那一刻。 这两个控制点之间的团队操作在发布之间应保持一致,并且在可能的情况下应实现自动化。 Sergey Pomazanov 专家在他的报告中描述了每个Yandex.Taxi池请求之后的过程。


-晚上好! 我叫Sergey,是Yandex.Taxi自动化小组的负责人。 简而言之,我们小组的主要任务是减少开发人员用于解决问题的时间。 这包括从CI到开发和测试过程的所有内容。

编写代码后,我们的开发工作是什么?

为了测试新功能,我们首先在本地检查所有内容。 对于本地测试,我们有大量的测试。 如果出现新代码,则还需要进行测试。



我们的测试覆盖范围不尽如人意,但我们尝试将其保持在足够的水平。

对于测试,我们使用Google Test和pytest自行编写的框架,通过该框架,我们不仅可以测试“ python”部分,还可以测试“ plus”部分。 我们的框架可让您启动服务,在每次测试之前将数据上传到数据库,更新缓存,清除所有外部请求等。一个功能齐全的框架可让您根据需要运行任何内容,锁定任何内容,以免意外获取任何内容外面的要求。

除了功能测试,我们还有集成测试。 它们使您能够解决另一个问题。 如果不确定服务是否可以与其他服务正确交互,则可以运行支架并进行一系列测试。 到目前为止,我们已经进行了一组基本的测试,但是它正在慢慢扩展。

该支架基于Docker和Docker Compose的技术构建,其中每个容器都有自己的服务,并且它们彼此交互。 这发生在孤立的环境中。 他们有自己的隔离网络,自己的数据库,自己的数据集。 测试以某种方式通过,就好像有人正在启动移动应用程序,单击按钮并下达命令一样。 这时,虚拟汽车驾驶并带动乘客,然后从乘客那里借钱,依此类推。 基本上,所有测试都需要同时进行许多服务和组件的交互。

自然,我们只测试我们的服务和组件,因为我们不应该测试外部服务,而我们将外部的所有东西都弄湿了。

该摊位非常方便,可以在本地运行,并从中拿出一辆出租车。 您可以采取这种立场,在本地计算机或虚拟机或任何其他开发计算机上运行它。 启动展台后,您可以使用适合小型出租车的移动应用程序,在计算机上进行配置并下订单。 一切与生产或其他地方完全相同。 如果您需要测试新功能,则只需插入代码,它将在整个环境中运行。

同样,您可以仅使用并运行所需的服务。 为此,您需要提升数据库,用必要的内容填充数据库,或者从现有环境中获取基础并将其连接到服务。 然后,您可以与他联系,进行一些查询,看看它是否正常工作。

另一个要点是样式检查。 如果对于“加号”来说一切都很简单,我们将使用clang格式并检查代码是否匹配,对于Python,我们将使用多达四个分析器:Flake8,Pylint,Mypy和autopep8。

我们主要在标准交付中使用这些分析仪。 如果有机会选择某种风格的设计,那么我们将使用Google风格。 我们通过添加自己的内容进行纠正的唯一一件事就是检查导入排序,以便对导入进行正确排序。

创建代码后,在本地检查,您可以发出池请求。 池请求在GitHub上创建。



在GitHub上创建池请求提供了TeamCity为我们提供的许多机会。 TeamCity自动运行上述所有测试,自动检查它们,并在池请求本身中记录通过状态,无论测试是否通过。 也就是说,无需访问TeamCity,您就可以查看它是否已通过,然后单击链接以了解出了什么问题以及需要解决的问题。

如果您没有足够的出租车和测试服务,而您想检查与某些实际服务的真实交互,我们将提供一个可重复生产的测试环境。 我们有两个这样的测试环境。 一种是用于测试人员的移动开发,另一种是用于开发人员。 测试环境尽可能接近生产环境,并且如果对外部服务有任何要求,则它们也是从测试环境发出的。 唯一的限制是测试环境会尽可能地对外部资源进行测试。 生产环境投入生产。

关于测试环境的更多信息,我们非常简单地通过TeamCity进行。 有必要在GitHub上放置适当的标签,设置好之后,单击“收集自定义”按钮。 所以我们称之为。 然后所有带有此标签的池请求将争用,然后将开始自动组装带有群集的软件包。

除了常规测试外,有时还需要进行负载测试。 如果您正在编辑属于高负载服务的一部分的代码,我们可以为此进行负载测试。 在Python中,很少有高负载的服务,其中一些是我们用C ++重写的,但是尽管如此,它们仍然存在,有时还有一个地方。 负载测试通过Lunapark系统进行。 它使用Yandex.Tank,它是免费提供的,您可以下载并观看。 该储罐允许您在某些服务时触发,构建图表,执行不同的加载方法,并显示服务当前所承受的负载以及所使用的资源。 通过TeamCity单击按钮就足够了,软件包将被收集,然后可以在需要时进行滚动。 或者只是手动填充并在那里运行。



在测试代​​码时,一位开发人员可能会在此时开始查看您的代码并进行审查。

在此过程中我们要注意的是:



要点之一-必须禁用该功能。 这意味着,无论代码是什么,是否存在错误,也许此功能都无法按其最初的意图工作,也许管理人员需要其他功能,或者此功能正在尝试放置另一项尚未准备就绪的服务到新的负载,您需要能够快速将其关闭并将所有设备置于正常状态。

我们也有一条规则,即在推出新功能时应将其关闭,并且仅在将其推广到所有集群和所有数据中心后再打开。

不要忘记,我们有一个可能长时间未更新的移动应用程序所使用的API。 如果我们在API中进行一些向后不兼容的更改,则某些应用程序可能会崩溃,并且我们不能强制所有应用程序仅下载和更新。 这将对我们的声誉产生负面影响。 因此,所有新功能都必须向后兼容。 这不仅适用于外部API,而且也适用于内部API,因为您不能同时将所有代码发布到所有数据中心,所有计算机和所有集群。 无论如何,旧代码和新代码将同时与我们一起生活。 结果,我们得到了一些无法在某处处理的查询,并且会出现错误。

您还应该考虑下一件事情:如果您的代码突然不起作用,或者您编写了一个新的微服务,其中存在潜在的问题,则需要为后果做好准备并能够降级。 我的同事将在下一个演讲中讨论这个问题。

如果您对高负载的服务进行了更改,而不必等待某些操作的结束,那么您可以在后台某处异步进行某些操作,也可以作为一个单独的过程进行操作。 最好这样做,因为单独的流程对生产的影响较小,并且系统总体上可以更稳定地运行。



同样重要的是,我们从外部接收的所有数据,我们不应该信任它们,必须以某种方式进行验证,验证等。我们拥有的所有数据应分为已形成的组或未通过验证的原始数据。 这包括所有可能从其他外部服务或直接从用户获得的数据,因为可能会发生任何事情。 也许有人特意发送了恶意请求,应该与我们一起检查所有内容。



在某些情况下,根据要求,服务可能无法在正确的时间响应。 可能是连接断开或出了点问题,可能有很多情况。 移动应用程序不知道最终会发生什么,只是重新请求。

非常重要的是,在这些重新请求的过程中,无论有多少个请求,最终所有功能都可以按单个请求原先的预期工作。 我们不应有任何特殊影响。 还应该记住,我们拥有不止一项服务,我们拥有许多机器,许多数据中心,我们拥有分布式基础,每个人都可以参加竞赛。 应该编写代码,以便如果它同时在多个地方运行,那么我们就不会出现竞争。

同样重要的一点是诊断问题的能力。 问题总是存在于所有事物中,您需要了解问题发生的位置。 在理想情况下,问题的存在不是通过支持服务来了解的,而是通过监视来了解的。 在解析某些情况时,我们最终可以通过简单地阅读日志而不阅读代码来了解发生了什么。 即使是从未看过代码的人,也可以通过最终登录来获取代码。

在理想情况下,如果情况非常复杂,则需要能够通过日志检查程序最终以哪种方式运行以及发生了什么情况,从而大大简化了汇报工作。 由于这种情况是过去的结果,而现在不太可能重现,因此已经没有数据或其他数据或其他情况。

如果要在数据库中执行新操作或创建新操作,则需要考虑可能会有很多数据。 也许您会向该数据库写入无数记录,并且如果您不考虑将其归档,则可能会出现问题,数据库将开始无限期地增长,并且将不再有资源,磁盘和分片。 能够存档数据并仅存储当前所需的操作数据非常重要。 并且还必须对所有数据库进行索引查询。 非索引查询可以放置整个产品。 对负载最大的中央集合的一个小请求就可以放置所有内容。 您必须非常小心。

我们不欢迎过早的优化。 如果有人试图使某种工厂成为一种非常通用的方法,可能会在将来处理案件,那么也许某天某人会想要扩展它-这不是我们的习惯,因为它可能会发展这将是完全错误的,也许最终这些代码将被掩埋,或者也许没有必要,但只会使代码的阅读和理解复杂化。 因为阅读和理解代码非常重要。 代码非常简单容易是很重要的。

如果您在代码中添加了新数据库或对API进行了更改,我们提供的文档部分由代码生成,部分由Wiki完成。 此信息对于保持最新非常重要。 否则,它可能会误导某人或对其他开发人员造成问题。 因为代码是单独编写的,但是它得到了很多支持。

重要的部分是遵守总体风格。 在这种情况下,最主要的是均匀性。 当所有代码以统一的方式编写时,它易于理解,易于阅读,并且您无需深入研究所有细节和细微差别。 统一编写的代码可能会在将来加速整个开发过程。

我们没有专门检查评论的另一点是我们不是在寻找错误。 因为作者应该从事寻找bug的工作。 当然,如果在审阅过程中有错误,他们会写出相应的错误,但不要有目的地进行搜索,这完全是编写代码的人的责任。

此外,编写代码时,检查已经完成,您可以冻结它,但是经常发生这种情况,您需要执行其他操作,然后迁移到数据库。



对于迁移,我们编写了可以与后端通信的Python脚本。 后端又连接到我们所有的基地,并可以执行所有必要的操作。 该脚本通过脚本启动管理面板启动,然后执行,您可以查看其日志和结果。 而且,如果您需要长期的光束操作,则无法一次更新所有内容,您需要对1000-10000的数据块进行一些暂停,以免意外地使这些操作基础变差。



编写,审查,测试代码后,所有迁移都将执行,您可以在GitHub中安全地合并它并继续发布它。

对于某些服务,我们有一项法规,必须在特定时间推出,但我们可以在任何时间推出的很大一部分服务。

这一切都由TeamCity完成。



这一切都始于构建软件包。 TeamCity会进行git flow或其外观。 我们正在逐渐从git flow转向我们认为更方便的最佳实践。 TeamCity产生所有这些,收集包裹,填充它们。 当测试将通过这些程序包时,我们将进一步等待。 要发布此版本,必须通过测试。 如果测试失败,那么您首先需要弄清楚它,然后看看最终出了什么问题。 使用的测试是相同的,常规的和集成的。 他们检查已经组装好的包装,准备好要投入生产的确切范围。 这只是为了以防万一,突然在组装好的包装中出现了问题,突然没有东西被复制,突然发现有些东西丢失了。

还要求我们在跟踪器中创建一个发行票据,每个开发人员都必须取消订阅他如何测试此代码,并且其中应包含所有必须完成的任务。

TeamCity中的提交列表也会自动完成此操作。 我们要求在每次提交中都应有一个关键字“ Relates”,后跟任务名称。 用Python编写的脚本会自动完成所有这些工作,编译已解决的任务列表,形成作者列表并创建发行通知单,敦促所有作者退订他们的测试并确认他们准备好“发布”。



当每个人都准备就绪时,将收集确认信息,然后进行部署,首先-在稳定状态下进行。 这只是生产的一小部分。 对于每种服务,使用多个数据中心,每个数据中心中可以有几台计算机。 其中一台机器是预稳定的,并且代码仅首先部署到一个或几台机器上。

压缩代码后,我们将按照图表,日志以及最终在服务上发生的事情进行操作。 如果一切都很好,如果图形显示一切都稳定,并且每个人都已验证其功能可以正常工作,那么它将滚动到环境的其余部分,我们称之为稳定环境。 部署到马stable时,一切都是相同的:我们查看图表,日志并检查一切是否适合我们。

推出已通过,一切都很好。 如果出现问题,是否突然出现问题?



我们收集修补程序。 它是按照与git flow相同的原理完成的,即从master分支分支。 从主服务器创建一个单独的池请求,并进行更正,然后从TeamCity启动的脚本将其冻结,执行所有必要的操作,以相同的方式收集所有软件包并继续运行。



最后,我想谈谈我们前进的方向。 当许多服务同时存在于一个存储库中时,我们正在朝着单个存储库迈进。 他们每个人都有独立的计算:在测试中,在发布中。 对于池请求,即使使用了TeamCity,我们也会检查哪些文件受到了影响,它们属于哪些服务。 根据依赖关系图,我们确定最终需要运行哪些测试以及要检查什么。 我们力求最大程度地将服务彼此隔离。 到目前为止,它不是很成功,但是我们为此努力,以使许多服务可以存在于一个存储库中,并具有一些通用代码,并且不会引起问题,并简化了开发工作。 仅此而已,谢谢大家。

Source: https://habr.com/ru/post/zh-CN439704/


All Articles