在 RIT 2019 上,我们的同事 Alexander Korotkov 提出
零级
“不存在零级这种事,我不知道有这种事”
电影《功夫熊猫》中的师父
CIAN 的自动化始于公司成立 14 年后。 当时开发团队有35人。 很难相信,对吧? 当然,自动化确实以某种形式存在,但持续集成和代码交付的独立方向在 2015 年开始形成。
当时,我们有一个巨大的 Python、C# 和 PHP 整体,部署在 Linux/Windows 服务器上。 为了部署这个怪物,我们有一组手动运行的脚本。 还有单体的组装,在合并分支、纠正缺陷和“使用构建中的不同任务集”进行重建时,由于冲突而带来痛苦和痛苦。 一个简化的过程如下所示:
我们对此并不满意,我们希望构建一个可重复、自动化且可管理的构建和部署流程。 为此,我们需要一个 CI/CD 系统,我们在 Teamcity 的免费版本和 Jenkins 的免费版本之间进行选择,因为我们与它们合作,并且在功能集方面都适合我们。 我们选择 Teamcity 作为更新的产品。 当时我们还没有使用微服务架构,也没有想到会有大量的任务和项目。
我们想到了自己的系统
Teamcity 的实施仅删除了部分手动工作:剩下的就是创建 Pull 请求、按 Jira 中的状态提升问题以及选择要发布的问题。 Teamcity 系统无法再应对这种情况。 有必要选择进一步自动化的道路。 我们考虑了在 Teamcity 中使用脚本或切换到第三方自动化系统的选项。 但最终我们决定需要最大的灵活性,而这只有我们自己的解决方案才能提供。 这就是名为 Integro 的内部自动化系统的第一个版本的出现。
Teamcity 处理启动构建和部署流程级别的自动化,而 Integro 则专注于开发流程的顶级自动化。 有必要将 Jira 中的问题处理与 Bitbucket 中相关源代码的处理结合起来。 在此阶段,Integro 开始拥有自己的工作流程来处理不同类型的任务。
由于业务流程自动化程度的提高,Teamcity 中的项目和运行数量也有所增加。 于是,新的问题出现了:一个免费的 Teamcity 实例不够(3 个代理和 100 个项目),我们添加了另一个实例(另外 3 个代理和 100 个项目),然后又添加了另一个实例。 结果,我们最终得到了一个由多个集群组成的系统,这很难管理:
当出现第 4 个实例的问题时,我们意识到我们不能继续这样生活,因为支持 4 个实例的总成本已不再受到任何限制。 出现了购买付费 Teamcity 还是选择免费 Jenkins 的问题。 我们对实例和自动化计划进行了计算,并决定使用 Jenkins。 几周后,我们转向 Jenkins,并消除了与维护多个 Teamcity 实例相关的一些令人头疼的问题。 因此,我们能够专注于开发 Integro 并为自己定制 Jenkins。
随着基本自动化的增长(以自动创建 Pull Request、收集和发布代码覆盖率以及其他检查的形式),人们强烈希望尽可能放弃手动发布并将这项工作交给机器人。 此外,公司开始转向公司内部的微服务,这需要频繁发布,并且彼此分开。 这就是我们逐渐实现微服务自动发布的方式(由于流程的复杂性,我们目前正在手动发布单体应用)。 但是,正如通常发生的那样,出现了新的复杂性。
我们自动化测试
由于发布的自动化,开发过程加快了,部分原因是跳过了一些测试阶段。 这导致了暂时的质量下降。 这听起来微不足道,但随着发布的加速,有必要改变产品开发方法。 有必要考虑测试的自动化,向开发人员灌输对已发布代码和其中的错误的个人责任(这里我们谈论的是“接受头脑中的想法”,而不是罚款),以及决定通过自动部署发布/不发布任务。
为了消除质量问题,我们做出了两个重要决定:我们开始进行金丝雀测试,并引入对错误背景的自动监控,并对错误背景进行自动响应。 第一个解决方案可以在代码完全发布到生产环境之前发现明显的错误,第二个解决方案减少了对生产中问题的响应时间。 错误当然会发生,但我们大部分时间和精力不是用来纠正错误,而是用来尽量减少错误。
自动化团队
我们目前拥有 130 名开发人员,并且我们将继续
DevOps 负责 CIAN 站点的 Dev/Beta 环境、Integro 环境,帮助开发人员解决问题并开发扩展环境的新方法。 Integro 的开发方向涉及 Integro 本身和相关服务,例如 Jenkins、Jira、Confluence 的插件,还为开发团队开发辅助实用程序和应用程序。
DI 团队与平台团队协作,平台团队在内部开发架构、库和开发方法。 同时,CIAN 内的任何开发人员都可以为自动化做出贡献,例如,进行微型自动化以满足团队的需求,或者分享关于如何使自动化变得更好的好主意。
CIAN 的自动化层蛋糕
所有涉及自动化的系统都可以分为几个层次:
- 外部系统(Jira、Bitbucket 等)。 开发团队与他们合作。
- 集成平台。 大多数情况下,开发人员不直接使用它,但它是保持所有自动化运行的原因。
- 交付、编排和发现服务(例如 Jeknins、Consul、Nomad)。 在他们的帮助下,我们在服务器上部署代码并确保服务相互协作。
- 物理层(服务器、操作系统、相关软件)。 我们的代码在这个级别运行。 这可以是物理服务器,也可以是虚拟服务器(LXC、KVM、Docker)。
基于这个理念,我们在DI团队内部划分了职责范围。 前两个级别属于Integro开发方向的职责范围,后两个级别已经属于DevOps的职责范围。 这种分离使我们能够专注于任务,并且不会干扰互动,因为我们彼此靠近并不断交流知识和经验。
积分
我们先关注Integro,从技术栈开始:
- CentOS 7
- Docker + Nomad + Consul + Vault
- Java 11(旧的 Integro 单体应用将保留在 Java 8 上)
- Spring Boot 2.X + Spring Cloud 配置
- PostgreSQL 11
- 的RabbitMQ
- 阿帕奇点燃
- 卡蒙达(嵌入式)
- Grafana + Graphite + Prometheus + Jaeger + ELK
- Web UI:React(CSR)+ MobX
- SSO:钥匙斗篷
尽管我们拥有 Integro 早期版本的整体形式的遗产,但我们仍坚持微服务开发的原则。 每个微服务都在自己的 Docker 容器中运行,服务之间通过 HTTP 请求和 RabbitMQ 消息进行通信。 微服务通过 Consul 找到彼此并向其发出请求,通过 SSO(Keycloak、OAuth 2/OpenID Connect)传递授权。
作为一个现实生活中的示例,考虑与 Jenkins 交互,其中包含以下步骤:
- 工作流管理微服务(以下简称 Flow 微服务)想要在 Jenkins 中运行构建。 为此,他使用 Consul 查找与 Jenkins 集成的微服务(以下简称 Jenkins 微服务)的 IP:PORT,并向其发送异步请求以在 Jenkins 中启动构建。
- 收到请求后,Jenkins 微服务会生成并响应作业 ID,然后可使用该 ID 来识别工作结果。 同时,它通过 REST API 调用触发 Jenkins 中的构建。
- Jenkins 执行构建,并在完成后将包含执行结果的 Webhook 发送到 Jenkins 微服务。
- Jenkins 微服务收到 webhook 后,会生成一条有关请求处理完成的消息,并将执行结果附加到该消息中。 生成的消息被发送到RabbitMQ队列。
- 通过 RabbitMQ,发布的消息到达 Flow 微服务,Flow 微服务通过将请求中的作业 ID 与接收到的消息进行匹配来了解处理其任务的结果。
现在我们有大约 30 个微服务,可以分为几组:
- 配置管理。
- 信息以及与用户的交互(消息、邮件)。
- 使用源代码。
- 与部署工具(jenkins、nomad、consul 等)集成。
- 监控(发布、错误等)。
- Web 实用程序(用于管理测试环境、收集统计数据等的 UI)。
- 与任务跟踪器和类似系统集成。
- 针对不同任务的工作流程管理。
工作流程任务
Integro 自动化与任务生命周期相关的活动。 简单来说,任务的生命周期将被理解为 Jira 中任务的工作流程。 我们的开发流程有多种工作流程变化,具体取决于项目、任务类型以及特定任务中选择的选项。
让我们看看我们最常使用的工作流程:
图中,齿轮表示转场是由Integro自动调用的,而人形表示转场是由人手动调用的。 让我们看看任务在此工作流程中可以采取的几种路径。
在 DEV+BETA 上进行完全手动测试,无需金丝雀测试(通常这就是我们发布单体应用的方式):
可能还有其他过渡组合。 有时,可以通过 Jira 中的选项选择问题将采取的路径。
任务移动
让我们看一下任务在“DEV 测试 + 金丝雀测试”工作流程中执行的主要步骤:
1. 开发人员或PM 创建任务。
2. 开发人员将任务投入工作。 完成后,切换至 IN REVIEW 状态。
3. Jira 向 Jira 微服务发送 Webhook(负责与 Jira 集成)。
4. Jira 微服务向 Flow 服务(负责执行工作的内部工作流程)发送请求以启动工作流程。
5. Flow 服务内部:
- 审阅者被分配给任务(了解用户一切的用户微服务 + Jira 微服务)。
- 通过 Source 微服务(它知道存储库和分支,但不与代码本身一起工作),搜索包含我们问题的分支的存储库(为了简化搜索,分支的名称与问题一致) Jira 中的编号)。 大多数情况下,一项任务在一个存储库中只有一个分支;这简化了部署队列的管理并减少了存储库之间的连接。
- 对于每个找到的分支,将执行以下操作序列:
i) 更新主分支(用于处理代码的 Git 微服务)。
ii) 开发人员(Bitbucket 微服务)阻止该分支进行更改。
iii) 为此分支(Bitbucket 微服务)创建拉取请求。
iv) 有关新拉取请求的消息将发送到开发人员聊天室(通知微服务以处理通知)。
v) 构建、测试和部署任务在 DEV(用于与 Jenkins 配合使用的 Jenkins 微服务)上启动。
vi) 如果前面的所有步骤均成功完成,则 Integro 将其批准放入拉取请求(Bitbucket 微服务)中。 - Integro 正在等待指定审阅者批准 Pull 请求。
- 一旦收到所有必要的批准(包括自动化测试已顺利通过),Integro 就会将该任务转移到测试开发(Jira 微服务)状态。
6. 测试人员测试任务。 如果没有问题,则任务将转移到“准备构建”状态。
7. Integro“看到”该任务已准备好发布,并开始以金丝雀模式(Jenkins 微服务)进行部署。 发布准备情况由一组规则决定。 例如,任务处于所需状态、其他任务没有锁定、当前没有该微服务的活跃上传等。
8. 任务转为金丝雀状态(Jira 微服务)。
9. Jenkins 通过 Nomad 以金丝雀模式启动部署任务(通常为 1-3 个实例),并将部署通知发布监控服务(DeployWatch 微服务)。
10. DeployWatch 微服务收集错误背景并根据需要做出反应。 如果超出错误背景(自动计算背景标准),则会通过 Notify 微服务通知开发人员。 如果 5 分钟后开发人员没有响应(单击“恢复”或“保留”),则会启动金丝雀实例的自动回滚。 如果未超出后台,则开发人员必须手动启动任务部署到生产(通过单击 UI 中的按钮)。 如果开发人员在 60 分钟内未启动生产部署,则出于安全原因,金丝雀实例也将回滚。
11. 启动生产部署后:
- 任务已转移到生产状态(Jira 微服务)。
- Jenkins 微服务启动部署过程并通知 DeployWatch 微服务有关部署的信息。
- DeployWatch 微服务检查生产中的所有容器是否已更新(在某些情况下并非所有容器都已更新)。
- 通过 Notify 微服务,有关部署结果的通知将发送到生产。
12. 如果检测到不正确的微服务行为,开发人员将有 30 分钟的时间开始从生产中回滚任务。 过了这个时间,任务就会自动合并到master(Git微服务)中。
13. 成功合并到master后,任务状态将变为已关闭(Jira微服务)。
该图并不假装非常详细(实际上还有更多步骤),但它允许您评估流程的集成程度。 我们认为这种方案并不理想,正在改进自动发布和部署支持的流程。
接下来是什么
我们对自动化的发展有很大的计划,例如,在整体发布过程中消除手动操作,在自动部署过程中改进监控,以及改进与开发人员的交互。
但我们现在就到此为止吧。 我们在自动化审查中粗略地讨论了许多主题,有些主题根本没有涉及,所以我们很乐意回答问题。 我们正在等待有关详细内容的建议,请在评论中写下。
来源: habr.com