非潮人(也就是C和c++程序员)的DevOps和依赖管理- Johannes Nicolai, GitHub
短的、可重复的构建步骤是任何持续交付管道的必备条件。在嵌入式和跨平台的C和c++世界中,声明式依赖管理仍然相对较新,并且是快速、可重复和安全发布的一大障碍。这个演讲展示了为什么包管理是一件好事情,以及它是如何让人满意的。io是一个独立于平台的开源包管理器,管理C和c++库的依赖关系。演示了如何在几分钟内使用conan在许多平台上构建一个有趣的GitHub应用程序。io, AppVeyor, Travis CI和Jenkins。
视频记录
好吧。欢迎大家参加面向非潮人的DevOps讲座,也就是C和c++程序员。事实上,非潮人的部分可能需要一点解释。但在我们开始之前,让我快速地介绍一下我自己。
我是Johannes Nicolai[听不清00:00:22]GitHub。我在GitHub担任解决方案工程师。解决方案工程师,他们与社区和客户讨论各种与开发相关的挑战。这些是我负责的一些客户。我主要在讲德语的地区工作,我想你们很多人都知道,德国在工程方面有很好的传统。我们有许多汽车制造商,其中许多客户生产咖啡机,MOT COT设备。因此,许多客户实际上在嵌入式领域做了一些事情也就不足为奇了。在C和c++空间中做一些事情。
我从德国坐飞机来参加这个会议,在法兰克福机场,我看到了这个广告,“为什么要在德国投资?”问问我们的机器吧。”其实我觉得这是一个很愚蠢的广告。不应该问机器,它们不会说话。你应该问问为这些机器编写代码的人。
我问了很多我的客户,也问了一些这里没有提到的人,关于他们在使用这些机器进行DevOps或持续交付时所面临的挑战。这张幻灯片上是这些人的匿名引用。
我本可以提到你们中的一些人可能会同情这样的陈述。通常从持续集成的想法开始,这是任何DevOps或持续交付的先决条件。在嵌入式C和c++空间中,构建需要72小时并不罕见,如果算上整个管道,则需要更多时间。
你只需要单独进行一次提交,然后用它进行测试,这种想法通常不存在。只有一个构建,它包含了你的提交,也包含了数百个同事的提交。如果您深入研究,为什么构建实际上需要那么长时间,您会了解到所有这些包都与其他包有很多依赖关系。这些依赖关系是从头开始一遍又一遍地构建的。
你会问,为什么他们总是从零开始做东西?为什么是以前的样子?有这么多不同的排列和优化,所以我们不能做任何其他事情。然后你会问,你怎么把所有这些依赖关系结合在一起?他们说,我们有定制的构建脚本。
我说,等一下。您有自定义构建脚本,您如何确定,例如,如果您在开放SSL中存在安全漏洞,心脏出血攻击,哪些组件受到影响?然后我得到了一个非常有趣的答案,那就是,我们雇佣实习生,这些实习生,他们看那些填满的脚本,然后找出哪些是受影响的。
我不知道我是否应该让你们举起手来看看这些引语是不是听起来很熟悉还是太尴尬了。好吧。有些人很勇敢。谢谢你。实际上,我也问过我在美国的同事,这是否只是德国的事情,事实并非如此。美国的许多汽车制造商和其他行业也面临着类似的挑战。
现在是非潮人的部分。有一个明确的解决方案可以解决这个问题,那就是声明式包管理。在这里,您不需要一直从头开始构建东西,您只需以声明性的方式说明您依赖的是哪些包。然后从那里得到二进制文件。您不需要将它们直接放到Git中。你有语义版本控制和所有的东西。任何使用Java或Java Script的人都知道我在说什么,但是当我最初问到这是否可以在C和c++空间中实现时,我得到的答案是:“包管理器,这不是那些时髦语言的东西吗?”Java Script, Java,因为它们很简单。它们只有一个二进制文件,对应一个版本。
在C和c++中,这取决于操作系统。这取决于编译器的灵活性。这取决于体系结构。32位,64位,静态链接,非静态链接,所有这些都很重要。即使库的版本相同,也有不同的二进制文件。
所以,谢谢,但是不,谢谢软件包管理器,这是一个东西,我们自己做了一些东西,但没有我们可以使用的东西,我想,我不是一个潮人。我认为您可以使用包管理器。[听不清00:04:57]有像柯南这样的包管理器。所以接受挑战。
这不是关于使用这些技术或指出这些技术,而是关于你如何说服你的同事,你如何说服你的老板,采用C和c++的包管理器,这真的很值得,而不仅仅是蛋糕上的樱桃?
从这次演讲的议程来看,我将花三分之一的时间来讨论辩论。你如何说服你的老板,你如何说服你的同事,包管理器是一个好东西,而不仅仅是一个漂亮的东西。这些论点在我一开始介绍的公司中确实奏效了。
第二部分是柯南IO。显然,这是一个很棒的包管理器,它是根据C和c++的要求指定的。我想,我在做一个有趣的例子,不是一个hello world的例子,而是一些接近GitHub和我们核心理念的东西。
最后但并非最不重要的一点是,那些不喜欢C和c++的人有一个观点,你需要所有这些不同的排列,而不仅仅是一个工件的一个版本。我将向你们展示如何用ESHA管道和其他CI系统轻松构建这样的东西。让我们开始吧。
你如何说服那些只会说:“这只是为潮人准备的,我们并不真的需要它。”我想说现在的任何公司都是敏捷的,我今天从Nicole那里学到了一个很好的术语,完全敏捷。所以基本上是假装你很敏捷。
如果你真的想付诸行动,你可以让你的经理或同事看看敏捷宣言。敏捷宣言的第一句话说:“我们的最高优先级是通过早期和持续交付有价值的软件来满足客户。”
这有两个重要方面。第一个是持续交付。你总是想要运送薄片的产品。你不想让风险在几个月,甚至几年里积累起来。你可以为你的公司做一个试金石,看看你是否真的很敏捷,你可以问问你是否还在庆祝你的发布。如果你确切地知道你在哪个版本上,如果你庆祝它们,或者如果你也不得不阻止你的假期,如果有一个月没有人可以去度假,因为这个风险积累得太多了,你不知道会发生什么,你不是真正的敏捷,你没有做任何与持续交付相关的事情。
发行必须是无聊的,它们必须是一件无关紧要的事情,没有什么值得庆祝的。这并不意味着您必须总是向您的客户交付这些版本,但它至少必须是可发布的。这只有通过包管理和适当的依赖管理才能实现,稍后您将看到。
另一个方面是关于这个价值。即使顾客说他们想要某样东西,但在他们尝试的那一刻,它不一定是他们需要的,这并不是一个秘密。我认识一些汽车客户,他们花了两年的时间来开发导航系统的某些功能,当这个功能出来的时候,客户只是说:“我手机上的谷歌地图已经有了这个功能,而且它的效果要好得多。”其实我唯一需要做的就是把我的谷歌地图装进你的头显里。给我这个功能。”
所以基本上两年的开发努力都白费了。如果你有能力更频繁地向更好的客户群体发布产品,那么你也有能力纠正路线,因为你的客户并不比你更了解他们想要什么。他们只有在使用的时候才会发现。如果你想做B测试和航向修正之类的事情,那么包管理就很重要。
为什么它如此重要?如果你看看DevOps圣经中的这七个原则,我想没有一个DevOps演讲不引用这本书是不完整的。它的一个方面是,只构建一次二进制文件。
只构建一次二进制文件是什么意思?它指的是你的管道中的各个阶段。其思想是,在第一阶段编译所需的所有内容,然后将其用作不可变工件,以便您可以确保在针对该工件运行测试时,该工件与您在生产中使用的工件是相同的。
我见过汽车制造商在真正的汽车中遇到问题,因为他们在汽车的最新阶段使用了不同的编译器,所有的性能测试都是针对不同的编译器版本进行的。这只有在不可变构建时才有可能防止。如果您构建了一些东西,然后可以真正地相信您所做的测试实际上是针对您要在生产中推出的东西进行的。
第二个方面是时间。这是一个标准的部署管道。现在想象一下,您在这些绿点中从头开始编译所有内容。那么就很容易解释为什么要花72小时,甚至更长时间。整个反馈循环,整个你可以纠正的想法,整个你可以单独测试的想法,发布是一个非事件,你可以忘记它。对于持续交付,您需要一些适当的工件管理,否则您只能永远等待,直到您看到结果。
好了,现在有些人可能会说,这有什么大不了的?不变性。我明白了。我编译所有内容,然后将其检入Git。当汽车或OEM客户第一次开始采用Git时,我们通常会接到这样的电话:“我们的服务器崩溃了。”原因是,假设这是一个开放的SSL库。它有100兆字节,虽然更小,但这是一个例子。然后你有了这个心脏出血漏洞,你修复了它,然后你有了另一个功能修复。
那么您的Git存储库中已经有301兆字节了。只针对这个库的一个版本。Git的设计是,如果您进行克隆,您将获得您的二进制文件的每个版本。你为任何[听不清00:11:31]或任何客户或开发人员检查东西获得300兆字节。现在想象一下,你没有三个版本,而是100个版本,你有不同的构建排列。
然后我们看到客户的每一个克隆都是800g。这不仅对您的网络有问题,而且对Git服务器的主内存也有问题。所以不管是GitHub还是其他服务器,它们都会崩溃。如果您启用或允许开发人员从Perforce或Subversion迁移并保留二进制文件,那么您将面临无尽的痛苦。
这就是依赖管理是一件好事的原因。我想每个人,甚至是目前没有使用标准包管理器的人,都知道它在理论上是一件好事,但是很多人并不知道那些额外的特性。这些现代的包管理器,它们做的不仅仅是下载一些二进制文件。他们确切地知道所有不同的构建系统和操作系统。
想象一下,有一个新的GCC版本,或者有一个新的Mac OS编译器。然后通常这些公司开始构建和修改他们的自定义构建脚本,通常甚至多次,所以检测新的编译器版本,或者你只是使用像Conan IO这样的东西,社区会注意到所有这些都是透明处理的。
那些现代的包管理器,他们也知道你所依赖的软件的许可证。您甚至可以阻止某些许可证,因此您不必让实习生弄清楚是否通过某些魔法包括预处理宏,您包含了GPL组件。你把这个从盒子里拿出来,只是作为一份报告。
安全警报也是如此。你也可以定义自己的安全警报,或者功能缺陷,如果你愿意的话,可以立即看到所有受影响的组件,这样实习生就可以做更重要的事情,而不仅仅是调试,因为调试很容易出错。
然后,如果你有任何SDK,假设你正在管理一个物联网集群,那么数百甚至数百万的设备,无论是汽车,还是烤面包机,都必须下载你的软件。您不希望从单个服务器下载此文件。这是不可能的。你想要自动地把这个分布到全球。您希望有适当的访问出口限制。你想要有报道。你想要签名。所有这些都包含在现代包管理器中。如果你只是使用最先进的技术,你就不需要从头开始构建这样的东西。
最后但并非最不重要的是,这可能是我最喜欢的功能。我见过这样的情况,你基本上是在使用一个新版本的库,它只是崩溃了,你只是得到一个[听不清00:14:25]每当你开始,你不知道为什么。源代码看起来与旧版本完全相同。然后它就变成了一个不同的编译器选项优化级别。
如果您有这些元数据,包括编译器版本、用于生成每个库的优化,那么您可以将它们在不同版本之间进行比较,从而使故障排除更加容易。
这些包管理器也有一个内置的推广概念,这意味着你可以,例如,在探索阶段给你的开发人员自由使用任何类型的C和c++库,然后你说,现在它要进入实际生产,现在我想把它锁定在10个包,我真的很信任。
所有这些东西都是包管理器的基本功能之上的,接下来的幻灯片我就不讲了。更重要的是,如果你不知道这些日期,如果你想做一个内部演示,你的公司,你只需要材料。就像为什么子模块不是银弹一样。就像LFS不能替代适当的依赖项管理一样。
简而言之,它们有一定的功能,但它们没有任何像安全警报、语义版本控制、依赖和检测或许可证检测这样的好东西,而且它们不知道交易,或者Git LFS也不知道任何与C或c++相关的不同维度。
让我们换个话题,多讨论一下Conan,它是专门为C和c++程序员的需要而设计的包管理器。
一些推动这个项目的人就在这个房间里,他们知道的比我多得多。他们也在楼上关于柯南的展示会上。所以如果你有更多问题就问他们。他们基于几十年来在C和c++领域的大量咨询经验开发了这个。他们知道他们的东西,他们确保它在多种操作系统上工作,因为它是用Python编写的。它是跨平台的,包括交叉编译。它了解大量不同的构建系统、操作系统以及它们之间的所有细节。这不仅仅是一个爱好项目。事实上,JFrog获得了柯南的商业部分,它也为它提供支持。
Conan与其他包管理器的工作方式没有什么不同,因为它只是计算出依赖关系图。它也有冲突解决方案,所以在这张图片中你可以看到,例如,开放SSL和boost,它们都依赖于[Zlive 00:17:11]和Conan协商,他们得到一个版本,其他消费者都可以接受,这样你就不会遇到任何有趣的链接问题。
Conan有一个免费的开源参考实现,它为这些包提供服务,但它也被内置到JFrog Artifactory和JFrog Bintray中,并且几乎所有单独的CI CD解决方案都有DSL支持。当我来到Azure管道的现场演示时,你会看到更多。
柯南的工作方式是,每当你要求一个库时,比如说,这台Mac的lip boost,然后是64位,然后它就会使用所有这些不同的维度,创建一个校验和,并检查柯南服务器中是否已经有二进制文件。如果是,它只是下载它。如果没有,柯南也有这种所谓的构建食谱的概念。因此,如果它们还没有在上游存储库中,也可以在本地构建。它不仅仅是二进制文件,它也是一个如何生成二进制文件的配方。
所以,我说,这是足够的理论。让我们来看一个例子。当我在GitHub工作时,我想我不应该只做简单的hello world项目,这更接近GitHub。在GitHub,不管你在哪个团队工作,有12条共同的原则是我们一直遵循的。这有点像地面上的指挥,只是做正确的事情。我们称之为GitHub的禅宗。我可以很容易地讲一整个小时的GitHub禅宗,但不要害怕我不会这么做。
我想提的唯一一件事是,你也可以得到GitHub的禅宗,如果你调用我们的API, APIGotHub/Octocat,你总是得到一个随机的GitHub禅宗在这个ASCII艺术。直到速度快了才算发货。
所以现在的想法是,让我们构建一个Conan应用程序,它使用C库来获取GitHub的随机Zen,但只有在一个好的存储库中才能这样做。如果工作目录是一个很好的存储库,那么给我们这个漂亮的Octocat ASCII艺术,否则不要。
这个项目被称为GitHub的禅宗,并且在GitHub上。在存储库中,请听我说,它准确地描述了它是如何工作的以及输出应该是什么。您还可以看到这个依赖关系图,源代码非常简单。它只是包含了禅宗的GitHub,自定义库和唇套件。
如果我们在一个好的存储库中,我们在这个If语句中计算出来,然后调用GitHub main函数的Zen。否则,我们只是说您必须切换到每个存储库。
我们在Conanfile.TXT中设置了对Conan库的依赖,这里你可以看到我们依赖于GitHub库的自定义Zen和一个lip kit。也可以指定一个版本范围。尽管这在C和c++世界中是很不寻常的。
现在让我们从柯南中获取这些依赖项。这些步骤在GitHub存储库中有文档。我首先克隆一些东西。这是可见的吗?是的,我想是的。我只是把它弄大一点。我正在克隆这个存储库并创建一个构建目录。这是我做的第一件事。然后我要做的第二件事是,我想在lip kit中安装这些依赖项到lips和GitHub。柯南安装,这实际上会失败。
它失败的原因是lip kit是一个公共项目,一个公共存储库,它在他们的标准柯南中央人工存储库中,这不是GitHub中自定义lips的情况。我刚刚为自己构建了这个,所以我必须在我的Bintray中添加另一个柯南遥控器,我的自定义库的包配方在那里。在公司内部,你通常只使用定制遥控器来确保控制的不变性。[听不清00:21:35]
现在我编辑这个远程,现在我试图再次安装依赖项,这一次它应该实际工作。它正在下载这些依赖项。如果我只是想获得配方,然后自己在本地构建一些东西,我也可以使用这个minusminusbuild参数,然后它会在GitHub中为我本地构建lips。
现在我已经安装好了所有内容,我必须以某种方式将其包含在make文件中。为此,我正在为发布版本生成一个代码片段,这个代码片段可供任何感兴趣的人使用,然后就包含在我的主C make文件中。我基本上有一个宏,它初始化柯南,然后它将填充柯南生活环境变量,然后我将其作为链接目标添加到它。
最后但并非最不重要的是,我会构建整个东西并进行测试。我转到bin目录并调用GitHub的Zen,这只是告诉我,我没有像预期的那样在一个好的存储库中。让我们创建一个好的存储库,然后重复,现在开始。保持它在逻辑上令人敬畏。
我所做的所有步骤,也是幻灯片的一部分。就像这个依赖关系图一样,正如你所看到的,也有一些间接依赖关系被引入。您将看到如何添加自定义的Conan遥控器。如何克隆存储库。如何安装依赖项。语义版本是如何工作的。如何从头开始构建某些库。如果您只对构建配方感兴趣,那么您可以了解如何更改这些维度,是希望静态链接还是动态链接,以及如何调用帮助。
说得好。当您需要Conan的任何帮助时,它是一个文档丰富的开源社区。所以如果你去GitHub查看文档,你会找到你需要的一切。
现在我们已经看到了如何实际使用柯南依赖项。那么,如何创建一个可以使用的库呢?有人把这叫做Conanify图书馆。来一个柯南版的。我只是展示了这个基于GitHub的Zen, lips和GitHub库,它基本上是一个漂亮的唇卷包装。如果你看一下lips和GitHub的源代码,它基本上只是使用lip curl来调用这个API GitHub.com/Octocatendpoint。
但它确实有一个包含构建配方的清单。只要找到它。我在GitHub中展示得最好。这基本上是清单,它说,我是这个库,这是我的版本,这是我的许可证,这些是选项。我支持静态和动态链接选项。我想包含源代码,这是我所依赖的依赖项。
我不需要从头开始。如果你安装了柯南,有一个很好的命令叫做Conannew,它会自动为你创建一个脚手架,这样你的库的清单就会自动创建。更好的是,有一个叫做Bin Crafters的社区,在那里你可以找到数百个标准库,这些库已经以conanfied的方式出现了。如果您想知道如何启用启用SSL,请不要自己动手,也不要启用SSL,也不要启用SSL。这些都已经在GitHub上了。只要搜索柯南和你的图书馆的名字,你可能会找到一个很好的例子,这是如何工作的。
但我们还是在当地建一个柯南图书馆吧。在本例中,我只是将lips和GitHub、存储库CD克隆到其中,运行测试用例,并构建整个解决方案。如果成功的话,我们将看到另一只可爱的八爪猫。所以这是有效的。最后一点是将它上传到我们的远程,以便其他人也可以使用这个库。
这里可能会说,好吧,约翰内斯,这有什么大不了的?你只需要为你的目标架构构建一个版本的库,目标架构是Mac,这通常不是他会在生产中嵌入的东西。我怎样才能为我们公司需要的所有操作系统和排列进行构建?如何涵盖32位、64位、Mac、Windows、GCC、5.4、5.3等所有这些排列?
我喜欢在Azure管道的基础上演示构建所有这些东西。这一切都从GitHub开始。我将进入一个池请求,我创建了这个热身的东西,这个唇和GitHub的热身版本。
你可以看到,构建定义,我是如何想出这样一个管道的,是源代码本身的一部分。如果你知道声明式管道,请举手示意?大概70到80%吧。这在C和c++之外是相当标准的。至少从我的经验来看,在C和c++社区中,这仍然是不寻常的。正如我们在主题演讲中听到的那样。
GitHub,你想要安全地实验你的构建管道。有多少次你去找你的发布经理说,“我有一个好主意,如果我们只使用这个和这个技巧,我们可以将构建时间缩短一半。”他们会说:“好主意,但我们两周后就要发布了。”两周后,你还有别的事要做。因此,如果你的源代码是构建管道版本,你就可以安全地在拉取请求中进行实验。
你可以看到我的第一个实验实际上失败了,但是在第二次提交中,在这个pull请求中,你可以看到所有的测试都成功地运行了。因此,请处理您的构建管道代码。如果我们看一下Azure的声明式管道,你会发现我们正在做一个指标构建。所以我们同时在Linux, Mac和Windows上进行开发。您还指定要在其上测试的所有不同的体系结构。最后,你基本上只是调用构建脚本,它在这里,第58行完成,我想给你更多关于这个构建脚本的想法,因为所有这些排列都是在这里出现的。
CI CD系统中的指标为您提供了操作系统,但是为了获得所有这些不同的静态链接、共享链接或编译器版本的排列,这是作为构建的一部分提前完成的,作为所谓的Conan多打包器的一部分,但是您指定了想要迭代的所有维度。
如果这太抽象了,让我们来看看Azure管道对这个拉取请求做了什么。如果我们去GitHub的检查选项卡看看它实际上是如何构建的?然后我们看到这个度规点。我们看到这三个不同的音符,在这些步骤中,建筑,解决方案,然后你可以弄清楚它实际上跨越了许多,许多不同的排列,32位,64位,静态链接,动态链接,我猜在你的公司你可能会有许多自定义维度,你可以把它们添加到柯南,然后你就说要通过这些维度中的哪一个。然后还可以删除某些子集。
例如,如果你说,对于Windows,我不关心32位二进制文件,你也可以从管道中删除它们,然后重新设置。这是Azure管道的一个例子,但你也可以很容易地在任何其他CI系统中做到这一点。
例如,柯南社区本身就在使用Jenkins,他们有一个实时的CI[听不清00:30:49]柯南,CI JFrog信息,你也可以锁定为只读,你可以学习他们如何,例如,构建包。
这里是groovy DSL,它可以跨越多个操作系统,甚至在Jenkins文件中有一些内置的DSL,您可以轻松地连接到Artifactory存储库并上传包。如果你进入我的GitHub库唇,然后GitHub,你也会找到Advair和Travis CI的例子。
我希望您已经了解DevOps或非潮人管道的样子。现在,您可以安全地对构建定义进行实验。您不必祈求构建管理器为每个人更改构建定义。你只需要在单独的pull请求中进行实验,如果效果良好,那么就可以合并,每个人都可以从中受益。
这个拉请求将触发你的CI系统,越来越依赖你的图书馆,你指定JFrog Artifactory然后这柯南多包装机在操作系统和所有的排列构建flex来生成所有你需要的包,然后上传回Artifactory如果运维同意客户想发布他们居住生活,他们可以通过工件推广JFrog Bintray,或者JFrog分发,这样你也可以从CDN下载它们,它们不会影响你的单个服务器,而是从地球上最近的服务器为你的客户提供服务。
你为什么要做这一切?因为你想要回你的周末。你不想把发布当成生活中的大事,你不能去度假,你祈祷有这么多的风险积累。你也想开发一些客户真正想要的东西,你能做到这一点的唯一方法是,你的客户能告诉你,有时他们指定的并不是他们真正需要的,你不想让你的实习生浪费在弄清楚影响和所有这些事情上。你想让他们从事有成效的工作。你不想浪费任何人的时间来增加你自己对Mac Yosemite的支持,甚至可能是你组织中三个不同部门的三个不同的人。你只想依靠站在巨人肩膀上的开源社区。
很重要的一点是,首先不是工具。我已经向您展示了如何使用GitHub, Conan, Artifactory和Azure管道来实现这一点,但如果您想说服您的团队讨论为什么,而不是如何,那么这一点非常重要。
他们首先要让你明白为什么要这么做。为什么你想要回你的周末。为什么你不应该庆祝你的释放。为什么需要不变性。
我希望这次演讲能给你们一些关于如何做到这一点的想法,不仅仅是如何做到,还有为什么要做到。所以请回到你的团队,告诉他们你的想法,你会发现,即使你不认为自己是一个潮人,我当然不认为,加入持续交付和开发的行列是有意义的。谢谢你的宝贵时间。