大家好!
多年来,Netracker一直在为电信运营商的全球市场开发和提供企业应用程序。 这种解决方案的开发非常复杂:数百人参与项目,而活跃项目的数量则为数十个。
以前,这些产品是单片产品,但现在我们有信心向微服务应用程序迈进。 DevOps面临一项艰巨的任务-提供这种技术飞跃。
结果,我们获得了成功的组装概念,我们希望将其作为最佳实践加以分享。 带有技术细节的实现描述将非常丰富;我们不会在本文的框架内进行描述。
在一般情况下,组装就是将某些工件转换为其他工件。
谁会感兴趣
那些向完全第三方组织提供现成软件并从中获得报酬的公司。
如果没有外部交付,则开发结果如下所示:
- 工厂的IT部门为其企业开发软件。
- 该公司从事外包给外国客户。 客户在自己的Web服务器上独立编译和操作此代码。
- 该公司以开放源代码许可证的形式向外部客户提供软件。 因此,大多数责任得以减轻。
如果您没有外部电源,那么下面写的很多内容似乎都是多余的甚至是偏执的。
实际上,必须按照所使用的许可证和加密的国际要求进行所有操作,否则至少会产生法律后果。
违规的一个示例是从具有GPL3许可证的库中获取代码,并将其嵌入到商业应用程序中。
微服务的出现需要改变
我们在组装和交付整体应用方面获得了丰富的经验。
几台Jenkins服务器,数千个CI作业,几条基于Jenkins的全自动装配线,数十名专门的发布工程师,其自己的配置管理专家组。
从历史上看,公司的方法是这样的:开发人员编写源代码,DevOps发明并编写组装系统的配置。
结果,我们为企业生态系统中的操作设计了两种或三种典型的装配配置。 从示意图上看,它看起来像这样:

构建工具通常是ant或maven,并且某些东西是通过可公开获得的插件实现的,有些东西则是自己编写的。 当公司使用少量技术时,这很好用。
微服务与单片应用程序的不同之处主要在于各种技术。
事实证明,至少每种编程语言都有很多汇编配置。 集中控制变得不可能。
需要尽可能简化汇编脚本并使开发人员能够独立编辑它们。
除了简单的编译和打包(在图中的绿色 )之外,这些脚本还包含许多用于与公司生态系统集成的代码(在图中的红色 )。
因此,决定将程序集视为“黑匣子”,其中“智能”程序集环境可以解决所有问题,但编译和打包本身除外。
在工作开始时,尚不清楚如何获得这样的系统。 为DevOps任务制定架构决策需要经验和知识。 如何获得它们? 可能的选项如下:
- 在Internet上搜索信息。
- 拥有DevOps团队的经验和知识。 为此,使这组程序员具有丰富的经验是很好的。
- 在DevOps团队之外获得的经验和知识。 公司中的许多开发人员都有很好的想法-您需要听听他们的想法。 交流是有帮助的。
- 我们发明和实验!
我需要自动化吗?
要回答这个问题,您需要了解我们的组装方法处于什么发展阶段。 通常,任务需要经历以下几个级别。
- 无意识水平

我们需要每周发布一个程序集,我们的员工做得很好。 这很自然,为什么要谈论呢?
- “工匠”的水平,最终转变为“道奇”的水平

有必要每天稳定地生产两个组件并且没有错误。 我们有Vasya,他做得很酷,只有他一个人度过这段时间。
- 工厂级别

事情已经过去了。 您每天需要20次集会,Vasya无法应付,现在已经有一个由10个人组成的团队。 他们有老板,计划,假期,病假,动力,团队建设,培训,传统和规则。 这是一个专业,必须对其工作进行研究。
在此级别上,任务与具体执行者分离,从而变成了一个过程。
结果将获得清晰,可行,可运行和可纠正的数百次文本描述。
- 自动化生产水平

装配的现代要求正在增长:一切都应该快速,可靠,每天必须提供800个装配。 这很关键,因为如果没有这样的数量,公司将失去竞争优势。
正在进行的自动化成本很高,并且有两个合格的DevOps可以使流程保持运行。 进一步缩放不再是问题。
并非每个任务都应该达到自动化的最后阶段。
通常,一个具有命令行的工匠会轻松有效地解决问题。
自动化“冻结”了流程,降低了运营成本并增加了变更成本。
您可以立即进行汽车组装,但是系统会很不方便,无法与业务要求保持一致,结果将变得过时。
什么是装配,为什么现成的装配系统不能解决问题

我们使用以下分类来确定程序集聚合级别。
L1 大型应用程序的一小部分独立部分。 它可以是一个组件,微服务或一个辅助库。 L1组装是线性技术问题的解决方案:编译,打包,使用依赖项。 Maven,gradle,npm,grunt和其他构建系统在此方面做得很好。 有数百个。
L1组装必须使用现成的第三方工具完成。
L2 +。 集成实体。 L1实体被组合成更大的形式,例如成熟的微服务应用程序。 这些应用程序中的几个可以捆绑为一个解决方案。 我们使用“ +”号,因为根据程序集聚合的级别,可能会分配L3甚至L4的级别。
在第三方世界中,此类程序集的一个示例是Linux发行版的准备。 元包在那里。
除了相当复杂的技术任务(例如ru.wikipedia.org/wiki/Dependency_hell )。 L2 +组件通常是最终产品,因此具有许多过程要求:权利体系,负责人的固定,没有法律错误,提供各种文档。
在L2 +上,过程要求由自动化确定优先级。
如果自动解决方案因对感兴趣的人而言不方便而无法使用,则将无法实施。
L2 +组装很可能由专门针对公司流程定制的专有工具执行。 您是否认为Linux软件包管理器就是这样?
我们的最佳实践
基础设施
铁的永久可用性
整个组装基础结构位于公司网络内部的封闭服务器上。 在某些情况下,商业云服务是可能的。
自治权
在所有CI流程中,Internet都不可用。 所有必要的资源都在内部进行镜像和缓存。 部分甚至是github.com(谢谢,npm!)这些问题中的大多数都由Artifactory解决了。
因此,在从Maven Central删除工件或关闭流行的存储库时,我们会保持冷静。 有一个示例: community.oracle.com/community/java/javanet-forge-sunset 。
镜像显着减少了组装时间,从而释放了公司的Internet通道。 较少的关键网络资源可提高构建稳定性。
每种工件的三个存储库
- 开发是任何人都可以发布任何来源的工件的存储库。 在这里,您可以从根本上尝试全新的方法,而无需从第一天开始就将其适应企业标准。
- 登台是仅用组装管道填充的存储库。
- 发布-单个组件,可以外部交付。 它充满了带有手动确认的特殊传输操作。
30天规则
从开发和存储库中,我们删除30天之前的所有内容。 通过花费有限的服务器磁盘空间,这有助于确保每个人都有平等的发布机会。
发行版将永久存储,必要时可进行归档。
清洁装配环境
通常在组装后,辅助文件会保留在系统中,这可能会影响其他组装过程。 典型示例:
- 最常见的问题是由于一个不正确的程序集而损坏的缓存(如何处理缓存,如下所述);
- 某些实用程序,例如npm,会将服务文件保留在$ HOME目录中,这些文件会影响这些实用程序的所有后续启动;
- 一个特定的程序集可以在/ tmp分区中花费所有磁盘空间,这将导致环境的普遍不可用。
因此,最好放弃统一环境,而使用docker容器。 容器应仅包含具有固定版本的特定程序集所必需的软件。
DevOps维护程序集docker映像的集合,该映像不断更新。 最初大约有六个,然后是30岁以下,然后我们从软件列表中设置了自动图像生成。 现在只需指定诸如require('maven 3.3.9','python')之类的要求-环境就准备就绪。
自我诊断
不仅需要组织用户对请求的支持,我们还必须自己分析我们自己系统的行为。 我们会不断收集日志,在日志中查找显示问题的关键字。
在“实时”系统上,编写20-30个正则表达式就足够了,这样,对于每个程序集,您都可以在以下级别分辨出其下降的原因:
- Git服务器崩溃
- 磁盘空间已用完;
- 由于开发者的过错而导致的构建错误;
- Docker中的已知错误。
如果有东西掉落,但未检测到单个已知问题,这是补充口罩收集的机会。
然后,我们去找用户,说他有一个构建,可以用这种方式修复它。
您会惊讶于用户没有报告支持方面的问题。 最好事先在方便的时间修理它们。 通常,会在两个星期内忽略一个较小的发布错误,而在星期五晚上,事实证明这会阻止外部输出。
我们仔细选择组装所依赖的系统
通常,理想情况下,要确保装配完全自治,但通常这是不可能的。 对于基于Java的程序集,您至少需要Artifactory进行镜像-有关自主性,请参见上文。 每个集成系统都会增加发生故障的风险。 希望所有系统都在体面的HA模式下工作。
流水线接口
单一接口用于调用程序集
我们使用一个系统进行任何类型的组装。 所有级别(L1,L2 +)的程序集均由程序代码描述,并通过一个詹金斯作业进行调用。
但是,这种方法并不理想。 最好使用Jenkins作业自动生成机制:例如1个作业= 1个git存储库或1个作业= 1个git分支。 这将实现以下目的:
- 詹金斯工作页面上的一个故事不会混淆来自异类程序集的日志;
- 实际上,您可以为团队或开发人员分配舒适的工作; 通过调整junit,cobertura和声纳的结果图表,可以增强舒适感。
自由选择技术
开始构建是对bash脚本“ ./build.sh”的调用。 然后-完成业务任务所需的任何汇编系统,编程语言和其他所有内容。 这提供了一种组装为黑匣子的方法。
智能帖子
组装线从黑匣子中截取出版物,并将其放入公司存储中。 为此,将自动解决无聊的问题,例如生成docker映像名称和选择正确的发布库。
登台和发布存储库始终有序。 需要支持不同类型的出版物的具体信息:maven,npm,文件,docker。
程序集描述符
Build.sh描述了如何编译代码,但这对于程序集容器来说还不够。
您还必须知道:
- 使用什么组装环境;
- build.sh中可用的环境变量;
- 将发表什么出版物;
- 其他特定选项。
我们选择了一种方便的方式来以类似于.gitlab-ci.yaml的yaml文件的形式描述此信息。
装配参数化
用户可以指定任意参数,而无需在程序集开始时立即执行git commit命令。
我们通过直接从Jenkins作业界面定义环境变量来实现这一点。
例如,我们将依赖库的版本转换为这样的汇编参数,并且在某些情况下,将此版本重新定义为一些实验性版本。 如果没有这种机制,用户将不得不每次执行“ git commit”命令。
系统可移植性
您不仅必须能够在主CI服务器上而且还可以在开发人员的计算机上重现组装过程。 这有助于调试复杂的构建脚本。 另外,代替Jenkins,有时使用Gitlab CI更方便。 因此,构建系统必须是独立的Java应用程序。 我们将其实现为gradle插件。
一个工件可以以不同的名称发布。
可能同时出现两个相反的发布要求。
一方面,对于长期存储和发布管理,有必要确保已发布工件名称的唯一性。 这至少将防止工件被覆盖。
另一方面,有时有一个带有固定名称(如最新名称)的实际工件很方便。 例如,开发人员不必每次都知道依赖项的确切版本,您只需使用最新版本即可。
在这种情况下,工件适合每个人,使用两个或多个名称发布。
例如:
- 带有时间戳或UUID的唯一名称-适用于需要准确性的人员;
- 名称“最新”-适用于始终选择最新代码的开发人员;
- 名称“ <主要版本> .x-latest”用于准备接收最新版本的相邻团队,但只能在特定专业的框架内进行。
Maven对SNAPSHOT的处理方式与之类似。
更少的安全限制
每个人都可以开始组装。 这不会对任何人造成伤害,因为程序集只会创建工件。
法律合规
控制组装过程中的外部交互
装配体不能使用其工作过程中禁止使用的任何东西。
为此,实现了网络流量的记录和对文件缓存的访问。 我们以URL列表的形式获取程序集的网络活动日志,其中包含接收到的数据的sha256散列。 此外,每个URL都经过验证:
- 静态白名单;
- 允许工件的动态数据库(例如,对于maven-,rpm-,npm-依赖性)。 每种成瘾被单独考虑。 自动许可或禁止使用可能会奏效,与律师的漫长讨论也可能会开始。
已发布工件的透明内容
有时,任务是在任何程序集中提供第三方软件的列表。 为此,他们制作了一个简单的成分分析器,可以分析装配中的所有文件和档案,通过哈希识别第三方并做出报告。
发出的源代码无法从GIT中删除
有时您可能需要查看两年前编译的二进制工件来找到源代码。 为此,必须在Git中使用外部输出自动分配标签,并禁止将其删除。
物流与会计
所有程序集都存储在数据库中。
我们将Artifactory中的文件存储库用于这些目的。 它包含所有支持信息:启动它的人,检查的结果是什么,发布了哪些工件,使用了哪些git hash等。
我们知道如何尽可能准确地复制装配体
根据组装的结果,我们存储以下信息:
- 所收集代码的确切状态;
- 发射使用什么参数;
- 调用了什么命令;
- 发生了什么对外部资源的访问;
- 二手装配环境。
如有必要,我们可以准确地回答其组装方法。
用JIRA票证进行组件的双向通讯
确保能够解决以下问题:
- 进行组装时,创建其中包含的JIRA票证列表;
- 在JIRA票证中写出其中包含的程序集。
在程序集和git commit之间提供了紧密的双向通信。 然后,从注释文本中,您可以找到JIRA的所有链接。
速度
装配系统缓存
缺少Maven缓存可能会使构建时间增加一个小时。
高速缓存违反了装配环境的隔离和装配的清洁度。 可以通过为每个缓存的工件确定其来源来解决此问题。 每个缓存文件都与一个https链接相关联,该链接是从该链接下载的。 此外,我们将读取缓存作为网络地址进行处理。
网络资源缓存
公司的地域增长导致需要在各大洲之间传输300 MB的文件。 花了很多时间,特别是如果您必须经常这样做。
Git存储库,组装环境的docker映像,文件存储-所有内容都需要仔细缓存。 好吧,当然要定期清洁。
组装-尽可能快地进行其他所有操作-然后
第一个阶段:我们进行组装,然后立即进行操作,而没有不必要的手势。
第二阶段:验证,分析,会计和其他官僚机构。 这可以在单独的詹金斯工作中完成,并且没有严格的时间限制。
结果如何
- 最主要的是, 程序集对于开发人员来说已经很清楚了 ,他们自己可以开发和优化它。
- 已经创建了用于构建依赖于组装的业务流程的基础:安装,交付管理,测试,发布管理等。
- DevOps团队不再编写汇编脚本:开发人员去做。
- 复杂的公司要求变成了带有最终检查清单的透明报告。
- 任何人都可以仅通过一个接口调用build.sh来构建任何存储库。 他只需指定源代码的git坐标就足够了。 此人可以是团队经理,质量检查/ IT工程师等。
还有几个
- . 15 Jenkins job build.sh. 15 docker-, , . . .
- . . 2200 . — on-commit-.
- 300 git-, .
- 30 , (25 ) — docker.
- , :
- glide, golang, promu;
- maven, gradle;
- python & pip;
- ruby;
- nodejs & npm;
- docker;
- rpm build tools & gcc;
- 使用ADT构建Android
- 商业公用事业;
- 我们的旧产品的实用程序;
- DIY构建脚本。