CircleCI-博客中文翻译-一-

CircleCI 博客中文翻译(一)

原文:CirecleCI Blog

协议:CC BY-NC-SA 4.0

黑人和拉丁裔人才在科技领域获得成功的 10 种方式

原文:https://circleci.com/blog/10-ways-to-get-ahead-for-black-and-latinx-talent/

上周,我们与 Code2040 合作,在 CircleCI 总部举办了他们的首次 PopUp 活动。PopUp 系列旨在吸引更广泛的社区,特别是新兴的黑人和拉丁裔技术专业人士。我们很高兴接待了 Code2040 团队,这是一个令人难以置信的特邀演讲者小组(包括我们自己的 Jose Browne!)和 75 名与会者。在整个晚上,我们探讨了领先的主题。小组成员讲述了他们作为拉丁裔和黑人技术专业人员的亲身经历,并分享了一些宝贵和来之不易的智慧。

小组成员包括 CircleCI 公司的工程师 Jose Browne 马尔科·罗杰斯,杠杆公司工程总监;雪莉·桑德伯格&戴夫·戈德堡家族基金会产品和人力运营主管雷娜·萨德勒·舍林格;Nadia Gathers 是 GitHub 的内部通讯人员,也是斯坦福大学教学团队的成员;以及主持人,Code2040 的社区动员高级主管米米·福克斯·梅尔顿。

popup2.jpeg

“15 年前,这里只有白人和我。我的旅程让我离我的人民越来越远。现在,能够坐在这个满是黑人和棕色人种的房间里,这只是在过去几年才发生的。我不得不说服自己这没什么大不了的,没关系的。我还在忘记这一点。你不能就这样让创伤过去。有更多的人在我身边,我感觉更好,更集中。在一个我不可能永远是黑人的地方,我可以是黑人。”—马可·罗杰斯

对于那些不能来的人,我们为你浓缩了 10 个最有价值的外卖。在 Code2040 的博客上查看它们。

再次感谢我们的合作伙伴 Code2040 和不可思议的小组成员,是他们让这一令人惊叹的活动得以发生!

DevOps -持续集成| CircleCI

原文:https://circleci.com/blog/10-ways-you-re-doing-devops-wrong/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


DevOps 这个术语最近得到了很多关注,这是有原因的:采用 DevOps 实践有助于团队更有效地工作,交付更好的代码,并让客户更开心。但是随着这个术语越来越受欢迎,我们也看到了一些试图“做开发工作”的团队容易陷入的陷阱。

(该列表基于我们在 11 月举办的网络研讨会“devo PS:你做错了”,我们的首席技术官 Rob Zuber 和 Waffle.io 的创始人 Andrew Homeyer 出席了会议。)

1。你有一个“DevOps”团队

DevOps 的概念是为了抵消开发和运营团队之间经常存在的“围墙心态”,作为一种想象这些团队更加紧密地合作并看到他们拥抱彼此的一些实践的方式。因此,如果做得好,开发运维是整个工程组织采用的一种文化转变——而不是一个团队“做开发运维”DevOps 的真正挑战是让您的开发人员像运营商一样思考,反之亦然。

2。你已经不再试图缩短发布周期

如果 DevOps 的意思是出货快且经常,那么多快才算够快?真的要看情况。迁移到 DevOps 意味着逐渐接近完全连续的部署模式。如果你在一家企业,每六个月出货一次,八周可能感觉真的很快。但是,如果你是一家初创公司,八周是一个迹象,表明你可能会一次交付大量的工作。不要衡量你多久释放一次,而是开始衡量每次释放有多痛苦。当一件事情完成后,你能部署它吗?如果答案是否定的,那么你能做些什么来消除这些障碍呢?你能把代码分成小块吗?添加特性标志或切换怎么样?最终,部署代码应该是一个非事件。虽然八周可能是大型组织的目标,但我们往往会看到一系列的阈值:一旦团队每周部署一次,就更容易转移到夜间部署。在那之后,我们开始看到部署迅速增加到每天 20 到 30 次。这是事情真正开始变得连续的时候。

3。您将开发团队视为成本中心

人员配备是昂贵的,开发人员可能是公司雇佣的最昂贵的员工之一。许多组织认为这种开销是可以控制的,但这不是正确的思考方式。公司不应该只考虑成本,而应该专注于最大化开发者的生产力。做到这一点的最好方法是确保开发人员做他们感兴趣的工作。事情是这样的:工程师加入你的组织,因为他们想解决有趣的问题,即:你的业务的问题。所以你能做的最好的事情就是消除一切阻碍他们解决问题的因素。最大的罪犯?无效的工具。当开发人员在快速的周期中交付并看到他们的工作对客户的影响时,他们对自己的代码有了更多的所有权,并且更有动力尽可能地做出最好的工作。通过向他们提供无摩擦的工具和反馈丰富的工作环境,您可以最大化开发人员投资的回报。

4。通过自己构建一切,你在做无差别的繁重工作

无差别的繁重工作指的是任何占用你时间但不是你业务核心的艰苦工作。举个极端的例子,你不会建立一个新的电子邮件程序,因为你想给你的同事发电子邮件;你可以注册 Gmail。同样,你不会收获自己的咖啡豆,因为你需要咖啡因来工作。当你处于构建还是购买的争论中时,问问你自己,“在内部做这项工作会给我们的客户带来直接的价值吗?这是我们不能外包的业务秘密吗?”如果其中一个或两个问题的答案都是否定的,那么考虑引入一个工具。其他人已经把你无差别的重活变成了他们的生意。所以站在那些已经完成工作的人的肩膀上,专注于你业务的核心。

5。您没有任何测试

DevOps 背后的业务需求是团队可以更快地一起工作。加快交付节奏的一个方法是通过自动化测试减少部署新代码时的偏执因素。将验证的负担从手工测试转移到自动化测试是一项时间投资,但是它可以让您以指数级的速度部署代码。自动化测试向您保证您检查的正是您上次检查的内容,并且让您感到安全,即使其他人已经对代码库进行了更改。

6。您有太多的测试

测试越多越好吗?也许吧。在 Andrew [Homeyer,Wafflie.io]之前的团队中,一套 Selenium webdriver 测试花了 8 个小时。该套件包括几个非确定性测试,这些测试有时会因为随机原因而失败。这样的测试能提高你自信部署的能力吗?大概不会。一个比“你有多少测试”更好的问题将会是“在你与 master 合并后,离开你的电脑你感觉舒服吗?”拥有一套专注于您的核心业务价值的快速、可靠的测试将让您有信心无所畏惧地进行部署。

7 .。一切都很紧急。你总是被打断。

持续是伟大的。非紧急问题的持续解决?不太好。人们越来越依赖实时聊天,随之而来的是一种隐含的期望,即你会对收到的每条信息做出回应。如果你有需要处理的操作问题,最好能联系到团队中的每个人。但是决定如何最好地投资你的时间也很重要,然后相应地安排你的一天。毕竟,持续模型的目标是将开发人员从持续的冲突中解放出来。

8。你花时间合并

如果合并总是花费大量的时间和大量的协作,那么你就做错了开发工作。过去,融合在一起需要做大量的工作:事物不断分化,软件中存在熵,而你想要控制它。因此,你越容易看到你的代码如何与他人的代码交互,你就能越快地对出错做出反应。当你以更小的增量做每一件事时,管理和从任何问题中恢复会更容易。许多开发人员在合并时都有过在一个松散的渠道中“众志成城”的经历。懈怠对于紧急情况非常有用。如果你的紧急情况是“合并”,松弛不应该是答案。采取不同的方法:例如,持续集成或基于主干的开发。找到最小化与他人代码合并的增量的方法。

9。部署吓到你了

部署应该不成问题。当您采用连续交付实践时,您如何使部署代码更安全?通过实现测试,是的,但是也通过思考当某些事情出错时会发生什么。团队应该花时间练习快速回滚。你能在 30 秒内修复吗?太棒了。通过练习这些解决方案,你将知道当紧急情况发生时如何迅速采取行动。

10。您的开发人员不关心生产中发生的事情

DevOps 是关于缩小差距的——不仅是开发者和运营之间的差距,也是你的团队和客户之间的差距。你如何让开发人员更关心生产中发生的事情?消除代码和客户所见之间的任何责任转移。换句话说,让开发人员自己做 QA。这是缩短反馈循环产生全面更好代码的另一种方式。当开发人员是代码到达客户手中的最后一站时,他们直接看到了失败的影响,并且这些失败是个人的。为了完全确信他们的代码能够工作,他们希望在部署之前不只是粗略地浏览一下代码。编写更好的测试成为一种对他们的工作表示自豪的方式,并确保它如预期的那样工作。让开发者更接近最终用户,让他们直接看到他们所创造的价值,这是有感染力的。开发人员最初进入这个行业的原因是早上创建东西,下午看到产品的兴奋感,所以为这个循环的扩散扫清道路是保持开发人员快乐和高效的关键。

不管你练习 DevOps 方法论有多长时间,总有改进的空间,并且有很好的理由这样做。集成更多的 DevOps 实践有益于整个组织,而不仅仅是工程团队。是的,DevOps 是为了让开发人员能够更快地行动,看到他们工作的结果,并在必要时做出快速的改进或修复。但是,通过授权开发人员更充分地拥有他们的工作,您不仅创建了一个快乐的工程团队,而且创建了一个可以专注于创建更快乐的客户的工程团队,这对整个组织来说是一个胜利。

2022 年 DevOps 趋势报告

原文:https://circleci.com/blog/2022-devops-trends-report/

软件交付从来没有像今天这样成为一个重要的商业功能

如果你和我们的许多客户一样,今年软件供应链这个词进入了你的词典。你已经开始感受到供应链的复杂性和脆弱性。您已经将更可靠的软件交付和商业成功联系起来。您已经认识到开发人员的效率可以提高盈利能力。你已经决定更深入地研究你的软件管道是如何运作的,这可能是商业成功的一个巨大的战略杠杆。

这就是我们需要帮助的地方。CircleCI 的 2022 年软件交付状态报告是您导航当前软件交付前景的指南。

这份报告是工程团队了解如何更好地取得成功的基准。但它也是领导者了解其创新引擎面临的需求和挑战的资源。

那么今天伟大的软件交付的真相是什么呢?

如果不了解代码中的内容,企业将面临更多的停机和安全风险

软件供应链包括从开发到生产影响应用程序的任何东西。当我们优先考虑速度时,我们越来越少依赖定制代码,越来越多依赖他人构建的库。企业领导人需要了解他们的代码库中有什么,以了解他们在哪里容易受到攻击,以及他们需要在哪里投资来保护他们的供应链。

确保供应链的安全不仅仅是安全——我们自己的软件业务报告显示,单个企业就因为无效的软件交付而面临高达 1 . 26 亿美元的收入损失风险。大多数错误和中断都是可以避免的生产力杀手,承担了不必要的成本。

伟大的软件交付是一个持续的循环,而不是一个线性的过程

我们的报告发现,优秀的开发团队优先考虑处于部署就绪状态,他们通过在一个小时内修复或恢复,从任何失败的运行中恢复。换句话说,伟大的软件开发被认为是一种持续的状态,而不是一种终结状态。目标不是更新你的应用程序;目标是在防止引入错误变更的同时,不断创新您的软件。

自动化是“设计安全”软件供应链的关键

我们的数据显示,年底假期的平均恢复时间全面增长,甚至超过了 COVID 第一年的增长。每年的这个时候,攻击事件会增加,而团队成员会减少。自动化对于填补缺口至关重要,证明了安全的软件供应链需要自动化来最大限度地减少中断。

专注于测试,无论是像测试驱动开发(TDD)这样的实践,还是在 SDLC 的所有阶段将验证集成到您的正常开发过程中,都会给你信心,即使在人员较少的时候。如果小团队优先考虑自动化,他们可以与大团队竞争,因为在不确定的时期,他们可以自信地依靠他们的工具。

快乐的开发人员和优秀的软件交付并不相互排斥

我们的报告测量了四个基线度量,它们分别为我们提供了最高性能团队的基准,但也提供了帮助开发人员保持流畅的动力。影响优秀软件交付的度量标准与影响开发人员生产力和满意度的度量标准密切相关。

此外,为他们的团队配备性能最强、功能最强大的工具的企业领导者,可以让他们的软件团队成为创新的引擎,为他们的整个公司更有效地运营开辟新的途径,并为客户更快地获得更好的产品创造机会。

变更验证是软件交付的下一个前沿

即使是最有天赋的工程师团队也无法理解他们构建和操作的产品中的所有组件和移动部件,然而他们仍然被期望以令人难以置信的速度交付没有错误或漏洞的软件。管理这种复杂性的唯一方法是通过变更验证,这允许您在向客户发布软件之前真正知道对代码的变更是否如预期的那样工作。

符合 CircleCI 基准的团队正在运行比以往更先进的管道,这表明我们的用户在持续集成方面越来越有经验,可以根据业务需求实施持续交付和部署场景。这种自动化是实现变更验证的关键途径。

无论你所在的行业或公司所处的阶段,软件交付都是现代商业的基础。这是你最具战略性的资产,也是你最大的弱点。2022 年是软件交付必须上升到每个领导者议事日程的首位的一年。您企业的成功和生存取决于此。

阅读《2022 年软件交付状况报告》,了解我们从全球软件、医疗保健、零售、金融服务、房地产、媒体服务和其他行业的 50,000 多家组织中收集到的打造优秀软件开发团队的最佳实践。

CircleCI | 5 个重构系统的技巧

原文:https://circleci.com/blog/5-lessons-from-our-system-rebuild-what-to-consider-when-it-s-time-to-start-over/

两年前,经过快速增长后,我们的基础设施效率接近本地最高水平,但我们知道重新架构有风险。我们绞尽脑汁,寻找一种方法来逐步更新我们的平台,但我们功亏一篑。最终,我们得出了一个令人生畏的结论:我们将不得不踏上一段危险的旅程——对我们的基础设施进行彻底的平台迁移。

因为我们的工具对许多公司来说都是至关重要的,所以我们感到了一种额外的压力,那就是要快速完成我们的重建,并确保它做得很好。在第一个顾客看到我们新系统的任何部分之前的六个月,真是紧张不安。但是,经过仔细的规划、建设、更多的规划和重建,我们终于出现了 -现在有了一个更新的平台,比我们原来的平台性能好得多。当我们在破土动工 9 个月后推出新平台 GA 时,它证实了我们的信念,即我们的新系统更智能、更一致,重新架构是值得的。

回顾过去,我可以看到对我们成功的重新架构至关重要的 5 个重要概念。以下是我学到的:

1.在找到适合市场的产品之前,优化速度,而不是优雅。

有一种关于完美架构系统的说法:我们从未听说过它们,因为那些公司从未起步。当您第一次开始时,选择目前有效的堆栈,而不是为未来的可能性过度优化,是正确的选择。

许多初创公司在找到产品市场契合度之前,关注了错误的事情。他们浪费时间谈论他们想要建立的公司,而不是创建一个版本。相反,把你所有的精力都集中在开发产品上,以支持你想在未来扩大规模的公司。一旦你有了这些,你就能找到一条可持续发展的道路。

2.基础架构的改变不会逐渐发生。

在你找到适合你的产品市场,并且达到临界质量后,等待太长时间来完善你的基础设施会破坏你的可靠性和发展能力。在 CircleCI,我们感受到了重新设计的日益增长的需求,但我们也感受到了这样做的巨大危险。

重组迫使我们认真审视我们的原则:在 CI/CD 的所有好处中,我们可以在重建中保留什么?我们要扔掉什么?在重写核心产品的同时,我们如何继续在产品的其他领域取得进展?我们本可以固执地坚持渐进式改进。但是相反,我们投入到这个项目中,因为我们知道另一面会是什么:更好的持续交付。

3.限制你的范围。

在整个重建过程中,我们尽最大努力缩小关注范围。我们专注于做出我们知道会产生影响的改变,同时尽量减少其他地方的改变。我们还通过分支级别的配置公开了新的流程,因此我们可以在不中断客户日常软件交付的情况下征求真实的反馈。这对我们很重要。我们知道在重建过程中会对客户产生暂时的影响,但我们希望尽可能地减少影响。

4.客户反馈是关键。

存在设计缺陷、架构缺陷和决策,这些决策在纸面上是有意义的,但是一旦我们交付了它们,它们就不起作用了。利用客户反馈来帮助我们识别这些缺陷是至关重要的。反馈帮助我们构建了一个更好、更智能的产品,我们非常感谢我们的客户给了我们他们的意见。

当你处于创业阶段,在非常早期的时候,你需要明白你是否在做客户真正想要的、市场会接受的东西。始终认真对待客户的反馈,并始终致力于打造一款为用户和市场增加价值的产品。

5.给你的团队一个“完成”的明确定义

不可否认的是,永远继续修补产品是很诱人的,陷入了无休止的完美主义的漩涡。但是我们知道我们需要回到我们的核心理念:不断为我们的客户提供价值。因此,我们承诺将这个升级版本作为我们的默认平台,并在此基础上构建了令人兴奋的功能。

结论

在一天结束时,一些风险是必要的,这是其中之一。尽管重新架构是一项极其艰巨的任务,但它帮助我们改善了客户的体验,并为更多的产品创新铺平了道路。如果你觉得是时候重建了,我的最终建议是倾听——倾听你的客户,倾听市场,倾听你为自己设定的目标和界限——在你开始重建的时候,用这些建议来帮助自己走向成功。

构建工程能力矩阵的 7 个步骤

原文:https://circleci.com/blog/7-steps-to-building-an-engineering-competency-matrix/

每个工程师都应该有一个清晰的成长路径,这样他们才能理解、规划和执行有意义的职业发展。为这种增长提供一个框架(我们称之为能力矩阵;它也被称为职业阶梯或职业发展阶梯)是一项重要的工作,也是任何想要培养和发展员工的组织的责任。回到 2018 年初,我们有 32 名开发人员,并计划在全年翻一番,我们已经有了一个能力矩阵,但它已经过时了。它专注于我们更初级的水平,在一些开发者已经达到的水平上最大化。它也与我们组织逐渐重视的技能不一致,这意味着在实践中,我们经常忽略它。是时候重新设计了。

建立一个新的能力矩阵是一个学习的过程,也是一个漫长的过程,大约需要八个月才能完成。在这个过程中,我们发现了我们重视的东西,以及建立职业阶梯的关键步骤是什么(以及哪些步骤是浪费的)。虽然每个矩阵都是不同的,并且会反映编写它的组织的价值观,但制作一个简洁的职业阶梯来指导您的团队的过程是一致的。

当我们在 12 月发布新的工程能力矩阵时,我们收到了许多来自团队的电子邮件,称他们正在开发类似的系统。因为这个反馈,我想分享我们所经历的步骤,以及我们所学到的教训,来帮助团队以更少的浪费和更短的时间达成一个富有成效的结论,而不是试图从头开始。

如果你想为你的员工和下属提供一个清晰、一致、定义明确的成长路径,那么这篇博文就是为你准备的。

CompetencyMatrixSteps.png

第一步:让这个人优先考虑

回想起来,这是我们漫长的重新设计过程中最大的因素。我最初接手这个项目是作为我的许多副业之一。我唯一必须奉献给《黑客帝国》的时间是清晨、深夜和周末。这对我来说是一个充满激情的项目,我喜欢为它工作,但是我不能给它所需要的关心。

当我们的新工程总监莉娜·赖因哈德加入时,她将此作为她的第一个大项目。此后,我们密切合作,但她现在“拥有”它的事实让我立即意识到,我有限的可用性对这个项目的阻碍有多大。如果我在空闲时间继续驾驶它,我不知道我们是否会看到它在 2018 年完成。

如果你接手这个项目,给它应有的关注,把它交给某人,作为他们的首要工作。

第二步:就你正在构建的东西达成一致

除非所有利益相关者都同意你的目标,否则任何实施的尝试都会停滞不前。

能力矩阵是设定文化基调和方向的有力工具,因此在设计矩阵时,你将有许多有影响力的选择。每一个都有后果,只有团队团结一致,你才能度过难关。我们反复遇到的一个问题是:这是对现状的编纂,还是一种期望?(我们花了大约一半的时间才明确同意这是两者的混合。)

另一个关键的共识是:谁会受到影响?在我们的案例中,这个问题取决于我们的新矩阵是否会影响我们的站点可靠性工程师(我们认为会)。也许你正在为你的整个公司构建一个矩阵,或者也许你正在为你的前端开发团队构建一个矩阵。受影响的角色范围将极大地改变您选择编纂的能力,以及您需要使用的抽象层次。所以问这些问题,并得到明确的同意。

最后,我们需要就矩阵的目标达成一致。我们决定,首要目标是 CircleCI 工程师个人的绩效评估和发展规划。其次,我们希望用它来影响我们的招聘过程,并对外传达成为不同层次的工程师意味着什么。了解潜在的用途,以及这些用途的优先级,可以指导某些决策。

第三步:以你的价值观为指导

对我来说,这是整个过程中最有趣的部分。这是你坐下来讨论“什么对我们重要?”我们优秀的人力资源主管大卫·曼恩给了我们一些帮助,他拿着卡片详细列出了 100 种作为专业人士很有价值的行为特征。这些都不是具体的工程,从沟通到政治意识。我们还利用了其他公开的能力矩阵来播种什么样的可能参与竞争的想法。

*作为一个团队,我们也喜欢提出自己的价值观。我印象最深刻的是经济思维,管理团队一直认为这是区分优秀开发人员和优秀开发人员的关键技能之一。我们没有从任何人力资源悬崖笔记或我们参考的其他矩阵中借用这种能力,但从一开始,这就是我们知道将在最终削减中的关键能力。

开始要宽,梦想要大!不如前期广撒网,后期合并切割。

一旦你有了一个清单,做几遍合并相似的能力是一个好主意,因为它会在以后的过程中节省你的时间。例如,我们合并了实用主义和经济思维,因为我们意识到这些能力的表现会导致相同的行为。

第四步:定义你的等级

在你真正开始填写内容之前,最好先定义一下矩阵的 x 轴:职位和职责的增长。在 CircleCI,我们已经有了我们想要使用的现有头衔(E1/E6 的助理软件工程师/首席软件工程师)。然后,我们明确地就每个标题的一般责任达成一致。

我们把它分成两类,都侧重于个人贡献者:E1,E2 和 E3 侧重于掌握软件工程的技能,成为一个高效的集成电路。E4、E5 和 E6 专注于利用这些技能来扩大影响,并在越来越多的人群中创造影响力。一旦我们有了这个高层次的指导,我们就把它进一步分解,给每一个执行层次分配范围。在我们开始创建内容之前,我们有以下指导原则:

  • E1 - E3:工程的实施
    • E1:在任务范围内
    • E2:项目内
    • E3:团队内部
  • E4 - E6:利用技能扩大规模并产生杠杆效应
    • E4:敬团队
    • E5:至一组相关团队
    • E6:至工程部

当然,这些是指导方针,不是规则。例如,我们期望 E4、E5 和 E6 以不同的频率为组织实践和流程做出贡献。职业发展,就像所有人类的追求一样,实际上比理论上更混乱。然而,通过预先设定明确的指导方针,你可以使低阻力路径变得容易,并利用你的时间专注于那些更困难、更混乱的部分。

步骤 5:创建内容

这一步肯定是最费力的。但是,如果您遵循了步骤 1-4,那么这一步对您来说会比对我们来说容易得多。在整个过程中,我多次试图解决这一步,但没有正确的框架来形成我的方法,我经常感到漫无目的和分散。一旦框架就位,填写内容就变得简单多了。

考虑到这一步的内容,我将把它进一步分解成子步骤。遵循这一顺序应该可以最大限度地减少任何浪费的工作。

  1. 为所有能力定义一个级别
  2. 寻找合并能力的机会
  3. 填写其余的关卡
  4. 作家

步骤 5.1:为所有能力定义一个级别

我强烈建议的第一步是为所有能力定义一个级别,比如你的高级软件工程师。与其试图描述 E2 和 E3 在提供反馈方面的细微差别,不如通过定义一个级别来具体说明每项能力的含义,这是一个非常有启发性和协调性的过程。

我们的方法略有不同,定义了两个瓦片:E3(最高级别的个人贡献者)和 E4(规模和杠杆的第一名)。考虑到我们执行和扩展的两种方法,这让我们更好地了解了 CircleCI 开发人员职业生涯中一个关键阶段的增长情况。

第 5.2 步:寻找机会合并能力

当你为每项能力定义一个级别时,令人惊奇的事情会发生:你会意识到一些价值观指向相同的行为。这是一个合并的机会!

在我们定义了单个级别之后,我们注意到我们有两个惊人相似的瓦片:自我启动和交付责任。尽管这些能力看起来不同,但是我们所编写的行为非常相似,因此我们将它们合并成一个单一的部分:可靠性和交付责任。

早些时候,我们达成了明确的协议,我们希望我们的矩阵是 CircleCI 开发人员增长情况的集体详尽定义,同时也尽可能简单。最终,我们获得了 27 项能力。然而,当我们开始 5.1 时,我们有将近 50 个。定义每种行为向我们展示了我们有机会巩固的地方。

步骤 5.3:填写剩余的关卡&文字师

现在你知道你的矩阵中有你想要的行为和能力,是时候填写剩下的单元格了!这一步需要做大量的工作,但是非常重要,因为它是矩阵的核心。

在这一步,将是开始言语表达的最佳时机。当你在痛苦地定义经济思维对一个团队或多个团队意味着什么的本质细节时,是批评你使用的语言是否恰当地传达了意图的最佳时机。我强烈建议不要在此之前进行文字修饰,但这将是一个很好的时间来重温你在第一遍中定义的图块。

步骤 5.4:抛光&一致性

我毫不怀疑在你定义每一个单幅图块的过程中,你会学到一些你想应用到你已经定义好的图块上的东西。大约进行到一半的时候,Lena 和我发现我们用“经常”和“通常”来表示同样的事情,比例大约是 50/50。由于一致性是我们的一大重点,我们决定“通常”并把它放在一个众所周知的停车场,以备后用。

我建议采用类似的方法。当你发现需要润色的机会时(比如用“通常”代替所有出现的“经常”),把它们写下来,并把它作为你最后一遍的待办事项清单。这样,如果您的学习内容相互矛盾,您可以在应用之前解决这些冲突,最大限度地减少流失,并确保更大的一致性。

第六步:让受影响的人参与进来

太好了,你有一个能力矩阵,你完成了!实际上,不完全是。对我们来说,莉娜和我坐下来,写下一堆价值观和行为,让我们的工程副总裁签字,然后向全世界展示它是一个可笑的提议。这将影响我们的文化,我们的招聘,最重要的是,为所有与我们一起工作的人以及我们关心其职业生涯的人塑造职业发展。在设计它的几个月里,我们一直在测试矩阵的不同版本和方面,但现在是真正测试它的时候了。第一遍完成后,我们开始收集反馈。

Lena 以焦点小组的形式组织了一次出色的反馈会议,由各个级别的个人贡献者、经理和人力资源组成,包括异步和同步部分。有许多方法可以收集反馈,例如让不同部分的个人贡献者与他们的经理一起完成模拟自我评估。不管你怎么做,重要的是确保你从将会受到影响的人那里收集反馈。

我们收到的反馈非常有价值,原因有两个:它提出了一些好问题,这让我们调整了矩阵,使它传达了我们的意图。这一过程还确保了我们已经将组织的价值观,而不仅仅是几个经理的价值观,以及价值观的进步代表了我们的工程师如何看待他们的职业发展。这一步非常重要,它让我们相信我们开发的产品代表了我们想要的,并且会受到欢迎。

第七步:发货!

所有步骤中最好的一步——发货!你已经有了一个经过文字修饰和压力测试的成品,并且得到了组织中受人尊敬的个人的认可。然而,即使你遵循一个彻底的过程,它也永远不会是完美的。当人们开始使用和测试你创造的东西时,他们会发现细微的不一致和改进的机会。现在就为反馈做好准备,并且是持续的。

现在有了矩阵,你应该建立两个机制:一个收集反馈的方法和一个达成一致的解决时间表。不断地重复你的能力矩阵不是一个好主意,因为这意味着不断地改变你组织中职业发展的目标。然而,假装它是完美的,永远不会改变是有勇无谋的,也是不现实的。

我们已经开发了一个收集反馈的机制(剧透:这是一个谷歌文档),并将在六个月后重新访问矩阵,以解决我们所学到的问题。这提供了一个重要的稳定性级别,同时还允许随着时间的推移进行迭代和演进。我建议您提供一种机制来实现相同的目标。

祝贺您获得新的能力矩阵!希望这个指南能让这个过程比我们想象的更快更简单。

如果你对构建能力矩阵有自己的想法、经验或问题,我很乐意倾听!请随时通过电子邮件(justin@circleci.com)或推特( @JustinC474 )联系我。

附言:想成为重视个人成长和职业发展的文化的一部分吗?我们正在招聘!

阅读更多信息:*

DevOps 简史,第一部分:瀑布| CircleCI

原文:https://circleci.com/blog/a-brief-history-of-devops-part-i-waterfall/

这是一个四部分系列的第一部分。在这里看零件两个三个,以及四个。)

软件工程师醒着的大部分时间都在他们前辈的泥淖中跋涉。只有少数人有幸在冲突改变地形之前看到绿色的田野;其余的都运到了前线(终点)。在那里,当断电的炮弹在他们周围爆炸时,他们在战壕中煎熬。进展通常是缓慢的,尽管可以通过英勇的冲刺来覆盖地面。

但是老兵们确实出现了,伤痕累累,身经百战。他们热衷于向新兵讲述他们最大胆的壮举和漏洞。就像个人学到了写代码的个人经验一样,我们的行业也学到了大规模软件开发的集体经验。当你在地面上时,并不总是容易看到这些更大的趋势——埋在 bug 中,把火力集中在功能上。 DevOps 就是这些大趋势中的一个。这是两个传统上截然不同的世界统一成一个紧密的循环。但这不是最近的发明或时尚;这是多年迭代的结果,因为工程师已经将风险分解成越来越小的块。不是每个工程师都亲身经历过这个过程。对于那些错过了持续发展进程的人来说,在理解为什么我们如何走到这一步上可能会有差距。

乔治·桑塔亚纳写道,“那些不记得过去的人注定要重复过去。”他写这篇文章的时候肯定没有想到软件,但他现在已经去世了,这意味着我可以断章取义地引用他的话。哦,公共领域的乐趣!

本系列将讲述软件开发方法的历史——尤其是它们与传统最佳实践相交叉的地方。你可以把它想象成硅谷的 Silmarillion ,只不过它更短,图片更多。在投入这湍急的时间之河之前,请注意,这个年表在理论上是完整的,实际上也在进行中。换句话说,即使一个术语或过程可能已经被创造出来,最佳实践也总是需要更多的时间才能渗透到实际产品中。涓涓细流是我们的起点。

瀑布

当软件开发还很年轻的时候,工程师们就在硬件世界中模拟他们的工作。这是一个犯错代价高昂的环境,因此在生产 1000 件产品之前消除缺陷是有意义的。例如,如果一家玩具公司决定一个填充的驼鹿也应该提供机智的政治评论,这个发现需要在开始生产之前发生在 T2。否则,他们会制造数以千计的静音驼鹿单元,每个人都知道那些卖不出去。

1970 年,Winston Royce 博士写了一篇名为管理大型软件系统开发的文章。这篇文章概述了一个过程(如下所示),这个过程与硬件世界公认的方法有着惊人的相似之处。

Screen Shot 2018-01-16 at 12.40.19 PM.png

或简称 SSAPCTO】

流程本身由几个阶段组成,在进入下一个阶段之前,每个阶段都必须全部完成——相邻阶段之间的少量迭代除外。罗伊斯在他的论文中从未使用过瀑布这个词;事实上,最初将这个词与过程联系在一起的天才已经消失在历史中了。

但是每个人都欣然接受了它,因为它是有意义的:正如瀑布向下流一样,软件开发的阶段也是如此。水(通常)不会向上流动——只有在受到压力时才会向上流动。多么出色的表现力!永远不要让别人说软件工程师没有激情、诗意的内核。

</blog/media/2018-01-19-waterfall.mp4>

Behold nature in all its Relatively Linear Sequential glory!

瀑布模型背后的意图是创建一个有条不紊的,不令人惊讶的生产周期。它的支持者大声宣扬它的优势:它甚至在编码开始之前就生成了大量的文档;它传递某种预先定义的东西;知道还剩下多少工作总是很容易的。理论上,这些听起来都很棒,但是瀑布开发在付诸实践时会受到影响。

压力下

虽然罗伊斯在概念上相信瀑布模型,但他认为实际的实现是“有风险的,会招致失败的”。这是因为该方法以不灵活著称。问题在于第一次就让每个阶段完全正确。从测试阶段吸取经验教训回到设计阶段的余地很小;这将破坏瀑布开发的真正目标:可预测性、确定性、一致性——所有那些保持事情缓慢和稳定的粘性好东西。

因此,具有讽刺意味的是,瀑布开发并不是非常流畅。在规模上,这种模式的移动速度不够快,无法利用软件的速度或适应市场不断变化的需求。因为时间表是最重要的,所以经常会有大规模的拖延和随后的死亡行军来保证火车准时。

WWRoyce.jpg “一个人不像开发硬件那样简单地开发软件。”——温斯顿·罗伊斯博士

就像你的祖父母一样,瀑布开发是出于良好的动机,但是有点脱离技术循环。当项目坚持他们的瀑布枪时,通常会有大量的工作堆积。在软件世界中,这可能意味着在任何编码发生之前需要数月的设计和计划。当它完成时,产品可能甚至不相关或者甚至不需要

敏捷的头脑

软件是建立在硬件之上的。

早期的软件从硬件那里获取指针,因为它不知道任何更好的东西。因此,软件开发人员继承了一种不太适合编写代码的开发策略。随着世界加速发展,公司意识到他们需要更加灵活。

瀑布经常被描绘成软件开发方法中的小丑。我们将它定义为与那些还没有成功地走向现代实践的“落后的传统主义者”保持距离。但事实是,瀑布开发一度是现实;如果没有它作为基线,我们就无法迭代到未来。

每个旅程都要从某个地方开始!在这个博客系列的例子中,那个地方是一个过时的实践——肯定会导致压力和戏剧性的紧张。

但是,当我们深入到敏捷运动时,这种戏剧性的紧张将在下一篇文章中得到解决!

来源

http://www . base 36 . com/2012/12/agile-waterfall-methodologies-a-side-by-side-by-side-comparison/https://en.wikipedia.org/wiki/Waterfall_model

DevOps 简史,第二部分:敏捷开发

原文:https://circleci.com/blog/a-brief-history-of-devops-part-ii-agile-development/

这是一个四部分系列的第二部分。在这里看零件一个三个四个。)

欢迎来到 DevOps 功能丰富的故事的另一个篇章!

上一次,我们讨论了为什么软件开发的历史很重要,以及瀑布式开发是如何融入其中的。具有讽刺意味的是,记住瀑布开发是相当严格的。它缺乏适应变化的灵活性,在一个日益动荡的世界中,这是一个明显的弱点。

在这一章中,我们将探索工程师如何(以及在多大程度上)通过敏捷软件开发迭代瀑布模型。敏捷方法不是试图通过锁定开发阶段来控制变化,而是关于拥抱变化。降低风险不是通过设计完美的计划,而是通过将项目分割成小块并快速适应。

但是剧透够多了!让我们挖掘敏捷哲学的根源。

敏捷的祖先

虽然如果瀑布式开发直接导致敏捷开发会非常方便,但实际的时间线并不那么清晰。甚至在温斯顿·罗伊斯 1970 年的论文中,人们已经普遍意识到迭代和轻量级过程的必要性。随着开发人员发现主流工作流中的弱点,这种意识才逐渐增强。

这些工作流程是沉重的、不灵活的东西。软件仍然被视为需要制造的东西,流行的方法反映了这种心态。像微软这样的巨头专注于计划和消除开发过程中的意外。完美主义受到鼓励,在宇航员建筑和大设计中得到体现。

Astronaut.jpg * 太空人建筑很难改变。*

一般来说,敏捷方法是对过分热心的文档和低变更门槛的反应。倡导者认为,软件业过于关注过程和计划,这对产品和客户都不利。当功能在官僚机构和企业的精炼中萎靡不振时,每个人都受到了影响:开发者无法交付他们的创作,产品落后于竞争对手,用户被迫要么使用过时的技术,要么转向竞争对手。

所有这些改进的压力导致了 90 年代的一系列工作流实验。这些实验的结果是敏捷方法的前身,并分享了关于速度、风险和混乱的共同信念。

快速应用开发

在 IBM,詹姆斯·马丁创建了快速应用开发,它强调过程和原型,而不是瀑布式开发的过度规划。他在 1991 年将自己的想法写成了一本书,创造性地命名为快速应用开发 (RAD)。

随着原型制作在整个行业变得越来越可行,RAD 很容易就找到了粉丝。但是它的粉丝们不同意 RAD 的正确方式,部分原因是马丁博士故意留下的模糊指示。企业很谨慎,在没有保证不会为了速度而牺牲质量的情况下,不愿接受一种时尚。

动态系统开发方法

1994 年,动态系统开发方法出现,以统一 RAD 的各种版本。不像它的前身,DSDM 不是由一个人,而是由一个由积极的供应商和专家组成的财团领导的。DSDM 并不只是概述一个过程,而是通过定义明确的技术和项目角色来动手。

其中两个值得注意的技术是时间盒优先化。时间框包括将一个项目分割成在时间和金钱上都有资源上限的块。如果这两种资源中的任何一种开始耗尽,努力将集中在莫斯科确定的最高优先事项上。虽然实际的首字母缩写值得怀疑,但莫斯科的意图是合理的。看看那些大写字母代表什么:

  • 我一定有

  • 本应如此

  • 本应如此

  • W 没有

通过将需求分成这四个层次,工程师们总是知道在时间或金钱紧迫的情况下应该放弃哪些需求。这是无法避免的。

T2 必须拥有每一种颜色。

timeboxing 和莫斯科都关心时间,反对软件业传统的完美主义。通过对能完成多少工作进行约束,并为工作的价值建立框架,DSDM 让人们不断前进。

Scrumdiddlyumptious

但是人们还没有完成迭代!1995 年,Kevin Schwaber 和 Jeff Sutherland 参加了一个关于面向对象编程、系统、语言和应用的研究会议。这个的首字母缩写是 OOPSLA,听起来很偶然,但实际上是很有意的。

在那里,Schwaber 和 Sutherland 提交了一篇关于一个叫做 Scrum 的新过程的合著论文。Scrum 是另一个开发软件的框架,专注于协作灵活性。这个词来自橄榄球,两队都低下头专注于拿球,重新开始比赛。这不是一个微妙的比喻。

scrum.jpg
勤奋、爱运动的低着头的软件开发人员。

Scrum 是最早明确强调敏捷性经验过程控制的哲学之一。Scrum 的所有价值观都围绕着一个原则:不确定性。你确定顾客会喜欢这个吗?你确定你了解这个项目的一切吗?

Scrumsters 认为,真正的确定性是不可能实现的——生产率的渐近线。瀑布试图提前定义一切,Scrum 放弃了这种幻想,支持更小的批量。因此,磨练交付过程,关注适应性和新出现的需求。这是 Scrum 的本质,也是其所有策略的驱动力。

这些策略包括熟悉的套路,如短跑每日站立回顾。所有这些都旨在推动工作效率的集中爆发。定期检查提供了纠正错误的机会,并将精力集中在新出现的优先事项上。

Scrum 的一个重要工具是 backlog 。在任何给定的时间点,通常会有多个积压:一个是产品,然后是每个 sprint 的积压。这些积压工作给团队提供了一个有效的方法来区分工作的优先级。这些工件对于正确的 Scrum 执行是如此重要,以至于整个行业都围绕着它们成长。 Pivotal LabsAtlassian 的 JIRA 只是创建问题跟踪器的两家公司。

极限编程

在这些混杂的方法中,最后一个是极限编程(XP),由 Kent Beck 于 1996 年 3 月提出。当 Beck 设计 XP 的基本原则时,他正在克莱斯勒公司从事一个工资项目。虽然这个项目被取消了,但是这个方法仍然存在。方法论教了什么?收集所有的最佳实践,并将它们提升到一个极端的水平。

例如,如果一些测试是一件好事,那么更多的测试肯定是一件更好的事情。这转化为测试驱动开发:描述行为的测试写在代码本身之前。这种实践不仅增加了应用程序中测试的总数,而且改变了代码本身的编写方式。

另一个例子涉及反馈回路的优化。XP 的拥护者认为,无情地关注短反馈循环会增加你做出正确决定的几率。为此,极限程序员应该为现在写代码,并尽快收到对代码的反馈。

这种对反馈的承诺体现在像结对编程这样的实践中,开发者以两个人为单位编写代码。通过清楚地表达他们正在写的东西,工程师可以写出更好的代码,并在错误发生之前找到它们。团队也致力于频繁的发布,通常表现在像持续集成这样的过程中,这对于本系列的未来章节来说是自私的预示!

敏捷主题幻想曲

如果你注意到了这里的一个主题,你并不孤单。虽然这些哲学在技术上是彼此独立出现的,但人们普遍意识到对速度的需求。但是伴随着这种集体认同,也存在着真正的语义统一的缺乏:像进化适应这样的词被抛来抛去,但是这些词有不同的细微差别,这取决于你碰巧生活在哪个方法论中。

厨房里的厨师实在是太多了,有些厨师甚至彼此都没有见过面!这些思想领袖中的一些人在其他会议上见过面,但需要一个名叫鲍伯·马丁的积极的灵魂将所有的猫聚集到一个地方。2000 年 9 月,马丁发出了一封电子邮件来衡量利息,其余的是…嗯,其余的在下面。

宣言

像所有伟大的哲学一样,敏捷本身起源于犹他州。在那里,17 名开发人员——他们中的许多人是上述方法的代表——聚集在一起组织他们对敏捷甚至意味着什么的想法。实际的敏捷宣言并不是很长,总共由十二条原则组成。这些原则遵循四个价值观,这四个价值观总结了敏捷倡导者如何做出决策:

个人和互动 >流程和工具

工作软件 >综合文档

客户协作 >合同谈判

响应变化 >遵循计划

敏捷宣言在一个众所周知的屋檐下整合了几种现有的方法:从极限编程,它们提升了客户满意度和协作;从 Scrum ,他们融入了站立和回顾的自省;而从务实 编程,他们采取了……务实。

值得注意的是,敏捷宣言本身并不是一种新的方法,而是现有方法的集合。“敏捷联盟”将所有最好的部分聚集在一起,希望整体比其各个组成部分更好。事实也的确如此:Version One 发布了一份年度“敏捷状态”报告,衡量该方法对行业持续的积极影响。

JackBeNimble.jpg 敏捷方法论的早期采用者。

然而,对许多工程师来说,“敏捷”已经成为一个时髦的词——一个被那些拼命寻求证明他们与浅薄的人才库相关的公司挥舞的术语。“敏捷联盟”本身可能看起来有些浮夸:为什么滑雪场的一群自称的领导者能够决定这个行业应该或不应该变成什么样子?

敏捷的想法有时会分散对敏捷宣言本身的注意力。归根结底,敏捷方法只不过是工程原则的集合,创建这些原则是为了帮助软件团队更快地做出实际的决策。这是而不是一个定义良好的过程或成功的神奇秘诀,那些声称“他们很敏捷”的公司通常离实践这些原则最远。

为什么这么愤世嫉俗?

但是,即使敏捷仅仅是工程原则的集合,那会使它无用或不切实际吗?不一定。敏捷是一种运动。就像所有的运动一样,很容易在不了解一切的情况下投入感情。“敏捷”也只是一个普通的词,意味着比整个运动更简单的东西,这并没有帮助。

敏捷方法是对开发人员认为不起作用的工作方式的反应和修正。这让他们非常恼火,他们不得不退到一个安全的地方来集中他们对此事的想法。由此产生的是一个原则列表,这些原则本身并不是过程,但是可以用来生成过程。

这是更大的主题:这次聚会的任务是帮助人们改变他们对开发软件的思维方式。代码不应该是一件需要制造的产品,而应该是一门需要精心制作的艺术。这种微妙的差异意味着大量的替代策略,例如时间盒、结对编程和 TDD。

瀑布缓慢而僵硬。它不能适应变化,所以工程师们试图控制它。但是这种响应不足以交付客户真正想要的高质量软件。敏捷方法是对这一弱点的回应,它的倡导者意识到他们永远不可能完全消除项目中的风险或不确定性。相反,他们专注于通过快速开发和不断验证他们的工作正朝着正确的方向前进来控制风险。

这种持续验证的想法将成为 DevOps 历史上下一章的种子:持续集成和交付!

来源

https://en.wikipedia.org/wiki/Agile_software_development

http://agilemanifesto.org/

DevOps 的历史:自动化测试和 CI | CircleCI

原文:https://circleci.com/blog/a-brief-history-of-devops-part-iii-automated-testing-and-continuous-integration/

这是一个四部分系列的第三部分。在这里看零件一个两个四个。)

亲爱的读者,欢迎回到我们正在进行的关于 DevOps 丰富历史的旅程!在的最后一章中,我们讨论了许多导致敏捷方法的运动。在最后,我们预示了常量验证在本章中的作用。

现在伏笔露出来了!这一次,我们将讨论两个与我们的心息息相关的过程:自动化测试和持续集成。

自动化测试

我们已经看到敏捷运动是如何从繁重的文档转移到轻松的工作中的。每个人都经历了一个集体启示:风险是不可避免的,所以你最好通过将食物切成小块来最小化风险——也就是说,在吞咽食物之前先咀嚼,否则你会窒息而死。

随着人们对失败越来越适应,他们创造了失败不仅可以被接受,而且被鼓励的地方。像 JUnitCucumber 这样的测试框架的兴起反映了这种对待风险的新态度。工具不会先于流程,我们创建工具是为了优化现有流程。这意味着工程师花了足够的时间编写测试来保证这些框架的创建。

第一个自动化测试框架非常有效,但是在伦理上有问题。

极限编程是这种测试热情背后的主要影响因素。肯特·贝克(Kent Beck)和罗恩·杰弗里斯(Ron Jeffries)用一个令人信服的论点支持测试驱动开发:“如果 bug 将要发生(并且它们将会发生),工程师应该在他们还记得代码的时候修复至少一些 bug。”因为代码从工程师头脑中蒸发的速度比一滴白酒还要快。

在编写代码之前编写测试迫使工程师考虑他们希望软件如何工作。在许多方面,这种实践与瀑布年的详尽文档服务于相同的目的:测试指定事情应该如何。但是自动化测试更进了一步,通过在每个代码变更上强制这些需求,当有什么不对劲的时候大声抱怨。

工程师们发现,通过更频繁地运行测试,他们增加了捕捉错误和防止灾难的几率。这意味着一边走一边检查,而不是在终点检查。这在今天看来似乎是显而易见的,但在当时,这是一种根本不同的软件开发方式。****

**正是从这种冒泡的原始测试中,持续集成诞生了。

论持续集成的起源

DevOps 时间线并不像我们希望的那样清晰。

这两个概念实际上是同时代的,而不是从敏捷时代到持续集成时代的离散进展。短语持续集成(简称 CI)实际上比许多敏捷的祖先还要古老。它是由 Grady Booch 在 1994 年创造的,Grady Booch 是一个有相当学术造诣的人。

Booch 最出名的是与两位同事开发了统一建模语言(UML ): Ivar Jacobson 和 James Rumbaugh。UML 是过度热心的设计阶段的一个很好的例子,敏捷联盟试图与它保持距离,但是在这个故事中没有位置。走开,兔子洞,尽管你的深度很诱人!

Booch.jpg
布奇惊讶地捂着自己的羊排。

这个短语出现在 Booch 的书《面向对象的分析和设计及其应用》中:

“微观过程的需求决定了开发团队将完成更多的内部发布,只有少数可执行的发布被移交给外部方。这些内部发布代表了系统的一种持续集成,其存在是为了强制关闭微流程。

虽然粗体和大写是我做的,其余的是直接来自布奇的头。他还写道,测试应该是“开发过程中的持续活动”,因此从早期开始,测试和持续集成就紧密地交织在一起。

请密切注意 Booch 的用词:他描述了“微过程的需求”,这是对软件开发的步调的认可。他没有讨论正常规模或宏观流程——不,这些是微观流程,因为交付的切片被切得越来越薄。

发现差异

如果你读过我们关于反馈循环的讨论(你最好读过!),这些话和想法应该是敲响了各种各样的钟。敏捷运动本身可以被描述为软件中反馈循环的大幅减少。持续集成——虽然比敏捷的各种策略更面向过程——是这种缩减的自然延伸。

做得好的话,持续集成就像一种无止境的过程修正——不确定性是确定性的一种形式化应用。虽然它和敏捷的同类有很多共同之处,但是持续集成在规模上是不同的。敏捷运动提供了一套策略,使得一个组织反应更加敏捷;持续集成是一种从根本上改变组织文化的战略转变

但是这些区别很快变得模糊。当 Kent Beck 和 Ron Jeffries 在 1996 年创建极限编程时,他们正式将持续集成作为其十二个核心实践之一。他们甚至走得更远,提倡每天多次集成

所以:敏捷和持续集成是同时代的,一个不一定导致另一个,持续集成不仅仅是另一种敏捷策略——尽管它是敏捷原则的应用。所有这些模糊不清是讨论最近事件的一个不幸的副作用:我们还没有时间就一个单一的叙述达成一致!

但是有一件事每个人都能同意:CI 是个好东西——好到可以围绕它构建工具,使它可重复和可扩展。

ThoughtWorks 和 CruiseControl

2000 年,敏捷联盟的创始成员之一成为 ThoughtWorks 持续集成的内部倡导者。他的名字叫马丁·福勒,他是相当有名的。Fowler 指出,持续集成在概念上并不局限于特定的软件;开发人员可以实践 CI,而不需要专门的工具。但是——和任何自制的解决方案一样——个性化是以时间、金钱和建立用户网络的能力为代价的。

因此,使用专门为日常构建和连续测试而构建的服务器,可以更容易地练习 CI。2001 年,另一个名叫马修·福梅尔的思想工作者建造了这个,并命名为 CruiseControl。该工具是开源的,并且仍然在附近,尽管现在维护人员较少。

巡航控制很重要。它有助于将最佳实践变为现实,其公共性质鼓励了整个行业的采用。它没有太多的职责:观察应用程序代码库的变化,如果有必要,创建一个新的版本,并通过一系列测试运行它,以验证一切实际上仍然工作。

Rickle.png 不连续积分会怎么样。

哈德森和詹金斯

虽然 CruiseControl 是一个相对简单的工具,但它代表了对软件和时序完整性的承诺。很快,其他竞争者就像水仙花一样围绕这个概念涌现出来。另一个用 Java 编写的 CI 工具 Hudson 于 2005 年发布,但直到 2008 年才真正开始超越 CruiseControl。

然后!正如所有好故事通常会做的那样,这是一场戏剧性的争论。Hudson 的作者 Kohsuke Kawaguchi 曾在 Sun Microsystems 工作,该公司于 2010 年被甲骨文收购。像 CruiseControl 一样,Hudson 是免费和开源的,所以当甲骨文宣布计划将其注册商标并商业化时,Hudson 社区在 2011 年以 Jenkins 的新名字逃离了现场,这个名字一直延续到今天。

熟能生巧

有很多地方可以读到那次分裂的余波,所以我们不会在这里涉及它们。相反,让我们后退一步,讨论** 这些不同的工具(和分歧)如何代表行业对最佳实践的快速接受**。

在短短的十年里,开发人员制定、提议、实现并商业化了一个建议的过程。甲骨文愿意抗争的事实表明,这个想法及其实施是有价值的。这种价值已经渗透到整个软件世界,直到它变得如此根深蒂固,以至于问题已经从“你在持续集成吗?”到"时,你多久持续积分一次?"

有一段时间,脸书的座右铭是“快速行动,打破东西”。随着软件行业的成熟,工程师们已经意识到,如果一切都在崩溃,你实际上不可能走得那么快。这就是为什么脸书将它的座右铭改为“在稳定的基础上快速前进”。

持续集成是这条道路上的第一步。在问题失去控制之前抓住它们,表明了对风险处理的转变。这是敏捷自动扶梯上合乎逻辑的下一步,之所以用这个比喻说它是自动扶梯,是因为旅程永远不会结束

至少在理论上是这样。实际上,本系列将在下一部分结束,届时我们将讨论持续交付和部署!

来源

http://www . CVA uni . edu . VN/imgupload _ dinhkem/file/PTT kht/object-oriented-analysis-and-design-with-applications-2nd-edition . pdf

https://Martin fowler . com/articles/continuous integration . html

https://shebanator . com/2007/08/21/a-brief-history-of-test-frameworks/**

持续交付与持续部署

原文:https://circleci.com/blog/a-brief-history-of-devops-part-iv-continuous-delivery-and-continuous-deployment/

持续交付与持续部署

持续交付与持续部署的区别是什么?最大的区别是连续交付需要手动部署,而连续部署不需要。

(要听我们对 DevOps 的历史和未来的最新见解,请听我们来自自信承诺的播客。)

什么是持续交付?

连续交付是确保软件总是准备好被部署的实践。保险的一部分是测试你所做的每一个改变。此外,您还努力将将要部署的实际工件打包——也许您已经将这些工件部署到了一个阶段环境中。

持续交付需要持续集成。它依赖于同样的基本原则:将工作分成小的增量。

回想一下我们解释持续集成是什么的时候。我们谈了很多关于不断合并代码的好处——都是为了避免开发周期结束时巨大的代码冲突。人们兴奋地收获这种方法的回报。

在这种快速采用中发现的困难是,开发人员对他们推向生产的代码洗手不干,安全地知道这是“别人的问题”。运营商开始害怕来自另一边的变化。

在开发方面,合作达到了前所未有的高度:开发人员在发布代码之前会聚在一起合并他们的代码;然而,持续交付的运营端并没有以同样的方式实现现代化。开发者所做的和“真实世界”之间仍然有距离,如果一个产品没有发布,客户就不能使用它。

开发者+运营商= DevOps

DevOps 是开发和运营的协同。这是一种思想,即代码在交付之前没有达到它的目的。这是开发商和运营商都能认同的。他们同意的另一件事是,代码没有交付的原因通常是因为恐惧。如果我们做了一个大的改变,却打破了一些关键的东西,那该怎么办?

巨大的变化意味着巨大的风险,这可能会扰乱运营稳定性。

如果没有持续交付,开发人员和操作人员会受到同样的压力——部署大量代码的行为。而持续投放的目的就是让部署的行为不那么恐怖。

什么是持续部署?

持续部署是创建健壮的测试框架并在测试通过时自动推出代码的方法。

记住,大的改变需要小的改变。因此,如果开发人员真的致力于提交小的更改,那么自动部署通过所有测试的更改应该不会有太大的危害。

正确的连续部署是困难的,因为很难知道你是否有详尽的测试覆盖。在开发和部署之间,团队通常需要一些喘息的空间,特别是对于像数据库迁移这样敏感的变化。

持续部署还鼓励其他种类的持续行动,如分析、错误报告和测试。滚动条将代码变更与错误频率和关键组织指标联系起来。page duty在整个团队中分担操作负载,使错误解决变得不那么忙乱。

与 DevOps 一起前进

有两种方法来应对这个不断变化的世界:你可以加倍努力创造一个“完美的”不变的计划。或者你可以增量地构建,在过程中获得有价值的反馈,并且知道你永远不会偏离太远。随着开发人员和操作人员继续合作,更频繁地发布更好的代码,这些团队将相互学习:开发人员将考虑高性能的代码,而操作人员将推动部署。

预测未来是不可能的,所以专注于理解现在。这是 DevOps 运动、服务监控和持续运输背后的信念。这已经成为实现有意义的改变的方式。

建立你的软件交付团队

高绩效的软件交付团队是什么样的?在 CircleCI,我们分析了来自 CircleCI 上 44,000 多个组织和 160,000 个项目的 5,500 多万个数据点,以了解团队如何制定有意义的目标,并为工程团队分享了有史以来第一个数据支持的基准。下载我们的数据报告,2020 年软件交付状态 了解更多。

(这篇博客是一个四部分系列的第四部分。阅读部分第一部分:瀑布第二部分:敏捷开发,以及第三部分:自动化测试。)

与 Jez Humble 关于 Accelerate 的对话:解释 DevOps - CircleCI 背后的数据和科学的新书

原文:https://circleci.com/blog/a-conversation-with-jez-humble-on-accelerate-the-new-book-explaining-the-data-and-science-behind-devops/

我们最近和 Jez Humble 坐在一起,Jez Humble 是 DevOps 研究与评估公司的创始人和首席技术官,是 DevOps 的思想领袖,也是一些关于大规模交付软件的基础书籍的作者。Jez 和他的合著者 Nicole Forsgren 和 Gene Kim 最近刚刚出版了他们的新书《加速:精益软件和 DevOps 的科学:建立和扩展高性能技术组织》 。请继续阅读,了解 Jez 所说的预测 60 年代 DevOps 文化的书,为什么对团队的玩世不恭是倦怠的表现,以及如何在组织中缓慢推动变革。

说说 加速 。你为什么写这本书?

Jez Humble:在过去的四年里,Gene Kim、Nicole Forsgren 和我与 Puppet 共同制作了《DevOps 报告》(今年我们与 Google 合作)。我们已经弄清楚了如何衡量技术团队的高绩效,以及预测高绩效的因素是什么。很多人说,“你知道,如果能把所有这些都集中在一个地方,并由你来解释为什么我应该相信这一点,为什么这不只是一些行业的废话,那就太好了。”

所以我们一直在想我们应该写一本书,然后马丁·福勒在一次会议上遇到了妮可,告诉她如果她不写,他就要写。妮可说,“哦,当然不!”所以我们坐下来写了它。

Accelerate 涵盖了从持续交付的技术实践到文化、产品管理、人员管理和领导力的方方面面。它讨论了什么是重要的,什么是不重要的,以及如何预测 it 性能、软件交付性能,以有意义的方式测量这些东西,并最终讨论它们如何影响组织的性能。

DevOps 和 DevOps 文化是人们谈论了几年的话题。你认为我们在这些想法的采纳曲线上处于什么位置?

Jez:这是一个很好的问题,坦率地说,这是一个让我感到沮丧的问题。我书架上的一本书叫做《企业的人性一面》 ,作者是一个叫麦格雷戈的人,写于 1960 年。

麦格雷戈谈到了这两种管理理论,X 理论和 y 理论。

X 理论说,你以胡萝卜加大棒的方式待人。理论 Y 说,你假设人们想把工作做到最好,并提供一个鼓励他们这样做的环境。

麦格雷戈说,经理们得到他们所相信的东西。所以,如果你认为胡萝卜加大棒是必要的,而且人们根本对他们的工作不感兴趣,那么人们就会那样做。然而,如果你相信人们从根本上想做到最好,并且你这样对待他们,他们就会这样做。

所以这不是一个新想法,但是 70 年后,我们沿着采纳曲线走了多远,你知道吗?所以这不是我们正在解决的新问题。

坦率地说,我们发现自己处于后资本主义范式中并不十分有利于这种转变。所以,我认为 DevOps 是它的新版本。

也就是说,我们有一些新的模型和新的数据表明文化影响性能,新的做事方式影响软件交付。因此,我们希望这些数据将有助于影响采用,但这非常困难。

这很难改变,我们的经济和组织结构正在朝着这个方向发展。

这很有趣,因为最终我们谈论的是建立一个更快乐、更高效的工作场所,但有时这可以归结为引入一种新工具,或引入一个 DevOps 团队,这将突然为我们提供这种数字化转型的东西。

Jez:对,这很容易让人想到,如果你改变一些有形的东西,比如一个工具,或者重新命名一个团队,你就能实现这个结果。

不过,事情不是这样的。你还必须改变人们的工作方式。

这是很难做到的。你如何平衡交付手头工作的压力,同时又关注“嗯,我们实际上如何变得更好?”或者,“我们如何投资?”

讽刺的是,通过投资和变得更好,你实际上能够更快地完成你的工作。我的意思是,这是它被称为 Accelerate 的原因之一,因为努力改善你做事的方式实际上会让你做得更好。但事实是你做这些事情很糟糕,这让人们说,“哦,我们没有时间做这个,我们没有能力做这个。”

所以,这是一个经典的第 22 条军规。

我们的目标受众是那些说“我们没有时间来修复这个系统,我们必须利用现有资源”的人为了说服他们,我们使用数据、论据、图表和简洁。

你认为速度是改变你的过程的一个好的论据吗?我想知道这是否会伤害团队,如果他们说他们需要重新评估他们的系统,这样做会让他们走得更快,那么他们将比以前负责更多的工作。“太好了,你能移动得更快吗?然后我们会给你更多的票。”是一把双刃剑吗?

耶兹:对,这很有意思,因为你做得更好而受到的惩罚就是做更多的工作。我认为愤世嫉俗是很容易的。有趣的是,从研究的角度来看,愤世嫉俗是倦怠的标志之一。倦怠是我们在 Accelerate 中实际测量的事情之一,我们讨论了可能影响倦怠的事情,其中一些事情是实践,一些事情是领导的影响。

我认为所有这些讨论都必须在一个相当人性化的背景下进行,那就是我们都在努力实现的目标。这是领导者的工作,说,“嗯,这是愿景,这是我们想要做的,”不仅仅是在我们想要如何销售我们的客户方面,而且在我们想要建立的组织类型方面。

我认为,如果你担心在工作中做得更好会导致你被解雇,因为不再需要你了,或者你会因此经历负面后果,这是一个真正的问题。已经发生了。领导的工作之一是确保人们不会真的害怕这些结果,因为这是确保一切都不会变好的绝佳方式。

书中有没有让你觉得惊讶的东西?你已经在这项研究中潜心多年,但有没有什么让你恍然大悟的时刻?

Jez:我是精益软件理念的早期采纳者,我们讨论过的很多东西并不一定是新的。我想强调的是,如果你对此了解很多,你就不会去读这本书,让你的整个世界天翻地覆。但肯定有一些事情,我没有那种“啊哈”的时刻,就像我有“是的!”那一刻,我说,“我就知道!”我们从数据中可以看到。

例如,显示高性能实现更高水平的吞吐量和稳定性。我对此颇有微词,因为时至今日,人们仍然认为,如果你要走得更快,你会创建更不稳定、低质量的系统。但是精益软件的理念是,这里实际上没有权衡。更快的速度可以实现高质量和高水平的稳定性,很高兴在数据中看到这一点。

此外,看到 IT 部门的高性能实际上会影响业务,这真是太好了。看到我们谈论多年的许多实践实际上显著地影响了性能,这真的很棒。我个人的一些肥皂盒是持续集成和基于主干的开发,数据非常清楚地表明这些实践对性能有重大影响。所以,很多好消息。

对于希望实现这些变化的团队,有什么实用的技巧吗?

Jez:首先,领导力使实践得以实施,包括精益管理、产品管理、软件交付实践,比如 CI。这些反过来会影响交付成果,影响文化成果,如工作满意度和减少倦怠。软件交付绩效影响组织绩效。

人们可能会问,“没有领导的支持,我能做到吗?”答案是,你可以,但是如果没有领导的支持,让它坚持下去并在整个组织中传播是非常困难的。领导的支持很重要,真的很重要。

但是光有好的领导还不够。你必须做艰苦的工作。2016 年有一个有趣的数据,我认为它加强了这一点。当我们看到从低性能到中等性能的转变时,除了变更失败率之外,一切都变得更好,对于中等性能的人来说,变更失败率实际上是上升了。同样,数据没有告诉我们为什么会发生这种情况,但我们的假设是,正在发生的是,人们试图更快地做同样的事情,而没有做实际改变组织流程的艰苦工作。

所以你只需要努力工作。流程改进是无可替代的,我想强调的是,除了领导力。

组织如何将这种改变作为优先事项?

杰斯:在某些时候,你必须能够对别人说不。那是非常重要的。在任何成功的组织中,永远不会缺少工作要做,有人,可能是中层管理人员,必须能够说,“听着,我们将投入一些能力来做改进工作,这意味着我们不会投入 100%的能力来构建功能,这没关系。”因此,能够对人们说不,然后还能够在定义目标和确保每个人都朝着同一个方向前进方面推动至少部分组织的一致性。我认为这两件事非常重要。

我们从那些成功推动变革的人那里听到的一件事是,他们在开始时有点隐瞒。因此,他们会说,“这里没什么可看的,我在做实验,”然后他们会让他们的一个小团队动起来,然后添加人员,直到他们创造了一些动力。然后,他们可以去说,“看看结果是什么。”

杰斯:对,我认为这完全正确。我特别将这与另一种非常冒险的常见做事方式进行对比,这种方式是自上而下的,“我们将立刻改变整个组织。”这经常失败。我认为这一部分,“让我们在这里试一试,犯一些错误,让它走上正轨,然后试着在组织的其他部分传播。”完全正确,它反映了组织如何工作的心理。在每个组织中,都有一群人想要尝试新的、闪亮的东西。另一端有一群人,他们无论如何都不会改变他们的工作方式。在或多或少反对改变的人之间有一条曲线。

你不能一下子改变整个组织。你必须找到那些有能力和兴趣尝试新事物并让他们成功的人,然后完全按照你描述的方式离开。

我听说你用 CircleCI 写 Accelerate。你能告诉我一些关于那个的情况吗?

杰斯:所以,我非常相信一些人所说的狗食,但我实际上并不喜欢狗食,所以我称之为喝自己的香槟。

如果我要告诉人们做某件事,我想确保我自己在实践它。显然,我从 2004/2005 年在 Thoughtworks 的时候就开始做持续集成,事实上,我写的第一本书, 持续交付 ,我们有一个小的持续集成服务器,在那里我们使用了 Ruby 版本的 CruiseControl,这样每次我们做更改时,我们都会创建一个 PDF,这样我们就可以看到 a)我们的文档是有效的,b)我们可以下载并通读一些东西来证明我们所写的内容。

所以这次我们又做了一次。技术略有不同,所以我们使用 GitHub 来保存我们的书,我们使用 markdown 作为我们的格式。我们使用 CircleCI 进行持续集成。

这个想法是,每一章都是 markdown 中的一个文件,每次我们对其中一个文件进行更改,我们都会将其提交到 GitHub,CircleCI 会拿起它并创建一个 HTML 格式的整本书的网页,以及一个 PDF 版本,以便我们总是有最新的版本。所以,你知道,如果我们有评论者或者其他我们想展示给他们看的人,我们可以把他们指向那里。我们将 HTML 发布到亚马逊上的一个 S3 桶中,所以你只需进入网页,就能随时看到最新版本。所以,我的意思是,非常简单的东西,但它是一个很好的方式,总是知道你在哪里,并以一种非常低摩擦的方式与其他人分享。

我第一次使用 CircleCI 是在 2016 年,当时我在美国联邦政府工作。我对工具有强烈的看法,而且 CircleCI 有很多我喜欢的地方。非常容易上手,这也是我们选它的原因。我们在 DORA 使用 CircleCI。我们有一个自己运营的平台,我们使用 CircleCI 来构建、测试和部署该平台。对此我当然只有好话可说。

关于为什么人们应该读这本书,或者他们应该把它给谁,有什么临别赠言吗?

Jez:我们的想法是让每个人都觉得有趣。反馈是这是一本很好的短篇书。很清楚。如果你是敏捷方面的专家,你可能不会发现什么特别新的东西,但你会得到一种非常强烈的确认感,实际上,这里有一种科学的方法来证明你一直在谈论的东西,它是如何工作的,为什么会工作,以及它如何与这个领域中的其他一切联系起来。所以,我认为这是一本适合所有人的书。

最终,我们想要做的是做出反映人们做事方式的东西。因为研究表明这是有效的。我们想让它变得正常。理想情况下,我们希望它能让人们看到后说,“是的,显然每个人都这么做。你在说什么?”没人需要它了。我们的工作就完成了。

弃用通知

原文:https://circleci.com/blog/a-deprecation-notice-on-job-triggering-with-api-v1-1/

今天,我们将针对单个终端发布一份特别的弃用通知。在 2020 年 3 月 1 日或之后,我们将终止使用 1.1 版 API 触发作业的功能(在 API 中,这是通过对特定项目的 POST 请求来完成的)。

总之,只有当您在自己的脚本和程序中使用 CircleCI API v1 或 v1.1 来触发项目的构建(又名“作业”)时,才需要采取行动——如果是这样,我们会要求您使用一个新的端点,这可能需要您更改代码,在某些情况下,还需要对您的.circleci/config.yml进行一些调整。作为替代,我们敦促所有需要使用我们的 API 在 CircleCI 上触发运行的人使用 API v2 中新的触发管道端点。要获得迁移到新 API 端点触发的帮助或给我们关于此变更的反馈,请访问我们的讨论页面

v1.1 API 的其余部分很可能会在未来发布弃用声明,但在 v2 API 拥有有效的特性对等性之前,我们不会发布该声明。一旦该公告发布,您将有至少六个月的时间迁移到 API v2。

要了解我们的 v2 API,请阅读公告帖子

工程个人回顾指南

原文:https://circleci.com/blog/a-guide-to-personal-retrospectives-in-engineering/

本文由 CircleCI 高级软件工程师斯蒂格·布劳塔塞特与 CircleCI 高级软件工程师钱璐·辛诺特合作撰写。

什么是个人回顾展?

回顾是软件和系统工程工具箱中的一种成熟的资源。从冲刺回顾事后回顾,我们回顾我们的工作,从中学习并变得更好。

我们可以将同样的想法应用到我们的专业实践中,进行个人回顾:写一份对我们经历的分析,以尽可能多的学习。我们可以回顾一整年的工作,或者更专注于某个特定的项目。

把这比作学习一种乐器:密切关注练习过程的录音是一种强有力的提高方法。

为什么要写个人回顾?

评估我们作为单个工程师的表现,并向我们的同事讲述这种表现的故事是很困难的,随着我们职业生涯的发展,这变得更加困难。

书面回顾是一个停下来思考的机会:退后一步,在高层次上看待事物。我们可以更容易地学习和自我纠正,以及想象可能的未来。通过与我们的同事分享我们的个人经历,我们可以“在公开场合”对学习进行建模和规范化。

从一个角色到另一个角色,带着一个相对正式的个人系统“解放”我们,使我们脱离本地的绩效管理系统。与其紧张,我们可以复制或改写我们个人回忆的一部分作为输入。

个人回顾是给谁看的?

  • 我们自己:提醒我们做了什么,并创造一个机会来反思事情是如何发展的。
  • 我们的团队:让他们对我们的工作有一个更广阔的视野,展示作为一名软件工程师的高与低的开放性,并鼓励一种成长的心态。
  • 我们的经理:“向上管理”,给他们所需要的东西,以评估我们的绩效,并为我们的目标提供意见。

个人回顾和例子的结构

选择回顾的形式可以让你的思考有一定的结构。这里我们将展示我们使用过的两种类型,但是还有很多可用的。例如,如果你有生产工程背景,你可能更喜欢一个简单的“事件回顾”格式:什么进展顺利?什么不顺利?我们会改变什么?

4Ls:喜欢、学习、缺乏、渴望

4Ls ”的输出简洁、易读,并且易于与您的同事转换成谈话要点。例如:“这是我需要更多支持的地方,”或者“这太棒了,让我们做得更多!”。

摘自钱璐 2021 年年度回顾:

喜欢

  • 我的经理一直表现出色,有时在困难的情况下也是如此。
  • 管道团队是灵活的,并有增长的心态。我们已经能够使用一套快速的实验过程和工作协议,一起学习,团结成一个团队。
  • 让我们谈谈工程总是这么高兴参加。谢谢雅克。

学会了

  • 一年来,我与同事们建立了更多定期的一对一关系。
  • 事实证明,这是一种强大的方法,既可以帮助他人,也可以通过上下文、建议和反馈获得帮助。
  • 当我离开团队时,用身份和权限维护这些同事是特别有价值的。
  • 这可能是我度过时间的最重要/最有影响力的方式。

缺少

  • 紧急$service 数据库升级占用了大量本来可以节省的时间——我觉得!-通过一个更好的原始计划。
  • 我希望记录和概括我们的软件和基础设施迁移方法的工作在将来会有所帮助。

慕离

  • 疫情的末日到了!这种“自由浮动的焦虑”增加了我全年不得不处理的每一个专业和个人问题,这让我精疲力尽。

叙述

一种叙事格式,或许可以分成几个主题,是一种很好的方式来反映一个特定的作品。

Stig 最近为一个长期项目撰写的回顾性文章节选如下:

强调缺乏进步

作为我 2020 年第四季度目标的一部分,我开始着手这个价值 10 亿美元的项目提案。我没有在最后期限前完成,把它推迟到了 2021 年的 Q1。几个月来,我一直在强调这一缓慢的进展,并在 Slack 中回答团队外部的问题,公关评论,并与新加入的团队成员进行配对,从而感到被忽视了。

我引入了一个中断轮换,松散地模仿了 Id & P,这样我可以忽略 N-1/N 周内来自团队之外的问题,安全地知道我们的中断人员会处理它们。

让新团队成员加入进来也是我季度目标的一部分,可以说是更重要、更紧迫的目标。所以事后看来,尽管我的计划受到了影响,但优先考虑入职是正确的选择。如果我能对此更理性一点,可能会给我带来更少的压力。

在某个时候,我向吉拉董事会添加了规划任务,以增加我的规划的责任性和可见性。(还因为我对在做计划的时候没有承担编码任务感到内疚。)我在这方面有成功也有失败。计划工作和讨论不太适合在吉拉进行跟踪,但在这方面更容易获得帮助。

我联系了几个人,询问他们对我的进步(或缺乏进步)的看法,他们没有任何抱怨。这让我想到吉姆有时讲的故事,鸭子在水面上看起来很平静,但如果你能在水下看到它们,它们的腿很忙,感觉有点贴切。(那个,还是我在决定问谁的时候选的好!)

配对作为驱动力

在规划的早期阶段(2020 年末-2021 年初),我一直在动力和重点上挣扎,至少部分原因是 COVID 锁定疲劳。我发现结对帮助了我,因为我可以“借用我的结对伙伴的动力”。

我试图在大部分工作中与人配对,特别是从 2021 年初开始。这并不总是容易的,因为与我最热情的配对伙伴的重叠(时区方面)很少。

结对也帮助我向更多的人介绍了这个项目背后的想法。他们有一些我没有想到的问题,这些问题反过来改进了项目建议书。

本次回顾展

这比我想象的要耗费更多的时间——我不断记起越来越多我想涵盖的内容。回顾和校准我当时的感受和我后来学到的东西——包括我自己的后知之明和与他人的交谈——是很有价值的。我真的希望这能帮助那些在他们自己的计划中经历过和我一样的问题的人。

编辑回顾展

像许多分析一样,一个完成的回顾可能不会展示所有的工作。

1.收集并做笔记

首先,把你的原材料放在一起。仔细检查你的日常笔记、日记、实验笔记、“吹牛文档”、“人们说的好话”文件、拉请求、你处理过的关键文档——任何想到的东西。

提示:如果你坚持每天或每周做笔记,做有效的个人回顾会变得容易得多,尤其是时间跨度较长的时候。(但是没有他们你还是可以的!)如果你刚开始定期做笔记,包括你在做什么,没做什么,以及你的感受。

来自 Stig 的每日笔记文件示例:

  • 陷入懈怠

  • 看铁路编程的思考:也许用例不完整,除非它们指定了错误处理?例如,“作为用户,我希望能够更新我的姓名和电子邮件地址”

    • 隐式:“如果出现问题,可以看到一个合理的错误消息”。

    • 参见 https://vimeo.com/113707214“面向铁路的规划”

    • 不愉快的路径也是需求(所以为错误设计)

      我想知道错误枚举如何转化为微服务。真的只有一个地方需要向用户报告错误吗?我不相信。

  • 花了一些时间阅读和评论周一事件的事件评论,其中一个坏演员在$service 中引起了麻烦。

  • 在我们的#事件处理频道 cf 中添加了一些热门镜头。钱璐附和着表示同意,这让我松了口气:-)

  • 在博文上动了针。这感觉真好!尽管只有几段。(他们可能不会留下来;)

  • 与 Fernando 快速讨论在客户端还是服务器上应用项目限额?我倾向于后者,越深越好。

提示:当你检查所有的原材料时,记下笔记。关注引发强烈情绪的问题或模式;如果没有,请注意!注意那些太难或太容易的事情,正面的和负面的。不要担心语言表达。一堆要点和几个时间戳就够了。

2.分组并分析

首先将相关的要点组合在一起。当它们变得更清晰时,用主题来标记这些组。现在你应该对发生的事情和你的感受有一个大致的了解。

考虑暂时离开,改天再来。一点点的距离会帮助你注意到你第一次错过的东西。

3.反思和写作

扩展你最初的想法和感受,开始把它们放进你选择的结构中。

在这里为你的听众着想是有帮助的。如果你想保持回顾展的私密性,那么你当然可以保持“原始”的形式,思考一下,把你学到的东西带走。但是,我们建议与同事分享。当我们能互相学习时,我们都学得更快。

如果你写的是记叙文,你可能在你的项目符号和主题中有你需要的一切来扩展它们成为段落。如果你正在使用类似 4Ls 的东西,你应该能够沿着那些线剪短你的分组。

边走边在笔记中添加内容是很有价值的:花时间向他人解释你的感受和想法通常会帮助你意识到你错过了什么。再次强调,当你觉得自己已经有 90%的把握时,考虑休息一下。一点点视角大有帮助。

4.分享和讨论

在这一点上,你已经做了大量的工作,从你的经历中学到了最多的东西。考虑在上面写一个总结。如果可以的话,和同事讨论一下,征求他们的反馈或者其他想法。

如果你觉得你的回顾照亮了其他工程师可能遇到的困难,在你的组织中更广泛地分享它。“开放”学习在工程文化中创造了一种安全感。来自 Stig:

我喜欢钱璐将他的年度回顾“公之于众”(对公司),这样我就可以深入了解他的观点。他通常给我留下镇定自若的印象,但从阅读他的复古作品来看,似乎并不都是阳光和彩虹。不是我想让他不好过!但很高兴看到我不是唯一一个偶尔感到受挤压的人,而且可以找到解决办法。😃

根据你的回顾采取行动

你可能会发现自己在特定的情况或话题中挣扎。也许你可以得到建议或相关的培训,甚至寻找这样的情况,以获得更多的实践。

你可能会发现你工作的某个方面特别有价值。你有足够的灵活性去获得更多吗?如果没有,也许是时候和你的经理谈谈了。反过来也是如此!你的工作中有没有你不喜欢的方面?有没有一种途径可以减少它,或者至少减少它对你的影响?

理想情况下,你可以利用回顾来磨练你的手艺,更有效,最大限度地从工作中获得乐趣。这可以帮助你确定你希望你的职业是什么样的,也许对你从事的工作更有意向。斯蒂格说:

几个月前,当我翻阅日常笔记准备年度回顾时,我意识到我喜欢与人结对,并帮助他们。我和我的经理 Riasat 谈过这件事,现在我做了更多这方面的工作。虽然当时我并不认为这是个人的回顾性努力,但现在回想起来(看我在那里做了什么?)我现在愿意。

把这种停下来思考的做法作为你职业实践的一等品。这是一项投资:像这样反思需要时间,但从长远来看,这减少了你和你的同事们学习相同课程的次数。

如果你正在寻找一个可以学习和成长的团队,看看我们的招聘信息

软件工程师的生产力| CircleCI

原文:https://circleci.com/blog/a-guide-to-productivity-for-software-engineers/

任务管理的演变

当你开始软件工程师的职业生涯时,任务和时间管理是非常简单的。要么你的老板要么你的团队敏捷委员会告诉你到底要做什么。有一些让你分心的事情,比如懈怠和电子邮件,你不得不与之抗衡,但在大多数情况下,这并不太糟糕。你喜欢编码,你的任务很有趣,你很容易把它们放在你的脑海中的待办事项列表中,检查它们,并在一天结束时感到很有成就感。

你很擅长你的工作!不久,你被要求超越团队敏捷板上的项目。你开始帮助设计和分解史诗,你被要求出席公司活动,你志愿帮助另一个团队与你的团队的服务集成,你发现自己花更多的时间在空闲时间回答需要你的专业知识的问题。你的工作变得越来越模糊,越来越复杂,越来越无界。你发现自己在拖延。你感到更加不知所措。但是你对自己说,“这就是成功应该有的感觉!”下定决心,通过努力工作,你一定会在所有的转盘上名列前茅——只是在你的脑海中的待办事项清单上多藏一些事情,你明白了吗!而且如果你忘了做某件事,没什么大不了的,肯定会有人提醒你的。

你的工作变得越来越模糊,越来越复杂,越来越无界,你发现自己在拖延。

再过一段时间,你又被提升了。现在,你发现自己在团队敏捷委员会的项目上越来越少,你的时间都花在了无休止的会议上,审查设计、领域模型、架构规范、研究新技术、领导工作,甚至写更多的文档,所有这些都在 Slack 和电子邮件中持续不断的问题和 pings 的弹幕下。你的工作似乎不那么“有趣”。当你回顾你的一天时,你意识到你大部分时间都花在了会议间隙和回答无数的问题上。你忘记或没有时间在重要的事情上取得任何进展,你的一些旋转盘子现在放在地板上,没有动过,你积极地拒绝为它们寻找时间,没有人提醒你所有正在掉落的东西。有时候,你真的不确定自己是否完成了什么…

模糊性上升,生产力下降

这听起来熟悉吗?如果有,恭喜你!您在工程职业生涯中取得了成功,并且您正在为一个推动您成长的组织工作!在我的职业生涯中,我肯定到达了这样一个点,在那之前,我再也不能依赖于为我工作的简单的任务流:那些简单的时候,我可以从我的团队敏捷板上拿起一些东西,或者在 Slack 中回答可管理数量的请求,并获得令人垂涎的:thankyou: emoji。对我来说,期望我所有的工作都是“有趣的”来自然地激励我,最终变得不合理。我不得不在不得不做更模糊、更复杂、周期很长的任务的同时保持良好的工作状态(很容易看到你编写的东西被部署到生产环境中,并感觉很有成就,但很难看到你阅读或创建的一堆文档,或你参与的松散线程,并有同样的感觉)。

在你的工程职业生涯中成长并承担领导角色需要一种新的生产力方法。

我一直对生产力感兴趣。在阅读了许多关于这个主题的书籍和文章并尝试了各种技巧后,我终于明白了生产力并不是做更多的事情。在我追求“更有效率”的过程中,一次又一次出现的问题非常简单,与我做过的事情的数量或我有多忙没有多大关系。取而代之的是简单的“我今天完成了什么?”。

一次又一次出现的问题是,“我今天完成了什么?”

利用来自众多生产力资源的建议(生产力项目Zen To Done子弹杂志把事情做完和许多其他人),我开发了一个为我工作的可持续系统,它可以让我跟踪所有旋转的盘子而不会不知所措,并且可以帮助我在一天结束时感觉自己完成了一些有用的事情。

当工程工作变得不明确时的生产力

1.选择我的焦点

第一步是对我的工作变得深思熟虑。作为工程师,我们可以做许多不同的事情,其中一些纯粹是令人愉快的,一些使我们的产品更好,一些使我们的队友,一些帮助其他团队或我们的公司。我们如何选择在任何一天关注什么?

为了帮助我做出决定,我列出了我可以投入精力的所有可能的重点领域:学习、编写特性代码、进行架构改进、指导/训练、改进团队或组织过程、增加工程影响力等等。每个月(或每个季度)我只选择 3 个我会投入大部分精力的重点领域。为了帮助我做出决定,我针对每个重点领域回答了以下问题:

  • 我喜欢这样吗?

  • 这对我/我的事业有帮助吗?

  • 这对我的团队有帮助吗?

  • 这对我的公司有帮助吗?

我的答案可能会逐月变化。例如,如果我的团队中有其他人已经在改进团队过程,那么现在改进团队过程实际上可能对我的团队没有帮助。我试着把我喜欢的事情和我可能抗拒但对我、我的团队或我的公司有帮助的事情结合起来。我不情愿地认识到,不适往往意味着成长,所以我强迫自己尝试一些感觉“艰难”或“不好玩”的领域。同时,我知道这些具有挑战性的领域需要更多的精力,所以同时选择 3 个“困难”领域很可能会导致我拒绝在任何一个领域取得进展。可持续的不适(或成长)才是关键!

不适通常意味着成长。

一旦我选择了我关注的领域,我会在整个月或季度中用它们来过滤我要做的任何工作。我还会在我最关注的 3 个领域中寻找机会并开始工作。这让我可以开始一些有趣的工作,没有人特别要求我去做。拥有这个过滤器并不一定意味着我不会做这些领域之外的任何工作(祝你好运,告诉你的老板你拒绝做某件事,因为它不符合你这个月的工作重点)。但是,如果我有选择的话,我会选择一致的任务,放弃不一致的任务。

2.把事情做完

第二步是仔细考虑如何在我选择的领域取得进步(或如何工作)。 Zen To Done (ZTD) 谈论选择大石头为你的一周,选择 MITs(最重要的任务)为你的一天。这个想法是,如果你不对你的“大石头”进行优先排序,微小的鹅卵石会填满你的一天,你不会完成很多。我发现一旦我确定了方向,我就可以通过选择 2-3 个我想在这一周取得进展的项目或任务(也就是大石头)来计划我的一周。然后,每天我可以有意识地选择我想完成的 2-3 项任务,这将有助于推进我的每周大石头计划。正如你可能注意到的,这是“选择前三名”的所有方式!

如果你不对你的“大石头”进行优先排序,微小的鹅卵石将会填满你的一天,你不会完成很多事情。

任何可行的计划都必须现实可行。在每周开始前,我会查看我的日程表,看看那一周我有哪些会议。我需要为他们中的任何一个做准备吗?如果是这样,预备会前一天会成为我的 MITs 之一。有没有与我关注领域的项目相关的会议?如果是这样的话,我可以选择那些项目作为我那一周的重点。展望未来也帮助我判断下周我实际上会有多少时间和精力。如果我一周中经历了太多的环境转换,我就不太可能有太多的精力去做任何有挑战性的事情。我也试着在我的时间表中找出大量的空闲时间,并有意地将它们用于专注的工作。

在每天开始之前,我会为当天做更多的战术计划:还有什么任务需要完成吗?我需要纠正方向,做一些一周内出现的事情吗?这比我计划要做的事情更重要吗?我也试着把我的一天分成“被动的”和“专注的”两部分。在反应时间里,我让自己“四处游荡”,简单地对出现的事情做出反应,在空闲时回复问题或请求,检查电子邮件,参加会议。但是在我集中注意力的时候,我会尽最大努力断开连接,关闭或至少最小化空闲时间,关闭电子邮件和其他通知来源,并实际上做我当天计划要做的事情。对于任何突然出现的新任务或要求,我都会简单地写下来,以便日后跟进。把它写下来(而不是把它保存在我的大脑空间里)给了我一种平静的心态,让我以后不会忘记做它,但它也让我继续专注于我计划好的工作,并抵制转换上下文的诱惑。考虑到我们实际上在一次做多件事情上的低效,我们的大脑如此喜欢多任务处理和分心的想法是很奇怪的。

3.分析和迭代

计划,更重要的是,坚持计划是我每周和每天例行公事的重要部分。它让我专注于对我真正重要的事情,并迫使我将大的模糊项目分解成更小的可实现的任务。深思熟虑和花时间计划还有另一个有趣的副作用:我可以回顾我的一周或一天,回顾和庆祝我已经完成的事情(这是一个很好的激励!),而且我还可以分析我抵制的任务。我们倾向于拖延那些太难或太无聊的事情。分析是什么让我抗拒一项任务,通常可以帮助我克服这种自然倾向。在我的每周计划中,我会花一些时间思考“为什么”我的大石头:我为什么要做这个项目或这个任务?仅仅回答这个问题就足以激励我去完成清单上一些更难或不那么“有趣”的项目。突然之间,一些无聊的事情变得更有动力了。如果某件事太难或不明确,我会试着回答一个简单的问题:为了取得一些进步,我下一步要做什么?通常第一步并不需要非常雄心勃勃,有时只需要安排一次会议与他人一起集思广益就足够了。一旦你迈出了第一步,你就能想出下一步该怎么走。

通过回答“为什么”,一些无聊的事情突然变得更有动力了。

计划和深思熟虑真的帮助我认识到,你可以通过向前迈出一小步,在大项目上取得重大进展。当我们训练长跑时,我们自然理解这个概念:如果我满脑子想的都是终点线,我很容易一累就气馁:我还有很长的路要走,我应该现在就停下来。但是,如果我集中精力跑到前面一英里处的那棵树,一旦我到达那里,我会庆祝这个小小的胜利,并决定我是否要停下来,或者我是否应该选择我的下一个目标,突然间,这条路就变得容易管理了。

我们常常意识不到庆祝这些小胜利对我们前进有多重要。这一点在处理大型、复杂、模糊和无限的项目时尤为重要。我们把目光放在最终目标上,直到整个项目“完成”才开始享受这个过程。但是随着你的成长,你的终点线离你越来越远。你的一些计划可能需要几个月甚至更长的时间才能产生明显的影响(如果有的话),而且通常当它真正取得成果时,许多不同的人和团队都为它的成功做出了贡献,这将使你很难回顾过去并感到有所成就。你到底有多重要?

专注于在重要的事情上取得进步,享受过程,这会让你变得有韧性、坚韧不拔,帮助你到达你想要的目的地,并选择一条有意义的道路。

我的计划中的一个例子

我实际上把我上面解释的一切都应用到了我写这篇博文的过程中。

我的第一个小步骤是:向我们了不起的内容经理咨询我的随机博客想法。她建议我们开个短会,集思广益。

第二个小步骤:安排会面。在会议的前一天,对我可能会在博客中涉及的内容进行一次头脑风暴。

会上,她问要多久才能完成初稿。我抛出了“两个星期”(不要太雄心勃勃,但也不要太长,让我有时间改变主意或失去兴趣)。

在接下来的两周里,“博客”是我的一块大石头,每天我都在看它是否能成为我当天的 MITs。我并没有打算在某一天写完所有的东西,相反,我设定了一个目标,就是取得一些进步。某一天抛弃想法,另一天提炼和编辑,然后意识到我还有一些想法要添加,再写一些,再编辑一些。有些日子我计划在我的博客上工作,但我没有,这没关系。我知道我每天和每周的计划和把事情分成小步骤会让我继续前进并取得进步。我在两周内进行了几次很好的写作练习,并在两天内准备好了草稿。成功!

测试分裂指南| CircleCI

原文:https://circleci.com/blog/a-guide-to-test-splitting/

测试分割是在 CircleCI 上加速构建的最简单的方法之一。特别是,通过计时数据分割测试可以极大地改善构建时间。本教程描述了如何使用 CirlceCI 进行测试分割。我们将使用 jest-junit 设置一个 React.js 应用程序来保存测试结果,这样您就可以在后续运行中通过计时数据进行分割。

先决条件

  • React.js 的基础知识
  • 系统上安装的 Node.js
  • 一个的账户
  • 包管理器(此流程也可应用于 npm)

https://www.youtube.com/embed/zeukH6V_gEk

视频

测试拆分会带来什么

CircleCI 接受一个测试列表,并将这些测试划分到 parallelism 键定义的多个节点上。每个节点都有自己独立的容器,所以每个节点都需要启动,检查代码,并执行运行测试所需的任何步骤。运行测试的步骤会使每个容器上运行的步骤在时间上有所不同。

了解为什么 CircleCI Rob Zuber 认为测试拆分是智能 CI/CD 的重要组成部分。

如果启动容器、签出代码和安装缓存的依赖项需要大约 60 秒,那么您应该预期这在所有并行节点上都是一致的。这些步骤在所有容器中都是相同的。测试步骤是我们在每个容器上运行的唯一独特的步骤。这就是为什么并行度为 2 并不能将时间精确地减少 1/2。

在许多测试中,并行性大大减少了执行冗长步骤所需的时间。CircleCI CLI 分散测试,以便步骤尽可能以均匀的步调完成。也就是说,没有神奇的排比数。每个测试套件都是不同的,所以我们建议您尝试找到最适合您的节点数量。

用纱线测试分裂

要分割测试,您需要给 CircleCI CLI 一个要分割的测试文件或类名的列表。

像许多其他测试套件一样,Jest 不接受作为文件或类名列表的测试文件。默认情况下,Jest 会寻找符合其命名约定的文件。这不是问题。我们可以通过 Jest 查找测试文件的方式将测试文件打包,并将其作为标准输入传送到 CLI。

 - run:
                name: Test application
                command: |
                    TEST=$(circleci tests glob "src/__tests__/*.js" | circleci tests split)
                    yarn test $TEST 

该示例查找位于任何__tests__子目录中的任何.js文件。如果您的设置不同, globster.xyz 是一个非常有用的工具,可以用来试验像大括号扩展这样的 globbing 模式。

CLI 可以按名称、文件大小或历史计时数据进行分割(稍后将详细介绍)。目前,由于没有定义任何内容,CLI 将退回到按名称拆分。

按时序数据分割

对于大多数项目来说,通过计时数据分割测试是最快的。CircleCI 查看历史计时数据,并跨节点动态拆分测试,以使测试套件尽可能一致地完成。要启用按时间划分,设置您的管道来收集和存储测试结果。这一步我们将使用 jest-junit。

要使这项工作成功,您需要在我们的项目中添加一些东西。首先将它安装为一个开发依赖项:

yarn add --dev jest-junit 

接下来,将您的测试分割步骤更新为按时序数据分割:

 - run:
                name: Test application
                command: |
                    TEST=$(circleci tests glob "src/__tests__/*.js" | circleci tests split --split-by=timings)
                    yarn test $TEST 

接下来,您需要在package.json文件中做一些修改。

  • jest配置添加一个reporters条目
  • 添加一个jest-junit条目
  • 让 jest-junit 知道向测试结果添加一个文件属性

CircleCI 需要这些信息来筛选计时数据。这些新增内容将确保测试结果得到正确存储:

...
  "jest": {
    ...
    "reporters": [
      "default",
      "jest-junit"
    ],
    ...
  }
  ...
  "jest-junit": {
    "addFileAttribute": "true"
  },
... 

接下来,让 CircleCI 知道它需要使用store_test_results步骤存储这些信息。使用store_artifacts步骤,可以将每个节点的结果保存为与作业相关的工件。这是我们最后的测试工作:

 build-and-test:
        docker:
            - image: cimg/node:12.16
        parallelism: 5
        steps:
            - checkout
            - node/install-packages:
                pkg-manager: yarn      
            - run: mkdir ~/junit
            - run:
                name: Test application
                command: |
                    TEST=$(circleci tests glob "src/__tests__/*.js" | circleci tests split --split-by=timings)
                    yarn test $TEST
            - run:
                command: cp junit.xml ~/junit/
                when: always
            - store_test_results:
                path: ~/junit
            - store_artifacts:
                path: ~/junit 

确认一切正常

要确认正在使用计时数据,请检查测试步骤的日志。Test logs

结论

就是这样!我们涵盖了并行运行测试套件和存储计时数据所需的一切。

本教程中使用的项目的完整存储库可从这里获得。

给酷儿青年的一封信,考虑在科技界谋职

原文:https://circleci.com/blog/a-letter-to-queer-youth-considering-a-career-in-tech/

在 CircleCI,我们重视各种类型的多样性。我们充满活力的团队造就了我们,也让我们能够生产出优秀的产品。“CircleCI Voices”是博客上的一个新专栏,我们的团队成员可以在这里讲述他们的故事,分享他们带给 CircleCI 的独特视角。我们的第一个故事来自我们的招聘协调员,戴维·洛佩斯。我们希望您喜欢!


亲爱的年轻漂亮的同性恋者们,

我希望这封信能让你身体健康。我的名字是戴维·洛佩斯。我是一名拉丁裔男同性恋,最近我从非营利部门进入了科技领域。从一个世界到另一个世界的转变并不容易。我想分享我自己加入科技大军的旅程,并向你们展示,虽然这是一场艰苦的战斗,但我作为一个同性恋者的生活经历实际上给了我许多在这里茁壮成长所需的工具。我希望我的故事能给你鼓励,在人们试图告诉你“不,你不行”的时候,帮助你看到自己的潜力。我们在技术领域的存在至关重要。根据谷歌 2017 年的多元化报告,他们公司的多元化细分为:男性 70%,白人 61%。这些公司每年做的报告也向我强调了为什么当我寻找像我这样的人在技术领域做我想做的工作的例子时,并不多。但它们确实让我意识到,我作为一名拉丁裔同性恋男性的存在,在这个科技空间里意味着一些东西,因此,你在这个科技空间里也意味着一些东西。

当你开始你的职业生涯时,你会听到“多元化倡议”这样的术语。这是因为你试图进入一个不是为我们设计的空间。这就是为什么你在这里很重要,这就是为什么你不能放弃!通过在一个变化缓慢的系统中表明你的主张,你正在为你身后的其他人创造一条道路,所以要知道你的失败和成功不仅仅是你自己的。

当我反思我的经历时,我想分享我所学到的帮助同性恋青年的东西,让他们通过任何科技公司的大门,进入一个团队,这个团队将接受他们的技能,支持他们的抱负,并接受他们的身份。以下是对我有帮助的:

1.学习这门语言,并用它来推销自己

我希望我的高中提供“像技术人员一样说话”作为语言选修课,就像西班牙语、汉语或意大利语一样。在我的一生中,我认为我在学习语言技能,以便以一种可以转化为任何工作或领域的方式与人交往。然而,我意识到非营利领域(迄今为止我整个职业生涯都在这里工作)和技术世界说着两种完全不同的语言。在一个世界里,我可以在简历上的“相关经历”下面列出一些东西,比如,“参与拆除有毒男性的霸权力量,为酷儿的声音构建叙事,”但在另一个世界里,这被视为“围绕不同主题的促进小组讨论。”这是对大脑的再训练,让它能够在两种声音之间摇摆。随着我对如何将我独特的背景和技能转化为科技公司使用的术语做了更多的研究,我开始收到更多的回复和更多的面试。所以,用他们的话来框定你的简历,尽可能地描述工作描述,避免在纸上写得太“清醒”。那是以后的事。

2.面试和获得工作机会

好了,现在你已经掌握了制作完美简历的艺术,接下来呢?对我来说,为了帮助我决定我想申请哪种类型的公司,我想知道一些事情。

1)公司看重什么?2)公司如何参与多元化/包容性?3)公司如何看待自己对社区的影响?4)我寻找的职位是否能在公司内提供发展机会?

这些是我个人的“北极星”问题,但你的可能不同。了解自己在职业生涯中希望如何成长是很重要的,因为这将有助于你提出对你感兴趣的任何公司都很重要的问题。我在每次面试时都会问这四个问题。事实证明,一些我认为在我的名单上名列前茅的公司因为他们的答案(或缺乏答案)而被排除在选项之外,其他公司则上升到了首位,因为我看到了价值观和目标的一致性。在这个过程中,我也意识到,如果我能熬过从十几岁开始的所有奋斗,那么我知道我可以粉碎一次面试。继续做自己的拥护者,去获得那些科技币吧!

3.找到你的盟友

我来到 CircleCI,知道我想制造什么样的波浪;我只需要成为引起涟漪的石头。为了做到这一点,我开始和同事谈论任何事情。我处理所有关系的方式是提供一点点我是谁,然后看看如何被接受,希望能得到回报。如果另一个人在我的谈话中遇到我,我知道他们可能是我的潜在盟友。我在 CircleCI 非常幸运,因为这里的许多人都支持我,理解我为自己创造的道路,并支持这一愿景,正如我在这里支持他们一样。和有决策权的人结盟也很重要!我特意与公司的副总裁进行了那些“有意的”交谈,现在他们对我有了更多的了解,他们知道我想要完成什么——不仅是在 CircleCI,也是为了我自己的职业追求。当你认识合适的人,他们知道你的目标是什么时,你会惊讶于你能产生多大的影响。写这封信的时候,我正在提升自己在同事和整个公司中的声誉,我不仅对多元化和包容性充满热情,而且愿意付出额外的努力,将言语付诸行动。我强调要做真实的自己,你应该努力为自己做到这一点。相信你的盟友会在那里支持你。

4.提醒自己是谁

这最后一点是我不断发现自己回去的东西,所以它需要被分享。科技空间让同性恋者很难感觉到他们可以做真实的自己。有太多的政治牵涉其中,有等级制度和体系在起作用,这可能导致同性恋者感觉他们不得不再次呆在那个隐喻的“壁橱”里。然而,我对它的看法是,我已经练习过了,所以我会正面迎接每一个挑战,如果我觉得那扇壁橱门正在向我靠近,最好相信我会让它成为一扇旋转门,每次它旋转时都会令人眼花缭乱。所以,练习照顾好自己,并且知道无论你的经历如何,你都值得为自己取得所有的成功。不要为了一份工作而失去自我;你作为真实的自己比作为别人叙述的虚构更有价值。

如果你在我的信中写到这里,那么我希望你已经从我的经历中看到了你的一部分,我希望你已经找到了一些相关的事实。最重要的是,我想让你知道你属于这里!技术世界正等待着你未开发的潜力,我想成为那面镜子,向你反射出一个经历过它的人的形象,并把它变成自己的。有一家公司在等着你向他们展示你是谁,等着他们下一次加入比以往任何时候都更需要你的文化。

郑泰,你留下来!

你诚挚的,
戴维·洛佩斯

我们在招人!如果你正在寻找重视你独特经历的团队,看看我们的空缺职位

查看 CircleCI 对过渡相关病假的行业领先支持| CircleCI

原文:https://circleci.com/blog/a-look-at-circleci-s-industry-leading-support-for-transition-related-medical-leave/

在 CircleCI 工作的一大好处是,公司认为每个人都应该能够在工作中成为完整的自己。包容性、展示信任和尊重,以及确保每个人都拥有他们需要的工具和支持,这些都深深植根于我们的文化中,是我们如何为客户提供最佳价值的重要组成部分。考虑到这一点,公司最近为变性员工推出了一项新的医疗政策。CircleCI 现在为从与过渡相关的医疗程序中恢复的员工提供全薪带薪休假,这在 CI/CD 领域的公司中尚属首次。这项政策是 CircleCI 福利计划中极其重要的一部分,也是公司致力于全体员工福祉的又一例证。

转变是一个非常紧张的过程,治疗一些变性人焦虑的医疗程序通常需要非常谨慎的康复方法。

该政策的目标是确保跨性别员工在这一困难时期感受到支持,通过确保跨性别员工获得适当补偿的病假,公司可以消除不必要压力的主要来源。这样,我们可以更快乐、更健康地重返工作岗位,并能更好地为我们的客户和同事尽最大努力。

这项政策是如何形成的

推动这些福利正规化是许多个人努力的结果。CircleCI 的跨性别员工一起在我们自己、我们的经理和其他支持我们将该话题带到我们的管理团队的利益相关者之间讨论了该问题。通过在公司的跨性别人群和我们的 cisgender 同事之间建立共识,我们成功地倡导了这一行业领先的跨性别医疗福利的正式化和承诺。CircleCI 为 LGBTQIA+员工设立的员工资源小组 QueerSphere 帮助协调宣传工作,其结果是制定了一项政策,帮助确保跨性别员工获得我们需要的支持。

对我来说,在 CircleCI 工作的最大好处之一是,我觉得自己有能力呼吁人们关注可以改进的问题或做法,并召集人们一起努力解决这些问题,这对整个公司以及代表性不足或被边缘化的员工都有好处。跨性别者面临系统性偏见:在美国,没有联邦层面的保护措施来防止基于性别身份的歧视,在 2015 年由国家跨性别平等中心调查的跨性别人口中,80%的就业者报告称遭受过骚扰、虐待或不得不采取措施避免这些敌对的工作场所行为。

我直接知道外出工作有多困难。在我转型初期的前一个雇主那里,我感到被同事们疏远和厌恶,他们在知道我是变性人之前一直很友好。为了保护我的工作,我不得不最小化我是跨性别者的事实,忍受来自同事的敌意,或者假装我是异性恋。

在 CircleCI,我感受到了支持、尊重和自信。我感到非常幸运,因为我有一群很棒的跨性别同事和朋友(我们占 CircleCI 工程部门的 7%,占整个组织的 3%,而总人口只有 0.6%),并且在我们的观点和经验受到组织最高层重视的地方工作。

如果你喜欢在一个有着先远文化的地方工作,喜欢与一群跨文化者和盟友合作解决有趣的问题,点击这里查看我们的招聘信息

生产测试的合理指南

原文:https://circleci.com/blog/a-rational-guide-to-testing-in-production/

在过去的十年中,我们已经看到了软件的复杂性和复杂性的急剧增长。越来越常见的是,公司的代码库中只有一小部分实际上来自内部开发人员,这导致了变更验证的挑战越来越复杂。

作为回应,我们看到许多测试方法越来越受欢迎,包括“在生产中测试”的概念生产中的测试可能是一个令人困惑的术语,因为它经常与看起来不像传统测试的实践联系在一起。因此,我们认为生产中的测试是验证范围的延伸。当与其他测试方法结合使用时,生产验证可以帮助团队降低风险,因为第三方服务的使用越来越多,数据集越来越大等趋势使得在代码投入生产之前对其进行完全验证变得更加困难。

虽然在生产中进行测试的想法本身并不新鲜,但对于那些希望降低成本并收集关于其产品或工具性能的真实数据的团队来说,它最近已经从一个笑话演变成了一个可行的策略。

但是在生产中测试到底意味着什么呢?有什么好处?有什么风险?你怎么能安全地去做呢?

在这本电子书中,CircleCI 首席技术官 Rob Zuber 分享了他对生产测试在实践中的看法,对企业和客户的潜在风险,以及在什么情况下这是风险最低的方法。

下载这本电子书了解更多关于持续验证的概念,以及如何对你的团队的测试工具和策略做出明智的决定。

调试| CircleCI 的科学方法

原文:https://circleci.com/blog/a-scientific-approach-to-debugging/

20 世纪 40 年代末,莫里斯·威尔克斯领导的团队建造了早期的计算机 EDSAC。1949 年,他写下了自己在使用计算机时的一个体会。EDSAC 位于大楼的顶层,磁带打孔和编辑设备位于其下一层:

“在我一次往返于 EDSAC 房间和‘在楼梯拐角处犹豫不决’的打孔设备之间的旅途中,我强烈地意识到,我余生的大部分时间都将用来寻找我自己程序中的错误。”—莫里斯·威尔克斯,《计算机先驱回忆录》,1985 年

因此,调试总是与编程携手并进。像持续集成或有效遥测这样的实践可以帮助我们发现问题,但是一旦问题摆在我们面前,我们就需要卷起袖子,弄清楚为什么它现在会显现出来。

问题可能出在我们自己的代码中,可能出在同事的代码中,也可能出在我们依赖的某个库或基础设施中。通常这是一种微妙而令人困惑的混合,包括贯穿我们软件和硬件堆栈的多种交互。

很难从症状(如问题报告或堆栈跟踪)找到原因。这是我们倾向于在工作中学习的东西,而且是不均衡的,而不是系统的。但是快速有效的调试是可以学习的,就像科技中的其他事情一样。

方法

我们可以通过一系列重复的步骤从症状中恢复。或者我们可以从系统开始工作的“顶层”开始,沿着代码向下,形成问题的模型。

无论采用哪种方法,这种方法多少都是科学的:

  • 看看我们掌握的事实
  • 试着推理系统崩溃时的状态
  • 对所发生的事情形成一个假设
  • 测试它,使用现有的遥测技术或新的代码,一次改变一件事
  • 重复这个过程,直到找到错误

例子

在讨论我们工程团队的调试时, Marc 举了一个很好的例子来说明这种方法:

上周,我调试了一个奇怪的 RabbitMQ 问题,在这个问题中,消息没有作为两个集群之间迁移的一部分进行传递。

  • 事实:消息没有被传递。
  • 假设:消息没有发送。

查看日志,我可以看到在消息发送之前的一行,并且我看不到发送消息的错误或异常的迹象。

  • 事实:消息已发送到队列。
  • 假设:消息已发送,但未送达。

也许交换机没有连接到队列。所以我们打开 RabbitMQ UI 并检查配置。新 RabbitMQ 集群上的队列绑定看起来合法,但它的行为与旧集群不同。

  • 事实:队列被绑定到交换。
  • 假设:两个集群上的拓扑在某些方面有所不同。

我们在一个浏览器选项卡中查看 web UI 上的旧集群,在另一个选项卡中查看新集群,并在两个选项卡之间快速切换以查看有何不同。我们看到了路由键的不同之处。

  • 事实:旧集群有一个为队列绑定定义的路由关键字,而新集群没有。当我们设置旧的队列绑定时,我们是手动完成的,而新的绑定是在配置管理中设置的。
  • 假设:配置管理代码没有正确设置绑定。

这很容易确认和修复。

陷阱

当形成假设时,需要实践来限制我们自己只看事实。一个常见的错误是在没有证据的情况下假设,如果我们看到 X,那么 Y 也一定是真的,然后从“X 和 Y”的位置进行假设

例如,我们可能会看到一组任务未能取得进展,假设问题在于容量,并立即扩大规模。然而,如果问题是我们的任务共享的队列上的争用,我们只会使它变得更糟。这是一个我们可以先核实的假设。

另一方面,经验可以让我们简化一些假设,更快地找到答案。这是一种平衡:有根据的猜测是很好的,但在继续之前,我们必须明确我们的假设并验证我们的猜测。

有时,特别是对于难以重现的问题,我们会意识到我们目前没有信息来检验我们的假设。然后,诀窍是获得新的工具或遥测到位,以便我们下次可以得到它。

写下来

在我们进行的过程中做笔记是很有价值的,尤其是在漫长的调查过程中:

  • 一旦我们收集了更多的数据并填充了系统的心理模型,或者第二天用新的眼光回顾早期的假设,就可以揭露不正确的假设,并成为新想法的强大来源。
  • 当我们试图对一些困难的事情写下清晰的解释时,它有助于我们挖掘表面的细节,突出我们没有真正理解的部分。
  • 分享笔记可以让其他人更容易跟上进度。调试和编程一样,是一项团队运动,效果最好!

变得更好

像许多技能一样,调试回报密切的关注,对方法的一点反省,和其他人一起学习。尝试上面的方法,寻找陷阱,并与同事和朋友分享你的工作。阅读下面的参考资料,了解更多的背景和想法。

调试是软件工程师的一级技能。作为我们每天面临的挑战,我们为做得更好而付出的任何努力都会一次又一次地回报我们。

资源

迈向开源的一步——circle ci

原文:https://circleci.com/blog/a-step-into-open-source/

编者按:本帖最初发表于 2014 年 8 月 28 日,为了准确和全面,于 2018 年 11 月 29 日更新。

我们创建 CircleCI 是为了帮助开发团队提高效率。作为其中的一部分,我们长期以来一直专注于生产 web 应用程序的工具,CircleCI 的大部分特性集旨在让团队更快地交付代码。

这意味着我们没有专注于为开源存储库构建特性,我们的客户在与合作者共享构建结果方面有困难,甚至求助于使用不同的服务。长期以来,您一直要求我们为公共项目提供更好的支持,今天我们很高兴地宣布了这方面的一些功能。

细节

每个用户和 GitHub 组织总共有四个免费的 linux 容器(每年价值 2400 美元)用于开源项目。通过公开您的项目,这将自动为您启用。我们还为 macOS 开源项目免费提供 Seed 计划(1 倍并发)。要获得该计划,请联系 billing@circleci.com 的。这些项目现在也可以进行未经认证的 API 访问,因此徽章不再需要特定用途的 API 密钥。另一个对开源项目来说很棒的特性是用于公共项目的公共构建页面,这样你的合作者就不必登录来查看构建页面。对于启用了 GitHub 检查的组织中的项目,协作者还可以在 GitHub UI 中看到 CircleCI 作业和工作流的状态。阅读我们在 GitHub Checks 上的博文,了解更多关于使用这个特性的信息。

为了解决开源项目开发人员经常遇到的凭证和设置的具体问题,我们引入了大量的特性和设置:

  • 私有环境变量允许您安全地存储公共项目的秘密,包括 API 令牌、SSH 密钥和密码。
  • 仅构建拉取请求以减少 CircleCI 构建的数量。默认情况下,CircleCI 从每个分支构建每个提交,这对于开源项目来说往往过于激进。在项目的高级设置中打开和关闭该设置。
  • 从分叉的存储库构建 pull 请求如果您允许从分叉的存储库获得 pull 请求,那么可以在任何手工评审之前运行测试。该选项可以在您的项目高级设置中启用。
  • 将秘密从分叉的拉请求传递到构建如果你觉得与任何分叉你的项目并打开拉请求的人共享秘密。默认情况下,CircleCI 隐藏四种类型的配置数据:环境变量、部署密钥和用户密钥、添加到 CircleCI 的无密码私有 SSH 密钥以及 AWS 权限和配置文件。在项目的高级设置中打开和关闭该设置。

注意:如果你还没有,你需要在项目设置>高级设置>免费开源中启用这个项目。从现在开始创建的所有公共项目都将自动进行此设置。

现已上市

如果你已经是 CircleCI 的客户,你会看到开源版本和其他版本一样:它在同一个应用程序中,在同一个仪表板中列出,使用相同的 API。

我们的所有功能都面向开源客户:

  • 我们的并行性帮助您的测试套件比构建时间增长得更快。
  • 我们有一个很好的内置机制来保存和上传构建工件
  • 当您的测试失败时,您可以 SSH 到我们的构建中来找出哪里出错了。😃
  • 我们的基础设施会自动扩展,因此您不会因为其他人的 而经历任何繁忙时间或排队时间

如果您正在构建一个更大的开源项目,并且需要更多的资源,请让我们知道如何帮助您!我们将重复这一点,所以在 CircleCI 上建立你的 OSS repos,让我们知道你的想法!

一封措辞强硬的电子邮件,让你的老板相信你需要 CI/CD - CircleCI

原文:https://circleci.com/blog/a-strongly-worded-email-convincing-your-boss-you-need-ci-cd/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


回想一下你上一次与经理的一对一谈话。

你去镇上散步了吗?你买咖啡了吗?你每次一对一的时候都喝咖啡吗?我希望不是,那是很多咖啡。这可能让你紧张,不是吗?让你很难组织有意义的词语。

你有一个新工具或流程的完美论点,但它在你的舌头上夭折了,因为咖啡因的毒性水平流过你的血管。是的,有时候言语很难。也很柔软。事实上,单词有各种各样的结构,这就是语言的美。

但我不是来和你谈论语言有多吸引人的。我在这里向你展示如何使用语言说服你的老板,持续集成和交付实际上是值得在你的组织中采用的概念。

这里有一封措辞强硬的电子邮件,你可以通过(微小的)编辑发给你的老板,让他们相信 CI/CD 是未来的

:boss@busyness.com
;futureself@busyness.com
主题:CI/CD 案例

**嘿老板:

所以,我知道我应该写一份关于昨晚停电的事后分析。我已经完成了一些草稿;营销变得越来越令人兴奋,因为没有什么比长篇大论、详细解释我们是如何把事情搞砸的更吸引人了。这是因为工程师更喜欢看热闹和幸灾乐祸。

但是让我为你设置场景:

我们过去一直在推送代码。我们是硅谷的宠儿,在每周的市政厅会议上宣布特性、集成和插件。我们让我们的客户习惯于每天服用最新和最好的多巴胺,他们无法获得足够的多巴胺,因为总是有更多的。

那些日子已经过去了。

我们已经四个月没有推出新功能了。没有人理解代码库的可怕部分,因为 Leroy 在放弃与技术的所有联系并裸奔到荒野之前从未解释过它们。每个工程师都害怕做出实质性的改变,因为应用程序离崩溃只有一步之遥。

我们不能再保持这种极快的速度了,因为这实际上会折断我们的脖子,很可能会断掉我们的产品。没有人费心去清理技术垃圾。我们有大量的人工 QA,因为没有人信任我们的代码。正如昨晚的停电所证明的那样,我们进展缓慢,仍在打破常规。

“我不想要投诉;我想要解决方案,”你说。太好了,我完全同意。数字来了。


我们的平均承诺部署时间(CDT)约为 30 分钟。这是构建脱离代码审查并为部署做好准备所需的时间。以下是这 30 分钟的细目分类:

  • 15 分钟构建和运行自动化测试
  • 5 分钟构建和部署到试运行
  • 10 分钟用于质量保证和抽查

一个 30 分钟的 CDT 看起来并不算长,但却有着涟漪效应:

30 分钟足够长了,工程师们会努力优化效率。提交一个仍然需要 30 分钟才能投入生产的一行程序并不是对时间的最佳利用,所以他们会将更多的代码压缩到一个拉请求中。

更长的拉取请求需要更多的时间来审查。审查代码已经需要相当多的时间。当审查者被要求查看超过 400 行代码的时,注意力就消失了。因此,对于更大数量的代码,您最终会得到更差的代码评审。

冗长的代码审查增加了请求变更的可能性。预计到这些变化,PR 的作者将不得不保持对更多代码的熟悉。即使他们设法在另一个特性上完成工作,他们仍然会产生切换上下文的成本

当工程师进入部署队列时,也会产生同样的成本。在 30 分钟内,他们除了监视他们的构建和后续部署的状态之外,什么也不能做。这在时间和金钱上都是极大的浪费。

假设一名普通工程师的工资为 50 美元/小时,每天部署两次,那么每年就是 13,000 美元。对于我们的 8 名工程师团队来说,这是 104,000 美元/年。因此,我们的基础设施实际上花费了我们额外的工程师工资。太棒了。

使用 CircleCI 这样的 CI/CD 工具(明目张胆的植入式广告!!)会节省我们的时间和金钱。9,000 美元/年为我们购买了 16 个容器,在这些容器上,我们可以以 4 倍的速度运行 4 个并发构建。这对我们的 8 人团队来说非常有用,因为我们很少尝试一次部署 4 个以上的特性。

假设这种并行化将我们的自动化测试时间减少了一半:大约 8 分钟。我们还可以说,我们的工程师正在合并更小的、更可审查的变更,因为他们不再玩弄系统了。这些更小的 PRs 意味着 QA 更快,所以让我们将其缩短到 5 分钟。如果我们使用 CircleCI 构建/部署,那么可以节省大约 3 分钟。

所有这些使我们的 CDT 降到了 15 分钟。我们花了 9000 美元,拿回了 52000 美元,净收益为 43000 美元。

我们刚刚通过实施一种理念和工具收回了时间和金钱。


我明白这不会在一夜之间发生;这将在一周内发生,甚至可能超过一个月。但这必须发生。

编写好的测试很难,我们的许多工程师甚至不认为编写一般的测试是“完成任务”所必需的这些工程师是绝对正确的:如果你不介意代码库中有狗屎,那么编写测试并不是绝对必要的。

听着,人类不擅长长期思考;这只是而不是我们如何连接。因此,毫不奇怪,我们倾向于短期的成功,并尽可能快地推出新功能。

但是为了保持这个速度,我们需要放慢速度,调整我们的流程。我意识到这听起来违背直觉,但是如果我们想摆脱技术债务,回到我们都想做的事情上来:构建客户喜欢的软件,这是必要的。

让我们让这一切发生

干杯,
未来的自己**

在 CI/CD 管道中实现访问控制策略

原文:https://circleci.com/blog/access-control-cicd/

本教程涵盖:

  1. GitHub 中受保护的分支和安全组
  2. 在 CircleCI 中使用上下文
  3. 在 CircleCI 中设置审批作业

想象一下你自己处于这种情况:你是一个积极的、熟练的 DevOps 或 DevEx 工程师。您计划实施自动化、完整的 CI/CD 管道。您知道如何去做,并且您知道额外的生产力和自动化将如何使您的团队和整个公司受益。但是出于安全考虑,该项目从未被批准。

许多组织,尤其是那些受监管行业的组织,对发布他们的软件有严格的要求,这是理所当然的。谁可以触发一个发布,什么时候,在什么条件下,以及在发布开始之前需要什么样的特定检查,都必须是严格控制的、可审计的和可逆的。

这些复杂的过程通常需要一个专门的交付经理来收集所有相关的特性简介、测试数据、安全评估和回滚计划,并提交给公司的决策者委员会。该团队可以为交付经理的部署开绿灯,通常也是按照特定的时间表。

还有另一种方法。您可以建立有效的和完全自动化的 CI/CD 管道,其中包括人工交付经理和批准委员会可以完成的所有检查。本教程将向您展示为管道创建细粒度访问控制的方法。您可以将这些方法用于企业严格管理的内部公司项目,也可以用于流行的开源项目。

先决条件

本教程假设您有使用 CI/CD 管道的经验,最好是 CircleCI。要实现这些步骤,您还需要对 GitHub 和 CircleCI 组织的管理员访问权限(以及这两个服务的帐户)。

如果您没有 GitHub 组织的管理员权限,您可以在 GitHub 中创建一个免费的,并与免费的 CircleCI 帐户一起使用。

企业项目中 CI/CD 管道的访问控制

对于企业组织来说,遵守现有的检查并进行良好的沟通是至关重要的。该过程通常包括在继续发布之前,对支持文档和对业务的潜在影响进行手动检查。我将指导您实现一个管道,该管道自动执行许多验证步骤,并在专门的交付经理手动批准流程后,仅在继续部署到生产

要实现访问控制,您需要设置一些东西:

  • GitHub 中受保护的分支
  • GitHub 中的安全组
  • CircleCI 的语境
  • CircleCI 的审批工作

受保护的分支

设置受保护的分支是设置访问控制的第一步。受保护的分支禁止团队成员将代码直接推送到该分支,而是强制所有的变更通过拉请求(PR)过程。通常你会保护你用来创建发布的分支;main比如说。您可以在 repo 的设置中切换受保护的分支。

Setting up protected branches in GitHub

您还可以指定规则和例外,例如审查要求、合并前需要通过的检查、历史规则等等。

Branch protection rules

在我的例子中,我将我的main分支设置为一个受保护的分支,需要通过审查和 PR。

设置安全组

如果没有正确上下文权限的用户触发了 CircleCI 管道(例如,通过提交),则需要该上下文的作业将被标记为 403(未授权)。

在我们的组织中,我们将建立两个安全组:development-leadsdelivery-managers

您组织中的所有开发人员都可以推进到他们自己的分支并发布拉请求,但是应该只要求团队领导审查任何新的拉请求。只有团队领导批准,变更才能合并到受保护的main分支中。然后,当交付经理获得放行许可时,他们就是触发软件发布的人。在本教程中,我们将为他们创建一个单独的安全组。

CircleCI 将使用这些带有上下文的安全组来限制哪些作业可以由谁来执行,以便只有交付经理可以手动触发它们。

使用 CircleCI 上下文

CircleCI 的语境有双重作用。一个角色限制与作业共享的机密范围,而另一个角色控制访问权限。

只有在工作流中指定了上下文的作业才能将上下文的秘密用作环境变量。在 CircleCI 中,上下文在整个组织中共享,因此它们可以在多个项目中重用。您可以使用 CircleCI 仪表板左侧的“组织设置”菜单进行设置。

对于访问控制,您可以为任何上下文指定安全组。对于本教程,
,我们将创建一个名为release的上下文,并为其指定delivery-mgrs安全组。

现在您可以设置您的管道了。示例项目使用以下流程:

  1. 试验
  2. 安全扫描
  3. 开发环境部署
  4. 交付经理的审批工作
  5. 生产部署
workflows:
 build-test-deploy:
   jobs:
     - test
     - security-scan:
         context: security
     - deploy-app:
         name: deploy-app-dev
         env: dev
         context: deployment-dev
         requires:
           - test
           - security-scan
         filters:
           branches:
             only:
               - main
     - approve-for-prod:
         type: approval
         requires:
           - deploy-app-dev
     - deploy-app:
         name: deploy-app-prod
         env: prod
         requires:
           - approve-for-prod
         context: release-prod 

管道使用环境变量的上下文,比如我们的 API 键,但是也使用release-prod上下文。release-prod上下文是封闭的,因此它只能由交付经理使用。

设置审批作业

难题的最后一块是审批工作:approve-for-prod。从技术上讲,任何人都可以登录并批准此作业。然而,它后面的deploy-app-prod作业使用release-prod上下文,该上下文只接受交付经理的凭证。如果没有这些凭证的人批准了它,发布作业将失败,并出现一个unauthorized错误,整个管道将失败终止。

其他有用的零碎信息

我们在工作流中设置的另一个控件是分支过滤器。由于这个过滤器,开发部署将只发生在main分支上。main分支被标记为受保护的,所以将代码放到main分支上的唯一方法是通过自动检查和代码审查,可能是由首席工程师进行。

您还可以使用需要人工批准的专门 QA 或安全审查步骤。将它们作为链条的一部分来构建。

开源项目的注意事项

到目前为止,我们已经讨论了访问控制的企业用例。大多数 OSS 项目没有像企业一样严格的遵从要求。相比之下,这些项目需要向更广泛的人群开放他们的贡献。这意味着允许更多的人触发管道。

通常发起并拥有一个流行的 OSS 项目的公司会继续雇佣核心贡献者。不属于该公司的其他定期贡献者和维护者可能会加入他们。然后是其他人——任何偶尔会贡献一个补丁或功能的人。

对于这种情况,您可以设置三个流:

  1. 公司内部流程。成员贡献和管理一切。公司员工可能是唯一被允许发布新版本软件的人。我们可以称他们为“内核贡献者”。
  2. 半内流。这些成员是组织的一部分,将能够直接推进到主要的 OSS 库。他们将能够使用 CircleCI 做任何事情,包括用 SSH 调试作业和触发管道。称他们为“外核贡献者”。
  3. 标准流程。社区中的其他贡献者可以发布 PRs,否则自己工作。我们称他们为“社区贡献者”。

对于社区贡献者。流程是标准的:创建一个 fork,然后打开一个 pull 请求。您可以切换 CircleCI 来构建将自动验证其更改的 pull 请求。

您应该仍然有一个受保护的分支和必需的检查。

对于核心贡献者群体,您可能也想包含一些秘密。秘密需要更多的工作。此外,通过 forks 传递秘密本身就不安全。任何人都可以通过修改他们 PR 中的管道来获取你允许传递的任何秘密。这可能包括某些服务的 API 密钥,或者项目分发位置的凭证。

一种选择是完全避免将秘密传递给 forks。您将不得不在审查任何代码之后将其推入您自己的组织,在那里它可以被执行,然后被合并到您的默认分支中。有篇很有帮助的文章解释了流程

另一个选择是允许将秘密传递给 forks,但是防止这些秘密被共享,除非代码已经被团队成员彻底审查和批准。您可以在 CircleCI 项目设置中进行设置。

CircleCI sharing secrets across forks

如果您切换密码,您将需要设置一个批准作业。将任何秘密放在一个只有属于该上下文的人才能访问的上下文中。

注意: 如果您使用这种方法,请确保您组织中的所有上下文都指定了安全组。如果您不这样做,恶意的参与者可能会在执行的 PR 中猜测您的上下文名称,并从中提取环境变量。

有了贡献者的内部和外部核心,您可以用非常相似的方式设置访问控制。也许只有内核可以发布新版本,但是在外核的任何人都可以合并任何拉取请求。或者也许你只有一组核心维护人员,他们可以做任何事情。细节由你决定!

结论

在本文中,我们研究了如何通过设置访问控制来进一步保护您的 CI/CD 管道,将管道的某些部分(以及相关的凭证)封锁给有限范围的人员。这是一种通用的方法,适用于企业组织和 OSS 项目。

我们使用 CircleCI 中的安全组、受保护的分支、上下文和批准作业来实现这种效果,对于 OSS 和专有项目都是如此。尝试适合您组织的最佳配置,安全可靠地发布您的应用程序。

保护您的 CI/CD 渠道- DevSecOps | CircleCI

原文:https://circleci.com/blog/adding-application-and-image-scanning-to-your-cicd-pipeline/

你听过有人说“安全是每个人的工作”吗?在我职业生涯的大部分时间里,我一直听到这样的话。像大多数有幸听到这句话的人一样,这句话主要是由安全团队成员说的,我也在听到这句话后翻了翻白眼。我记得自己被冒犯了,心想,“这个人有什么权利把他们的责任推卸给我?”直到我职业生涯的后期,在经历了几次安全事故后,我才意识到这句话的含义。他们说安全是每个人的工作,这是正确的。

DevSecOps

DevSecOps 是将安全实践融入软件开发生命周期(SDLC)的哲学。这意味着在应用程序开发过程中添加安全检查,而不是事后才添加。将安全性构建到 DevOps 实践中并不是一个新概念,但是根据我的经验,安全性实践通常是在 SDLC 结束时附加的。

您可能会问,“只要安全操作已经完成,在哪里和什么时候执行安全操作有什么关系?”好吧,这里有一个场景可能会澄清一些事情。想象一下,你刚刚为一个项目开发了一些很棒的新功能。您已经编写了新的特性,编写了所有相应的测试,并且您的工作正在您的 CI/CD 平台上通过。您很高兴您的工作得到部署,但是在这个过程的最后,您的发布被安全标记。他们手动对您的软件进行了扫描,扫描报告了项目中使用的一些依赖关系库中的高风险漏洞。您现在必须修复所有的安全问题,然后重新运行整个过程。

DevOps 使我们能够快速高效地交付高质量的软件。然而,我上面提到的场景效率不高。将安全流程自动化并实施到 CI/CD 管道中可以解决这一问题。通过将安全扫描集成到 CI/CD 管道的各个部分,团队可以在开发过程的早期就注意到安全差异。修复易受攻击的库越早出现就越容易缓解。考虑到更新后的库不支持易受攻击版本中的一些功能,而这些功能在应用程序中被大量使用。这是一个很大的问题,它需要一些重新设计来适应更新库的变化,在开发过程中增加了意想不到的时间和精力。同样,将安全性集成到 CI/CD 管道中会在早期提醒您注意安全性问题,并使您能够在过程中尽早修复它们。

既然我已经分享了什么是 DevSecOps 以及它的一些好处,我想演示如何使用一个名为 Snyk 的安全扫描工具轻松地将安全扫描添加到您的 CI/CD 管道中。

Snyk

CircleCI 与许多安全公司合作,但出于本文的目的,我们将使用 Snyk 来集成安全检查。Snyk 使您能够发现,更重要的是,修复您的应用程序和容器中的已知漏洞。Snyk 是 CircleCI 的技术合作伙伴,他们创造了 T2 Snyk orb,使开发者能够将安全扫描添加到他们的 CI/CD 管道中。在这篇文章中,我将展示给你的管道增加安全性是多么容易。

先决条件

开始之前,您需要准备一些东西:

一旦完成了所有这些先决条件,我们就可以进入下一部分了。

没有安全性的基本管道配置

让我们从一个基本的管道配置开始,这样您就可以看到一个没有安全集成的管道。这些天我一直在使用 Python 应用程序,所以下面是一个不包含任何安全操作的 Python 应用程序的管道配置示例。

version: 2.1
jobs:
  build_test:
    docker:
      - image: circleci/python:3.7.4
    steps:
      - checkout
      - run:
          name: Install Python Dependencies
          command: |
            echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV && source $BASH_ENV
            pip install --user -r requirements.txt
      - run:
          name: Run Unit Tests
          command: |
            pytest
  build_push_image:
    docker:
      - image: circleci/python:3.7.4
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: false
      - run:
          name: Build and Push Docker image to Docker Hub
          command: |
            echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV
            echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
            echo 'export IMAGE_NAME=orb-snyk' >> $BASH_ENV && source $BASH_ENV
            pip install --user -r requirements.txt
            pyinstaller -F hello_world.py
            docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
            echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
            docker push $DOCKER_LOGIN/$IMAGE_NAME
workflows:
  build_test_deploy:
    jobs:
      - build_test
      - build_push_image:
          requires:
            - build_test 

这种管道配置实现了以下功能:

  • 安装在requirements.txt清单文件中定义的应用程序依赖项
  • 执行应用程序的单元测试
  • 为应用程序创建/构建新的 Docker 映像
  • 将新创建的 Docker 映像发布到 Docker Hub 注册表以备后用

这个管道的目标是通过 Docker 映像构建、测试和部署代码。在这个过程中,没有任何安全或漏洞扫描。

启用安全的管道 Snyk 应用程序扫描

现在你已经看到了一个“不安全”的管道,它应该会让你起鸡皮疙瘩。接下来,我将向您展示一个支持安全的管道配置示例。

version: 2.1
orbs:
  snyk: snyk/snyk@0.0.8
jobs:
  build_test:
    docker:
      - image: circleci/python:3.7.4
    steps:
      - checkout
      - run:
          name: Install Python Dependencies
          command: |
            echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV && source $BASH_ENV
            pip install --user -r requirements.txt
      - snyk/scan
      - run:
          name: Run Unit Tests
          command: |
            pytest
  build_push_image:
    docker:
      - image: circleci/python:3.7.4
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: false
      - run:
          name: Build and Push Docker image to Docker Hub
          command: |
            echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV
            echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
            echo 'export IMAGE_NAME=orb-snyk' >> $BASH_ENV && source $BASH_ENV
            pip install --user -r requirements.txt
            pyinstaller -F hello_world.py
            docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
            echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
            docker push $DOCKER_LOGIN/$IMAGE_NAME
workflows:
  build_test_deploy:
    jobs:
      - build_test
      - build_push_image:
          requires:
            - build_test 

在我解释这个管道中发生了什么之前,我想提一下 Snyk 有许多功能,但是为了这篇文章的目的,我只打算涵盖应用程序Docker 图像扫描。上面的例子演示了应用程序扫描。现在,我将解释为使管道更加安全而添加的新部件。

下面的管道块使用 Snyk orb 轻松地将 Snyk 工具集成到管道中。该块相当于脚本或编程语言中的 import 或 include 语句。在这个块中,您还声明了想要使用的 Snyk orb 的版本。

version: 2.1
orbs:
  snyk: snyk/snyk@0.0.8 

下一个管道块定义了用于运行构建的 Docker 映像。然后,它会将您的源代码checkout或“git 克隆”到容器中。接下来,run:块将安装在requirements.txt文件中列出的依赖项。该文件列出了您所有的应用程序库和依赖项,它们可以被认为是项目 Python 方面特有的软件材料清单(SBOM) 。它还将软件列表提供给 Snyk,以便它知道要扫描和测试什么。

jobs:
  build_test:
    docker:
      - image: circleci/python:3.7.4
    steps:
      - checkout
      - run:
          name: Install Python Dependencies
          command: |
            echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV && source $BASH_ENV
            pip install --user -r requirements.txt 

下面您会发现之前讨论过的requirements.txt文件的内容。

Example requirements.txt file.

下一个块是我们在管道中执行一些 DevSecOps 操作的地方。- snyk/scan从 Snyk orb 调用scan命令。它将读取requirements.txt文件,然后将该软件列表与 Snyk 漏洞数据库进行比较,寻找任何匹配。如果有任何匹配,Snyk 将对其进行标记,并使该管道段失败。这里的目标是尽早提醒团队注意安全问题,以便可以快速缓解这些问题,并安全地继续 CI/CD 流程。

 - snyk/scan
      - run:
          name: Run Unit Tests
          command: |
            pytest 

示例流水线配置的其余部分处理 Docker 映像构建段。

Snyk 应用程序安全扫描完成后,如果没有检测到漏洞,您的构建将通过。如果扫描检测到漏洞,构建将会失败。这是 Snyk orb 的默认行为(关于这些参数的更多细节,参见 Snyk orb 参数)。随着扫描后管道的失败,Snyk 将提供一个关于构建失败原因的详细报告。

以下是 CircleCI 仪表板中报告的应用程序安全扫描失败的示例。

Example of Snyk scan failure within a pipeline.

扫描结果提供了有关安全扫描的有用详细信息。它们表明该应用程序正在使用易受攻击的版本 Flask 库,0.12.4。研究结果还提出了一个解决方案。该修复需要将 Flask 升级到较新的 1.0 版本。

您还可以从 Snyk 仪表板上看到这次失败扫描的结果。该仪表板具有更高层次的细节。下图显示了 Snyk 仪表板的示例。

Example of a scan failure within the Snyk dashboard.

安全扫描提供了对应用程序漏洞的洞察,以及如何缓解问题的有用建议,以便团队可以修复它们并继续下一个任务。有些修复比其他的更复杂,但是对问题的提醒是一个非常强大的特性。

安全启用管道 Snyk Docker 图像扫描

上面我已经讨论了 Snyk 应用程序扫描功能,现在我想讨论 Docker 图像扫描功能,它也可以轻松集成到 CI/CD 管道中。下面显示的build_push_image:块来自之前没有安全示例的基本管道配置。

 build_push_image:
    docker:
      - image: circleci/python:3.7.4
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: false
      - run:
          name: Build and Push Docker image to Docker Hub
          command: |
            echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV
            echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
            echo 'export IMAGE_NAME=orb-snyk' >> $BASH_ENV && source $BASH_ENV
            pip install --user -r requirements.txt
            pyinstaller -F hello_world.py
            docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
            echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
            docker push $DOCKER_LOGIN/$IMAGE_NAME 

上面的运行块没有集成安全扫描。这增加了暴露 Docker 映像中严重漏洞的风险。Synk 的 Docker 图像扫描是检查您的 Docker 图像漏洞的重要功能。以下是如何将 Snyk Docker 图像扫描集成到您的管道配置中的示例。

 build_push_image:
    docker:
      - image: circleci/python:3.7.4
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: false
      - run:
          name: Build and Scan Docker image
          command: |
            echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV
            echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
            echo 'export IMAGE_NAME=orb-snyk' >> $BASH_ENV && source $BASH_ENV
            pip install --user -r requirements.txt
            pyinstaller -F hello_world.py
            docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
      - snyk/scan:
          fail-on-issues: true
          monitor-on-build: true
          docker-image-name: $DOCKER_LOGIN/$IMAGE_NAME:$TAG
          target-file: "Dockerfile"
          project: ${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BRANCH}-app
          organization: ${SNYK_CICD_ORGANIZATION}
      - run:
          name: Push Docker image to Docker Hub
          command: |
            echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
            docker push $DOCKER_LOGIN/$IMAGE_NAME 

上面的管道配置块示例与原始示例有很大不同,现在我将讨论这些不同之处。

下面的块是一个新的- run:块,它设置了一些环境变量,用于命名和版本化正在构建的 Docker 映像。这个块中的最后一行构建了图像。

 - run:
          name: Build and Scan Docker image
          command: |
            echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV
            echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
            echo 'export IMAGE_NAME=orb-snyk' >> $BASH_ENV && source $BASH_ENV
            pip install --user -r requirements.txt
            pyinstaller -F hello_world.py
            docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG . 

下面的下一个块是 Docker 图像安全扫描声明和执行的地方。- snyk/scan:与之前的 app 安全扫描中使用的命令相同,但有所不同。为了从- snyk/scan:命令执行 Docker 图像扫描,您必须声明并设置docker-image-name:target-file: 参数的值。同样,我建议你熟悉一下 Snyk orb 参数,以了解该工具的功能。

 - snyk/scan:
          fail-on-issues: true
          monitor-on-build: true
          docker-image-name: $DOCKER_LOGIN/$IMAGE_NAME:$TAG
          target-file: "Dockerfile"
          project: ${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BRANCH}-app
          organization: ${SNYK_CICD_ORGANIZATION} 

下面是 CircleCI 仪表板中报告的管道内失败的 Docker 扫描的示例。显示的详细信息看起来与应用程序扫描相似,但绝对不同。它表明 Docker 映像中的漏洞源于基础映像,该映像是由语言维护者发布的。

Example of a Docker image scan failure within the CircleCI dashboard.

与 Snyk 应用程序扫描一样,关于失败的 Docker 图像扫描的更多详细信息可以从 Snyk 仪表板中找到。Snyk 仪表板将包含缓解检测到的漏洞所需的所有详细信息。Snyk 仪表板中 Docker 图像扫描失败的示例如下所示。

Example of a Docker image scan failure within the snyk dashboard.

最后一个流水线块访问 Docker Hub,并将扫描的 Docker 映像推送到并发布到 Docker Hub 注册表。这是在build_push_image:程序块中执行的最后一个命令。

 - run:
          name: Push Docker image to Docker Hub
          command: |
            echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
            docker push $DOCKER_LOGIN/$IMAGE_NAME 

包扎

在这篇文章中,我讨论了 DevSecOps 以及采用和集成到 CI/CD 管道中所带来的好处。将安全性集成到开发周期中的团队可以节省时间和精力,并避免潜在的安全事故。这意味着将安全扫描集成到 CI/CD 管道的每个可能的部分,以确保您的应用程序和基础架构得到适当的保护。

使用 CircleCI 合作伙伴的工具,如 Snyk 的 orb T1,将使团队能够轻松地将 DevSecOps 实践集成到他们的 CI/CD 管道中,保护他们的应用程序免受已知的威胁和漏洞。

你对这篇文章有什么反馈吗?欢迎在 twitter 上@我 @punkdata 开始关于 DevSecOps 的对话。

感谢阅读!

将审批作业添加到 CI 渠道| CircleCI

原文:https://circleci.com/blog/adding-approval-jobs-to-your-ci-pipeline/

明确的批准过程可以帮助减少生产问题,提供审计跟踪,并让您的组织相信您的工程团队正在部署正确的代码。

在本文中,我们将详细介绍如何在您的持续集成管道中设置一个批准作业,以便您的开发人员必须在 CI 管道部署到生产之前请求批准。

它是如何工作的?

一个request-approval作业被添加到您的管道中,利用 xMatters orb ,它将工作流的细节传递给 xMatters 。然后,xMatters 将通知发送到一系列设备,以到达当前的随叫随到批准资源。使用响应选项,批准人可以选择批准或拒绝请求。在批准时,请求 CircleCI 的批准作业 API 端点

让我们来看一下如何设置您的批准请求,以便您可以在生产代码发生任何更改之前对其进行监督。

先决条件

首先,您需要以下内容:

  • 一个的账户
  • 一个 xMatters 账户
  • 需要审批作业的项目的回购

我们将讨论的批准流程使用 approve a job API 调用来批准作业。对于各种工具来说,这样做的过程是相似的,但是这篇文章将集中在使用 xMatters 上。

配置 CircleCI

在高层次上,我们需要更新.circleci/config.yml文件,设置XM_URL值,然后将个人令牌存储在 xMatters 中。让我们开始吧。

这是一个简单的构建、保持、请求批准和部署配置文件。

version: 2.1

orbs:
  xmatters: xmatters/xmatters-orb@1.0.4

jobs:
  build:
    docker: 
      - image: circleci/node:4.8.2
    steps:
      - run: echo "Building building building!"

  request-approval:
    docker:
      - image: circleci/node:4.8.2
    steps:
      - xmatters/notify:
          recipients: Engineering Managers

  deploy-stuff:
    docker:
      - image: circleci/node:4.8.2
    steps:
      - run: echo "Deploy stuff here"

workflows:
  build-test-and-approval-deploy:
    jobs:
      - build
      - request-approval
      - hold:
          type: approval
          requires:
            - build
            - request-approval
      - deploy-stuff:
          requires:
            - hold 

这里要注意的项目是holdrequest-approval作业。您可以在 workflows 部分看到,标记为类型approval的挂起作业依赖于buildrequest-approval作业的成功执行。然后deploy-stuff作业取决于hold作业的批准。这意味着只有当应用程序成功构建并且使用 xMatters orb 的request-approval作业被执行后,才能批准hold。审批作业没有要执行或运行的步骤,它们只是一个工作流构造。

我们的目标是Engineering Management组,以便当前的电话技术经理可以批准(或拒绝!)管道。一旦挂起作业被批准,管道继续执行deploy-stuff作业,代码被部署到生产环境中。

我们需要在 CircleCI 的项目设置中设置XM_URL值。在此之前,我们将获取一个个人 API 令牌,以便 xMatters 可以正确地进行身份验证。进入你的账户设置,选择个人 API 令牌标签。

点击创建新令牌并将值复制到文本文件,直到我们准备好使用它。

配置 XML 模式

接下来,转到您的 xMatters 实例并导航到工作流屏幕。点击导入工作流,找到CircleCI.zip文件。这个文件可以从 repo 下载,这里。导入后,点击进入工作流程并导航至流程选项卡。将会列出两个画布。在这种情况下,我们需要批准请求,它具有处理大多数响应的逻辑。

单击画布,在右侧显示步骤面板中的步骤。

我们在这里获取入站 URL,所以双击左上角的批准请求-从 CircleCI 入站步骤。这将显示带有我们的入站 URL 的 HTTP 触发器对话框。复制这个值并保存起来以备后用。

退出该窗口并导航回工作流程。点击导航菜单底部的管理齿轮,点击自定义字段。这些字段是与用户相关的元数据。这是我们存储访问令牌的地方。

点击添加字段,并将其命名为“CircleCI Token Plain”。

注意 : 使用此名称很重要,因为它将在流设计器画布上的“查找用户属性值”步骤中使用。如果您在这里使用不同的名称,请确保也在那里更改名称。

将类型设置为Text。点击保存。然后,我们将向用户添加令牌。如果是你的令牌,点击右上角的用户名,然后点击个人资料

找出我们从 CircleCI 复制的个人令牌,并将其粘贴到我们刚刚创建的字段中。

注意 : 此令牌以纯文本形式存储,但只有您和您的主管有权查看此值。我们正在评估存储该令牌的其他方法,因此请密切关注这一领域的变化!

最后,回到 CircleCI,导航到需要批准的项目。确保选择管道列表中的项目,因为这将启用右上角的项目设置按钮。单击此按钮将显示项目的设置。我们将添加XM_URL值作为一个新的环境变量。

最后一步需要设置接收者。如果您的目标是一个组,请导航到 Groups 部分,创建一个新组,并添加适当的用户。

注意 : 您需要在个人资料中填写您自己的令牌。

测试和批准

就是这样!当管道被触发时,它将在我们在.circleci/config.yml文件中配置的hold步骤暂停。

这将向我们在上面的Create xMatters Event步骤中定义的用户触发一个通知。指定的收件人将收到通知,其中包含管道详细信息以及必要的响应选项。这是它在 xMatters 移动应用程序上的外观,但电子邮件非常相似。右侧的图像打开了响应抽屉,显示了可用的响应。

就像这样,选择 Approve 使 API 回调 CircleCI 以批准管道并将您的代码部署到生产环境中。

用 Anchore | CircleCI 进行集装箱安全扫描

原文:https://circleci.com/blog/Adding-container-security-scanning-anchore/

Docker 让开发人员能够大规模地简化应用程序的打包、存储和部署。随着软件开发团队越来越多地使用容器技术,保护这些映像变得很有挑战性。由于容器映像成为开发生命周期的常规部分,对这些工件的安全检查需要被编织到自动化管道中,并成为测试的标准化最佳实践。

使用锚定保护容器图像

当我们转向构建容器映像时,持续集成起着至关重要的作用。容器图像本质上是不可变的。意思是,一旦我们建立了一个图像,它是不变的。如果我们想对容器内运行的应用程序进行任何更改,我们将使用代码更改构建另一个映像,然后部署新的、更新的容器。这减少了开发和测试时间,并且非常适合 CI。

CI 的核心部分之一是自动运行测试。当我们在 CI 管道中构建容器映像时,我们应该使用能够对构建工件进行适当级别分析的工具。 Anchore 是一种对集装箱图像进行静态分析的服务,并应用用户定义的可接受的策略,以允许自动化的集装箱图像验证和认证。这意味着 Anchore 用户不仅可以深入了解映像中包含的操作系统和非操作系统包,还可以围绕工件及其内容创建治理。

在 circleci 构建中集成锚定扫描

在下面的例子中,我们将介绍如何将 Anchore 扫描集成到 CircleCI 构建中。

注意:这些例子利用了 anchore/anchore-engine @ 1 . 0 . 0orb。此外,它们需要一个 circleci 目录和config.yml文件。

将公共图像扫描作业的锚定扫描添加到 CircleCI 工作流

如果你需要扫描一个公共图像,这里有一个例子,我们使用 orb 扫描anchore-engine:latest公共图像。

version: 2.1
orbs:
  anchore-engine: anchore/anchore-engine@1.0.0
workflows:
  scan_image:
    jobs:
      - anchore/image_scan:
          image_name: anchore/anchore-engine:latest
          timeout: '300' 

将私有图像扫描作业的锚定扫描添加到 CircleCI 工作流

如果你需要扫描一个私有图像,这里有一个例子,我们使用 orb 扫描 anchore-engine:private Docker 注册表中的最新图像。

version: 2.1
orbs:
  anchore-engine: anchore/anchore-engine@1.0.0
workflows:
  scan_image:
    jobs:
      - anchore/image_scan:
          image_name: anchore/anchore-engine:latest
          timeout: '300'
          private_registry: True
          registry_name: docker.io
          registry_user: "${DOCKER_USER}"
          registry_pass: "${DOCKER_PASS}" 

将锚点图像扫描添加到容器构建管道作业中

在这里,我们从连接的存储库构建一个 Docker 映像,然后扫描该映像。作为扫描的一部分,我们已经将analysis_fail: True添加到配置中。当此项设置为 true 时,还将根据 anchore-engine 附带的默认策略包对分析的图像进行评估。如果策略评估的结果失败,管道将在这一步失败。

version: 2.1
orbs:
  anchore: anchore/anchore-engine@1.0.0
jobs:
  local_image_scan:
    executor: anchore/anchore_engine
    steps:
      - checkout
      - run:
          name: build container
          command: docker build -t ${CIRCLE_PROJECT_REPONAME}:ci .
      - anchore/analyze_local_image:
          image_name: ${CIRCLE_PROJECT_REPONAME}:ci
          timeout: '500'
          analysis_fail: True 

结论

通过上面的例子,我们可以看到如何无缝地将这些扫描添加到我们的 CircleCI 配置中,以便获得容器图像静态分析的好处。

请记住一个小提示:我们明确建议使用 Anchore 策略检查来获得更多的控制,以控制您希望或不希望哪些图像进入管道的下一步。这里的使用情形是,组织通常希望能够围绕正在构建的映像创建治理,并且只允许将合规的映像提升并推送到受信任的注册中心。

要了解更多信息,请注册参加我们即将于 2018 年 12 月 4 日举办的网络研讨会: Anchore + CircleCI:集装箱安全与合规


Jeremy Valance 曾担任过 IT 解决方案的管理、设计、开发、实施和测试等多个职位。他目前在 Anchore 担任高级解决方案架构师。

将 IaC 安全扫描添加到您的 CI 管道| Bridgecrew 和 CircleCI

原文:https://circleci.com/blog/adding-iac-security-scans-to-ci-pipelines/

随着工程师们寻求更快、更高效地部署云基础设施的方法,近几年来,基础设施作为代码 (IaC)的采用量激增。IaC 是指使用机器可读语言(代码)管理和配置基础架构的技术和流程,而不是低效的手动操作。像 Terraform 和 Pulumi 这样的语言和框架,提供了一种统一的语言来编纂基础设施,并简化跨不同环境和提供商的云编排。云提供商使用原生 IaC 框架,如 AWS CloudFormation 和 Azure Resource Manager (ARM ),将手动、一次性流程转变为一致、可扩展和可重复的供应。

将云安全转移到 IaC

IaC 将基础架构部署向左转移,为自动化、扩展和保护云环境提供了新的机会。

从历史上看,团队不得不在事后处理云安全。他们通过在运行时对违反策略的情况进行资源监控来做到这一点。即使有了自动化,对于需要被动解决已识别问题的工程师来说,这种方法也会变得既耗时又费力。通过将云基础设施外推到代码中并将其嵌入到开发生命周期中,团队现在可以预防性地解决云安全问题。

有了 IaC,除了容器扫描、依赖扫描、SAST、DAST 等,您还可以实施安全最佳实践。然而,为了使 IaC 安全真正有效,它必须嵌入到自动化 CI 管道中。

有关 CI/CD 安全最佳实践的更多信息,请阅读我们的CI/CD 安全和开发安全终极指南

安全实施入门

如何发现用于构建 AWS、Google Cloud、Azure 和 Kubernetes 资源的基础设施中的政策违规和错误配置?将物联网安全扫描添加到您的 CI 渠道中。Bridgecrew 通过持续扫描基础设施来识别已知的安全漏洞,并提供代码来修复这些漏洞。例如,以下是一些经常被忽视的政策:

  • 确保存储在 S3 存储桶中的所有数据在静态时都得到安全加密
  • 确保没有安全组允许从 0.0.0.0:0 进入端口 22
  • 确保存储在发布配置 EBS 卷中的所有数据都得到安全加密

为了使 IaC 安全高效可行,it 需要提供一致的反馈。获得这种一致性的最佳方式是将 IaC 安全性嵌入到您的自动化 CI 管道中。我们将使用 Bridgecrew CircleCI orb 来自动化每次提交的 IaC 安全性。

先决条件

要跟进,你需要几样东西:

  1. 一个的账户
  2. GitHub 的一个账户
  3. 一个 Bridgecrew 账户

分叉错误配置的地形项目

在本教程中,我们将分叉 Bridgecrew 的“设计脆弱”项目,TerraGoat。前往 TerraGoat 项目并将其转入你自己的 GitHub 账户。

terragoat project on GitHub

在你的分叉项目中,选择添加文件,然后创建新文件。将文件路径设置为.circleci/config.yml,并添加以下 CircleCI 配置:

version: 2.1
orbs:
    bridgecrew: bridgecrew/bridgecrew@1.0.3
jobs:
    build:
      executor: bridgecrew/default
      steps:
        - checkout
        - bridgecrew/scan:
            directory: .
            api-key-variable: BC_API_KEY 

滚动到底部并选择直接提交到主分支。这将把 Bridgecrew orb 添加到我们接下来要创建的 CircleCI 管道中。

使用 Bridgecrew 实现自动化 IaC 安全

前往 Bridgecrew 平台中的 CircleCI 整合页面。在那里,您将找到环境变量、您的 API 键以及我们刚刚添加到我们的存储库中的 CircleCI config.yaml。

CircleCI integration page on Bridgecrew

我们的下一个任务是将集成添加到 CircleCI 中。进入 CircleCI 项目页面。如果设置了 GitHub 集成,那么新分叉的 TerraGoat 项目将会列出。

CircleCI project page

选择terragoat旁边的设置项目。选择使用现有配置开始构建。构建将会失败,因为我们没有将 API 键作为环境变量添加。为此,请转到项目设置并选择环境变量。选择添加环境变量。从 Bridgecrew 集成页面添加BC_API_KEY和您的 API 密钥。

CircleCI environment variables page

新的构建应该开始。如果没有,选择从构建旁边的开始重新运行工作流。如果您打开管道和扫描部分,您可以找到在构建过程中失败的所有检查。

CircleCI scan page

由于列出了 129 个失败的检查,此构建失败。为了防止这种情况,您可以通过跳过不相关的检查来自定义您组织的防护栏,或者您可以在没有使构建失败的情况下触发失败检查的警报。

更进一步

要按严重性、类别或合规性基准对事件进行进一步调查,您可以前往 Bridgecrew 平台。一旦到了那里,您就可以深入问题,了解它们的影响和受影响的资源,并根据需要修复代码。

Bridgecrew platform example

有了拉式请求集成,您可以立即将补救推送到您的 VCS、合并并重新构建。这种连续的工作流是确保错误配置不会部署到调配的云资源中的最佳方式。🙌

结论

IaC 帮助云原生团队将其基础设施提升到一个新的水平,带来与速度、可扩展性、成本节约和安全性相关的好处。通过自动化您的 IaC 安全,您可以利用 IaC 固有的不变性来强化您的云安全状况并节省时间。

改进 Docker 容器管理和性能的指标

原文:https://circleci.com/blog/adding-metrics-for-docker-container-performance/

当运行云服务时,让客户成为第一个注意到问题的人从来都不是好事。在几个月的时间里,我们的客户遇到了这种情况,我们开始积累一系列关于码头工人工作不可预测的启动时间的报告。起初报告很少,但频率开始增加。具有高度并行性的工作在报告中出现得不成比例。

虽然我们怀疑哪些部分的旋转速度较慢,但我们还是想确定一下。首先,我们查看了报告问题的具体工作。为了帮助解决这个问题,我们改进了启动步骤中的日志,以便它们显示每个阶段花费的时间。日志显示,在问题案例中,下载、提取和创建阶段花费的时间最多。

原因如下。当 Docker 作业启动时,我们会查看用户在其配置中指定的 Docker 映像列表,并提取所有映像以确保它们位于主机虚拟机的映像缓存中。拉取图像是每个图像层的多个阶段的结果:

  • 在下载过程中,我们从 Docker 存储库中获取压缩层
  • 在提取期间,该层被解压缩
  • 然后,我们在配置中为每个图像创建一个容器
  • 然后,作业步骤在主容器中运行

这个过程重复 n 次,其中 n 是作业的并行度。作业的每个实例都被称为一个“任务”。使用这些定义,可以清楚地了解为什么具有高并行性的作业可能会受到更大的影响。这份工作只是将“放慢速度”掷出更多次。

更糟糕的是,我们自己也无法快速轻松地发现问题。我们决心解决客户的报告,但我们也想确保我们将来更有可能自己发现问题。我们在制定解决方案时考虑了这两个想法。

我们的方法

最初,我们需要回答下载、提取和创建阶段的以下问题:

  • 典型的表现是什么样的?
  • “慢”是什么意思?
  • 事情“慢”的频率有多高?

衡量绩效

我们开始研究如何为下载、提取和创建这三个阶段中的每一个阶段生成度量。事实证明,跟踪层下载和提取的性能有点棘手。

  • Docker 的 pull API 并不直接提供下载或提取图层所需时间的详细汇总信息。
  • pull API 只提供了一个 JSON 消息流,用于生成进度条。如果您曾经在命令行上运行过 docker pull,您可能对消息流很熟悉。

经过对消息流的深思熟虑,我们发现,通过合计跨每一层和下载/提取阶段传输的时间和数据,我们可以产生相当准确的性能指标。

幸运的是,测量容器创建的性能就像添加一个计时器一样简单!

除了将这些数据输入我们的指标处理系统之外,我们还将这些数据包含在启动步骤的日志中:

...
Starting container cimg/go:1.16
  image cache not found on this host, downloading cimg/go:1.16
1.16: Pulling from cimg/go
...
Digest: sha256:6621f92d57703a89f82d20660e8947aa00eb07baba90be16e64666a3c726077c73MB/1.873MBB
Status: Downloaded newer image for cimg/go:1.16
  pull stats: download 124.3MiB in 1.283s (96.84MiB/s), extract 124.3MiB in 2.806s (44.28MiB/s)
  time to create container: 3.201s
  using image cimg/go@sha256:6621f92d57703a89f82d20660e8947aa00eb07baba90be16e64666a3c726077c
Starting container circleci/postgres:12.4-ram
  image cache not found on this host, downloading circleci/postgres:12.4-ram
12.4-ram: Pulling from circleci/postgres
...
Digest: sha256:368f896494f6cc56cbd1993b1f789548e78a1348e88e94a79aeca7c6ca8f8ac328kB/8.228kBB
Status: Downloaded newer image for circleci/postgres:12.4-ram
  pull stats: download 108.1MiB in 1.099s (98.33MiB/s), extract 108MiB in 3.853s (28.03MiB/s)
  time to create container: 2.973s</b>
  using image circleci/postgres@sha256:368f896494f6cc56cbd1993b1f789548e78a1348e88e94a79aeca7c6ca8f8ac3
Time to upload agent and config: 1.256190772s
Time to start containers: 1.347100754s 

决定什么是“慢拉”

CircleCI 不控制下载速度;它依赖于外部托管的 Docker 存储库的性能。在可能的情况下,我们确保能够快速连接到流行的存储库,包括 Docker Hub 和 Amazon 的 ECR。谷歌似乎在 GCR 速度方面做得很好,尽管我们的连接依赖于公共互联网。根据我们的指标得出的数据,我们选定 20 MiB/s 作为一个好指标。

Extract 在我们的控制之下,完全依赖于运行 Docker 的虚拟机的可用资源。我们发现 10 MiB/s 是一个很好的指标,表明提取性能已经下降到足以引起明显的用户影响。由于小数值的舍入问题,我们发现必须排除完成时间少于 5 秒的拉动。

对于 create,我们决定(有点武断地)创建容器的时间不应该超过 10 秒。我知道我不想再等了!

那么问题有多严重呢?

遗憾的是,情况比我们预期的要糟糕得多。这是我们的仪表盘显示的 2020 年 11 月的情况:

  • 3.5%的下载速度很慢。
  • 6.7%的提取物反应缓慢。
  • 12%的创作都很慢!

这些数字(尤其是对 create 而言)令人震惊!然而,该团队对可能的原因以及如何改善这些数字有一些想法。

我们做了什么来改进我们的发现

我们做的第一件事,也是最简单的一件事,就是升级我们运行的 Docker 基础设施。我们已经发现了 AUFS 文件系统驱动程序中的各种补丁,这些补丁看起来与容器创建性能有关。结果是:

  • 3.5%的下载速度很慢。
  • 7.8%的提取物是缓慢的。
  • 0.77%的创建很慢——好得多!

这为容器创建时间提供了显著的即时改进,尽管提取时间的代价很小。这是朝着正确方向的一个有希望的转变,但光靠它还不够。

图层下载和提取性能怎么样?

既然我们对容器创建性能感到满意,我们就把注意力转向另外两个指标:层下载和提取。在这种情况下,找到解决方案并不容易。我们花了相当多的时间检测 Docker 守护进程,以确定 Docker 层子系统的哪些部分运行缓慢。我们希望发现类似锁争用的问题。这个搜索产生了不确定的结果,可能是因为在个人 Linux / Mac 机器上运行 Docker 容器的兼容性问题。事实上,AUFS 将被否决也可能是一个促成因素。

另外值得注意的是:作为切换到 overlay2 文件系统驱动程序的一个副作用,我们看到了磁盘性能的显著提高。

我们还研究了从我们运行的 AUFS 驱动程序到重叠 2 文件系统驱动程序的迁移。我们决定在 overlay2 driver 上测试我们 Docker 车队的一小部分,这将使我们能够与 AUFS 进行性能比较,并解决客户可能面临的任何问题。

比较指标看起来很有希望。虽然在创建指标上有轻微的回归,但是提取性能有显著的提高,下载也有轻微的改善。

缓慢提取改进

Slow extracts

缓慢创造进步

Slow creates

这一过程的下一部分是慢慢地开始向我们所有的用户推出这一变化。我们希望在这个过程中遇到一些小的兼容性问题,并努力与客户合作,帮助他们解决遇到的任何问题。虽然统计数据看起来更好,但我们认为仍有改进的空间,特别是因为随着推出的进展,缓慢创建统计数据回升到 1.7%。

改进 CircleCI Docker 垃圾收集器

我们有一个名为 docker-gc 的组件,它负责维护 docker 实例的健康。它的两个职责是确保实例不会耗尽存储空间,并删除任何现在未使用的 Docker 对象,如容器、映像、卷和网络。

当前的实现是一个简单的 BASH 脚本,由 cron-job 安排定期运行。这有几个问题:

  • 它是不可观察的,只产生简单的日志。
  • 如果一个 GC 周期花费了很长时间,cron 调度程序会尝试同时运行两个周期。
  • 测试并不容易,因为 BASH 本身并不特别容易测试。
  • 推出更改需要很长时间,因为它们包含在实例的虚拟机映像中。需要实例替换来更新。

对于编写这样的低资源占用代理来说,Go 是一种优秀的语言。我们决定将脚本移植到 Go,并将其部署为 Docker 容器本身。除了立即解决我们遇到的所有问题,我们还深入了解了 docker-gc 对我们的 docker 实例的正面和负面影响。

我们看到的非常清楚。当 docker-gc 运行其垃圾收集周期时,会对启动阶段(下载、提取和创建)产生重大影响。因为我们的车队很大,所以这不会对整体数字产生很大的影响。然而,对于那些不幸在 docker-gc 收集垃圾时开始运行的任务,它有负面影响。

我们花了大约一个月的时间,在新的可观察性的推动下,对事情进行了调整:

  • Docker / CircleCI build-agent 只会泄漏网络。
  • 在 Docker 中修剪卷和容器花费了很长时间,修剪结果显示没有删除任何内容。由于这些新的见解,我们将 docker-gc 改为只修剪网络。
  • 快速删除大量图像与旋转减速密切相关。这似乎与 Docker 守护进程中的锁争用有关,但事实证明很难解决这个问题。

我们希望更频繁地运行垃圾收集周期,但我们担心这会使问题“不那么严重,但更频繁”。经过一番思考,我们决定当实例上的任何任务处于“旋转”状态时,暂停垃圾收集。

项目的成果——成功!

遵循所有这些优化,我们能够实现以下改进。

  • 下载速度慢:3.6%到 3.2%,提高了 0.4%
  • 慢提取物:6.7%到 0.84%,提高了 5.8%
  • 慢速创建:12%到 1.7%,提高了 10.3%

所以,进步了。尽管如此,该团队认为可能还有进一步改进的余地。我们一定会与您分享。

将测试覆盖范围添加到 CI 管道|工作服和 CircleCI

原文:https://circleci.com/blog/adding-test-coverage-to-your-ci-pipeline/

健康代码库的关键指标之一是良好的测试覆盖率。一旦你接受了 CI/CD 的价值,使用测试覆盖服务来跟踪项目测试覆盖随时间的变化是有意义的。它不仅可以确保测试以与代码相同的速度增长,还可以帮助您控制您的开发工作流,通过/失败检查和 PR 注释来显示哪里缺少覆盖率以及如何改进它。

在本教程中,我们将把一个包含测试覆盖的简单代码库放入 CircleCI 上的一个 CI 管道中,然后配置 CircleCI 将我们项目的测试覆盖结果发送给 Coveralls ,这是一个流行的测试覆盖服务,被世界上一些最大的开源项目所使用。

我们将通过使用 CircleCI 的 orb 技术来实现这一点,这使得它可以快速而轻松地与第三方工具(如工作服)集成。

先决条件

要跟进这篇文章,您需要以下内容:

  • 对 Ruby 足够熟悉,可以阅读一些基本代码和测试
  • 一个的账户
  • GitHub 的一个账户

注意 : 我们会创建一个免费的工作服账户。

测试覆盖率,而不是测试

如果你是测试覆盖率的新手,下面是它的工作方式:

对于由代码和测试组成的项目,可以添加一个测试覆盖库来评估项目的代码被其测试覆盖的程度。(在我们的 Ruby 项目中,我们使用了一个叫做 Simplecov 的测试覆盖库。)

在项目测试套件的每次运行中,测试覆盖都会生成一个测试覆盖报告

它在 CI/CD 中如何工作

  1. 你在你的 SCM(即 GitHub)。
  2. 您的 CI 服务构建您的项目,运行您的测试,并生成您的测试覆盖报告。
  3. 您的 CI 向工作服发布测试覆盖报告。
  4. 连体工作服将覆盖范围更改发布到共享工作空间。
  5. 如果您选择这样做,工作服会向您的 PRs 发送评论和通过/失败检查,以控制您的开发工作流程。

一个具有测试覆盖的简单应用程序

下面是一个极其简单的 Ruby 项目,它同时采用了测试和测试覆盖:

simple project

(在 GitHub 上找到这里。)

这是该项目的全部代码:

class ClassOne

  def self.covered
    "covered"
  end

  def self.uncovered
    "uncovered"
  end

end 

这些是测试:

require 'spec_helper'
require 'class_one'

describe ClassOne do

  describe "covered" do
    it "returns 'covered'" do
      expect(ClassOne.covered).to eql("covered")
    end
  end

  # Uncomment below to achieve 100% coverage
  # describe "uncovered" do
  #   it "returns 'uncovered'" do
  #     expect(ClassOne.uncovered).to eql("uncovered")
  #   end
  # end
end 

注意 : 现在,ClassOne中的两种方法只有一种正在测试。

我们已经安装了我们的测试覆盖库 Simplecov,作为我们的Gemfile中的一块宝石:

source 'https://rubygems.org'

gem 'rspec'
gem 'simplecov' 

我们在spec/spec_helper.rb中向 Simplecov 传递了一个配置设置,告诉它忽略测试目录中的文件:

require 'simplecov'

SimpleCov.start do
  add_filter "/spec/"
end 

运行测试

让我们第一次运行测试套件,看看结果:

bundle exec rspec 

结果:

ClassOne
  covered
    returns 'covered'

Finished in 0.0028 seconds (files took 1 second to load)
1 example, 0 failures

Coverage report generated for RSpec to /Users/jameskessler/Workspace/2020/afinetooth/coveralls-demo-ruby/coverage. 4 / 5 LOC (80.0%) covered. 

注意 : 除了测试结果本身,Simplecov 告诉我们它在一个新的/coverage目录中为我们生成了一个测试覆盖报告。

方便的是,它生成了 HTML 格式的结果,我们可以像这样打开:

open coverage/index.html 

我们的第一份报道是这样的:

coverage_80_percent_index

整个项目的覆盖率达到了 80%。

点击lib/class_one.rb调出文件结果:

coverage_80_percent_file

你会注意到绿色的覆盖线和红色的未覆盖线。

在我们的例子中,4/5 的线路被覆盖,相当于 80%的覆盖率。

添加测试以完成覆盖

要添加测试,取消对 ClassOne 中第二个方法的测试的注释:

require 'spec_helper'
require 'class_one'

describe ClassOne do

  describe "covered" do
    it "returns 'covered'" do
      expect(ClassOne.covered).to eql("covered")
    end
  end

  # Uncomment below to achieve 100% coverage
  describe "uncovered" do
    it "returns 'uncovered'" do
      expect(ClassOne.uncovered).to eql("uncovered")
    end
  end
end 

现在再次运行测试套件:

bundle exec rspec 

coverage/index.html打开新结果。

新报告如下所示:

coverage_100_percent_index

覆盖率从 80%提高到 100%(并变绿)。

现在,如果我们点击lib/class_one.rb,我们会看到:

coverage_100_percent_file

现在已经覆盖了五个相关行中的五个,导致了文件的 100%覆盖率,这意味着我们的单个文件项目的 100%总覆盖率。

设置 CI 渠道

既然我们理解了测试覆盖在这个项目中是如何工作的,我们很快就能通过工作服来验证同样的结果。

首先,我们需要建立 CI 渠道。

将项目添加到 CircleCI

注意 : 如果你想跟进,现在是一个好时机从这个回购中分支出项目,并将其克隆到你的本地机器上。一旦你做到了这一点,你可以按照这些步骤与你自己的副本。从现在开始,我们假设您正在开始一个全新的项目,对原始项目没有任何更改。换句话说,测试覆盖率从 80%开始。

要向 CircleCI添加新的公共回购,请使用您的 GitHub 登录名登录:

circleci-login.png

如果您属于多个 GitHub 组织,请选择适用于您的项目的组织:

circleci-choose-org.png

然后您将看到贵组织的 GitHub 项目列表:

circleci-org-projects.png

点击新项目旁边的设置项目:

circleci-setup-project-coveralls-demo-ruby.png

然后你会看到新项目设置页面:

circleci-project-ready-prompt.png

在这里,您可以选择让 CircleCI 引导您设置项目,或者手动添加您自己的配置文件。

我们将手动添加我们的配置文件,以便更仔细地查看,因此单击手动添加:

circleci-start-project-options.png

您将收到一个提示,询问您是否已经将一个./circle/config.yml文件添加到您的 repo:

circleci-start-project-add-config-manually.png

我们还没有,所以我们现在就去做吧。

将配置文件添加到项目存储库中

在项目的基本目录下,创建一个名为.circleci/config.yml的新的空文件。

vi .circleci/config.yml 

现在,将以下配置设置粘贴到空的.circleci/config.yml文件中:

version: 2.1

orbs:
  ruby: circleci/ruby@1.0

jobs:
  build:
    docker:
      - image: cimg/ruby:2.6.5-node
    steps:
      - checkout
      - ruby/install-deps
      - ruby/rspec-test

workflows:
  build_and_test:
    jobs:
      - build 

那些配置设置是什么意思?

值得指出的是,我们使用的是 CircleCI 管道配置规范的 2.1 版,这是最新版本,在我们的文件顶部有所说明:

version: 2.1 

v2.1 配置规范的两个核心概念是 orb 和工作流。

orb 是可重用的配置包,为了方便和标准化,可以跨项目使用。这里我们利用 CircleCI 新提供的 Ruby orb ,它可以快速建立一个新的 Ruby 项目。

orbs:
  ruby: circleci/ruby@1.0 

工作流是收集和编排工作的一种方式。这里我们定义了一个简单的工作流build_and_test

workflows:
  build_and_test:
    jobs:
      - build 

这调用了一个我们已经定义的作业,称为build,它检查我们的代码,安装我们的依赖项,并在 CI 环境中运行我们的测试——一个运行 Ruby 2.6.5 和 Node:

jobs:
  build:
    docker:
      - image: cimg/ruby:2.6.5-node
    steps:
      - checkout
      - ruby/install-deps
      - ruby/rspec-test 

作业是你的管道的主要构件,它包括步骤和完成你的管道工作的命令。

请注意,在我们工作的最后一步,我们使用一个内置命令来运行 RSpec 测试,这是 CircleCI 的新 Ruby orb ,称为 rspec-test :

steps:
   [...]
   - ruby/rspec-test 

这不仅为运行我们的 RSpec 测试提供了一行程序,还为我们提供了一些免费的东西,包括自动并行化和默认的测试结果目录。

为什么是自动并行化?

它允许我们从我们的测试套件中并行运行测试,这提高了速度,并且在运行大量测试时特别方便。更多的实际操作,参见circle ci中的测试拆分教程。

为什么有一个默认的测试结果目录?

为了方便起见,这给了我们一个单一的地方来存储我们的 CI 环境中的测试结果,这些结果已经从任何并行运行中合并了。

保存文件,提交并推送:

git add .
git commit -m "Add .circleci/config.yml."
git push -u origin master 

就是这样!CircleCI 在其远程 CI 环境中构建您的项目。

确认您的首次构建

CircleCI 在您提交最后一个提交时开始构建您的项目:

git push -u origin master 

要向自己证明这一点,只需访问你在 CircleCI 的项目。

对我来说,这意味着去这里:
https://app . circle ci . com/pipelines/github/coverallsapp/coveralls-demo-ruby

您的 URL 会有所不同,但应遵循以下格式:

https://app.circleci.com/pipelines/github/<your-github-username>/<your-github-repo> 

所以我们正在检查我们的第一个构建,而且——哎呀,这看起来不太对劲

我们的第一次构建失败了:

circleci-first-build-failed.png

为什么?

*请注意错误消息:

bundler: failed to load command: rspec [...]
LoadError: cannot load such file -- rspec_junit_formatter 

CircleCI Ruby orb 似乎在寻找rspec_junit_formatter,查看 orb docs ,有道理:https://circle ci . com/developer/orbs/orb/circle ci/Ruby # commands-r spec-test

circleci-ruby-orb-rspec-test-command-docs.png

rspec-test命令上的注释如下:

You have to add `gem `spec_junit_formatter`` to your Gemfile. 

所以我们就这么做吧。

rspec_junit_formatter宝石安装在您的Gemfile中:

# Gemfile
[...]
gem 'rspec_junit_formatter' 

运行bundle install:

bundle install 

推动变革:

git add .
git commit -m "Add 'rspec_junit_formatter'."
git push 

然后再次检查我们的构建…然后- 太好了!

成功的构建:

circleci-first-build-success.png

注意那些测试结果,看起来很像我们在本地运行时得到的结果:

[...]

ClassOne covered returns 'covered'
  0.00042 seconds ./spec/class_one_spec.rb:7

Finished in 0.0019 seconds (files took 0.12922 seconds to load)
1 example, 0 failures

Coverage report generated for RSpec to /home/circleci/project/coverage. 4 / 5 LOC (80.0%) covered. 

就像在我们的本地环境中一样,Simplecov 正在生成一个覆盖报告,并将其存储在/coverage目录中。

Coverage report generated for RSpec to /home/circleci/project/coverage. 4 / 5 LOC (80.0%) covered. 

我们现在在 CI 中有测试覆盖率。

将项目配置为使用连体工作服

现在,让我们告诉 CircleCI 开始发送那些测试覆盖结果给工作服。

我们很幸运,因为连体工作服已经发布了一个连体工作服 orb 遵循 CircleCI orb 标准,这使得这个即插即用。

但在我们设置这个之前,我们需要在 Coveralls 创建一个新账户,这个账户对拥有公共(开源)回购的个人开发者是免费的。

将项目添加到连体工作服

要将您的回购添加到工作服,请前往 http://coveralls.io/sign-in并登录 GitHub:

coveralls-sign-in.png

首次登录时,您不会有任何活动的回购,因此请转到添加回购并找到您的公开回购列表:

coveralls-add-repo.png

要添加您的回购,只需点击您的回购名称旁边的切换控件,将其切换到上的:

coveralls-add-repo-turn-on.png

太好了!工作服正在追踪你的回购。

完成设置

在发布连体工作服 orb 之前,设置 Ruby 项目以使用连体工作服的默认方法是安装连体工作服 RubyGem,它利用 Simplecov 作为其主要依赖项,并负责将 Simplecov 的结果上传到连体工作服。

然而,为了坚持 CircleCI 的 v2.1 配置,并利用 CircleCI 的新 Ruby orb 的优势,我们将设置工作服 orb 来与 Ruby orb 一起工作。

准备使用连体工作服圆球

现在,第一个考虑,这有点违反直觉,是工作服 orb 是用 Javascript 而不是 Ruby 编写的,它依赖于 Node。不过这没关系,因为,如果您还记得,我们配置了 Ruby orb 来安装包含 Ruby 节点的 Docker 映像:

jobs:
  build:
    docker:
      - image: cimg/ruby:2.6.5-node
    steps:
      [...] 

然而,工作服 orb 的另一个要求是它期望 LCOV 格式的测试覆盖报告。因此,为了满足这个要求,我们将对我们的项目进行一些修改。

首先,我们将把simplecov-lcov宝石添加到我们的Gemfile中:

# Gemfile
[...]
gem 'rspec_junit_formatter'
gem 'simplecov-lcov' 

其次,我们将在spec_helper中更改一些与 Simplecov 相关的配置:

# spec_helper.rb
require 'simplecov'
require 'simplecov-lcov'

SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
SimpleCov.start do
  add_filter "/spec/"
end 

这里我们需要simplecov-lcov,我们告诉 Simplecov 做两件事:

首先,将多个报告文件合并成一个文件。

SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true 

第二,以 LCOV 格式导出结果。

SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter 

更新您的.circleci/config.yml

接下来,我们将把工作服球体添加到我们的.circelci/config.ymlorbs部分:

# .circleci/config.yml
version: 2.1

orbs:
  ruby: circleci/ruby@1.0
  coveralls: coveralls/coveralls@1.0.4

[...] 

jobs部分,我们将为我们的build工作添加一个新步骤:

# /circleci/config.yml
[...]
orbs:
  ruby: circleci/ruby@1.0
  coveralls: coveralls/coveralls@1.0.4

jobs:
  build:
    docker:
      - image: cimg/ruby:2.6.5-node
    steps:
      - checkout
      - ruby/install-deps
      - ruby/rspec-test
      - coveralls/upload:
          path_to_lcov: ./coverage/lcov/project.lcov

[...] 

这个命令coveralls/upload调用了连体工作服 orb 的upload命令。在它下面,我们将传递path_to_lcov参数,它告诉 orb 在哪里找到它应该上传到工作服 API 的覆盖报告。

添加一个COVERALLS_REPO_TOKEN

最后,如果您使用像 CircleCI 这样的私有 CI 服务,那么工作服 API 需要一个访问令牌来安全地标识您的 repo。这叫你的COVERALLS_REPO_TOKEN

如果您在构建之前访问连体工作服项目的起始页,就会遇到这种情况:

coveralls-repo-token-start-page.png

但是您也可以随时从您项目的设置页面获取它:

coveralls-repo-token-settings.png

要让 CircleCI 代表您的 repo 安全地发布到工作服 API,只需将您的COVERALLS_REPO_TOKEN作为环境变量添加到 CircleCI web 界面的项目设置>环境变量下,如下所示:

circleci-env-var-repo-token.png

现在我们准备把覆盖结果发给 CircleCI 的工作服。

因此,让我们推动我们刚刚做出的所有更改:

git add .
git commit -m "Finish coveralls setup."
git push 

通过工作服验证测试覆盖率

既然我们了解了测试覆盖在这个项目中是如何工作的,那么让我们通过工作服服务来验证那些相同的结果。

假设我们将我们的项目配置为使用 CircleCI 和工作服,并将这些更改推送到我们的 repo,最后一次推会触发 CircleCI 的新构建:

circleci-new-build-80-percent.png

然后根据构建日志将测试结果上传到连体工作服 API:

#!/bin/bash -eo pipefail

[...]

sudo npm install -g coveralls
if [ ! $COVERALLS_REPO_TOKEN ]; then
  export COVERALLS_REPO_TOKEN=COVERALLS_REPO_TOKEN
fi
export COVERALLS_ENDPOINT=https://coveralls.io

[...]

cat ./coverage/lcov/project.lcov | coveralls

[...]

[info] "2020-09-25T21:53:13.404Z"  'sending this to coveralls.io: ' '{"source_files":[{"name":"lib/class_one.rb","source":"class ClassOne\\n\\n  def self.covered\\n    \\"covered\\"\\n  end\\n\\n  def self.uncovered\\n    \\"uncovered\\"\\n  end\\n\\nend\\n","coverage":[1,null,1,1,null,null,1,0,null,null,null,null],"branches":[]}],"git":{"head":{"id":"c6b825b7bd7d4f7bbe4e75e530884a4b9fd9d9cd","committer_name":"James Kessler","committer_email":"afinetooth@gmail.com","message":"Configure project for CircleCI & Coveralls using the Coveralls orb.","author_name":"James Kessler","author_email":"afinetooth@gmail.com"},"branch":"circle-ci","remotes":[{"name":"origin","url":"git@github.com:coverallsapp/coveralls-demo-ruby.git"}]},"run_at":"2020-09-25T21:53:13.376Z","service_name":"circleci","service_number":"1917bc85-51f8-4646-80db-8b15cc40ad6c","service_job_number":"17","repo_token":"*********************************"}'

CircleCI received exit code 0 

引发了工作服的新版本:

coveralls-first-build-80-percent.png

显示覆盖率为 80%。这正是我们所期望的。

现在,让我们验证一下,在我们项目的测试覆盖中,工作服正在跟踪变化。为此,让我们重新添加那个将覆盖率提升到 100%的测试。

打开测试文件/spec/class_one_spec.rb,取消对文件中第二个测试的注释:

require 'spec_helper'
require 'class_one'

describe ClassOne do

  describe "covered" do
    it "returns 'covered'" do
      expect(ClassOne.covered).to eql("covered")
    end
  end

  # Uncomment below to achieve 100% coverage
  describe "uncovered" do
    it "returns 'uncovered'" do
      expect(ClassOne.uncovered).to eql("uncovered")
    end
  end
end 

现在,保存文件,提交更改,并将其推送到 GitHub:

git commit -m "Add tests to make coverage 100%."
git push 

这一推动将引发 CircleCI 的新建筑:

circleci-first-build-100-percent.png

这反过来又引发了工作服的新款式:

coveralls-new-build-100-percent.png

现在读数是 100%:

coveralls-new-build-100-percent-zoomed.png

嘭!从工作服自动测试覆盖更新。

后续步骤

既然您的项目已经设置为自动跟踪测试覆盖率,接下来您可能想要做的一些事情包括:

  1. 获得徽章 -给你的回购人的自述文件添加一个漂亮的“覆盖”徽章。
  2. 配置 PR 注释——在合并之前通知合作者测试覆盖的变化。
  3. 设置通过/失败检查 -除非达到覆盖阈值,否则阻止合并。
  4. 探索更复杂的场景 -为更大的项目利用并行性。

从这里的工作服文档开始。

结论

一个健康的代码库是经过良好测试的代码库,一个健康的项目是测试覆盖率在整个开发过程中保持在前端和中心的项目。

一个测试覆盖服务,像工作服,让你跟踪项目测试覆盖随时间的变化,让你的整个团队看到这些变化,甚至停止降低项目质量的合并。

使用 CircleCI 的最新 orb 规范,本教程展示了通过使其成为您的 CI/CD 管道的一部分,将您的项目与测试覆盖服务连接起来是多么容易,特别是当服务利用您的 CI 平台的配置标准时。*

CircleCI 可靠性的更新

原文:https://circleci.com/blog/an-update-on-circleci-reliability/

可靠性更新 2023-01-30

在过去的四个月中,我一直在向您介绍可靠性更新,与您分享我们的成功和挑战,以及我们在幕后所做的工作,以确保我们的平台随时可以构建。

这个月,我们经历了一次安全事件,它破坏了您的许多团队的能力。

所以,尽管可靠性在过去的一个月里有所提高,我还是想长话短说,留给你们两件事:

  1. 此时此刻,我们的首要任务是继续关注事件的后果,与我们的客户合作,确保您的管道是安全的,您的秘密是循环的。我们会一直这样做下去,但是一旦我们像往常一样回到工作中,我会带着一个更注重数字的更新回来,给你们带来实质性的指标,你们可以把它作为我们一直在做的工作的指标。
  2. 当我思考系统时,可靠性和安全性实际上是一致的:让我们更可靠的东西也让我们更安全。因此,我们为提高可靠性所做的工作(即更干净地隔离系统的各个部分,维护第三方验证的审计点和日志记录)也增强了我们从安全事故中恢复的能力。您的工作需要安全性和可靠性,我们的目标是继续提高拥有高度安全、高度可用的系统的标准。

如果你还没有轮换你的秘密,请这样做。如果有,谢谢。

下个月我会带着更多的可靠性更新回来。

抢劫

可靠性更新 2022-12-21

在最近的几个更新中,我谈到了我们一直在采取的一些行动,以建立我们服务核心的长期可靠性。在这次更新中,我想描述一下我们是如何发现潜在问题的。具体来说,围绕系统问题定位,无论它们是组织的还是架构的。

像许多组织一样,在 CircleCI,我们有一个“您构建它,您运行它”的软件交付方法,这意味着团队对他们自己的交付流的整个生命周期负责。我们还分享了事故调查和跟进的实践。然而,随着团队能够独立管理整个生命周期,我们开始忽略更多的系统问题,认为它们只存在于处理后续工作的单个团队中。

今年早些时候,我们通过汇总所有来源的数据,将注意力转向了这些系统性问题。这包括查看事故后报告、相关历史数据以及各个团队完成的所有后续工作。清点所有数据是重要的,但最突出的是获得进行更改所需的聚合视图需要什么。

我们的大多数工具都是面向单个事件的,而不是总的回顾。即使显示事件数据的工具也没有揭示我们想要的东西。我们查看了从事故响应中花费的时间到原因分类的所有内容,以便我们能够组织自己和我们的系统以获得最大的影响。

虽然我们能够在电子表格中完成大部分汇总工作,但大部分事件后跟进工作都是由叙述驱动的。对这些历史数据进行充分的组织以得出结论是很困难的,但这对于看到更大的图景非常有帮助。通过阅读一份事故报告或与我们的一个团队深入探讨,我们能够看到一些不清楚的事情。

所有这些工作都强调了一种有趣的张力,即保持我们快速发展的、与流一致的 DevOps 文化,同时为我们的团队带来一个消除系统范围挑战的有利位置。这项工作为我们提供了更清晰的见解,我们需要在哪里解决在我们的组织或架构(或两者)中发现的问题。因此,我们正在缓解本质上更系统的问题,并提供防护栏,以便我们的团队仍然可以快速移动并拥有他们所构建的东西。有了这个总体视图,我们在发现和解决故障点方面取得了进展。

为了更好的建筑,

罗伯·朱伯

罗布@ circles . com

可靠性更新 2022-10-27

上个月,我向你承诺,我会给你带来我们可靠性工作的每月更新:进展如何,什么在起作用,我们还有哪些工作要做。

我想通过这些更新实现三个目标:

  1. 加强我们对可靠性的承诺,
  2. 分享更多我们的可靠性路线图,以便您对我们在这项工作上分享的任何未来更新有更广泛的了解
  3. 公开我们的工作和见解,这样社区也可以从我们的经历中受益。

在软件社区中,当服务中断时,我们都会受到影响,但是我们通过分享我们的经验而共同进步。

上个月(向下滚动查看完整更新),我分享了我们一直致力于隔离平台的各个部分,以保护客户的构建并保持它们运行。

今天,我想分享更多关于我们当前方法的细节:系统隔离,以便无论如何保护你的构建。我想强调的是,这不仅仅是我们做的和报告的事情,而是保护构建的原则将指导我们完成即将到来的可靠性投资,以及平台上所有新的开发工作。

像所有平台一样,我们是通过进化来到这里的,所以让我带你看一段 CircleCI 平台的(简略)历史。

像许多其他公司一样,我们从一个整体开始。默认情况下,在 monolith 中你所有的工作都是混合在一起的。这必然会产生耦合,并导致级联故障的可能性。如果没有分离,代码库中某个地方的失败会导致其他地方的失败。

当我们分解这个整体时,我们是基于工作阶段,或者在不同的点上发生了什么(比如工作流编排和作业执行)。这种方法简化了我们的代码库,使交付更加容易,但是在这些阶段中,我们结合了活动的工作和工作的历史报告。

我们现在正在做的工作是在这些阶段中的每一个阶段进行隔离,这样运行活动构建的每个组件都可以免受其他任何东西的影响。

我们正在逐步开展这项工作,以确保快速取得成果,同时最大限度地减少中断。第一阶段涉及简单的工具,比如在需要时禁用历史查看的功能。这就产生了一个释放阀。

我们还增加了只读副本在历史查询中的使用。我们正在利用这些服务的分离部署来隔离计算资源,即使代码是共享的。

我们正在进入的下一个阶段包括完全分离系统。换句话说,实时构建的代码路径和数据与历史构建的代码路径和数据不同。虽然副本有助于分配负载,但它们要求所有存储具有相同的数据量。这可以通过分片来解决,但是即使这样,您仍然会被试图支持两种访问模式的模式设计所困扰。当它们完全分离时,我们可以优化每个设计,包括规模和产品功能。我们在这方面还处于起步阶段,但我们再次采取渐进措施,开始尽快实现收益。

这给我们带来了一个很好的问题:为什么我们一开始没有这样做?这是规模的需要,一开始就这么做是错误的。为什么?当你第一次创建一个像我们这样的平台,并做出早期的架构决策时,我相信做出让你能够快速转向并响应早期客户需求的决策是非常重要的。你不知道他们想要什么,因此你也不知道你的团队会继续构建什么样的功能来支持和取悦这些客户。在开始的时候,可以想象我们最终会面对这样一个项目,但是我不认为我们能够知道我们会达到一系列的临界点中的哪一个。

总之,我希望你从这次更新中得到的是,我们正在认真对待这件事,并以我们对待所有工作的方式来对待它:循序渐进,将你的需求作为我们决策的核心。虽然我们不希望系统崩溃,但它还是发生了。更好的隔离意味着我们可以朝着真正的目标前进:确保你的构建每天都在运行,不管我们的平台或者更大的生态系统中发生了什么。

如果你对我们在这个旅程中的渐进步骤感兴趣,请回来查看每月更新。如果不是,那也没关系;回去做支持和取悦你的顾客的事情,但是我们想让你知道 CircleCI 幕后发生了什么。

为了更好的建筑,

罗伯·朱伯

罗布@ circles . com

可靠性更新 2022-09-19

上周,管道页面在一天中的大部分时间都不可用。这阻碍了许多团队按照预期管理他们的工作。作为一名工程师和一名领导者,我知道保持心流,并在需要时随时准备好工具是多么重要。我们很抱歉给你的团队工作带来的干扰。

正如我在四月份所说的(全文如下),作为首席技术官,我的首要任务是减少 CircleCI 事故的持续时间和影响。

但当事情看起来像上周那样时,我们取得的进展可能并不明显。

除了从我们的原始帖子开始关注诊断速度之外,我们已经开始投资保护您完成工作的能力(即,运行管道),即使在出现问题时。虽然我们的工作仍在进行中,但我们已经取得了一些重要成果。但是如果你到目前为止还看不到或感觉不到这项工作的影响,那么我们就没有成功。不是作为一个技术团队,也不是为了建立我们希望与您建立的信任关系。

一个值得注意的收获是团队已经开始分离我们架构的部分。这使我们能够限制事故影响,并在事情发生意外时保护您的管道。它允许我们做一些事情,比如暂时关闭一些用户界面,以确保管道仍然可以运行,这就是我们上周所做的。但是我们没有告诉你。相反,您看到站点关闭了,并合理地假设什么都没有改变。

再说一遍,我们在这方面还有更多工作要做,我们仍然对此投入很大。我们不能阻止事情的发展,但是我们可以继续寻找新的方法来确保您的构建可以一直运行,并且为您提供更好、更及时的关于如何完成工作的信息,即使 A 计划失败了。

此外,距离我们上次的可靠性更新已经过去了 5 个月,我们可以做得更好。展望未来,我承诺每月向您更新我们新的可靠性开发。同时,我欢迎您的反馈。

可靠性更新 2022-04-13

在 CircleCI,我们的使命是管理变化,以便软件团队能够更快地创新。但是最近,我们知道我们的可靠性没有达到客户的期望。作为客户运输管道的核心,我们知道当我们停机时,您的运输能力也会停止。我们对您的工作中断感到抱歉,并对给您和您的团队带来的不便表示歉意。

发生了什么事

我们平台或基础架构的任何一个部分都不会因为最近的中断而出现问题。相反,我们已经看到了各种各样的问题,从导致 bug 的更新到依赖性问题,以及上游提供者的不稳定性。我们的定价计划的1 月更新为我们的平台带来了更高的流量和使用率。虽然我们为此进行了规划和建模,但它促使我们在一些系统中达到了拐点。

虽然最近事件的原因没有明确的模式,但我们知道我们解决问题的总时间太长了。深入我们的事件响应协议有助于我们发现我们的团队在压力下的执行力对我们没有帮助的地方。我们完全接受无可指责的工程文化和 DevOps 原则“你构建它,你运行它”,但是我们的系统和我们的团队的分布式本质使得这种联系、交流和解决变得困难。

为什么?在过去的 12 个月里,我们的工程团队几乎增加了一倍。这种增长是有意的,并提供了一些令人难以置信的速度-仅在上周,我们就部署了 850 多次。但这种增长也意味着我们的直觉知识基础变得不那么重要和有凝聚力。我们需要在我们所有的团队中重建广泛和深入的系统理解。

我们正在做什么来前进

对我们来说,技术与人息息相关,提高我们的可靠性需要以人为本的方法。从上周开始,我们建立了一个老虎小组,包括我自己在内的第一反应人员随叫随到,随叫随到。这是一个由个人组成的全球团队,能够通过流程和技术快速解决问题并实现长期变革。我们的目标是加强工程师的影响力,他们可以推动事件从识别到解决,然后帮助与更大的团队分享见解。

过去,我们将可靠性工作的重点放在系统“热点”上,这些“热点”是停机时间的已知来源,包括车队管理和机器供应。我们在那里进行了深度投资,并取得了回报。但是随着我们组织的成长,我们的问题不再是服务级别的中断,而是大型分布式系统的复杂交互。我们 tiger 团队的目标是让您尽快恢复工作,然后利用我们学到的知识解决这些事件的根本原因。

我们还对我们的平台进行投资,着眼于未来进行构建和重建。我们最近聘请了一位新的首席架构师来领导我们在平台可扩展性和构建长期产品创新方面的工作。

你如何知道我们正在取得进展

虽然这是不明智的(而且难以置信!)为了保证我们永远不会再发生事故,我们可以承诺减轻客户的负担。

随着我们继续投资于我们的长期平台稳定性,我们的短期重点是减少事件长度。对于客户影响超过一小时的事件,我们承诺在 status.circleci.com 上发布事件报告。

作为首席技术官,提高事件响应能力是我的首要任务。我们知道我们在这方面还有工作要做,我们相信我们现有的计划和团队将帮助我们立即做出改进。感谢我们的客户和社区一直以来的支持和耐心。

宣布自动测试平衡- CircleCI

原文:https://circleci.com/blog/announcing-automatic-test-balancing/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


Kegoneko-Balance

TL;DR:circle ci 不再天真地在构建容器之间划分测试,而是基于执行时间智能地划分测试。

CircleCI 的现状

多年来,CircleCI 通过在许多构建容器(引擎盖下的 LXC 容器——与 Docker 使用的轻量级容器化相同)中平均分配测试,实现了大型测试套件的快速执行。虽然我们总是提供通过手动指定每个容器上应该发生什么来设置并行动作的选项,但是使用 CircleCI 并行性的最简单方法是允许我们自动推断的测试步骤并行运行您的测试。测试分布在不同的容器中,以便在每个容器上运行相同数量的测试,所有这些都不需要使用流行的框架和语言对标准项目设置进行任何配置。

新的热点

现在,我们的自动并行性更进了一步,根据过去的执行时间,而不仅仅是测试数量,自动将测试划分到不同的容器中,让您的测试套件运行得更快。例如,如果您有 90 个浏览器测试,每个花费 10 秒钟,100 个单元测试,每个花费 1 秒钟,都由 RSpec 运行,那么使用 10X 并行度,CircleCI 将跨 9 个容器运行浏览器测试,并在 1 个容器上运行所有单元测试(或者平均分布它们),以便每个容器花费相同的时间运行测试。所有这些都是自动发生的,不需要任何额外的设置,并且您的测试将会不断地重新平衡,以尽可能快地运行您的测试套件。

如何打开它

以下测试运行程序当前支持自动测试平衡:

  • RSpec

  • 迷你测试

  • 鼻子

  • 黄瓜

只要您的测试使用 CircleCI 推断的命令运行,就不需要额外的配置,尽管 RSpec 和 minitest 确实要求您在项目的依赖项中简单地包含一个特殊的测试格式化程序 gem。详见本文档

它是如何工作的

在引擎盖下,新的自动测试平衡使用相同的 JUnit 格式的测试输出,支持我们的详细测试失败报告。CircleCI 解析并存储每次测试的计时数据。然后,该信息被用于执行跨构建容器的时间加权测试分布,然后通过将命令行标志传递给每个测试运行程序来应用该分布。

后续步骤

我们将很快增加对更多测试跑步者的支持——如果您想请求对某个特定跑步者的支持,请给我们发短信到sayhi@circleci.com!我们还将致力于将一个接口暴露给先前的测试计时数据,以便同样的自动平衡可以被定制为与不太常见的测试套件一起工作。测试愉快!

宣布 CircleCI 波士顿- CircleCI

原文:https://circleci.com/blog/announcing-boston-office/

今天,我们自豪地宣布我们的波士顿办事处开幕,这是我们在旧金山总部之外的第二个美国办事处。虽然我们从一开始就是一家分布式公司,并在世界各地拥有许多团队成员,但在东海岸扎根标志着我们成长中的一个重要时刻,我们非常高兴加入波士顿蓬勃发展的技术社区。

繁荣的技术中心

我们的销售总监 Chris Calkin 分享了为什么波士顿是 CircleCI 发展的理想之地:“开设波士顿办事处将使我们能够在一个已经对 DevOps 生态系统进行了巨额投资的市场中占据一席之地。我们很高兴成为这一新浪潮的一部分。新的存在使我们更接近我们的客户,如 BCG、MassMutual 和 MIT,并且距离其他东北市场也只有一趟火车的距离。”

以下是波士顿地区的一些客户对 CircleCI 的评价:

“CircleCI 已经成为我们软件开发生命周期中不可或缺的一部分。我们发现 CircleCI 很容易集成到我们基于 Go、Ruby、Python 和 JavaScript 的所有项目中。使用 Docker 和 AWS ECS 的持续部署使得 EverQuote 的新服务开发比以往任何时候都更容易。最近,CircleCI 2.0 的可配置性帮助我们提高了工作性能。”-Ryan Grimard,EverQuote 的 SVP 产品工程

“我们每天都依赖 CircleCI,因为它可靠、易于使用,并且与 GitHub Enterprise 完美集成。对于切尔莱西,我只有积极的话要说。”麻省理工学院林肯实验室的汤姆·多诺万

该办公室位于波士顿北部 TD Garden 附近,我们预计在未来一年将在那里进行扩展,以适应我们不断增长的东海岸客户群。

和我们一起庆祝吧!

我们的正式开业时间是 8 月 22 日星期三,我们将敞开大门欢迎当地客户和 DevOps 社区的任何人。有兴趣过来看看吗?详情请联系 chris.calkin@circleci.com。

波士顿见!

CircleCI 开发者中心- Docker | CircleCI

原文:https://circleci.com/blog/announcing-circleci-developer-hub/

TL;DR: 您现在可以通过 CircleCI orbs 和 Docker 便利映像,在我们新的开发者中心的一个方便的地方找到所有最好的 CI/CD 配置优化、迁移指南和 CircleCI 文档,以及 CI 配置包等资产。

在过去的 9 年里,我们已经看到 CI/CD 从一个利基实践发展成为团队的一个众所周知的关键杠杆点。CircleCI 首席执行官吉姆·罗斯(Jim Rose)表示:“随着公司适应快速变化的市场环境,我们看到采用率有所上升。“我们发现,这些团队需要更多的教育、支持和帮助,以快速、安全和规模化地优化他们的开发运维实践。我们的开发人员中心旨在弥合这一差距,并在提供最佳 CI/CD 实践的同时指导每个人的采用流程。”

开发者中心提供了开发人员在 CircleCI 上启动和运行所需的一切,包括配置优化、迁移指南和文档,以及通过 CircleCI orbs 和 Docker 映像的 CI 配置包等资产。

</blog/media/2020-08-25-devhub_home_scroll.mp4>

探索资源,学习新知识,并与您的团队共享开发人员中心。对开发人员来说,一些最强大的资源包括:

针对 CI 优化的 Docker 图像

Docker 映像包含工具层、文件集和其他指令,代码在部署和安装到各种机器上时需要这些指令才能很好地运行。CircleCI 为各种编程语言和数据库维护了一个 Docker 映像的车队,专门为 CI 环境中的性能而设计,并可通过开发人员中心进行搜索。

一系列 CircleCI 接口和集成

有了多种界面体验,开发人员可以选择如何构建、测试和部署他们的应用程序,以及管理各种其他任务,包括语法分析、集成管理和配置验证。除了我们重新设计的 UI 体验之外,我们还通过我们的命令行界面(CLI)新架构的 API 为我们的用户社区提供了两种额外的保持流畅的方式。

此外,开发人员可以使用 CircleCI orbs 将他们现有的工具链集成到他们的 CI/CD 构建流程中,circle CI orbs 是可重复使用的 YAML 包,有助于自动化重复的流程。

从自动化基础设施部署到保护管道,CircleCI 资源的完整集合现在可以在开发人员中心轻松访问。

展望未来

开发者中心的下一阶段将包括跨 CircleCI 文档的简化搜索功能、改进的 orb 浏览、甚至更多的优化工具,以及对教育内容和社区资源的更多关注。

随着越来越多的软件团队开始为他们的软件开发探索、采用和优化 CI 和 CD,我们将继续致力于为他们提供构建未来所需的最佳资源。

宣布 CircleCI 日本!-切尔莱西

原文:https://circleci.com/blog/announcing-circleci-japan/

英文のあとに日本語訳が続きます。

今天,我们自豪地宣布 CircleCI 的子公司 CircleCI G.K. 成立。虽然我们已经与日本开发商合作了一段时间,但这是我们第一个正式的国际办事处,也是我们致力于日本市场的标志。

日本的 CircleCI

日本长期以来一直是 CircleCI 的关注点:它是我们最大的市场之一,我们在那里与一些非常创新的公司合作,如 CyberAgent、Mercari、DMM、DeNA、CrowdWorks 和许多其他公司。在东京设立专门的办事处将有助于我们更好地服务这些客户,并为日本的所有用户提供更好的 CircleCI 使用体验。

以下是一些客户对 CircleCI 的评价:

“自从集成 CircleCI 以来,开发软件变得更加高效。它高度可配置,并使我们的成本最小化。CircleCI 已经成为我们发展过程中不可或缺的一部分。”长谷川诚&香织·奥库在赛博代理

“我们喜欢 CircleCI,因为它提供了高度的可配置性,无需管理 CI 基础架构。这种灵活性使我们能够大幅减少花费在 CI 上的时间。设置 CircleCI 很容易,我们可以将其用于我们的许多存储库,无论其大小如何。”Hideki Igarashi,CrowdWorks 的工程师

“将 CI 整合到您的开发流程中对于构建优秀的产品至关重要。CircleCI 的设置很简单。事实证明,它非常可靠,并促进了我们生产力的提高。我们每天都使用 CircleCI 来测试、分析和构建我们的代码。”软件工程师肖玛·铃木在梅尔卡里进行测试

壮大我们的团队

我们从东京的一个小而强大的团队开始,并希望能很快扩大规模。我们希望您能加入我们- 在此申请

和我们一起庆祝

我们的团队本周在东京的 GitHub 卫星上,我们想和你们一起庆祝我们在日本的扩张。请光临我们的展位,或者用日语发推文给我们 @CircleCIJapan


本日、私たちはCircleCIの子会社であるCircleCI 合同会社 (以下CircleCI Japan)を東京に設立したことをお知らせいたします。CircleCI Japanを設立することは、初の海外オフィスの立ち上げというだけではなく、同時に日本のマーケットへの強いコミットを意味します。

CircleCIと日本

CircleCI 創立以来、日本は私たちにとって常に最も大きなマーケットの一つでした。すでに、サイバーエージェント、メルカリ、DMM.com、DeNA、CrowdWorksなど、日本を代表する革新的な企業にCircleCIをご利用いただいています。

東京オフィスを開設することにより、既存のユーザーへのよりきめ細かなサポート、新規のユーザーのみなさまには、より快適な導入のお手伝いなど、より素晴らしい日本の開発環境をビルドし続けます。

以下は、CircleCIのお客様の声の一例です。

“CircleCIの導入によって我々はより効率的に開発することができるようになりました。 コストを抑えつつ最適な環境にカスタマイズすることができ、もはや我々の開発にとって必要不可欠なものになっています。“

サイバーエージェント 長谷川 誠 & 大倉香織

“CircleCIはCIインフラの管理を気にすることなく、柔軟な設定が行えることが魅力です。 クラウドワークスではその柔軟さを活かし、CIの時間を大幅に短縮することができました。 また、設定も容易であるため、標準のCIツールとして大小さまざまなリポジトリでCircleCIが利用されています。“

CrowdWorks エンジニア 五十嵐英樹

“CIはプロダクトを作るために欠かせないツールです。CircleCI は容易に導入でき、高い信頼性でチームの開発生産性を高めてくれました。テストやコード解析、ビルドなど様々な場面で利用しています。“

メルカリ Software Engineer in Test 鈴木祥真

CircleCIで一緒に働きませんか?

一緒にCircleCI Japanを大きくしていきませんか?今はまだ少人数のJapanチームですが、これから積極的に採用していく予定です。興味がある方は採用ページをご確認ください。

CircleCI USチーム来日のお知らせ

6 月 12 日と13 日の両日、CEOのJim Rose 率いるCircleCI USチームが東京・五反田のGitHub Satelliteにいます。ユーザーのみなさま、ぜひ、私たちのブースにお立ち寄りください。新しくオープンした @CircleCIJapan への投稿もお待ちしております。

宣布 CircleCI 伦敦| CircleCI

原文:https://circleci.com/blog/announcing-circleci-london/

今天,我们自豪地宣布 CircleCI London 开业,这是我们继东京之后的第二个国际枢纽,也是我们进军更广阔的欧洲市场的第一步。

CircleCI 下一个地区总部自然选择了伦敦。我们拥有一个庞大的客户群,这些客户来自一些最具创新性的公司,不仅在欧洲,而且在世界各地:康泰纳仕国际、Deliveroo、Netguru、Monzo、Transferwise 等。这是 CircleCI 更好地服务于欧洲客户网络的绝佳起点。

更广泛地说,欧洲正在见证对风险投资公司的投资激增,目前占全球风险资本投资的 16%。2019 年上半年,3.5B 投资于总部位于英国的公司,随着后期生态系统的成熟,进一步允许强大的创业社区实现显著的全球规模和规模。

CircleCI 的用户群反映了欧洲科技公司日益增长的多样性,涵盖金融科技、健康科技、按需服务、移动性、内容管理和协作工具等行业。我们决定扩展到该地区的部分原因是基于这些不断增长的需求。我们专门设计了我们的工具,使开发人员能够花时间做对他们来说最重要的事情,并且更快、更有效、更大规模地完成。

我最近加入了 CircleCI,领导我们的欧洲扩张,很高兴成为团队的一员。一个原因是 CircleCI 被视为技术领先公司的一个重要工具,这些公司希望以更快的速度构建更好、更稳定的软件,并加快产品交付和随之而来的价值创造的步伐。

Adam Nowak, Netguru 的软件开发实践主管说“灵活性和高性能是我们的客户寻求的首要因素。因为我们从 CircleCI 那里获得了这一点,所以有了 CircleCI,我们的增长和我们为客户提供的产品比没有 circle ci 时要好得多。为了自己提供优秀的服务,我们需要依靠 CircleCI 这样的优秀服务商。”

Condé Nast International 的软件工程师 Emily Atkinson 说:“我发现与 CircleCI 合作的一个有趣的事情是,作为一个基础设施团队,我们现在能够与所有其他产品工程团队合作,并允许他们使用 CircleCI 以满足他们需求的方式开发他们的管道。我们可以直接与这些团队合作,定制他们的管道,尽可能提高效率。CircleCI 对我们很有价值,因为它促进了 DevOps 思维。”

我们从一个小但强大的团队开始,并希望迅速增加。我们希望你能加入我们!了解更多关于 CircleCI 的公开职位的信息。

宣布 CircleCI 每个项目的见解- CircleCI

原文:https://circleci.com/blog/announcing-circleci-per-project-insights/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


我们很高兴宣布每个项目的见解。CircleCI per-project Insights 通常面向所有付费和开源客户,帮助您了解您的项目在一段时间内的表现。

开始:点击主导航上新的“洞察”图标。在 Insights 仪表板上,您可以单击您的回购名称来访问每个项目的见解。

“每个项目的细节”页面为您提供了对所选分支的以下两个图表的访问:构建状态和构建性能。

Per-project Insights 每个项目的见解

  • 构建状态: *目前,在 Insights 仪表板上,用户可以访问其默认分支的最后 50 个构建。作为该页面的一部分,用户可以选择所需的分支,并访问所选分支的 100 多个构建状态。

Build status graph 构建状态图

Build Performance:Build Performance 聚集了特定一天的构建数据,并绘制了该天的中位数,可以追溯到 90 天。现在,您可以通过选择一个特定的分支机构来监控回购的性能。

Build performance graph 构建性能图

我们将继续向此页面添加功能,并帮助您优化构建。我们总是很高兴收到你的来信。请随时在feedback@circleci.com向我们发送反馈。

宣布 CircleCI 价值 1 亿美元的 E 系列

原文:https://circleci.com/blog/announcing-circleci-s-100m-series-e/

今天,我们宣布 CircleCI 已经筹集了 1 亿美元的 E 轮融资,由 IVP 牵头,Sapphire Ventures 也参与其中,使我们的总融资额达到 2.15 亿美元。

我想亲自感谢我们的客户、团队、合作伙伴以及所有帮助我们完成构建市场上最好的持续集成和持续交付(CI/CD)工具这一使命的人。这笔新资金将帮助 CircleCI 继续我们的全球增长,同时对我们的产品和客户进行更多投资。

去年 7 月,当我们宣布 D 轮融资时,我们说我们会把钱投入到 CircleCI 作为一个平台的发展中,改善我们的核心产品和全球扩张。从那时起,我们看到:

  • 总体使用量的增加。CircleCI 目前每天处理 180 多万份工作。在过去的八个月里,我们还看到信贷消费增长了 200%。

  • 最近推出的 CircleCI 洞察端点 。端点显示诸如构建运行了多长时间、平均恢复时间、变更失败率以及构建是通过还是失败等指标。

  • 增加了窗口支持T3。通过将 Windows 添加到 CircleCI 支持的执行环境(Linux、Docker、macOS)中,团队可以在单个工作流上跨多个平台运行作业,并在其开发管道上实现更大的灵活性。

  • orb 采用率的增加。Orbs 将 CircleCI 配置捆绑到可重用的包中,使用户能够执行数千种开箱即用的用例,而无需复杂的配置。今天,超过 22,000 个组织已经将 CircleCI orbs 集成到 65,000 个存储库和近 1,800 万个 CI/CD 管道中。

  • 伦敦办公室的开业T3。这使得 CircleCI 公司在 EMEA 的业务进一步扩大。

  • 包括美国运通和 T2 温室在内的成千上万新客户的加入。

我们也欢迎来自 IVP 的 Cack Wilhelm 加入我们的董事会。Cack 之前是领先的种子和早期风险投资公司 copiety 的合伙人,也是 Scale Venture Partners 的负责人,她在之前的几轮融资中与我们合作。她的经验和空间知识将有助于引导 CircleCI 前进。她能加入我们的团队,我们非常激动。

DevOps 不断增长的市场

当 CircleCI 在 2011 年推出时,CI/CD 是尖端技术团队的一小部分的利基业务。如今,CI/CD 和 DevOps 工具通常是各种规模公司的每个工程团队的筹码。几乎所有拥有在线应用或服务的公司都使用 DevOps 实践,包括我们的客户,如 NPR、福特、花旗集团、安泰和联合利华。

所有这一切的核心是 CI/CD,它是开启组织敏捷性的钥匙。CircleCI 通过自动化构建、测试和交付流程来推动创新,因此团队可以更快、更安全地交付软件。随着软件开发变得越来越复杂,变化已经成为每个工程团队都必须处理的大规模供应链问题。验证变更是开发人员所做一切的生命线,CircleCI 消除了与之相关的挑战。

而且时间还早。DevOps 工具市场目前约为 40 亿美元,预计到 2026 年将增长到 150 亿美元。随着我们现实世界的更多任务和职责在网上转化——零售体验、供应链和物流、自动驾驶汽车——devo PS 将继续增长,对能够推动大规模快速产品开发的开发者工具的需求将进一步推动这一市场的增长。

“我们是 DevOps 市场的早期投资者,所以很高兴看到它继续发展和成熟,”Scale Venture Partners 的合伙人安迪·维特说。“CircleCI 在过去几年的快速增长证明了 CI/CD 等 DevOps 实践如何使公司能够在他们用来大规模构建更丰富产品的工具上进行创新。”

这笔资金使我们能够投资于我们的业务、平台和愿景。这不仅能让我们继续为客户打造最好的产品,还能投资于我们的客户成功和客户工程团队,以确保在用户努力使其组织更加灵活和创新时为他们提供支持。

应对不确定性

我们正处于世界历史上一个前所未有的新时代,生活中几乎所有的事情——当然也包括商业——都充满了不确定性。市场每天都在变化,如今几乎每个企业的首要任务就是保持领先。

这种全新的环境也意味着几乎每天都会带来新的“常态”同情和理解一直是 CircleCI 最重要的价值观,在当前不确定的背景下,它们变得更加重要。倾听我们的客户,了解他们不断变化的需求,并找出我们可以做些什么来成为他们的好伙伴,这将是我们在这段时间内共同成功的核心。

复杂的问题需要复杂的解决方案,我们与生态系统中的每个人合作得越多,就越容易找到这些解决方案。

我们很高兴看到我们、我们的客户以及世界各地每天使用 CircleCI 的工程团队的下一步计划。我想再次感谢他们对我们的信任。我们迫不及待地想回报他们,帮助他们在现在和更光明的未来建设伟大的事业。

宣布详细的测试失败报告

原文:https://circleci.com/blog/announcing-detailed-test-failure-reporting/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


Test Failure Reporting

TL;DR: CircleCI 现在会告诉你哪些测试在我们的 web UI、电子邮件通知和聊天集成中失败了。

CircleCI 建立在让开发人员测试不那么痛苦的承诺上,今天我们宣布在实现这一承诺方面向前迈出了一大步。我们大多数人都习惯于知道 CI 失败是一个小狩猎期的开始。我们收到一封电子邮件或一条聊天消息,说一个构建失败了,然后洞穴探险开始追踪根本原因。CircleCI 现在通过在我们的 web UI、构建电子邮件和聊天集成中显示关于哪些测试用例失败的详细信息来减轻或消除这种麻烦。

当然,在某些情况下,很难找到 CI 失败的确切原因,但是我们发现,仅仅看到失败的特定测试就会产生一种“哦,是啊,是啊…”的感觉,很明显为什么最后一次推送会导致一些集成测试中断,完全消除了在 STDOUT 中查找失败细节的麻烦。

谢谢你,朱尼特!

我们许多日常编程 Clojure、JavaScript 或 Ruby 的人可能不会经常想到领先的 Java 单元测试运行程序,但是 JUnit 的 xml 输出格式已经成为机器可读测试输出的事实上的标准,受到从 Ruby 到 Node.js 的许多平台上的测试运行程序的支持。(我们实际上也处理 Cucumber 的 JSON 输出,但是 JUnit XML 更像是一个标准。)

开箱即用的支持

CircleCI 推断的测试命令将自动收集和解释以下测试运行程序的测试失败详细信息:

  • RSpec

  • 迷你测试

  • 黄瓜

  • 鼻子

  • Xcode

如果您使用 CircleCI 的自动测试命令为这些运行程序之一运行您的测试,一切都应该正常工作。(请注意,在 RSpec 或 Minitest 的情况下,您需要在项目中包含一个特殊的 JUnit 格式 gem。更多信息见文档。)

其他测试跑步者

如果您使用不同的测试运行程序,或者如果您覆盖了 CircleCI 的默认测试命令,不要绝望!您仍然可以享受详细测试失败信息的所有好处。您需要做的就是确保将任何 JUnit 格式的 XML 保存到目录“$CIRCLE_TEST_REPORTS/<my-runner>/<my-tests>.xml , where <my-runner> and <my-tests>”是您选择的名称。我们 CircleCI 仍然使用这种方法来保存我们的 Karma 测试输出。详见文档

更多精彩即将到来!

这还不是 CircleCI 向更聪明、更强大、更好理解您的测试的进化的终点!我们有很多相关的功能即将推出,所以请关注更多。此外,如果你对我们如何更明智地运行你的测试有任何想法,或者如果你有任何其他反馈,请在sayhi@circleci.com给我们一声欢呼!

在 CircleCI | CircleCI 上宣布 GitLab 支持

原文:https://circleci.com/blog/announcing-gitlab-support/

GitLab 团队,欢迎来到 CircleCI!

今天我们很高兴宣布 CircleCI 支持 GitLab。使用 GitLab SaaS 的团队现在可以在 CircleCI 上构建、测试和部署,并访问 CircleCI 最受欢迎的功能,如 Docker 层缓存和自动测试分割。GitLab 现在是我们支持的第三个版本控制系统,除了 GitHub 和 Bitbucket。

GitLab 团队今天可以报名 CircleCI。

从 GitLab 开始 CircleCI 构建

这一版本意味着团队现在可以将新的或现有的 CircleCI 帐户连接到 GitLab 上存储的 repo。当 GitLab repo 中的代码发生变化时,您可以在 CircleCI 中自动开始构建、测试和部署管道。以行业领先的速度和最大的资源类阵列在任何地方运行您的管道 CircleCI 内置的强大功能。

GitLab 客户还将享受增强的控制,以保护 CircleCI 内的配置:在组织级别为用户设置粒度权限和角色的能力。我们将继续改善和增强 GitLab 支持的体验,团队和项目级别的控制即将推出。这些控制将有助于确保管道访问的安全性、合规性和负责任的管理。

在 GitLab,我们相信每个人都可以做出贡献,这是通过提供与 CircleCI 等行业同行的互操作性而实现的。这种集成为希望利用 GitLab 进行源代码管理等的客户提供了更容易的迁移途径,进一步支持了 GitLab 为客户提供现代解决方案的使命。git lab 联盟副总裁 Nima badi ey

高质量软件的未来是处理变化——所有的变化

启用对 GitLab repos 的支持不仅仅是代码存储的灵活性。我们很早就认识到软件交付的世界正在快速变化。虽然代码库曾经是变化的主要场所(当出现问题时,您知道去哪里找),但生态系统已经发展,现在包括开发人员在第三方工具、开源库和他们几乎无法控制的其他依赖项上构建产品。当事情发生变化时(不是如果),代码回购只是寻找变化的众多地方之一。

CircleCI 的新触发器即将推出

GitLab 支持只是 CircleCI 即将推出的众多变化中的第一个。除了扩展我们的平台以支持基于 GitLab 的触发器和增强的权限控制,我们将很快向代码报告之外的变更源开放 CircleCI:第三方平台、API、图像存储库等等。我们正在构建一个世界,在这个世界中,软件生态系统中可能影响您的代码的每一个变化都可以在 CircleCI 中触发一个管道,给开发团队更多的信心和对开发和交付过程的更多控制。

今天就开始用 GitLab 构建 CircleCI 吧

立即注册,连接您的 GitLab repo,获得您一直在寻找的信心、灵活性和速度。

集成工作完美无缺。它简单、快速、高效。 Yoel Astanovsky,Moneytor 软件工程师

GitLab 团队,欢迎来到 CircleCI

我们欢迎您对 GitLab 支持的反馈!此外,如果你有改进或新功能的想法,我们很乐意听到他们

宣布在 CircleCI - CircleCI 上推出 OS X 和 Android 支持

原文:https://circleci.com/blog/announcing-ios-and-android-support/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


我们的客户要求移动应用测试和部署支持已经有一段时间了,今天我们已经做了一些事情。我们收购了 Distiller,一家专注于 OS X 测试和部署的公司,这是我们的首次收购!我们已将他们的技术和专业知识融入 CircleCI,现在您可以使用它来测试您的移动应用程序!

我们为什么要做移动?为什么用蒸馏器?

移动应用程序中的错误是毁灭性的。web 应用程序中的错误并不简单,但至少你可以马上找到解决方法。移动漏洞通常会持续几周或几个月,并依赖于令人讨厌的外部因素,如应用商店和用户升级。在复杂的应用程序提交和移动部署过程中,错误很容易被引入,这进一步加剧了这种情况。让一些关键的错误配置或愚蠢的错误进入生产,降低你的应用商店评级,让你的客户失望,让你损失金钱,这是非常容易的。

作为一家承诺让每个人都能轻松测试和发布高质量软件的公司,我们知道我们必须为此做些什么。成千上万依赖我们测试和部署其后端和基于浏览器的应用程序的开发人员中,许多人也有移动需求,因此这是我们进入的一个明显方向。

我们非常幸运地与 Distiller 相遇,Distiller 是一家完全认同我们让测试和部署变得非常简单的愿景的公司。他们有我们需要的 OS X 专业知识,他们了解生态系统,而且他们是 Clojurians!

到目前为止的故事…

我们一直在努力与运行 Android 和 OS X 构建的客户一起工作,他们有各种各样的工作流和需求(比如构建从 Haskell 生成的 OS X 项目的人!)我们已经用 Android 构建所需的所有工具、SDK 和库增强了我们的 Linux 容器,并且已经在一个月前的私人测试版中提供了 OS X 支持。从今天开始,我们完全致力于为移动应用提供最好的持续集成和交付工具。

我们已经看到了一些早期用户的反馈。使用 CircleCI 测试 OS X 和安卓应用的基础设施总监劳伦特·劳法斯特(Laurent Raufaste)说,“随着我们扩大产品规模,我们的重点一直是保持频繁的小发布流,CircleCI 让我们有信心做到这一点。” Trunk Club 的工程副总裁 Michael Cruz 说:“CircleCI 让我们的技术团队有信心每天在众多平台上快速一致地构建、测试和部署。”

入门指南

现有的 CircleCI 用户应该有宾至如归的感觉——只需使用我们普通的 UI 和基于 YAML 的配置。Android 构建运行在我们的标准 Linux 构建容器中。要使用能够构建 OS X 项目的 OS X VM,只需在项目设置>实验设置中启用实验设置。

如果你是 CircleCI 的新手,不要担心,它仍然超级容易设置!只需注册,跟踪一个项目,如有必要,启用如上所述的 OS X 构建,你的第一个构建就会开始。我们将自动推断基于 Ant 或 Gradle 的 Android 项目的构建操作,在 OSX 虚拟机中,我们将使用xcodebuild命令行工具检测并构建您的项目。

更多信息请见 AndroidOS X 的完整文档,如果您需要任何帮助,请不要犹豫联系我们

更多即将到来

这只是我们计划的移动支持的开始。我们很高兴能很快推出更多的功能和细节。目前 CircleCI 的客户会注意到,我们的 OSX 虚拟机的能力有几个限制因素——请参见文档了解更多详细信息。我们非常高兴能够从更多的用户那里得到反馈,因为我们已经推出了移动服务,所以请通过 sayhi@circleci.com联系我们,让我们知道您的想法以及我们还应该为您做些什么!

讨论黑客新闻

在 CircleCI 的 API v2 中宣布新的见解端点

原文:https://circleci.com/blog/announcing-new-insights-endpoints-in-circleci-s-api-v2/

今天,我们很高兴地宣布 CircleCI 的 API v2 中的 insights 端点的稳定性。我们的 API 的最新版本提供了所有的灵活性和智能工程团队想要的,所以你可以得到更多的 CircleCI。管道优先的方法提高了测试套件的效率,而我们的 insights 端点则释放了工作流和消费的关键数据。

在本帖中,我们将概述目前稳定用于生产的端点。

更多数据意味着更明智的决策

insights 端点已经预览了几个月,允许用户:

  • 跟踪状态— 查看哪些工作失败,哪些工作流有不稳定的测试,并确定流水线改进工作的优先级。
  • 监控持续时间— 找出哪些工作流或作业花费的时间最长,并找出缓存、并行化和我们新的便利映像可以帮助加快速度的机会。
  • 优化消费— 通过洞察每项工作和/或工作流程的信用支出,优化 CircleCI 上的使用。可预测的逐月消耗计划。

四个新端点

借助 insights 端点,用户可以在工作流和/或工作级别检索信用消费和持续时间的汇总历史数据,以构建自己的仪表盘。端点当前返回项目和分支内特定工作流的数据。

在作业和工作流级别查看聚合数据

GET https://circleci.com/api/v2/insights/{project-slug}/workflows  
GET https://circleci.com/api/v2/insights/{project-slug}/workflows/{workflow-name}/jobs 

检索有关指定分支的项目作业和工作流的聚合数据。有效负载包含以下字段:

  • 名字
  • 聚集时段开始时间(UTC)
  • 聚集时段结束时间(UTC)
  • 成功运行
  • 失败的运行
  • 总运行次数
  • 接通率
  • 吞吐量(平均运行次数/天)
  • 平均恢复时间
  • 使用的信用总额
  • 持续时间统计:最大值、最小值、平均值、中值、p95、标准差(全部以秒为单位)

查看作业和工作流执行数据

GET https://circleci.com/api/v2/insights/{project-slug}/workflows/{workflow-name}
GET https://circleci.com/api/v2/insights/{project-slug}/workflows/{workflow-name}/jobs/{job-name} 

检索指定分支的命名项目工作流的最近 250 次运行(或最近 90 天内的运行,以先到者为准)。有效负载包含以下字段:

  • 身份证明
  • 状态
  • 使用的信用
  • 持续时间(秒)
  • 创建时间(UTC)
  • 停止于(UTC)

查看我们的文档了解更多信息。

用有意义的度量改进软件交付

工程性能是许多组织的首要考虑因素,成功的团队已经发现持续集成是交付成果的关键。在 CircleCI,我们非常荣幸能够亲眼目睹这一演变,并在我们的报告数据驱动的 CI 案例中分享了这些发现。

我们的结果证明,竞争情报将高绩效者与其他人区分开来。考虑到这一点,我们很高兴能够继续提供工具和智能,推动团队踏上 DevOps 之旅。

通过利用这些洞察端点,工程团队可以访问关键的工作流聚合信息,这些信息显示工作流在更长时间内的执行情况,使组织能够跟踪成功/失败率、吞吐量、平均恢复时间以及持续时间指标。类似地,工作流运行数据允许用户跟踪用于特定工作流的执行时间和信用。最后,对作业聚合信息的访问提供了特定工作流中的作业在更长时间内如何执行的概览,从而增强了衡量成功率/失败率和吞吐量的能力。

我们迫不及待地想看到用户如何使用这些数据,并期待继续为我们的客户增强智能。如果您有任何问题或想了解更多信息,请查看以下相关资源或观看circle ci 新见解端点网络研讨会,了解您的管道。

相关:

宣布 CircleCI Orbs 和我们新的技术合作伙伴计划

原文:https://circleci.com/blog/announcing-orbs-technology-partner-program/

在 CircleCI,我们从一开始就是 DevOps 的支柱,致力于重新定义软件交付的效率,并与世界上许多最有效的软件组织合作。随着我们行业的成长和发展,我们一直致力于帮助世界上最好的软件组织改进他们构建、测试和部署软件的方式。今天,我们以一流的方式向合作伙伴和社区贡献者开放我们的平台,标志着我们在这一使命上向前迈进了一大步。

今天,我很高兴地宣布我们新的技术合作伙伴计划。随着我们合作伙伴计划的推出,CircleCI 不再仅仅是成千上万顶级工程组织首选的 DevOps 工具,而是一个开放平台,可供全球合作伙伴、团队和开发人员构建。

我们今天将与顶级 DevOps 工具领域的合作伙伴一起发布:

Orbs_partners.png

随着这一合作伙伴生态系统的推出,我们也很高兴推出 CircleCI Orbs,这是世界上第一个专门为软件交付自动化配置而设计的包管理器。orb 实现了一个可共享组件的生态系统,现在可以通过我们的平台获得。orb 是“可参数化”的包,它将命令、执行器和作业压缩成一行可重用的代码,为您的 CircleCI 配置带来新的效率。我们今天发布了一组用于云项目的初始 orb,这些 orb 使团队能够利用自动化的力量、简化配置,并高效地扩展他们的 DevOps 组织。

" CircleCI 球体是继 Docker 容器之后 CI 世界中最令人兴奋的东西."

“CircleCI Orbs 是继 Docker containers 之后 CI 世界中最令人兴奋的东西,”Cypress 工程副总裁 Gleb Bahmutov 说,他是早期访问 Orbs 的客户和贡献者。“从开发人员的角度来看,orb 是对常规‘阅读文档、复制/粘贴示例、调整 30 分钟直到 CI 通过’的一种非常需要的改进,这是一种过时的工作流。这绝对是一次不可思议的经历。CircleCI 不断创新软件交付自动化,这只是他们最新的获胜功能之一。”

使用 orbs,您可以在团队和项目之间共享您喜欢的 CI/CD 设置,并且只需几行代码就可以轻松集成工具和第三方解决方案。开发社区的成员可以编写 orb 来解决常见问题并帮助管理配置。在常见用例中共享和重用 orb 使团队能够解决有趣的、独特的问题,使他们的业务与众不同,同时为社区中的其他人提供加速 CI/CD 工作流的解决方案。

Gif-hires.gif

了解所有社区成员在我们的平台上访问和创作 orb 的可能性:

| 对于使用 orb 的开发人员和团队: | 对于创作 orb 的技术合作伙伴和团队: |
| 使用 Orbs 帮助编写配置文件,在 CircleCI 上快速启动并运行。 | 通过创作 orb 来帮助开发人员集成您的解决方案,从而减少采用的摩擦。 |
| 通过打包常用的技术有效地扩展,以分发给跨多个项目的团队。 | 利用使用 CircleCI 的庞大开发人员网络来扩大您的覆盖范围。 |
| 将您其余的 DevOps 工具与我们的技术合作伙伴提供的可信 orb 轻松集成。 | 成为不断增长的 orb 作者社区的一员,贡献您的解决方案来帮助他人。 |

有了 orbs,现在开始使用 CircleCI、与他人共享解决方案以及将您的 CircleCI 工作流与您的 DevOps 工具链所依赖的所有其他工具连接起来比以往任何时候都更加容易。

CircleCI 学习平台- CircleCI 学院

原文:https://circleci.com/blog/announcing-our-new-learning-platform-circleci-academy/

今天标志着我们面向开发者的免费学习平台 CircleCI Academy 正式发布。通过我们不同的技术培训课程,新老 CircleCI 用户都可以学习在 CircleCI 上构建、测试和部署的基础知识,并学习如何使用我们的功能加快上市速度。

每个课程都是由我们的开发团队设计的,从初级到高级培训材料的复杂程度不等。目前,我们的平台包括 20 多个课程和资源,分为三个培训课程。

课程详情

一般开发人员培训 -本课程旨在教导用户如何开始使用我们的云托管 CircleCI.com 解决方案。这些课程将引导您从版本控制系统(VCS)到您的第一个绿色构建,详细介绍我们平台的基础知识。如果您想了解以下内容,本课程适合您:

CI/CD 101 研讨会——本课程提供了持续集成和交付的概述,并详细介绍了使用 CircleCI 实现您的目标的一些最佳实践。如果您想了解以下内容,您应该参加本课程:

Docker 层缓存 -本课程将带你了解如何使用 DLC 来减少 CircleCI 上的构建时间。它还将涵盖 DLC 的局限性以及使用它的一些最佳实践。涵盖的概念包括:

您可以按任意顺序浏览每个主题。对于新的 CircleCI 用户,我们建议按顺序进行。高级用户可能想跳过相关的培训——特别是我们的秘密管理构建优化主题。

预习课程材料

观看下面我们关于 Docker 图层缓存的课程片段:

</blog/media/20-02-13-how-docker-layer-caching-works.mp4>

Sorry, your browser doesn’t support embedded videos.

接下来会发生什么?

在接下来的几个月里,我们将继续为免费和付费用户扩展我们的课程。您可以期待关于 orb、迁移培训、执行环境等主题的内容。

点击这里报名 CircleCI 学院。要从我们的主页访问 CircleCI 学院,请单击导航栏中的“支持”,然后从下拉菜单中选择“培训”。您也可以在主页页脚和通过我们的支持中心找到链接。

我们相信,我们的课程将赋予开发人员所需的知识,让他们能够做好最有影响力的工作。关于 CircleCI 学院的任何评论、反馈或问题,请发送电子邮件至 academy@circleci.com。

宣布我们的下一代便利图像:更小、更快、更确定——circle ci

原文:https://circleci.com/blog/announcing-our-next-generation-convenience-images-smaller-faster-more-deterministic/

CircleCI 为各种编程语言和一些数据库维护了一批 Docker 映像,我们称之为便利映像。这些映像是专门为在持续集成 (CI)环境中运行良好而设计的。它们的存在是为了给用户提供一个快速方便的起点。

然而,我们的第一代便利图像是大约四年前设计的。从那以后,我们学到了很多关于设计高效映像的知识,发布了名为 orbs 的可重用配置包,Docker 工具本身也成长起来,变得更加复杂。

我们当前的 14 个 Docker 映像基于 14 个独立的上游 Docker 库映像,这些映像不是为 CI 设计的。这给我们和用户带来了令人惊讶的突破性变化,臃肿的图像,以及无处不在的低效架构。我们开始设计一个更好的系统。

介绍我们的下一代便利图像

我们新的便利形象是在 CI、效率和决定论的理念下从头开始构建的。以下是一些亮点:

  • 更快的起转时间 -在 Docker 术语中,这些下一代图像通常具有更少、更小的层。使用这些新映像将导致构建开始时更快的映像下载,并且更有可能映像已经缓存在主机上

  • 提高可靠性&稳定性 -当前图像几乎每天都在重建,来自上游的潜在变化,我们无法总是足够快地进行测试。这导致频繁的破坏性变更,对于稳定的、确定性的构建来说,这不是最好的环境。下一代映像将只针对安全性和关键错误进行重建,从而产生更加稳定和确定的映像。

新一代图像现已推出

我很高兴地宣布,我们下一代车队的第一张便利图片正式发布。

基础图像

image: cimg/base:2020.01 

这是一个全新的基于 Ubuntu 的镜像,旨在安装最少的。我们将在未来几周发布的所有下一代便利图片都基于这张图片。你可以在 Docker Hub 上找到这张图片,在 GitHub 上找到源代码和文档。

什么时候用?

如果您需要一个通用映像在 CircleCI 上运行,与 orbs 一起使用,或者作为您自己的定制 Docker 映像的基础,这个映像就是为您准备的。了解如何迁移到新一代便利映像

语言图像

节点图像

image: cimg/node:12.16 

这是对传统 CircleCI 节点映像(circleci/node)的直接替换。你可以在 Docker Hub 上找到这张图片,在 GitHub 上找到源代码&文档。

去形象

image: cimg/go:1.13 

这是对传统 CircleCI Go 图像(circleci/golang)的直接替换。你可以在 Docker Hub 上找到这张图片,在 GitHub 上找到源代码&文档。

OpenJDK 图像

image: cimg/openjdk:14.0 

这是对传统 CircleCI OpenJDK 镜像(circleci/openjdk)的直接替换。你可以在 Docker Hub 上找到这张图片,在 GitHub 上找到源代码&文档。

红宝石图像

image: cimg/ruby:2.6.5 

这是对传统 CircleCI Ruby 图像(circleci/ruby)的直接替换。你可以在 Docker Hub 上找到这张图片,在 GitHub 上找到源代码&文档。

铁锈图像

image: cimg/rust:1.43.0 

这是对传统 CircleCI Rust 图像(circleci/rust)的直接替换。你可以在 Docker Hub 上找到这张图片,在 GitHub 上找到源代码&文档。

下一步是什么

我们今天发布了这两个新的便利图片,不久的将来还会发布更多图片。我们目前有几个图像预览,将在未来几周内公开发行。你现在就可以在 CircleCI 讨论上查看这些预览图片。

附:一切都是开源的,都是为你设计的。投稿欢迎

与 Joi | CircleCI 的 API 合同测试

原文:https://circleci.com/blog/api-contract-testing-with-joi/

本教程涵盖:

  1. 设置 Joi 合同测试框架
  2. 使用 Joi 编写契约测试并验证 API 响应
  3. 根据模式验证 API 响应

当你签合同的时候,你希望双方都能信守承诺。测试应用程序也是如此。契约测试验证服务可以相互通信,并且服务之间共享的数据符合一组指定的规则。在这篇文章中,我将指导您使用 Joi 作为一个库来为使用 API 的服务创建 API 契约。

先决条件

要完成本教程,您需要:

  1. JavaScript 的基础知识
  2. 写作测试的基础知识
  3. 您系统上安装的 Node.js (版本> = 11.0)
  4. 一个的账户
  5. GitHub 的一个账户

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

在这篇文章中,我将演示如何为一个开源 API 端点编写契约测试。您将测试的端点监控不同货币的比特币价格指数。然后,您将使用 NodeJS 应用程序来测试由比特币基地 API 返回的响应的契约。

克隆演示项目

要开始这个练习,您需要克隆这个演示项目。该项目是一个构建在Express.js之上的节点应用程序。Coinbuzz 应用程序本身尚未开发。在本教程中,您将关注契约测试,这些测试将有助于确保应用程序的稳定性,就像它将被开发一样。

该项目将使用CoinbaseAPI,对于本教程,您将使用端点https://api.coindesk.com/v1/bpi/currentprice/CNY.json。该端点获取人民币相对于美元的比特币价格指数。

通过运行以下命令克隆项目:

git clone https://github.com/CIRCLECI-GWP/coin-buzz-contract-testing.git 

从项目文件夹安装依赖项:

cd coin-buzz-contract-testing;

npm install 

不需要运行应用程序;您将只运行该项目中的测试。

为什么使用合同测试

在开始之前,让我给你一些关于契约测试的背景知识。这种测试提供了不同服务在需要时工作的信心。假设一个组织有多个使用Authentication API 的支付服务。API 用一个username和一个password让用户登录到一个应用程序。然后,当登录操作成功时,它会给它们分配一个access token。其他服务如LoansRepayments需要用户登录后使用Authentication API 服务。

Microservices dependencies

如果Authentication服务改变了它的工作方式,需要email而不是username,那么LoansRepayments服务将会失败。通过契约测试,LoansRepayments服务都可以跟踪Authentication API,让服务发出的请求具有一组预期的行为。这些服务可以访问有关故障发生的时间、方式和位置的信息。还有关于故障是否由外部依赖引起的信息,在本例中是Authentication

设置合同测试环境

契约测试旨在监控应用程序的状态,并在出现意外结果时通知测试人员。当依赖于其他服务的稳定性的工具使用契约测试时,它们是最有效的。两个例子:

  • 一个前端应用程序,依赖于后端 API 的稳定性(就像在这个项目中)
  • 微服务环境或依赖另一个 API 来处理信息的 API

从 CoinDesk 测试端点将帮助您了解不同货币在不同日期的比特币价格指数。要了解更多信息,请查看克隆应用程序中的app.js

在开始测试 API 之前,您需要了解它的结构。这些知识会帮助你写合同。首先,使用浏览器向这个 URL 发出一个简单的GET请求:

https://api.coindesk.com/v1/bpi/currentprice/CNY.json 

这个请求获取某段时间内比特币的当前价格。然后显示美元和 CNY 的换算。

Bitcoin Price Index browser response

虽然响应对象一开始看起来有些吓人,但我会帮助您完成它。在教程的下一部分,我们将使用Joi把它分解成单独的对象。

创建 Joi 合同

Joi 是一个工具,它使得分析对象并把它们分成可以验证的块成为可能。Joi 将一个响应视为具有两个键值timebpi的单个对象。每个值都属于特定的数据类型。通过分解响应,Joi 可以分析响应,并根据定义的模式契约创建成功或失败的断言。

要为您的项目安装joi包,请打开一个终端窗口并运行以下命令:

npm install joi 

太好了!您已经开始创建您的 Joi 合同。本节教程将介绍如何创建对象的一部分,然后可以扩展到创建响应的其他部分。您将使用一个示例代码块来展示如何将对象分解成块。您可以测试currencyObject并在以后将其重新用于美元或 CNY。

const currencyObject = Joi.object({
        code: Joi.string().required(),
        symbol: Joi.string().optional(),
        rate: Joi.string().required(),
        description: Joi.string().required(),
        rate_float: Joi.number().required()
  }).required(); 

在这个代码块中,您正在分解货币对象,并告诉 Joi 您希望货币对象具有关键字codesymbolratedescriptionrate_float。同样在契约中,描述了你对象中物品的状态,包括它们应该是optional()还是required()。在这种情况下,您添加了可选的symbol键,因为您希望在具有类似结构的其他响应中重用currencyObject

Partial API response and JOI contract

货币对象与美元和 CNY 的货币响应并行匹配。它可以被重用,因为currencyObject合同符合 CNY 和美元响应对象的标准。完整的BPIContractcurrencyObject集成在一起,可以多次重复使用。回应的完整合同的更大背景是来自coin desk CNY的请求。

教程的这一部分已经介绍了如何创建 Joi 契约,以及如何定义需要由契约模式验证的响应的属性。请记住,为了“收紧”模式,您应该定义契约模式值是required还是optional。这就为失败时是抛出错误还是忽略它创造了一个界限。

处理 Joi 合同错误

在撰写合同时,处理可能由响应问题引起的错误是很重要的。您需要知道每次执行测试时,以及当契约模式失败时,响应是否一致。理解潜在的契约失败将帮助您学习如何调整应用程序来处理失败。

为了处理这些错误,创建一个目录并将其命名为lib。将两个文件添加到目录中:

  1. 一个是请求助手,它使用axios帮助发出 API 请求
  2. 另一个文件供schemaValidation()方法根据定义的模式验证响应

Side by side lib files

模式验证文件验证所提供的响应和契约是否一致。如果不是,就会导致错误。下一个代码块展示了一个方法如何根据定义的 Joi 契约模式来验证收到的响应。

 async function schemaValidation(response, schema) {
    if (!response || !schema) throw new Error('An API response and contract are required');
    const options = { abortEarly: false };
    try {
        const value = await schema.validateAsync(response, options);
        return value
    }
    catch (err) {
        throw new Error(err);
    }
}

module.exports = {
    schemaValidation
} 

Joi 方法负责根据创建的模式验证响应。在这种情况下,您已经在文件contracts/bpi-contracts.js中创建了一个模式。在下一节中,您将通过编写一个测试并传入模式和从比特币基地 API 收到的 API 响应来验证这个方法是否有效。

编写测试和断言

您已经成功地编写了合同和方法,在检查不一致性的同时根据 API 响应来验证它们。下一步是编写一个使用这种方法的测试。要使用您的模式测试 API,您需要安装 jest(一个 JavaScript 测试框架)和 axios(一个 JavaScript 请求生成框架)。

npm install jest axios 

您将使用 Jest 运行您的测试,使用 axios 向比特币基地端点发出 API 请求。要进行设置,添加test命令来运行package.json文件中的测试:

"scripts": {
    "start": "node ./bin/www",
    "test": "jest"
  }, 

在 scripts 部分添加这个命令可以让 Jest 扫描您的项目,查找任何扩展名为.spec.test的文件。当一个被发现时,Jest 把它们当作测试文件并运行它们。

现在,在contract-tests目录中创建一个测试。该测试在发出 API 请求后调用模式验证方法。

const { getData } = require('../lib/request-helper')
const { schemaValidation } = require('../lib/validateContractSchema');
const { BPIContract } = require('../contracts/bpi-contracts');

describe('BPI contracts', () => {
    test('CNY bpi contract schema check', async () => {
        const response = await getData({
            url: 'https://api.coindesk.com/v1/bpi/currentprice/CNY.json' });
        return schemaValidation(response, BPIContract);
    });
}); 

这个调用使用getData()方法发出请求。getData()方法使用 axios 来调用比特币基地 API。然后,它使用响应根据模式验证方法schemaValidation()来验证 API 响应。对于从端点接收到的响应,这里已经有了 Joi 模式定义。运行您的测试并验证它是否有效:

npm test 

检查你的终端有一些好消息。

Successful test run

瞧啊。你的测试通过了。

然而,我们还没有完成。您需要验证测试是否也能处理合同正确的情况。在之前的契约模式中,您在currencyObject中使用了字段symbol: Joi.string().optional()。您可以将 Joi 期望更改为required(),并查看 Joi 是否会产生错误。编辑currencyObject使其与此匹配:

const currencyObject = Joi.object({
        code: Joi.string().required(),
        symbol: Joi.string().required(),
        rate: Joi.string().required(),
        description: Joi.string().required(),
        rate_float: Joi.number().required()
    }).required(); 

重新运行测试后,您确实会看到一个错误。

Failed test run

Joi 能够捕获错误,现在期望货币对象中的symbol对象项是required()而不是optional()。来自 API 的响应没有将symbol作为响应的一部分,Joi 拒绝了 API 响应。回应不符合合同标准。

您已经能够证明您的契约模式是可行的。对响应的修改将导致失败,您将收到警告。

currencyObject中的字段symbol: Joi.string().required()改回optional()继续本教程。

编写 CI 管道配置

在本节中,您将通过为 CircleCI 添加管道配置来自动化测试。首先在根目录下创建一个名为.circleci的文件夹。在文件夹中,创建一个config.yml文件。现在添加配置详细信息:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:10.16.3
    steps:
      - checkout
      - run:
          name: update npm
          command: "npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: run coinbuzz contract-tests
          command: npm test
      - store_artifacts:
          path: ~/repo/coinbuzz 

在这种配置中,CircleCI 使用从环境中提取的节点 Docker 映像。然后,它会更新 npm 程序包管理器。接下来,如果缓存存在,它将被恢复。仅当使用save-cache检测到变化时,应用程序依赖关系才会更新。运行 Coinbuzz 测试,缓存的项目存储在工件的coinbuzz目录中。

在 CircleCI 建立一个项目

如果您克隆了示例项目存储库,那么它已经在 git 中初始化并设置好了。了解您的项目如何与 CircleCI 集成会有所帮助,因此,要设置 CircleCI,请通过运行以下命令在您的项目中初始化 GitHub 存储库:

git init 

接下来,在根目录下创建一个.gitignore文件。在该文件中,添加node_modules来忽略 npm 生成的模块被添加到您的远程存储库中。添加一个提交,然后将你的项目推送到 GitHub

登录 CircleCI ,进入Projects。列出了与您的 GitHub 用户名或您的组织相关的所有存储库,包括您想要在 CircleCI 中设置的存储库。这种情况下是coin-buzz-contract-testing

Select project

点击设置项目按钮。将提示您是否已经在项目中定义了 CircleCI 的配置文件。输入分支名称(对于本教程,您将使用 main)。点击设置项目完成该过程。

这将成功运行。

Select project

结论

在本教程中,您已经为 API 响应创建了一个契约模式,一个处理模式中错误的方法,以及一个验证契约模式工作的测试。我希望我已经展示了为依赖于外部服务可用性的 API 或前端应用程序建立契约测试是多么容易。既然您可以将失败的服务精确定位到已更改的响应对象,那么您就可以向未检测到的依赖性剥落说再见了。


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

自动化 API 的负载测试| CircleCI

原文:https://circleci.com/blog/api-load-tests/

在大多数情况下,当用户开始访问和使用一个新的应用程序或新版本时,app 的表现相当好。随着用户群的增长和使用量的增加,应用程序可能会超出其基础架构。用户可能会开始体验到性能下降。延迟增加,带宽和内存很快耗尽,一些代码架构开始失败,因为它们不能很好地随着用户数量的增加而扩展。这不是一个意料之外的问题;基础设施和应用程序架构应该随着使用量的增加而扩展。风险在于无法提前知道应用程序何时会“崩溃”,或者基础架构何时会因服务器负载增加而停机。这个问题会导致巨大的损失,尤其是在财务方面。

负载测试是软件应用程序生命周期中最重要的测试之一。通过负载测试,您可以让您的应用程序承受生产中预期的压力类型。这为您提供了微调基础架构和体系结构的信息,确保您的应用程序不会低于您的性能阈值。

在本教程中,我们将使用 Apache benchmark 对一个简单的 Node.js API 进行负载测试,这是一个针对 HTTP 服务器的负载测试和基准测试工具。

先决条件

要遵循本教程,需要做一些事情:

  1. Javascript 的基础知识
  2. 您系统上安装的 Node.js (版本> = 10.3)
  3. 英雄的叙述
  4. 一个的账户
  5. GitHub 的一个账户

所有这些安装和设置,我们可以开始教程。

为主机设置 Heroku

第一步是建立一个 Heroku 应用程序来托管我们的 API。导航至您的 Heroku 应用仪表板。点击新建,然后新建 app 。使用您喜欢的名称,创建一个新应用程序。

New App - Heroku

记下您刚刚输入的应用程序名称(node-loadtest此处)。在本教程的后面部分,您将需要用到它。

接下来,在你的仪表板的 Account Settings部分找到你的 Heroku API 密匙。

Heroku token

在教程的后面部分,您也将需要它。

克隆测试项目

下一步,您将需要克隆 API 项目。API 项目是一个简单的 Node.js API 应用程序,具有一个根端点和另一个用于获取用户集合的端点。通过运行以下命令克隆项目:

git clone --single-branch --branch base-project https://github.com/coderonfleek/node-loadtest 

克隆过程完成后,进入项目的根目录,通过运行以下命令安装依赖项:

cd node-loadtest
npm install 

安装完成后,使用以下命令运行应用程序:

npm start 

应用程序将开始监听默认端口3000。打开邮递员,向http://localhost:3000/users/get端点发出GET请求。这应该会返回一个用户数组。

Get Users - Postman

结果?API 似乎工作得很好。

连接到 CircleCI

要在 CircleCI 上设置 API 项目,首先需要将项目推送到 GitHub

接下来,进入 CircleCI 仪表板上的项目页面(选择合适的 GitHub 账户)添加项目。

Add Project - CircleCI

点击设置项目按钮开始。这将加载下一个屏幕。

Add Config - CircleCI

在设置页面上,点击 Use Existing Config 以指示 CircleCI 我们正在手动添加一个配置文件,并且不使用显示的示例。接下来,将提示您下载管道的配置文件或开始构建。

Build Prompt - CircleCI

点击开始建造。这个构建将会失败,因为我们还没有设置配置文件。我们稍后将完成这一步。

我们需要在 CircleCI 控制台上做的最后一件事是为我们刚刚添加的项目设置环境变量。这将使我们的项目能够对我们的 Heroku 应用程序进行身份验证访问以进行部署。

点击 Pipelines 页面上的项目设置按钮,转到您的项目设置。确保您的项目是当前选定的项目。

Project settings - CircleCI

在项目设置页面,点击环境变量(在侧边菜单中)。

在环境变量页面上,点击添加环境变量

Add Environment variable - CircleCI

添加以下环境变量:

  • HEROKU_APP_NAME是您的 Heroku 应用程序的名称(在本例中是cci-node-loadtest
  • HEROKU_API_KEY是你的 Heroku 账号 API 密匙(前面提到过)。

Environment variable - CircleCI

现在,您已经在 CircleCI 控制台上为部署到 Heroku 做好了一切准备。

在管道配置中添加负载测试

现在是时候构建部署管道了,它将在每次进行部署时对 API 的/users/get端点进行负载测试。在项目的根目录下,创建一个名为.circleci的文件夹和一个名为config.yml的文件。在config.yml内,输入该代码:

jobs:
  build:
    executor: heroku/default
    steps:
      - checkout
      - heroku/install
      - heroku/deploy-via-git

  loadtestapi:
    docker:
      - image: circleci/node:10.16.3
    steps:
      - run:
          name: Run Load Test
          command: |
            sudo apt-get update
            sudo apt-get install apache2-utils
            ab -k -c 20 -n 250 https://$HEROKU_APP_NAME.herokuapp.com/users/get

orbs:
  heroku: circleci/heroku@0.0.10
version: 2.1
workflows:
  deploy:
    jobs:
      - build
      - loadtestapi:
          requires:
            - build 

在您刚刚创建的配置中,有两个作业:

  • build作业使用 CircleCI 的 Heroku orb 将 API 项目从 GitHub 存储库部署到托管平台上创建的 Heroku 应用程序

  • 一旦部署了 API,loadtestapi安装 Apache 基准测试工具,然后使用该工具将250请求发送到/users/get端点,使用20的并发性和Keep-Alive的连接类型实现带有-k标志的持久连接

使用工作流,loadtestapi作业在build作业完成之前不会运行,确保我们在 API 成功部署之前不会运行负载测试。

运行负载测试管道的时间。提交对项目的所有更改,并推送到您的远程 Github 存储库。这将自动触发管道,您应该有一个成功的构建。

Build Successful - CircleCI

单击工作流中的loadtestapi任务,查看负载测试的结果。

Build Details - CircleCI

结果显示所有的250请求都在0.44秒内得到了服务。这是否是一个好结果取决于您或您的组织为您的应用程序设置的性能指标。还显示了测试运行的其他细节。这些细节有助于调整您的应用程序架构,以便更好地为使用该应用程序的数千到数百万用户进行伸缩。

结论

任何为用户提供软件服务的企业或创业组织都应该认真对待负载测试。负载测试使您能够在用户使用应用程序时避免停机的后果。在本教程中,我们已经能够部署一个 API 并立即运行一个负载测试来验证它处理 web 流量的能力。使用 Apache benchmark 有许多方法来扩展这些负载测试。还有一些专门的工具可以插入到流程中,或者作为 Apache benchmark 的替代品,比如 k6

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

用 Nock | CircleCI 进行 API 模拟测试

原文:https://circleci.com/blog/api-mock-testing-nock/

本教程涵盖以下主题:

  1. 设置 Nock 节点库
  2. 使用 Nock 编写 API 模拟测试
  3. 使用 Nock 启用实际的 HTTP 请求

为了让最新的全栈应用程序工作,需要后端服务。当前端服务依赖后端服务来呈现数据时尤其如此。在许多情况下,很难复制后端服务的设置,以便测试应用程序。本教程将向您展示如何模拟来自 API 的 HTTP 请求,以便您可以测试端点,而无需在测试中真正调用它们。

Nock 是什么?

Nock 是一个 HTTP 服务器模仿和期望库。您可以使用这个库来测试正在执行 HTTP 请求的前端模块。您可以单独测试,因为 Nock 让您决定我们的 API 响应是什么。

在本教程中,我们将使用一个现有的 API todo 应用程序来测试它如何使用 Nock 对后端 API 做出反应。因为这是一个托管应用程序,所以您可以只关注测试,而不是开发应用程序本身。

先决条件

为了从本教程中的步骤中获得最大收益,您需要准备一些东西:

  1. JavaScript 的基础知识
  2. HTTP 请求和测试的基本知识
  3. 您系统上安装的 Node.js (版本> = 10)
  4. 一个的账户
  5. GitHub 的一个账户

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

本教程的第一步是从 GitHub 克隆测试库。

一旦您克隆了存储库,就不需要设置应用程序了。在验证文件todo.spec.js中的步骤时,您可以简单地按照教程进行操作。该文件位于克隆存储库的应用程序根目录中。

为什么我应该使用模拟测试?

一个简单的客户端-服务器架构 web 应用程序既有后端服务又有前端服务。前端服务包含应用程序的表示逻辑,在大多数情况下充当用户界面。当用户执行一个动作时,客户端向后端应用程序发送一个请求。在本教程中,我将把后端应用程序称为服务器。

模拟测试使运行测试的过程更快,因为您可以消除对外部系统和服务器的依赖。本质上,您“模拟”了后端应用程序。使用模拟依赖项的另一个好处是,您可以在不导致外部系统资源(如数据库)紧张的情况下进行测试。

注意 : 模拟测试应该总是与其他类型的测试一起使用,以提供最好的测试覆盖。好的测试可以增加发布过程中的信心。

测试 API HTTP 架构

第一个示例测试应该检查向一个todo应用程序的实际后端发出请求需要多长时间。这个请求将让您了解模仿可以节省多少时间,尤其是在运行模仿后端的测试的情况下。

对于用户来说,应用程序创建、显示和获取所有的todo项。该测试检查返回所有已经创建的todo项所花费的时间。

describe('todo-app-barkend mocked tests', () => {
   it('can get todos', async () => {
       const response = await getTodoItems.getAllTodos();
       console.log(response)
   })
}) 

结果显示了执行简单测试并返回结果所花费的时间。因为我们调用的是一个真实的数据库,所以我们将返回所有的结果,而不仅仅是我们需要的特定结果。

Runtime for application

检索所有已添加的项目大约需要 3.591 秒。当运行测试时,每当您想要在响应中断言一些东西时,检索这些项目大约需要 3.591 秒。当运行测试时,这增加了大量的开销和时间限制。

没有模拟的测试执行架构

这里有一个图表,显示了在没有任何模拟服务干预的情况下,该测试如何运行的简化架构。

Unmocked API testing architecture

通过建立一个测试数据库来测试这个架构以确保得到响应可能是一件令人头疼的事情。相反,您可以隔离所有后端依赖项,只关注前端应用程序。然后使用 Nock mock 服务拦截对 API 后端的请求,并返回自定义响应。

模拟测试执行架构

Mocked API testing architecture

这个架构图显示了返回的内容不是来自 API,而是自定义响应。

如果需要,您可以覆盖模拟 API 响应并直接访问端点。在这一节中,我将指导您使用 Nock 库来测试如何模仿 API 服务,甚至覆盖被模仿的调用。

首先,花点时间在您的存储库上设置 CircleCI 和 Git。如果您已经克隆了存储库,并且已经设置了应用程序,则不需要执行此步骤。

设置 Git 并推送到 CircleCI

要设置 CircleCI,请通过运行以下命令在项目中初始化 Git 存储库:

git init 

接下来,在根目录下创建一个.gitignore文件。在文件中添加node_modules,防止 npm 生成的模块被添加到您的远程存储库中。下一步是添加一个提交,然后将你的项目推送到 GitHub

登录 CircleCI,进入项目。列出了与您的 GitHub 用户名或您的组织相关的所有 GitHub 存储库。在本教程中,您希望在 CircleCI 中设置的特定存储库是api-mock-testing-with-nock

在项目仪表盘上,点击设置项目按钮。然后点击使用现有配置

Start building page

出现提示时,点击开始建造。管道出现故障,这是意料之中的。在项目正确构建之前,您需要将定制的.circleci/config.yml配置文件添加到 GitHub。

Select config page

编写 CI 管道配置

设置完 CircleCI 管道后,就该将 CircleCI 添加到本地项目中了。首先在根目录下创建一个名为.circleci的文件夹。在文件夹中,创建一个config.yml文件。现在添加配置详细信息:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:10.16.3
    steps:
      - checkout
      - run:
          name: update npm
          command: "npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: run api mock tests
          command: npm test
      - store_artifacts:
          path: ~/repo/api-mock-testing-with-nock 

在此配置中,CircleCI 使用从环境中提取的节点 Docker 映像,然后更新 npm 程序包管理器。下一阶段恢复缓存(如果存在)并在 save-cache 检测到更改时更新应用程序依赖关系。最后,它运行api-mock-testing-with-nock测试,并将缓存的项目存储在工件api-mock-testing-with-nock目录中。

将您的更改推送到 GitHub 和 CircleCI 会自动开始构建过程。因为还没有测试,所以您的管道将(再次)失败。这是可以的,因为您将在添加测试后重新运行它。即使没有测试,也可以查看管道的详细信息。

用 Nock 测试

就像在架构图中一样,Nock 位于后端应用程序和前端应用程序之间,拦截任何被测试的请求。不是调用后端服务来测试应用程序,而是提供一组模拟(模仿)应用程序的已知响应。

在这个测试中,您将重写先前的测试,模拟返回对todo项的响应的 API 请求。测试位于文件todo.spec.js中的克隆存储库中。

it('can get todos', async () => {
       const todoObject = {
           todos: [
               { task: "Two", _id: 9, "completed": false },
               { task: "three", _id: 84, "completed": false }]
       }
       nock('https://todo-app-barkend.herokuapp.com/todos/')
           .get('/')
           .reply(200,
               todoObject
           )
       const res = await got('https://todo-app-barkend.herokuapp.com/todos/')
       expect(res.body).to.eq(JSON.stringify(todoObject))
       expect(res.statusCode).to.equal(200)
   }) 

Nock 拦截任何路由到/todos/端点的 GET 请求。Nock 返回使用nock()方法定义的响应,而不是使用来自 API 的响应进行测试。

: Heroku 自由层 dynos 睡眠 30 分钟无活动后。如果测试失败,dynos 可能处于非活动状态,您可能需要重新运行测试。

当您检查测试的运行时间时,您应该会发现使用模拟服务运行测试比测试真实服务更快。被模仿的服务运行大约需要1.58秒。这比之前没有模拟服务的请求要好得多,之前的请求运行了大约3.59秒。

Running with mock runtime

运行新的测试清楚地表明,模拟请求运行得更快,这可以为您的团队节省大量时间。当多个测试需要调用后端时尤其如此。

注意 : 当 Nock 模仿 API 请求时,它需要在测试执行时在端点上执行 HTTP 请求,以确保端点存在。因此,如果被模拟的端点不可用,或者测试中指定的路由无效,测试将会失败。

绕过 Nock HTTP 模仿

虽然模拟端点有助于理解应用程序如何与后端服务交互,但您可能需要在测试中绕过模拟请求。当向后端服务发出 HTTP 请求时,如果需要验证是否从端点获得了正确的响应,就必须绕过模仿。

下一个示例测试使用 Nock enableNetConnect方法来定义在模拟我们的测试时可以绕过的链接。一个很好的例子是启用到localhost URL 的连接,并用托管的 URL 进行模拟,以验证一切都正常工作。您可以在文件todo.spec.js的克隆存储库中找到这个测试。

it('can create todos - enabled backend', async () => {
       var options = {
           headers: {
               'content-type': 'application/json'
           },
           body: JSON.stringify({
             task: 'Cook Lunch'
           })
         };

       nock.enableNetConnect(/(todo-app-barkend)\.herokuapp.com/)
       const res = await got.post('https://todo-app-barkend.herokuapp.com/todos/', options)
       expect(JSON.parse(res.body)).to.have.property('task', "Cook Lunch");
   }) 

创建todo项的后端测试指定了要绕过的 URL 的正则表达式。然后,它稍后会向该 URL 发出请求。第二个请求是确保当您向远程 URL 发出请求时,应用程序确实创建了一个todo项。

清除模拟和块

在运行每个测试后清除模拟是很重要的。您还需要启用任何被阻止的 HTTP 请求。清除模拟和阻塞可以防止它们干扰后续测试,并允许其他测试发出 HTTP 请求。

要在运行测试后清除模拟,请在测试的afterEach部分输入以下代码:

afterEach(() => {
       nock.cleanAll()
       nock.enableNetConnect()
   }) 

验证 CircleCI 管道成功

既然测试已经开始,CI 管道已经建立,那么您可以将所有文件添加到 git 中,并将它们推送到 GitHub 远程存储库。我们的 CircleCI 管道应该可以自动运行我们的测试。要观察管道执行,请转到 CircleCI 仪表板,并单击项目名称:(api-mock-testing-with-nock)

要查看构建状态,请从 CircleCI 仪表板中选择构建。您将能够查看 CircleCI 配置文件中定义的每个步骤的状态。

Successful pipeline run

要观察管道执行,请转到 CircleCI 仪表板,并单击项目名称:(api-mock-testing-with-nock)

要查看构建状态,请从 CircleCI 仪表板中选择构建。您将能够查看 CircleCI 配置文件中定义的每个步骤的状态。

Review build

太棒了。我们有一栋绿色建筑。

结论

在本教程中,我已经向你展示了什么是 API 模仿,以及它有多有用。我们在测试中使用 Nock 模拟 HTTP 请求,并展示了模拟如何减少测试执行所需的时间。您已经学习了如何单独测试应用程序的行为,而不涉及外部依赖。

您练习了使用模拟运行测试,并学习了清除模拟的价值以及何时这样做。有了这些知识,您和您的团队就能更好地测试 API,而不需要调用它们进行响应。快乐(更快)测试!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

使用 k6 进行 API 性能测试

原文:https://circleci.com/blog/api-performance-testing-with-k6/

本教程涵盖:

  1. 什么是 k6,它是如何工作的
  2. 编写和执行 k6 性能测试
  3. 分析 k6 性能测试结果

性能测试衡量系统在各种工作负载下的表现。被测试的关键品质是稳定性和响应性。性能测试通常显示系统的健壮性和可靠性,以及特定的潜在断点。在本教程中,您将使用 k6 在 Heroku 平台上托管的简单 API 上进行负载测试。然后你将学习如何解释从测试中获得的结果。本教程是用 k6 测试 HTTP 请求的指南。

先决条件

要学习本教程,您需要:

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

k6 是一个开源框架,旨在为开发者带来性能测试的乐趣。k6 脱颖而出,因为它能够封装可用性和性能,同时还捆绑了一些工具,如执行脚本的命令行界面和监控测试运行结果的仪表板。

k6 Logo

k6 是用 goja 编程语言编写的,是 ES2015(ES6) JavaScript 在纯 Golang 语言上的实现。这意味着您可以使用 JavaScript 编写 k6 脚本,尽管语言语法将只与 JavaScript ES2015 语法兼容。

注意 : k6 不在 Node.js 引擎上运行。它使用 Go JavaScript 编译器。

既然您已经知道了 k6 下运行的是什么,为什么不着手设置 k6 来运行您的性能测试呢?

克隆存储库

首先, GitHub 库中克隆示例应用程序。

git clone https://github.com/CIRCLECI-GWP/api-performance-testing-with-k6.git 

下一步是在您的机器上安装 k6。

安装 k6

与 JavaScript 模块不同,k6 必须使用包管理器安装。在 macOS 中你可以使用自制。在 Windows 操作系统中,你可以使用巧克力糖。k6 文档中有更多适用于其他操作系统的安装选项。对于本教程,我们将遵循 macOS 安装指南。在 Homebrew 中执行此命令:

brew install k6 

这是我们开始编写 k6 测试时需要运行的唯一命令。

编写您的第一个性能测试

干得好!你已经安装了 k6,知道它是如何工作的。接下来是理解 k6 测量什么,以及您可以使用什么度量来编写您的第一个负载测试。负载测试通过模拟多个用户同时访问系统来模拟系统的预期使用情况。在我们的例子中,我们将模拟多个用户在指定的时间段内访问我们的 API。

负载测试我们的应用程序

示例中的负载测试测量我们的 API 系统在面对不同用户组时的响应性和稳定性。示例测试确定了系统的断点和可接受的限制。

在本教程中,我们将针对 k6 测试文件运行脚本,该文件首先在 Heroku 上已经托管的 API 应用程序中创建一个 todo。要将这视为负载测试,我们必须包括可伸缩虚拟用户、可伸缩请求或可变时间等因素。在我们的例子中,随着测试的进行,我们将关注应用程序的用户规模。这个特性是用 k6 框架预构建的,这使得我们的实现非常容易。

为了模拟这一点,我们将使用 k6 概念的执行者。执行器为 k6 执行引擎提供动力,并负责脚本如何执行。跑步者可以确定这些:

  • 要添加的用户数量
  • 要提出的请求的数量
  • 测试持续时间
  • 测试应用程序收到的流量

在我们的第一个测试中,我们将使用一个 k6 执行器和一个被 k6 称为ramping up的方法。在加速方法中,k6 逐渐增加虚拟用户(VUS)来运行我们的脚本,直到达到峰值。然后在规定的时间内逐渐减少数量,直到执行时间结束。

编写这个负载测试来结合执行者、虚拟用户和测试本身的思想。这里有一个例子:

import http from 'k6/http';
import { check, group } from 'k6';

export let options = {
   stages: [
       { duration: '0.5m', target: 3 }, // simulate ramp-up of traffic from 1 to 3 virtual users over 0.5 minutes.
       { duration: '0.5m', target: 4}, // stay at 4 virtual users for 0.5 minutes
       { duration: '0.5m', target: 0 }, // ramp-down to 0 users
     ],
};

export default function () {
   group('API uptime check', () => {
       const response = http.get('https://todo-app-barkend.herokuapp.com/todos/');
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
   });

   let todoID;
   group('Create a Todo', () => {
       const response = http.post('https://todo-app-barkend.herokuapp.com/todos/',
       {"task": "write k6 tests"}
       );
       todoID = response.json()._id;
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
       check(response, {
           "response should have created todo": res => res.json().completed === false,
       });
   })
}); 

在这个脚本中,options对象在给定的时间内在定义的阶段中逐渐增加用户的数量。虽然默认没有定义执行人,但是 k6 识别了options对象中的stagesdurationstargets,确定执行人是 ramping-vus

在负载测试脚本中,我们的目标是在1m 30 seconds时间段内最多有4个并发用户。因此,脚本将逐渐增加应用程序用户,并在这段时间内执行尽可能多的create-todo请求迭代。

换句话说,我们将尝试在指定的时间范围内发出尽可能多的请求,同时根据提供的阶段改变活动虚拟用户的数量。该测试的成功将取决于应用程序在该时间段内发出的成功请求的数量。这是我们测试的虚拟用户随时间变化的图表:

Performance testing vus

这个ramp-up graph图显示了随着时间的增加,虚拟用户逐渐增加到负载测试中,直到用户数量达到峰值。当虚拟用户完成他们的请求并退出系统时,这个数字在最后阶段达到。对于本教程,峰值用户数为4ramping-vus执行器类型不是唯一可以运行负载测试的类型。其他执行人有可用在 k6。使用取决于您想要执行的性能测试的需要和性质。

示例性能测试脚本显示,我们可以使用 k6 来定义我们的测试,我们的机器将运行的虚拟用户的数量,并且还可以在我们的执行脚本的group()块下使用check()创建不同的断言。

注意:K6 中的组可以让你结合一个大的负载脚本来分析测试的结果。检查是组内的断言,但是工作方式不同于其他类型的断言,因为它们不会在负载测试失败或通过时暂停负载测试的执行。

使用 k6 运行性能测试

现在您已经准备好开始执行测试,这将帮助您了解您的系统和应用程序的性能。我们将测试 Heroku free dyno 和部署在其上的托管 Todo API 应用程序的极限。虽然这不是运行负载测试的理想服务器,但它让我们能够确定系统何时以及在什么情况下达到其临界点。对于本教程,断点可能出现在 Heroku 或我们的 API 应用程序中。该测试将通过虚拟用户(VUS)向 Heroku 发出多个HTTP请求。从一个虚拟用户开始,增加到 4 个,虚拟用户将创建 todo 项,持续时间为 1 分 30 秒。

注意 : 我们假设 Heroku 能够处理 4 个与我们的 API 交互的并发用户会话。

要从前面的代码片段中运行性能测试,只需在我们的终端上运行以下命令:

k6 run create-todo-http-request.js 

当这个测试完成后,你将得到 k6 测试的第一个结果。

Create todo performance test results

使用结果来验证性能测试执行了 1 分 30.3 秒,并且测试的所有迭代都通过了。您还可以验证在测试执行期间,4 个虚拟用户总共进行了 390 次迭代(完成了创建 todo 项的过程)。

您可以从数据中获得的其他指标包括:

  • checks,测试中声明的已完成的check()个断言的数量(全部通过)
  • Total http_reqs,发出的所有 HTTP 请求的数量(780个请求)
  • 创建新 todo 项的总迭代次数和用于检查 Heroku 服务器正常运行状态的 HTTP 请求的组合

这个测试是成功的,我们能够获得关于应用程序的有意义的信息。然而,我们还不知道我们的系统是否有任何断点,或者在一定数量的并发用户开始使用我们的 API 后,我们的应用程序是否会开始表现异常。这是我们将在下一节中尝试完成的任务。

添加另一个 HTTP 请求将有助于我们发现是否可以破坏 Heroku 服务器甚至我们的应用程序。我们还将减少虚拟用户的数量和测试的持续时间,以节省资源。这些调整可以在配置部分通过简单地注释掉执行的最后两个阶段来完成。

注: “破坏应用”指的是在不改变资源或应用代码的情况下,使系统崩溃到返回错误而不是成功执行的地步。这意味着系统仅根据用户数量或请求数量的变化返回错误。

stages: [
       { duration: '0.5m', target: 3 }, // simulate ramp-up of traffic from 1 to 3 users over 0.5 minutes.
       // { duration: '0.5m', target: 4 }, -> Comment this portion to prevent execution
       // { duration: '0.5m', target: 0 }, -> Comment this portion to prevent execution 

有了这个配置,我们能够缩短执行的持续时间,并减少用于执行测试的虚拟用户的数量。我们将执行与之前相同的性能测试,但是添加一个 HTTP 请求到:

  • 获取创建的Todo
  • 验证其todoID与创建的内容一致
  • 验证其创建的statecompleted: false

为了执行这个性能测试,您将需要create-and-fetch-todo-http-request.js文件,它已经在我们克隆的存储库中的项目目录的根目录中。转到终端并执行以下命令:

k6 run create-and-fetch-todo-http-request.js 

一旦该命令执行完毕,您将获得测试结果。

Create and fetch todo performance test results

这些结果表明,我们的测试实际上打破了我们的应用程序的免费 Heroku dynos 的限制。虽然有成功的请求,但并不是所有的请求都及时返回了响应。这导致了一些请求的失败。仅使用 3 个恒定的虚拟用户并对30 seconds执行测试,有56个完整的迭代。在这 56 人中,checks95.97%成功了。

失败的检查与获取我们创建的 Todo 项目的todoID的响应有关。这可能是我们的 API 或系统出现瓶颈的第一个迹象。最后,我们可以验证我们的http_reqs total 168,它是来自创建待办事项、获取待办事项数据和验证 Heroku 服务器正常运行时间的请求总数:

56 requests for each iteration * 3 - each of every request item

从这些测试来看,很明显,性能测试指标并不是所有系统和应用程序的标准。这些数字可能会根据机器配置、应用程序的性质甚至测试中应用程序的依赖系统而有所不同。目标是确保识别和修复瓶颈。您需要了解您的系统可以处理的负载类型,以便您可以规划未来,包括使用高峰和高于平均水平的资源需求。

在上一步中,我们能够对 Heroku free 计划上托管的应用程序执行性能测试,并分析命令行结果。使用 k6,您还可以使用 K6 云仪表板来分析性能测试的结果。仪表板是一个基于 web 的界面,允许我们查看性能测试的结果。

k6 云配置和输出

在您的终端上运行 k6 性能测试并获得输出是很棒的,但是与团队的其他成员共享结果和输出也是很酷的。除了许多很酷的分析功能,共享数据是 k6 仪表板真正闪耀的地方。

要配置 k6 仪表板的输出,请使用您之前创建的帐户详细信息登录 k6 cloud。接下来,找到您的访问令牌。

k6 could API token

一旦您进入 API 令牌页面,复制访问令牌,这样您就可以运行测试并将它们上传到云中。

接下来,添加K6_CLOUD_TOKEN作为环境变量。在您的终端中运行以下命令来设置 k6 云令牌:

export K6_CLOUD_TOKEN=<k6-cloud-token> 

注意: 将示例中的云令牌值替换为您刚刚从仪表板中复制的 k6 令牌。

现在,您可以执行负载测试了。使用此命令:

k6 run --out cloud create-and-fetch-todo-http-request.js 

--out cloud告诉 k6 将结果输出到云端。当执行开始时,k6 自动创建带有测试结果的仪表板。您可以使用仪表板来评估性能测试的结果。

仪表板使结果的解释比终端输出更方便、更容易共享。上传运行的第一个指标是请求总数以及不同虚拟用户发出请求的方式。还有关于不同用户何时发出请求的信息。

k6 performance overview graph

此图显示了虚拟用户的总数与发出的请求数的关系,以及每个请求的平均响应时间。随着你对每个请求的深入研究,事情变得越来越有趣。k6 仪表盘使用groups()checks()实现了这一点。

尝试这样做:选择 Checks 选项卡,然后使用树视图过滤结果。

k6 performance insight checks

测试运行中的所有请求都按时间顺序列出,并通过所有请求花费的平均时间以及执行的请求总数来评估失败和成功的执行率。

要评估单个请求及其时间,请在同一个 performance insight 页面中选择 HTTP 选项卡。您可以查看发出的所有请求、它们执行所用的时间,以及执行与其他请求相比的情况。例如,一个请求属于第 95 百分位,而另一个请求属于第 99 百分位。仪表板显示了执行最长请求所花费的最长时间,并附有一个标准偏差图以供参考。该页面为您提供了几乎所有类型的数据点,您和您的团队可以使用这些数据点来识别瓶颈。

k6 performance insight http requests

您还可以使用 k6 仪表板来选择一个特定的请求,并根据平均运行时间来确定其性能。这将有助于识别当多个用户访问资源时由大量锁定资源导致的数据库瓶颈等问题。

您和您的团队可以从使用 k6 仪表盘的功能中获益良多。

下一步是用 CircleCI 触发 k6 性能测试,并配置它们在每次部署时运行。

设置 Git 并推送到 CircleCI

注意 : 如果您已经克隆了项目存储库,那么您可以跳过这部分教程。如果你想学习如何建立自己的项目,我在这里添加了一些步骤。

要设置 CircleCI,通过运行以下命令初始化项目中的 Git 存储库:

git init 

接下来,在根目录下创建一个.gitignore文件。在文件中添加node_modules来防止 npm 生成的模块被添加到您的远程存储库中。然后,添加一个提交,将你的项目推送到 GitHub

登录 CircleCI 并转到项目仪表板。您可以从与您的 GitHub 用户名或您的组织相关联的所有 GitHub 存储库列表中选择您想要设置的存储库。本教程的项目名为api-performance-testing-with-k6。在“项目”面板上,选择设置所需项目的选项。选择使用现有配置的选项。

注意: 在启动构建之后,预计您的管道会失败。您仍然需要将定制的.circleci/config.yml配置文件添加到 GitHub 中,以便正确构建项目。

设置 CircleCI

在根目录下创建一个.circleci目录,然后添加一个config.yml文件。配置文件保存每个项目的 CircleCI 配置。在此配置中使用 CircleCI k6 orb执行 k6 测试:

version: 2.1
orbs:
 k6io: k6io/test@1.1.0
workflows:
 load_test:
   jobs:
     - k6io/test:
         script: create-todo-http-request.js 

使用第三方球体

CircleCI orbs 是 YAML 配置的可重用包,它将代码压缩成一行。要允许使用像python@1.2这样的第三方球体,您可能需要:

  • 如果您是管理员,请启用组织设置,或者
  • 向您组织的 CircleCI 管理员请求权限。

现在,通过进一步的调查,您可以验证在 k6 中运行您的性能测试是成功的,并且它们已经被集成到 CircleCI 中。干得好!

k6 performance tests execution on CircleCI

现在是时候添加度量来测量每个端点的执行时间了。

评估 API 请求时间

从前面的性能测试中可以清楚地看出,测试结构没有系统在任何特定时间对测试负载的反应重要。k6 附带了一个名为Trend的指标特性,可以让你为你的控制台和云输出定制指标。您可以使用Trend通过定制如何定义时间来找出向端点发出的每个请求的具体时间。下面是一个代码块示例:

import { Trend } from 'k6/metrics';

const uptimeTrendCheck = new Trend('/GET API uptime');
const todoCreationTrend = new Trend('/POST Create a todo');

export let options = {
   stages: [
       { duration: '0.5m', target: 3 }, // simulate ramp-up of traffic from 0 to 3Vus
   ],
};

export default function () {
   group('API uptime check', () => {
       const response = http.get('https://todo-app-barkend.herokuapp.com/todos/');
       uptimeTrendCheck.add(response.timings.duration);
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
   }); 

要实现 k6 Trend,从k6/metrics导入,然后定义你想要的每个趋势。对于本教程的这一部分,您只需要在进行正常运行时间检查或创建新的 todo 时检查 API 的响应时间。一旦创建了趋势,导航到特定的测试并将您需要的数据嵌入到声明的趋势中。

使用以下命令执行 todo 创建性能测试文件:

k6 run create-todo-http-request.js 

检查控制台响应。您可以在克隆的存储库的根目录中找到这个测试文件。一旦这在本地通过,提交并将更改推送到 GitHub。

k6 performance results with trend

一旦您的测试完成执行,您就可以回顾您在上一步中添加的趋势描述。每个都有单独请求的时间,包括平均、最大和最小执行时间以及它们的执行百分比。

结论

本教程向您介绍了什么是 k6,以及如何使用它来运行性能测试。您经历了创建一个简单 k6 测试的过程,该测试用于创建一个针对 Heroku 服务器运行的 todo 列表项。我还向您展示了如何在命令行终端中解释 k6 性能测试结果,以及如何使用云仪表板。最后,我们探索了使用自定义指标和 k6 趋势。我鼓励你与你的团队分享这个教程,并继续扩展你所学到的东西。

我希望你和我一样喜欢这篇教程!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

用 Cypress | CircleCI 测试 API

原文:https://circleci.com/blog/api-testing-with-cypress/

有没有可能只用一种工具来测试所有的东西?尽管这听起来像是开发人员的白日梦,但使用 JavaScript 前端测试框架 Cypress 几乎是可能的。Cypress 是专门为 JavaScript 前端开发者打造的,他们可以使用它快速开始编写测试,而不需要添加第三方依赖项或包。这是 Selenium 等其他工具所没有的好处。在本教程中,我将指导你使用 Cypress 测试一个 API。

本教程涵盖以下内容:

  1. 建立 Cypress 框架
  2. 理解 Cypress 如何对 API 进行 HTTP 调用
  3. 了解如何使用 Cypress cy.request()方法

先决条件

要遵循本教程,您需要准备一些东西:

  • JavaScript 和 Git/GitHub 的基础知识
  • 您系统上安装的 Node.js (版本> = 10.3)
  • 一个的账户
  • GitHub 的一个账户

使用 Cypress 进行 API 测试

因为 Cypress 是一个独立的前端测试工具,它在执行时代表 web 应用程序发出 HTTP 请求。虽然看起来 Cypress 是从浏览器发出这些请求,但它实际上使用 Node.js 作为引擎向 API 服务器发出 HTTP 请求。它使用 Node.js 返回从服务器收到的响应。

Cypress Http Calls architecture diagram

注意 : Cypress 公开了cy.request()方法来从框架内部发出 HTTP 请求。这就是我们在本教程中使用 Cypress 测试端点的方法。

初始化目录并设置 Cypress

首先,通过在终端上运行以下命令,创建一个空目录并初始化一个空节点项目:

mkdir testing-apis-with-cypress && cd testing-apis-with-cypress 

第一个命令创建目录,而第二个命令使用默认配置创建 Node.js 项目。我们将在本教程的后面更新默认配置。我们将在初始化的节点项目中编写我们的测试。

npm init -y 

你需要知道npm init -y用默认配置创建一个节点项目,并且不会提示你定制项目的package.json文件的内容。如果只运行了npm init,那么package.json文件将会产生不同的结果,因为将会提示您配置节点项目属性。

现在您已经有了一个初始化的目录,您可以安装 Cypress 框架了。Cypress 的安装就像任何其他 npm 包的安装一样,因此您可以使用以下任一选项来安装它:

npm install cypress 

或者:

yarn add cypress 

安装完 Cypress 之后,下一步是初始化 Cypress 框架。如果需要的话,这个过程将创建您需要编写测试和包含包的默认 Cypress 目录。

在终端中,运行命令cypress open。这个命令将首次初始化 Cypress 并创建默认的 Cypress 目录和文件。

注意: 如果你遇到问题,你可能需要在全球范围内安装 Cypress。

Initializing Cypress

设置 Git 并推送到 CircleCI

在开始编写测试之前,您需要设置 CircleCI。首先,通过运行以下命令初始化项目中的 Git 存储库:

git init 

接下来,在根目录下创建一个.gitignore文件。在文件内部,添加node_modules以防止 npm 生成的模块被添加到您的远程存储库中。下一步是添加一个提交,然后将你的项目推送到 GitHub

现在登录 CircleCI 并导航到项目。将会有一个与您的 GitHub 用户名或您的组织相关的所有 GitHub 库的列表。在 CircleCI 中找到您想要设置的存储库。在我们的例子中,它是api-testing-with-cypress项目。在“项目”面板上,选择设置所选项目的选项。选择使用现有配置的选项。

start-building-page

现在,在提示符下,选择开始构建过程的选项。您可以预期您的管道会失败,因为您仍然需要将定制的.circleci/config.yml文件添加到 GitHub。

start-building-prompt

编写 CI 管道

一旦建立了 CircleCI 管道,就该将 CircleCI 添加到本地项目中了。首先在根目录下创建一个名为.circleci的文件夹。在这个文件夹中,创建一个config.yml文件。将这些配置详细信息添加到 config.yml:

version: 2
jobs:
  build:
    docker:
      - image: cypress/base:14.16.0
        environment:
          ## this enables colors in the output
          TERM: xterm
    working_directory: ~/repo
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-deps-{{ .Branch }}-{{ checksum "package.json" }}
            - v1-deps-{{ .Branch }}
            - v1-deps
      - run:
          name: Install Dependencies
          command: npm ci
      - save_cache:
          key: v1-deps-{{ .Branch }}-{{ checksum "package.json" }}
          # cache NPM modules and the folder with the Cypress binary
          paths:
            - ~/.npm
            - ~/.cache
      - run: $(npm bin)/cypress run
      - store_artifacts:
          path: ~/repo/api-testing-with-cypress 

在这个配置中,CircleCI 从环境中提取一个 Cypress Docker 映像,并检查我们的依赖关系的任何存储的缓存。

下一步是恢复缓存(如果存在),并在使用save-cache检测到更改时,只更新的应用依赖关系。它运行api-testing-with-cypress Cypress 测试,并将缓存的项目存储在~/repo/api-testing-with-cypress目录中的工件中。

将您的更改推送到 GitHub,CircleCI 应该会自动开始构建过程。在这里,由于我们目前还没有任何测试,我们的管道将再次失败。我们将在添加测试后重新运行。在 CircleCI 仪表板上,即使没有在我们的存储库中运行测试,您也应该能够看到创建的管道和执行。

Cypress 入门

对于本教程,我们将使用 heroku 上已经构建好的 API 来测试我们的应用程序。但是要做到这一点,我们首先需要 Cypress 理解我们将在测试中使用的 API Url(Cypress 称之为baseUrl)。

设置baseUrl

Cypress 框架有一个由 Cypress 初始化过程生成的配置文件cypress.jsoncypress.json文件存储了我们提供的所有配置变量,这是我们设置baseUrl的地方。这是为了确保我们在运行测试时不需要重复整个 URL。baseUrl将被设置为 Heroku API URL 的配置变量,如下面的cypress.json文件中的代码片段所示。

{
   "baseUrl": "http://todo-app-barkend.herokuapp.com/"
} 

设置运行命令

现在我们已经准备好了测试 URL,我们需要创建一个简单的方法来使用终端运行我们的测试。为此,我们将在脚本部分的package.json文件中添加一个脚本。为此,我们需要将下面的test命令添加到package.json文件脚本部分。

"scripts": {
   "test": "cypress open"
 }, 

这个命令告诉 Cypress 在非 headless 模式下运行,并在执行我们的测试时打开一个浏览器。

package.json文件中的脚本将您必须在终端上运行的命令和脚本自动化。相反,您使用npm来执行它们,同时以正在运行的程序可以理解的方式对它们进行打包。

用 Cypress 编写测试

Cypress 是一个前端测试工具,它生成许多文件和目录,这些文件和目录是如何编写 Cypress 测试的例子。现在我们可以忽略它们。

既然已经初始化了 Cypress,并且配置了 CircleCI 管道,那么是时候开始编写测试了。

编写您的第一个 Cypress API 测试

请记住,Cypress 的工作原理是使用 Node 来触发 HTTP 请求,并将它们路由回 Cypress,以进行断言并验证是否进行了预期的调用。在第一个测试中,我们将向 todo 应用程序发出一个GET请求,以确保它实际上将 todo 项返回给 API。在我们这样做之前,我们需要删除目录cypress/integration/中的所有内容。该目录包含由 Cypress 初始化生成的虚拟支架。在cypress/integration/中,创建另一个名为api-tests的目录。为了确保 Cypress 理解测试文件夹的动作,用这个配置值更新cypress.json文件。

 {
d   "integrationFolder": "cypress/integration/api-tests"
  } 

api-tests文件夹中,创建todo.spec.js文件,这是我们编写测试的地方。令人敬畏的是:Cypress 是自我维持的,所以我们不需要任何其他外部依赖来测试我们的 API。

打开todo.spec.js文件,为 todos 的 API GET请求设置测试。添加此代码:

describe('TODO api testing', () => {
   let todoItem;
   it('fetches Todo items - GET', () => {
       cy.request('/todos/').as('todoRequest');
       cy.get('@todoRequest').then(todos => {
           expect(todos.status).to.eq(200);
           assert.isArray(todos.body, 'Todos Response is an array')
       });
   });
}); 

在这个例子中,Cypress 将baseUrl附加到了cy.request()方法上。该方法向托管的 API 端点发出 GET 请求,以获取我们的 todo 项,并验证 API 是否被测试调用。

注意 : cy.request()没有被传递任何 HTTP 请求操作时,默认为 GET 请求。这就是为什么您不需要告诉测试您正在进行 API GET请求。

执行 Cypress API 测试

要执行第一个测试并检查一切运行正常,在终端中运行命令npm test。我们在前面的 package.json 中定义了npm test命令。

Cypress dashboard

当我们运行命令来执行测试时,任何添加的测试规范(文件)都会显示在 Cypress 仪表板上。选择实际的测试并观察 Cypress 执行它。

Cypress dashboard executing tests

仪表板显示测试执行成功,我们能够获得所有的待办事项。默认情况下,Cypress 在浏览器中运行,因此我们可以进一步验证测试返回的所有元素。

在屏幕左侧,单击显示返回数组的最后一个断言。这会将输出打印到控制台。然后,您可以在同一个窗口中右键单击 inspect 元素,打开浏览器开发工具。单击控制台选项卡,显示我们在测试中断言的项目阵列。有理由庆祝,因为我们可以验证我们的 API 和测试工作正常。

编写和添加更多测试

现在您已经熟悉了如何添加 Cypress,您可以添加更多的测试。例如,您可以测试添加新的 todo 项和删除 todo 项。

 it('deletes Todo items - DELETE', () => {
       cy.request('DELETE', `/todos/${todoItem}`).as('todoRequest');
       // deletes Todo item with id = 9
       cy.get('@todoRequest').then(todos => {
           expect(todos.status).to.eq(200);
           assert.isString(todos.body, 'todo deleted!')
       });
   });

   it('Adds Todo item - POST', () => {
       cy.request('POST', '/todos/', { task: "run tests" }).as('todoRequest');
       // adds new Todo item by defining Todo name
       cy.get('@todoRequest').then(todos => {
           expect(todos.status).to.eq(200);
           cy.wrap(todos.body).should('deep.include', {
               task: 'run tests',
               completed: false,
           });
       });
   }); 

在这个代码片段中,我们在cy.request()中声明了 HTTP 请求的类型。这也是声明任何其他参数的地方,比如创建新 todo 项的POST请求的body。使用这些测试,您可以验证您可以使用我们的 API 创建一个 todo 项,创建所有的 todo 项,并删除一个 todo 项。成功!我们的 API 没有坏。

现在我们需要验证测试是否也通过了已经创建的 CircleCI 管道。你已经初始化了 CircleCI,所以你只需要提交并推送你的修改到 GitHub。CircleCI 会自动运行我们的测试。

Successful CircleCI execution

再次,成功!你所有的测试都通过了。

结论

在本教程中,您已经设置了 Cypress 并配置它来运行 API 测试。您学习了如何为测试设置baseUrl,并在测试框架中配置了目录。您学习了使用cy.request()进行不同的 HTTP 操作,以及如何使用 Cypress 测试运行器。我希望你和你的团队能够从本教程中学到的东西中受益。享受开发应用程序的乐趣!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

用 Jest | CircleCI 超越 API 测试

原文:https://circleci.com/blog/api-testing-with-jest/

Jest 是一个基于 JavaScript 的测试框架,可以测试前端和后端应用程序。Jest 非常适合验证,因为它捆绑了一些工具,使得编写测试更加易于管理。虽然 Jest 最常用于简单的 API 测试场景和断言,但它也可以用于测试复杂的数据结构。在本教程中,我们将介绍如何使用 Jest 来测试嵌套的 API 响应,并希望在这个过程中获得乐趣。

先决条件

要完成本教程,您需要:

  1. 系统上安装的 Node.js
  2. 一个的账户
  3. GitHub 的一个账户
  4. Postman 或任何其他 HTTP 客户端来测试 API
  5. JavaScript 的基础知识
  6. 对 API 和单元测试的基本理解

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

克隆存储库

本教程将带您完成为虚拟空间 API 项目编写 Jest 测试的过程。要访问整个项目,请通过运行以下命令克隆存储库:

git clone --single-branch --branch base-project https://github.com/CIRCLECI-GWP/space-app-api 
cd space-app-api 

创建我们的空间应用编程接口

要开始编写我们的 cool Jest 测试,我们首先需要运行我们的 API。这就是我们将要编写测试的内容。对于本教程,我们将使用一个简单的 API,它将给出我们可以用来编写测试的响应。我已经选择了一个基于太空旅行的 API,所以准备好未来的太空探险家吧!

本教程的 API 已经开发出来了。使用以下命令进行设置,以安装应用程序依赖项:

npm install 

启动应用程序(API 服务器):

npm start 

运行这些命令后,我们可以开始编写测试了。

API 返回太空旅行所需的最关键项目的响应。它有以下端点:

  • 可用的空间目的地
  • 现有航班和相关信息
  • 精选的航班座位及其配置

注意 : 本教程的重点是用 Jest 进行测试,而不是开发一个空间 API 库,所以没有相关步骤。如果您对所有可用的端点或应用程序架构感兴趣,请在这里查看应用程序的路由文件

假设您已经在机器上克隆并设置了应用程序,所有端点都已经存在。用 Postman 这样的 API 客户端向http://localhost:3000/space/flightshttp://localhost:3000/space/destinationshttp://localhost:3000/space/flights/seats发出GET请求。

细节已经被硬编码到 API 端点上。

space-api-response1

space-api-response2

space-api-response3

既然您已经体验了我们的 API 如何工作和模拟响应,我们可以继续测试了。

用 Jest 编写 API 测试

在本节中,您将测试 API 响应,学习如何测试嵌套的 JavaScript 对象和数组,并提高您作为开发人员或测试工程师的测试技能。我们将使用 JEST 来测试 API 响应的组合,从简单的数组到包含对象的复杂数组响应。我们甚至将测试一个包含有更多子数组和对象的数组的对象。

安装 Jest

要开始用 Jest 进行测试,请将其添加到您当前的 space API 中。要将 Jest 作为开发依赖项安装,请运行以下命令。

npm install --save-dev jest 

: 开发依赖是仅用于开发目的的依赖。这些依赖项不应安装在生产环境中。

接下来,您需要对package.json文件进行一些更改,以确保测试在正确的配置下运行。您将添加两个命令,一个用于测试 API 端点,另一个用于请求 Jest 监视我们文件中的更改并重新运行测试。在 package.json 文件中添加 Jest 命令:

"scripts": {
    "start": "node ./bin/www",
    "test": "jest"
}, 

您可以添加一个 Jest 配置文件,如果这对您的项目更好的话。不过,对于本教程,我们将使用 Jest 的默认配置。

在 Jest 中设置 API 测试

在根目录下创建一个tests文件夹。在这个文件夹中,为主路由创建一个测试文件,并添加扩展名.spec.js。对于教程,这将是space.spec.js。添加.spec.js扩展名告诉 Jest 该文件是一个测试。

像航天飞机发射一样,我们从静态点火开始我们的测试过程,以检查我们的测试配置中的一切都正常工作。

tests/space.spec.js文件中,使用以下代码实现这个测试:

describe('Space test suite', () => {
    it('My Space Test', () => {
        expect(true).toEqual(true);
    });
}); 

现在,当您在终端上运行npm test时,您将获得一个成功执行的测试。当测试运行并通过时,终端上的测试结果应该是绿色的。您可以进一步将断言从true更改为false,在您的终端上的执行将导致红色,表示失败。

在我们开始火箭发射序列之前,您可能想要将 Jest 的watch命令添加到package.json中。添加这个命令意味着每当应用程序代码改变时,测试都要重新运行。当您的应用程序在一个终端上运行,而您的测试在另一个终端上运行时,这是可能的。在package.json文件中添加该命令:

"scripts": {
    "start": "node ./bin/www",
    "test": "jest",
    "test:watch": "npm run test -- --watch"
}, 

当您在运行 watch 命令的同时执行 Node.js API 时,您可以在更改任何代码时实时观察我们的测试的重新运行。当您的 API 由于更改而中断时,这可以方便地检测到。在一个终端窗口中运行应用程序,与测试并排,显示 watch 选项(在右边)。

watching-jest

测试我们的空间应用编程接口

为了在 Jest 测试中发出 API 请求,我们需要一个模块来查询我们的端点,并将响应返回给我们的测试。该模块是 SuperTest,您可以使用以下命令安装它:

npm install  --save-dev supertest 

Jest 和 SuperTest 已经建立,并且在 Jest 测试框架中编写了一个基本测试。在本节中,我们将重点测试端点提供的不同 API 响应对象。从端点http://localhost:3000/space/destinations提供的响应开始。这是一个简单的对象数组,代表太空飞行将前往的目的地。

[
  "Mars",
  "Moon",
  "Earth",
  "Mercury",
  "Venus",
  "Jupiter"
] 

数组是您可以从 API 端点接收到的最基本的响应之一,但是 Jest 为我们提供了无数种方法来断言接收到的响应符合我们的期望。

我们的第一个实际测试将集中在这个对象数组上。用这个代码片段替换space.spec.js文件的内容。

const request = require('supertest');
const app = require("../app");

describe('Space test suite', () => {
    it('tests /destinations endpoints', async() => {
        const response = await request(app).get("/space/destinations");
        expect(response.body).toEqual(["Mars", "Moon", "Earth", "Mercury", "Venus", "Jupiter"]);
        expect(response.body).toHaveLength(6);
        expect(response.statusCode).toBe(200);
        // Testing a single element in the array
        expect(response.body).toEqual(expect.arrayContaining(['Earth']));

    });

    // Insert other tests below this line

    // Insert other tests above this line
}); 

在这个测试中,我们使用 SuperTest 与 API 进行交互。该测试首先从/space/destinations端点获取响应,然后使用该响应对几个断言进行测试。最值得注意的断言是最后一个断言,它使用 Jest array arrayContaining()方法来验证可以在数组中找到单个项。在这个断言中,Jest 映射整个数组,知道哪些数组元素出现在数组中,哪些没有。

虽然上面的数组只包含五个项目,但是您可能会遇到更复杂的情况,在这种情况下,如果不分解对象,您将无法完全测试整个对象。

在下一个测试中,我们将测试/space/flights/seats端点。这个端点提供了一个比上面的数组更复杂的响应,我们将再次使用 Jest 来测试它。

鉴于我们的响应的性质,我们将使用从端点接收的响应的一部分,这将为我们提供一个测试模板,我们希望为响应中的后续对象编写该模板。

{ "starship": [
    {
      "firstClass": {
        "heatedSeats": true,
        "chairOptions": [
          "leather",
          "wollen"
        ],
        "vaultAccess": true,
        "drinksServed": [
          "tea",
          "coffee",
          "space-special",
          "wine"
        ],
        "windowAccess": true,
        "privateCabin": "2",
        "VRAccess": "unlimited",
        "cost": "$20000",
        "seatHover": {
          "cryoMode": [
            "extreme",
            "ludacris",
            "plaid"
          ],
          "staticMode": [
            "ludacris",
            "plaid"
          ]
        }
      },
      "businessClass": { ... }
    }
  ],
  "blueOrigin": [
    {
      "firstClass": { ... },
      "businessClass": { ... }
    }
  ]

} 

注意 : 你可以通过使用 Postman 这样的工具调用端点来查看完整的响应。

这个测试的重点是为对象编写一个详尽的测试,同时确保测试的可读性和简单性。鉴于回答的复杂性,我们将在保持测试可读性的同时,尽可能地覆盖更多内容。

...

    it('tests /space/flights/seats endpoint - starship', async () => {
        const response = await request(app).get("/space/flights/seats");
        expect(response.body.starship).toEqual(expect.arrayContaining([expect.any(Object)]));
        // Checking that the starship Object contains firstClass Object which then contains a set of objects
        expect(response.body.starship).toEqual(expect.arrayContaining(
            [expect.objectContaining({ firstClass: expect.any(Object) })]));

        expect(response.body.starship).toEqual(expect.arrayContaining([expect.objectContaining(
            { businessClass: expect.any(Object) })]));

        // Checking that under the bussinessClass Object we have the array drinks served
        expect(response.body.starship)
            .toEqual(expect.arrayContaining([expect.objectContaining({
                businessClass: expect.objectContaining({ drinksServed: expect.any(Array) })
            })]));

        // Checking that under the firstClass: Object we have the option ludacris in the seatHover Object
        expect(response.body.starship)
        .toEqual(expect.arrayContaining([expect.objectContaining({
            firstClass: expect.objectContaining({ seatHover: expect.objectContaining({
                cryoMode : expect.arrayContaining(['ludacris'])}) })
        })]));

        // Checking that under the firstClass: Object we have the option plaid in the seatHover Object
        expect(response.body.starship)
        .toEqual(expect.arrayContaining([expect.objectContaining({
            firstClass: expect.objectContaining({ seatHover: expect.objectContaining({
                staticMode : expect.arrayContaining(['plaid'])}) })
        })]));
    });

... 

这个测试使用 Jest matchers 遍历上面提供的对象的不同级别。它首先测试顶级对象,缩小到嵌套在响应中的对象和数组。这个测试一开始可能看起来有点吓人,但是它表明我们可以专门测试 API 的响应区域,而不需要断言整个响应对象。

在下一节中,我们将学习如何在发射到太空之前用 Jest 编写自定义错误消息!

使用 Jest 自定义匹配器进行错误消息传递

Jest 匹配器允许您以多种方式测试数据。您可以显式地测试一个特定的条件,甚至可以扩展您自己的自定义匹配器,如果该条件没有被现有的条件覆盖的话。运行测试时,您可以声明自定义匹配器并显示自定义错误。

例如,一旦我们进入太空,我们需要能够覆盖指挥中心发出的命令。为此,我们将使用来自上面创建的/space/flights API 端点的响应。

使用expect.extend(matchers)方法添加定制期望。在我们创建的 flights 端点中,对象返回每个设置为truefalse的活动状态。为了只返回活动的航班,我们可以创建一个匹配器。

要创建一个使用定制匹配器的测试,需要声明匹配器并定义哪些参数会导致通过或失败。这个匹配器可以在 Jest describe块中的任何地方声明。为了可读性,将它放在测试文件的顶部,紧接在 describe 块之后。对于这个项目,我们希望检查返回的航班是否处于活动状态。

...

    expect.extend({
        toBeActive(received) {
            const pass = received === true;
            if (pass) {
                return {  
                    message: () =>
                        `expected ${received} is an acceptable flight status`,
                    pass: true,
                };
            }
        }
    });

... 

自定义匹配器期望接收航班的状态值并返回自定义消息。但是当我们运行我们的测试时,我们得到错误的消息,即使测试不满足传递的飞行状态值的期望。换句话说,是假阴性。要解决这个问题,添加一个else语句来适应我们的断言不符合标准的情况。else 语句将捕获运行测试时可能发生的所有假阴性,并报告它们。像这样更新代码:

...

    expect.extend({
        toBeActive(received) {
            const pass = received === true;
            if (pass) {
                return {
                    message: () =>
                        `expected ${received} to be an acceptable flight status`,
                    pass: true,
                };
            } else {
                return {
                    message: () =>
                        `expected ${received} to be an acceptable flight status of flight - only true is acceptable`,
                    pass: false,
                };
            }
        },
    });

... 

当测试出现假阴性时,将发出第一条定制消息。第二次发射是在试验失败时,通过接收来自太空飞行活动状态的false值。

将这个代码块添加到我们之前添加的expect.extend代码块下面。

...

    it('tests /space/flights endpoint - positive test', async () => {
        const response = await request(app).get("/space/flights");
        expect(response.body[0].active).toBeActive();
    });

    it('tests /space/flights endpoint - false negative', async () => {
        const response = await request(app).get("/space/flights");
        expect(response.body[0].active).not.toBeActive();
    });

    it('tests /space/flights endpoint - failing test', async () => {
        const response = await request(app).get("/space/flights");
        expect(response.body[1].active).toBeActive();
    });

... 

当您运行这些测试时,有些测试会失败,并显示以下输出:

> jest

 FAIL  tests/space.spec.js
  Space test suite
    ✓ tests /destinations endpoints (25 ms)
    ✓ tests /space/flights/seats endpoint - starship (4 ms)
    ✓ tests /space/flights endpoint - positive test (3 ms)
    ✕ tests /space/flights endpoint - false negative (2 ms)
    ✕ tests /space/flights endpoint - failing test (3 ms)
  ...

GET /space/destinations 200 2.851 ms - 51
GET /space/flights/seats 200 0.699 ms - 1073
GET /space/flights 200 0.610 ms - 1152
GET /space/flights 200 0.382 ms - 1152
GET /space/flights 200 0.554 ms - 1152
Test Suites: 1 failed, 1 total
Tests:       2 failed, 3 passed, 5 total
Snapshots:   0 total
Time:        0.512 s, estimated 1 s
Ran all test suites. 

注意 : 这两个失败的测试是出于演示的目的而编写的。为了让测试在 CI 上通过,用it.skip()函数跳过它们,这样它们在默认情况下就不会运行。

...

    it('tests /space/flights endpoint - positive test', async () => {
        const response = await request(app).get("/space/flights");
        expect(response.body[0].active).toBeActive();
    });

    it.skip('tests /space/flights endpoint - false negative', async () => {
        const response = await request(app).get("/space/flights");
        expect(response.body[0].active).not.toBeActive();
    });

    it.skip('tests /space/flights endpoint - failing test', async () => {
        const response = await request(app).get("/space/flights");
        expect(response.body[1].active).toBeActive();
    });

... 

我们通过了测试。复查 GitHubtests/space.spec.js文件的最终状态。

添加 CircleCI 配置文件

这里,您将把 CircleCI 的管道配置添加到我们的本地项目中。首先在根目录下创建一个.circleci文件夹。在该文件夹中,创建一个config.yml文件。现在,通过如下代码块所示的配置,向config.yml文件添加细节:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:14.20.0
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: run space test
          command: npm test
      - store_artifacts:
          path: ~/repo/space 

在此配置中,CircleCI 使用从环境中提取的节点 Docker 映像,然后更新 npm 程序包管理器。下一步是恢复缓存(如果存在)。仅当使用save-cache检测到变化时,应用程序依赖关系才会更新。最后,我们将运行我们的空间测试,并将缓存的项目存储在工件的space目录中。

现在,如果您克隆了示例项目存储库,那么它已经在 git 中初始化并设置好了。了解我们的项目是如何与 CircleCI 集成在一起的会很有帮助。要设置 CircleCI,通过运行以下命令初始化项目中的 GitHub 存储库:

git init 

添加一个提交,然后将您的项目推送到 GitHub

接下来,登录您的 CircleCI 帐户。如果您注册了您的 GitHub 帐户,所有与您的 GitHub 用户名相关联的存储库都将在您的项目仪表板上可用。从列表中选择space-app-api项目。

Select project

点击设置项目按钮。将提示您是否已经在项目中定义了 CircleCI 的配置文件。输入分支名称(对于本教程,我们使用main)。点击设置项目按钮完成该过程。

这将启动 CircleCI 管道并成功运行测试!

Test run successfully

结论

在本教程中,您已经设置了一个 space-api,安装了 Jest,并编写了针对该 api 的测试。您还学习了如何使用 Jest 匹配器,甚至编写自己的自定义匹配器。通过本教程,我们还介绍了如何将 CircleCI 集成到项目中,将测试推送到 GitHub,以及为 CI 管道创建不同的构建步骤。最后,我们介绍了在管道上运行我们的测试,并验证管道的所有步骤都按预期工作。

所有系统都在为我们进入太空做准备。倒计时快乐!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

用 Flask 记录应用程序

原文:https://circleci.com/blog/application-logging-with-flask/

本教程涵盖:

  1. 烧瓶测井模块
  2. 按严重性级别记录烧瓶事件
  3. 测试日志模块

如果没有日志,或者对日志没有很好的理解,调试应用程序或者查看错误堆栈跟踪可能会很困难。幸运的是,Flask 日志记录可以改变您理解调试的方式,以及您与应用程序生成的日志交互的方式。Flask 日志模块为您提供了一种记录不同严重级别的错误的方法。Python 标准库中包含一个默认日志模块,它提供简单和高级日志功能。

在本教程中,我将介绍如何根据事件的严重程度记录 Flask 应用程序事件,以及如何测试日志记录模块。

先决条件

对于本教程,需要以下技术:

  • Python 编程语言的基本理解
  • 了解烧瓶框架
  • 对测试方法有基本的了解

您还需要安装以下软件:

  1. 计算机中安装的 Python 版本> = 3.5。
  2. GitHub 账户。你可以在这里创建一个。
  3. CircleCI 账户。在这里创建一个。

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

克隆示例项目存储库

从从 GitHub 克隆项目库开始。

一旦项目被克隆,您还需要安装依赖项。使用项目根文件夹中的命令pip install -r requirements.txt

了解烧瓶记录

Flask 使用标准的 Python 日志模块来记录消息:[app.logger](https://docs.python.org/3/library/logging.html#module-logging)。这个日志记录器可以扩展并用于记录自定义消息。为 Flask 应用程序实现一个灵活的事件日志记录系统,使您能够知道应用程序执行时什么时候出错了,或者什么时候遇到了错误。下图显示了 Flask 日志模块的不同部分,以及它们如何帮助处理应用程序日志。

Flask logging module

Python 记录器使用四个子模块:

  • Logger 是记录应用程序事件的主要接口。这些事件被记录时,称为日志记录。
  • 处理程序将日志事件/记录导向各自的目的地。
  • 格式化器指定日志记录器编写消息时消息的布局。
  • 过滤器帮助开发者使用参数管理日志记录。这些参数可以是日志级别的补充。

实现烧瓶记录器

日志允许开发人员监控程序的流程和所采取的行动。您可以使用日志记录器来跟踪应用程序流,比如跟踪电子商务应用程序中的事务数据,或者在 API 调用与服务交互时记录事件。

要在 Flask 中开始日志记录,首先从 Python 导入日志记录模块。这个日志模块是 Python 安装自带的,不需要配置。Python 日志记录模块基于预定义的级别记录事件。记录的日志事件称为日志记录。每个记录级别都有不同的严重性级别:

  • 调试:10
  • 信息:20
  • 警告:30
  • 错误:40
  • 临界:50

记录器仅在严重性大于日志级别时记录日志。然后记录器将它们传递给处理程序。

这个片段展示了不同类型的记录器以及它们在 Flask route /中的用法。

@app.route('/')
def main():
  app.logger.debug("Debug log level")
  app.logger.info("Program running correctly")
  app.logger.warning("Warning; low disk space!")
  app.logger.error("Error!")
  app.logger.critical("Program halt!")
  return "logger levels!" 

您可以在app.py文件中找到这个片段。当 Flask 应用程序运行时,在浏览器中导航到/ home route 以查看日志。

以下是如何使用记录器:

  • Debug为开发人员提供诊断程序错误的详细信息。
  • Info显示一条确认消息,表明程序的流行为正在按预期执行。
  • Warning表示发生了意想不到的事情,或者在不久的将来可能会出现问题(例如,磁盘空间不足)。
  • 表示严重的问题,如程序无法执行某些功能。
  • Critical显示应用程序中出现严重错误,如程序失败。

配置基本记录器

对于许多应用程序来说,只提供基本功能的记录器就足够了。要在您的app.py文件中配置这种类型的日志记录,请添加以下内容:

from flask import Flask
import logging

logging.basicConfig(filename='record.log', level=logging.DEBUG)
app = Flask(__name__)

@app.route('/')
def main():
  # showing different logging levels
  app.logger.debug("debug log info")
  app.logger.info("Info log information")
  app.logger.warning("Warning log info")
  app.logger.error("Error log info")
  app.logger.critical("Critical log info")
  return "testing logging levels."

if __name__ == '__main__':
  app.run(debug=True) 

这个代码片段指定了 Flask 将根据从DEBUG开始的级别记录应用程序的位置。它还设置了当您使用 Postman 等客户端呼叫您的/回家路线时将被记录的消息。

注意 : 应该在创建 Flask app 对象之前完成日志配置。如果在配置之前访问了app.logger,它将使用默认的 Python 处理程序。

这个使用logging.basicConfig的基本配置记录消息并将日志信息存储在一个.log文件中。对于我们的示例项目,它是record.log文件。

现在,使用以下命令执行 Flask 应用程序:

FLASK_APP=app.py flask run 

打开您的客户端应用程序,向您的路线发出一个GET请求,以获取正在运行的应用程序。这种情况下是http://127.0.0.1:5000/。当程序中的main函数被调用时,它会创建record.log文件,然后将日志记录级别设置为 DEBUG。日志记录活动应该出现在文件record.log中,输出应该是这样的:

## record.log file output

DEBUG:app:debug log info
INFO:app:Info log information
WARNING:app:Warning log info
ERROR:app:Error log info
CRITICAL:app:Critical log info
INFO:werkzeug:127.0.0.1 - - [01/Mar/2022 12:35:19] "GET / HTTP/1.1" 200 - 

您能够操作 logger 对象,根据不同的 logger 级别记录所有已配置的 logger。当您设置了不同的记录器来记录不同级别的信息时,您可以禁止在控制台上显示某些日志,而启用其他日志。为了在终端上打印出日志,您可以删除记录日志的logging.basicConfig()对象中的文件配置filename='record.log'

虽然这些日志输出是可读的,但是它们可能不是很有用,尤其是因为您不知道事件是何时发生的。要解决这个问题,您可以向日志添加一种格式,如下一节所述。

格式化日志输出

Python 格式化程序将记录的结构格式化为特定的结构,这样便于读取日志并将它们与特定事件联系起来。它可以应用在basicConfig配置中。

日志格式化程序由以下配置组成:

  • %(asctime)s将时间戳配置为字符串
  • %(levelname)s将记录级别配置为字符串。
  • %(name)s将记录器名称配置为字符串。
  • %(threadName)s是线程名。
  • %(message)s将日志消息配置为字符串。

您可以将这些格式选项应用到您的配置中,以获得更准确的对象输出:

.........
logging.basicConfig(filename='record.log',
                level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s')
app = Flask(__name__)
......... 

使用前面代码片段中指定的format配置,您的日志输出可以绑定到特定的时间戳、特定的线程,甚至特定的 threadname。当您运行应用程序并观察您的output.log文件时,产生的输出日志更有意义,看起来更清晰:

2022-03-01 13:09:11,787 DEBUG app Thread-3 : debug log info
2022-03-01 13:09:11,788 INFO app Thread-3 : Info log information
2022-03-01 13:09:11,788 WARNING app Thread-3 : Warning log info
2022-03-01 13:09:11,788 ERROR app Thread-3 : Error log info
2022-03-01 13:09:11,788 CRITICAL app Thread-3 : Critical log info
2022-03-01 13:09:11,788 INFO werkzeug Thread-3 : 127.0.0.1 - - [01/Mar/2022 13:09:11] "GET / HTTP/1.1" 200 - 

日志级别不仅有消息,还有时间戳、日志级别、应用程序名称、进程线程,最后还有您定义的日志的消息。如果您遇到错误,这使得通过时间戳来识别特定的进程、时间戳甚至消息变得容易。这个过程在调试时比仅仅阅读简单的错误日志有用得多。

现在您已经用 Python basicConfig创建了日志,您可以为您的日志模块编写测试了。

测试记录器

测试 Python 记录器与编写普通 Python 函数的测试几乎是 T2 一样的。要编写您的第一个测试,创建一个名为test_app.py的文件。打开它并添加您的第一个测试片段:

from flask import Flask
from  app import app

import logging
import unittest

class TestLogConfiguration(unittest.TestCase):
    """[config set up]
    """
    def test_INFO__level_log(self):
        """
        Verify log for INFO level
        """
        self.app = app
        self.client = self.app.test_client

        with self.assertLogs() as log:
            user_logs = self.client().get('/')
            self.assertEqual(len(log.output), 4)
            self.assertEqual(len(log.records), 4)
            self.assertIn('Info log information', log.output[0]) 

在上面的测试片段中,我们使用test_client首先向/路由发出请求,就像我们在运行我们的应用程序时所做的一样,在我们这样做之后,我们可以验证日志输出记录了INFO级别的日志信息。正如智者所说,知道我们的测试是否运行的唯一方法是执行它们,我们可以在您选择的终端上使用下面的命令来做到这一点:

pytest -s 

回顾你的跑步结果。

Test Execution

恭喜你!您已经成功执行了第一次测试。现在,您可以将您的测试扩展到应用程序中定义的其他日志级别。这里有一个例子:

def test_WARNING__level_log(self):
      """
      Verify log for WARNING level
      """
      self.app = app
      self.client = self.app.test_client

      with self.assertLogs() as log:
          user_logs = self.client().get('/')
          self.assertEqual(len(log.output), 4)
          self.assertIn('Warning log info', log.output[1])

def test_ERROR__level_log(self):
    """
    Verify log for ERROR level
    """
    self.app = app
    self.client = self.app.test_client

    with self.assertLogs() as log:
        user_logs = self.client().get('/')
        self.assertEqual(len(log.output), 4)
        self.assertIn('Error log info', log.output[2]) 

这些测试代码片段中每个不同阶段的日志级别。

注意: 日志对象包含更多的信息,这些信息可以根据应用程序的需要进行测试和断言。

你最后的任务是与世界分享你的测试。您可以使用 CircleCI 作为您的持续集成平台来实现这一点。

设置 Git 并推送到 CircleCI

要设置 CircleCI,通过运行以下命令初始化项目中的 Git 存储库:

git init 

在根目录下创建一个.gitignore文件。在该文件中,添加您希望防止添加到远程存储库中的任何模块。添加一个提交,然后将你的项目推送到 GitHub

现在,登录 CircleCI 仪表盘并导航至Projects。将会有一个与您的 GitHub 用户名或组织相关的所有 GitHub 库的列表。本教程的具体存储库是logging-with-flask

从项目仪表板中,选择选项来设置所选项目,并将该选项用于现有配置,该配置使用存储库中的config.yml。开始构建。

提示 在启动构建之后,预计您的管道会失败。这是因为您还没有将定制的.circleci/config.yml配置文件添加到 GitHub 中,这是项目正确构建所需要的。

设置 CircleCI

在根目录下创建一个.circleci目录,然后向其中添加一个config.yml文件。配置文件包含每个项目的 CircleCI 配置。对于这个设置,您将使用 CircleCI Python orb。使用此配置执行您的测试:

version: 2.1
orbs:
  python: circleci/python@1.4.0
workflows:
  build-app-with-test:
    jobs:
      - build-and-test
jobs:
  build-and-test:
    docker:
      - image: cimg/python:3.9
    steps:
      - checkout
      - python/install-packages:
          pkg-manager: pip
      - run:
          name: Run tests
          command: pytest 

注意 : CircleCI orbs 是 YAML 配置的可重用包,它将多行代码压缩成一行:python: circleci/python@1.4.0。您可能需要启用组织设置(如果您是管理员)或向您组织的 CircleCI 管理员请求权限,以允许在 CircleCI 仪表板中使用第三方 orb。

设置好配置后,您的配置推送到 GitHub。CircleCI 将自动开始构建您的项目。

检查 CircleCI 仪表板并展开构建细节,以验证您已经成功运行了您的第一个 PyTest 测试。应该融入 CircleCI。

Pipeline step success

太棒了。您不仅编写了 Flask loggers,还测试了它们,并与世界共享——或者至少与您团队的其他成员共享。

结论

在本教程中,您已经学习了如何为不同的日志输出级别配置 API,以及如何以一种不仅简单明了而且在出现问题时易于识别的方式格式化日志输出。此外,您还学习了为 logger 方法编写测试,以及断言已定义要记录的不同消息。这就把我们带到了本教程的结尾!一如既往,我喜欢创建它,我希望你喜欢跟随。


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

在 Flask | CircleCI 中使用身份验证装饰器

原文:https://circleci.com/blog/authentication-decorators-flask/

本教程涵盖:

  1. 了解烧瓶装饰工
  2. 编写认证装饰器
  3. 设置和测试端点

您的团队是否开发了一个 API,并希望(以某种方式)实现更强大的安全特性?如果您对 API 中的安全级别不满意,有改进它的解决方案!在本教程中,我将引导您完成创建 API 端点的过程,这些端点使用身份验证令牌进行保护。使用这些端点,我们将能够只为经过身份验证的用户向 Flask API 发出请求。

先决条件

要完成本教程,您需要:

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

使用 Flask API 示例项目

我们将在本教程中使用的应用程序是一个简单的图书管理 API。我们将使用这个 API 来创建、阅读和删除书籍。在本教程中,我们将不会关注开发 API 端点的过程,而是关注通过强制使用认证令牌来确保端点安全的过程。值得注意的是,本教程的重点是如何在 Flask 中使用和配置身份验证令牌,而不是令牌的结构或各种令牌配置,例如它们何时过期或它们的组成。关于创建和配置令牌过程的更多细节,您可以在 PyJWT 文档中阅读更多内容。

克隆示例项目

要克隆项目,请在终端中运行以下命令:

$ git clone https://github.com/mwaz/flask-authentication-decorators.git

$ cd flask-authentication-decorators 

现在,您可以访问我将在本教程的剩余部分中引用的代码库。在我们开始之前,让我解释一下什么是装饰者。

了解烧瓶装饰工

装饰器是一个函数,它接受另一个函数作为参数,然后返回一个函数。这是可能的,因为 Python 赋予函数特殊的地位。一个函数可以被用作参数和返回值,同时也被赋值给一个变量。换句话说,装饰者总是扩展一个函数的行为,而不修改那个函数的行为。

您可能用过的一个烧瓶装饰器的例子是用于定义路线的@app.route(‘/’)。当向浏览器显示输出时,这个装饰器将函数转换成浏览器可以访问的路由,而不必在程序中显式调用函数。

Authentication Decorator call

该工作流程图显示了装饰器函数是如何执行的,以及它是如何在请求可以继续或响应被返回之前强制要求的。

在 Flask API 上设置身份验证装饰器

现在我们可以探索如何使用 decorators 进行身份验证。

端点必须先经过身份验证,然后才能在应用程序中发出请求。身份验证意味着端点具有现有会话,并且对于特定用户是唯一的。

您可以为经过身份验证的用户设置额外的角色,比如具有提升权限的管理员角色。这样,装饰者可以锁定某些资源,这些资源应该只由一种类型的用户(例如,管理员)访问。把它想象成一家银行。所有员工都可以进入银行,但并不是所有人都有授权交易的特权。

例如,@login_required decorator 将在每次调用路由时执行。下面的代码片段展示了这是如何实现的:

# Decorator usage on an endpoint
@app.route("/bookapi/books/<int:book_id>") 
@login_required
def add_book():
      # Adding a book requires that the endpoint
      # is authenticated
	return book[book_id] 

装饰器@login_required用于保护 Flask 应用程序中的bookapi/books/:bookid路径。它强制执行一条规则,即客户端必须通过有效登录用户的身份验证,或者拥有一个现有令牌。对于要执行的add_book()方法,请求必须首先通过已定义的装饰器来验证它是否具有所需的访问权限。

注意:decorator 可以协助实现干代码原则,比如接受来自查询、JSON 或者带有类型检查的表单请求的输入。在这种情况下,如果装饰器的输入不正确,或者缺少必需的输入,就会出现错误。

编写认证装饰器

使用您之前克隆的 API,您可以生成我们自己的定制装饰器。这个装饰器将通过要求用户提供由 PyJWT 提供的认证令牌来强制授权。生成的令牌将只允许注册和登录的用户在我们的端点中创建和查看图书。

这看起来很复杂,但是我们可以一步一步来。首先使用以下代码片段创建您的身份验证装饰器:

# Authentication decorator
def token_required(f):
    @wraps(f)
    def decorator(*args, **kwargs):
        token = None
        # ensure the jwt-token is passed with the headers
        if 'x-access-token' in request.headers:
            token = request.headers['x-access-token']
        if not token: # throw error if no token provided
            return make_response(jsonify({"message": "A valid token is missing!"}), 401)
        try:
           # decode the token to obtain user public_id
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
            current_user = User.query.filter_by(public_id=data['public_id']).first()
        except:
            return make_response(jsonify({"message": "Invalid token!"}), 401)
         # Return the user information attached to the token
        return f(current_user, *args, **kwargs)
    return decorator 

注意: 这段代码片段位于克隆的存储库中的library/models.py中。

这段代码首先检查我们的请求头中是否有一个名为x-access-token的令牌。如果没有,它会发送一个响应,指出请求中缺少有效的令牌。

当请求中出现有效的令牌头时,使用我们的应用程序SECRET_KEY对其进行解码。然后,提取与解码数据相关联的用户的public_id。这是装饰器返回的内容。

创建身份验证令牌

现在,您有了一个装饰器来确保您的受限端点在发出请求之前有一个有效的访问令牌。接下来,我们需要在端点中利用创建的装饰器。要完成这项任务,我们需要回答两个问题:

  • 令牌是如何生成的?
  • 如何在请求方法上调用装饰器?

如何创建访问令牌

在确定令牌是否有效时,登录过程是最重要的考虑因素之一。登录过程中生成的令牌与我们的 decorator 方法解码时收到的令牌相同。

以下是创建令牌所需的内容:

# Login method responsible for generating authentication tokens
@app.route('/login', methods=['POST'])
def login():
    auth = request.get_json()
    if not auth or not auth.get('username') or not auth.get('password'):
        return make_response('Could not verify!', 401, {'WWW-Authenticate': 'Basic-realm= "Login required!"'})

    user = User.query.filter_by(username=auth['username']).first()
    if not user:
        return make_response('Could not verify user, Please signup!', 401, {'WWW-Authenticate': 'Basic-realm= "No user found!"'})

    if check_password_hash(user.password, auth.get('password')):
        token = jwt.encode({'public_id': user.public_id}, app.config['SECRET_KEY'], 'HS256')
        return make_response(jsonify({'token': token}), 201)

    return make_response('Could not verify password!', 403, {'WWW-Authenticate': 'Basic-realm= "Wrong Password!"'}) 

这段代码的作用:

  1. 验证用户名和密码是否已随请求一起传递。如果没有,则向用户返回一个错误。
  2. 验证用户名是否存在;如果没有,用户会收到注册账户的提示。
  3. 检查存储在数据库中的哈希密码是否与提供的密码哈希相同。如果是这种情况,则通过对用户的public_idSECRET_KEY进行编码来创建令牌。这些是在解码来自请求的提供的x-access-token时使用的相同成分。

如果用户名和密码与数据库中已经存储的值相匹配,代码将生成一个访问令牌,您可以在接下来的步骤中使用它来访问 API 资源。

注意: 我们只能比较密码散列,因为使用相同的散列算法,两个散列文本将总是具有相同的散列结果。另一方面,解码散列密码文本以将其还原为纯密码文本并将其与实际密码进行比较需要太多的计算能力(使用好的算法,这几乎是不可能的)。

Authentication Flow

此流程图显示,一旦身份认证过程成功,就会创建一个令牌并发送给用户。这是用于访问受限端点的令牌。

您可以通过向signuplogin端点发出 CURL 请求来验证这个流。然后,确保您可以创建一个新用户,并使用详细信息让该用户登录并获得一个令牌。

Creating a user

Authenticating a user

好啊,你有代币了!现在可以投入使用了。

在 API 端点中使用身份验证装饰器

在前面的步骤中,您已经创建了一个令牌,并且还解码了该令牌以获得身份验证装饰器的用户信息。现在,您可以开始将身份验证装饰器集成到 API 端点的过程了。

# Endpoint to delete a book
@app.route('/bookapi/books/<book_id>', methods=['DELETE'])
@token_required
def delete_book(book_id): 

   book = BookModel.query.filter_by(id=book_id).first()  
   if not book:  
       return jsonify({'message': 'book does not exist'})  

   db.session.delete(book) 
   db.session.commit()  
   return jsonify({'message': 'Book deleted'}) 

这段代码片段向bookapi/books/:bookid端点发出 API 请求,并调用@token_required装饰器。一旦装饰器被调用,它将验证请求在请求头中有一个有效的x-access-token令牌。如果请求中传递的令牌有效,则发送对已删除图书的成功请求的响应。

就这样,您拥有了一个经过身份验证的端点,只有拥有有效令牌的用户才能访问它!您可以通过向 API 端点发出 CURL 请求来创建和获取已创建的图书,从而再次验证这一点。

Creating and fetching books

注意 : 大多数令牌都是以 JWT (JSON Web Tokens)格式创建的。JWT 是一种开放标准,用于在双方之间共享安全信息,例如在客户端和服务器之间。

测试需要身份验证令牌的端点

俗话说,不测试代码,开发就不会完整。我们的下一步是为使用认证装饰器的端点创建测试。我们将使用 pytest 来创建测试。

: 测试可以在tests/test_book_api.py文件下的克隆项目根目录下找到。

首先定义一个方法,在我们每次执行测试时让用户登录,以确保有一个有效的令牌。这段代码片段展示了如何:

# Method in test file to generate a user token
def getLoginToken(self):
        """"Method to get a login token
        """
        user_register = self.client().post('/signup', data=self.user_details, content_type="application/json")
        self.assertEqual(user_register.status_code, 201)
        user_login = self.client().post('/login', data=self.user_details, content_type="application/json")
        self.assertEqual(user_login.status_code, 201)
        token = ast.literal_eval(user_login.data.decode())
        return token['token'] 

在这个代码片段中,我们使用 pytest 的app.client()方法向我们的 API 发出一个请求,创建一个用户并使用同一个用户登录以获得身份验证令牌。一旦我们有了令牌,我们现在就可以在测试中使用它来发出请求。

注意 : 为了确保数据不被破坏,我们总是在每次测试运行时删除测试数据库并添加一个新用户。这由test_db处理,因为将测试数据库用作试运行/开发数据库是反实践的。

一旦我们有了令牌,在测试中发出 API 请求就很简单了。只需通过您的测试客户机发出请求,并在头部请求中传递令牌。这段代码片段展示了如何:

# Test to ensure that a user can get all books from the app
def test_user_logged_in_user_can_get_books(self):
      """"Method to test fetching books with logged in user
      """
      logintoken = getLoginToken(self) # Get a login token from our method
      headers = {
          'content-type': "application/json",
          'x-access-token': logintoken # pass the token as a header
      }
      fetch_books = self.client().get('/bookapi/books', data=self.user_details, content_type="application/json", headers=headers)
      response = fetch_books.data.decode()
      self.assertEqual(fetch_books.status_code, 200)
      self.assertEqual(ast.literal_eval(response), {"Books":[]}) 

在上面的代码片段中,我们首先获得一个登录令牌,然后向/bookapi/books端点发出请求。然后我们断言响应代码是200,并且response是一个空的图书列表,因为我们还没有创建任何图书。

同时,我们还可以测试没有有效令牌的用户无法访问 books 端点。让我们在下面的代码片段中看到这一点。

# Test to ensure that a user without a valid token cannot access the books endpoint
def test_user_without_valid_token_cannot_get_books(self):
        """Method to check errors with invalid login
        """
        headers = {
            'content-type': "application/json",
            'x-access-token': 'invalid-token'
        }
        fetch_books = self.client().get('/bookapi/books', data=self.user_details, content_type="application/json", headers=headers)
        response = fetch_books.data.decode()
        self.assertEqual(fetch_books.status_code, 401)
        self.assertEqual(ast.literal_eval(response)['message'], 'Invalid token!') 

在下面的代码片段中,我们没有在要传递给/bookapi/books端点的头中将有效的令牌传递给x-access-token,而是传递了一个无效的令牌,由于令牌无效,这将导致一个401错误。

为了验证上述测试是否有效,我们可以在终端中运行以下命令来执行测试:

pipenv run pytest 

为了学习和获得更多图书管理 API 的测试示例,您可以查看我们克隆的存储库中的tests/test_book_api.py文件的完整实现。

既然我们知道了如何创建认证令牌,如何用 decorators 对它们进行解码,以及如何在我们的 API 端点中使用它们,那么我们需要一种合适的方式在云中运行我们的测试。更简单的方法是使用pytest命令在本地运行它们,但是我们希望向全世界展示它们。为了实现这一点,我们需要将我们的测试与 CI/CD 工具集成在一起,在我们的例子中就是 CircleCI。

CircleCI 给了我们测试工作的信心,这提高了信任度,尤其是当我们进行持续集成和持续部署的时候。

设置 CircleCI

要设置 CircleCI,提交您的工作并将您的更改推送到 GitHub

注意 : 如果您已经克隆了项目存储库,那么您可以跳过这部分教程。如果你想学习如何建立自己的项目,我在这里添加了一些步骤。

登录 CircleCI 并转到项目仪表板。您可以从与您的 GitHub 用户名或您的组织相关联的所有 GitHub 存储库列表中选择您想要设置的存储库。本教程的项目名为flask-authentication-decorators。在“项目”面板上,选择设置所需项目的选项。选择使用现有配置的选项。然后在提示符下选择开始构建过程的选项。

Start building prompt

注意: 在启动构建之后,预计您的管道会失败。您仍然需要将定制的.circleci/config.yml配置文件添加到 GitHub 中,以便正确构建项目。

编写 CI 管道

在根目录下创建一个.circleci目录,然后添加一个config.yml文件。配置文件保存每个项目的 CircleCI 配置。使用此代码:

version: 2.1
orbs:
  python: circleci/python@1.2

workflows:
  build-app-with-test:
    jobs:
      - build-and-test
jobs:
  build-and-test:
    docker:
      - image: cimg/python:3.9
    steps:
      - checkout
      - python/install-packages:
          pkg-manager: pipenv
      - run:
          name: Run tests
          command: pipenv run pytest 

在上面的配置中,CircleCI 使用了 python 版本的3.9。我们使用pipenv包管理器来安装依赖项。我们也使用pytest包来运行我们的测试。一旦安装了所有的依赖项,并且设置了环境,使用pipenv run pytest命令执行。

添加这个配置后,将您的更改推送到 GitHub。CircleCI 将在构建 Python 映像后开始执行测试。因为在tests目录中已经有了测试,最后一个命令将执行这些测试,您的管道将通过。

Successful test run

瞧啊。您已经成功地设置了 CircleCI 管道,以便在云中运行测试。这使得我们的身份验证装饰者使用 Flask 的旅程接近尾声,至少现在是这样。

结论

恭喜你走到这一步!在本教程中,我们已经学习了如何创建自定义身份验证装饰器,并在我们的 API 中使用它们来接收和解码来自注册用户的登录过程中的 JWT 令牌。我们还学习了如何使用headers将认证令牌传递给 API,期望应用程序收到的请求具有相同的头。最后,我们了解了如何通过确保使用身份验证装饰器保护端点来实施身份验证。作为奖励,您可以向您的团队展示如何设置 CircleCI 和执行自动化测试。如果您还没有为您的应用程序采用持续集成和持续部署,这是一个很好的时机。

我希望你像我写这篇教程一样喜欢它。下次见!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

使用 CircleCI orbs | CircleCI 自动化和扩展您的 CI/CD

原文:https://circleci.com/blog/automate-and-scale-your-ci-cd-with-circleci-orbs/

在过去两年半的时间里,作为 CircleCI 的解决方案工程师,我非常高兴能够与 CircleCI 的一些大客户一起工作,帮助他们将健康的 CI/CD 实践融入到他们的开发流程中。

领先的组织试图确保他们的应用程序是可伸缩的、可靠的和安全的。快速可靠地将产品运送给用户是获得竞争优势的必要条件。

是什么让公司能够快速可靠地运送产品?

对于 CircleCI 用户来说,这个问题的一个答案是 orbs。orb 正成为团队寻求简化具有相似需求的多个项目的 CI/CD 的首选加速器。orb 还使开发人员更容易、更快地掌握项目进度。

在这篇博客文章中,我们将介绍 CircleCI orbs 是什么、为什么以及如何使用,并为您提供一些关于如何在您自己的 CI/CD 实践中实施 orbs 的指导。

什么是球体?

orb 是一个指令列表,它允许您遵循成熟的逻辑,跨多个项目自动化一个特定的任务。它们帮助您最大限度地降低配置复杂性,同时充当软件包管理器,快速高效地集成您的软件和服务堆栈。orb 是可定制的,并且配备了利用作业、命令和执行者来满足你对项目的独特需求的能力。

有公共和私有 orb,用户可以在具有相似用例的项目中采用。公共 orb 集成了流行的工具,可供使用 CircleCI 的广大开发人员使用。此外,我们提供私人 orb,仅限于同一组织内的用户。自 2021 年初以来,我们的规模计划客户可以使用私人球体,现在我们的付费计划的所有客户都可以使用。

公共球体与私有球体

公共球体

  • 任何 CircleCI 用户都可以在自己的配置中使用公共 orb。
  • CircleCI orbs 是开源的,因此创建或使用公共 orb 是向其他开发人员支付的一种方式。发布的 orb 可以在我们的开发者中心找到。

私人宝珠

  • 私有球体只在内部发布。公司可能会选择创作私有 orb,因为他们不想让竞争对手知道他们在开发过程中使用了某种工具。
  • 私有 orb 不会出现在 CircleCI 开发人员中心,您组织之外的人不能查看或使用它们,也不能在不属于您组织的管道中使用它们。

关于 orb 最令人兴奋的事情之一是,你可以使用 orb 开发工具包自己创建公共和私有 orb。

值得注意的是,私有球体并不意味着被用作秘密管理工具。任何可能被视为“秘密”的信息,比如 API 密钥、身份验证令牌和密码,都不应该作为参数值直接输入。CircleCI 的 orb 开发最佳实践有更多关于这方面的信息。出于存储秘密的目的,我们强烈建议使用上下文和环境变量,你可以在这里阅读关于的内容。

不要多此一举

在管道中使用 orb 可以节省大量时间。在这个例子中,创建和采用 orb 来测试节点应用程序意味着只使用项目配置文件中的test作业(已经预先配置好),而不是实际创建作业并概述测试节点应用程序的步骤。这不仅节省了开发人员配置 CI/CD 管道的时间,而且有助于使配置符合不要重复自己(DRY)的最佳实践,这意味着您不必每次编写代码时都重新发明轮子。

企业客户有成千上万的 CI/CD 管道,这意味着需要匹配配置文件。使用 orb 可以帮助这种规模的公司确保他们的管道和配置文件在规模上保持可靠和高效。

orbs 如何保护你的应用安全

orb 对于建立和维护健康的 CI/CD 渠道至关重要。对于希望跨多种安全工具部署和测试应用程序的团队来说,它们也非常有用。CircleCI 已经与几个安全组织合作,他们的合作伙伴 orb 可以用来测试你的应用程序。

只需调用这些 orb,根据您的应用程序测试它们,并收集关于安全漏洞的有意义的数据见解,以确保质量控制。通过“安全”过滤合作伙伴 orb以查看所有 CircleCI 安全合作伙伴 orb。

为什么应该使用球体?

orbs 的最终目标是开发人员可以更容易地使用配置文件,而不是感觉被它消耗掉了。简单地能够调用 orb 允许开发人员将他们的注意力转移到构建作业和工作流上,而不是花时间配置代码。

凭借直观的设计和框架,orb 可以通过配置和版本控制轻松维护。这超越了优化——orb 减少了构建时间和信用消耗,我敢说,提高了开发人员的理智。

无论它们是公共的还是私有的,orb 都是确保 CI/CD 流程的速度、可靠性和安全性的最佳方式。要查看公共 orb 或开始创作自己的 orb,请查看 CircleCI orb 开发工具包

自动化环回部署| CircleCI

原文:https://circleci.com/blog/automate-deploy-loopback/

本教程涵盖:

  1. 环回入门
  2. 自动部署环回应用程序
  3. 为回送 API 创建问题模型和数据源

在软件开发团队普遍使用自动化之前,瓶颈、重复任务和人为错误非常普遍。自动化为组织释放了宝贵的人力资源,同时降低了活跃的人脑试图执行平凡的重复任务所导致的人为错误的风险。最近在持续集成和持续部署 (CI/CD)领域取得的进展使得自动部署软件应用程序的更新变得更加可行。在本教程中,我将向您展示如何自动部署一个回环 app 到 Heroku 。使用 CircleCI,我将指导您如何实现连续部署(CD)管道。CD 管道将使您能够在任何时候推送更改时自动发布应用程序的更新。

本教程是为环回应用建立 CI/CD 实践系列的第二部分。前一篇教程向您展示了如何自动测试回送 API

先决条件

开始之前,请确保您的系统上安装了以下项目:

  • 8.9.x 的最低 Node.js 版本
  • 一个最新的 JavaScript 包管理器,如 NPM
  • 环回 4 CLI

对于存储库管理和持续集成/持续部署,您需要:

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

LoopBack 和 Heroku 是什么?

LoopBack 是一个高度可扩展的 Node.js 和 TypeScript 框架,用于构建 API 和微服务。Heroku 是一个平台即服务(PaaS ),使开发人员能够完全在云中构建、运行和操作应用程序。通过处理维护基于云的应用程序的更高要求方面,Heroku 允许开发人员专注于构建应用程序,而不是管理服务器。

这个项目始于之前的教程,在那里我们为一个测验应用程序构建了一个 API。

概括一下:API 有端点来处理以下操作:

  1. 获取所有问题
  2. 获取数据库中问题的总数
  3. 获取具有特定 ID 的问题

入门指南

如果您尚未安装 LoopBack 4 CLI,请运行以下命令进行安装:

npm install -g @loopback/cli 

使用 Loopback CLI 命令搭建新项目。

lb4 app 

当 CLI 提示时,如下所示进行响应。请注意,指定了不同于默认值的应用程序类名。

? Project name: circle-ci_heroku
? Project description: Loopback project to demonstrate automated deployment to h
eroku via CircleCI
? Project root directory: circle-ci_heroku
? Application class name: MainApplication
? Select features to enable in the project Enable eslint, Enable prettier, Enabl
e mocha, Enable loopbackBuild, Enable vscode, Enable docker, Enable repositories
, Enable services
? Yarn is available. Do you prefer to use it by default? Yes 

配置 Heroku

接下来要做的是在 Heroku 上创建一个新的应用程序。我们通过 Heroku 仪表盘来完成这项工作。点击新建,然后新建 App。填写表格。如果您愿意,可以为应用程序使用不同的名称和地区。

Create Heroku App

接下来,点击创建应用按钮,完成创建过程。然后,您将被重定向到新创建的应用程序的部署视图。

接下来是添加构建包;只需点击设置选项卡。在构建包部分,点击添加构建包。

Add buildpack

一个表单允许您选择一个官方支持的构建包或提供一个 URL。选择 nodejs 使用官方支持的 Heroku nodejs 构建包。点击保存修改。NodeJS 将用于构建您的下一个部署。

您最不需要的就是 API 密钥。您将使用它和应用程序名称将 CircleCI 管道连接到 Heroku。要获取 API 密钥,请打开账户设置页面,向下滚动到 API 密钥部分。

Reveal API Key

点击显示按钮,复制显示的 API 密钥。

接下来,在项目的根目录下,创建一个名为Procfile的新文件。注意,这个文件没有扩展名。在其中,添加以下内容:

web: node . 

如果没有这个 procfile,Heroku 将默认通过调用npm run来启动应用程序。npm run尝试运行构建过程,这将导致应用程序错误,因为应用程序的开发依赖项尚未安装。

配置 CircleCI

接下来,为 CircleCI 添加管道配置。对于这个项目,管道由两个步骤组成:

  1. 构建步骤构建项目并安装项目依赖项。理想情况下,您应该在这个阶段运行项目测试。为了简洁起见,我们将跳过本演示中的测试。相反,使用创建应用程序时提供的预构建测试。
  2. 部署到 Heroku 步骤中,当构建阶段成功完成时,您可以将最新的更改部署到 Heroku。

创建 CircleCI 配置文件

在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在新创建的文件中,添加以下配置:

version: "2.1"

orbs:
  heroku: circleci/heroku@1.2.6
  node: circleci/node@4.7.0

jobs:
  build-and-test:
    docker:
      - image: "cimg/base:stable"
    steps:
      - checkout
      - node/install:
          node-version: 16.0.0
          install-yarn: true
      - node/install-packages:
          pkg-manager: yarn
          cache-path: ~/project/node_modules
          override-ci-command: yarn install
      - run: yarn run test

workflows:
  main:
    jobs:
      - build-and-test
      - heroku/deploy-via-git:
          force: true # this parameter instructs the push to use a force flag when pushing to the heroku remote, see: https://devcenter.heroku.com/articles/git
          requires:
            - build-and-test 

该配置引入了circleci/heroku orb,它自动让您访问一组强大的 Heroku 作业和命令。其中一个任务是heroku/deploy-via-git,它直接从你的 GitHub repo 将你的应用程序部署到你的 Heroku 账户。这个配置也使用了circleci/node球体。除此之外,这个 orb 允许您安装默认启用缓存的包。

注意 : 你应该安装了 16 的节点版本。在发布时,node 16 是与 LoopBack 兼容的最新版本。

配置指定了build-and-test作业,它:

  1. 签出最新代码
  2. 安装节点
  3. 安装在package.json中声明的软件包
  4. 运行项目中的测试

该配置还指定了一个工作流,该工作流先运行build-and-test作业,然后运行heroku/deploy-via-git作业。注意,有一个requires选项告诉 CircleCI 只有在build-and-test任务成功完成时才运行deploy-via-git任务。

接下来,在 GitHub 上建立一个存储库,并将项目链接到 CircleCI。看到这个帖子帮助把你的项目推到 GitHub

在你写代码之前,花点时间做些家务。使用此命令 Lint 您的代码并修复任何问题。

yarn run lint:fix 

登录您的 CircleCI 帐户。如果你注册了你的 GitHub 账户,你所有的库都会显示在你项目的仪表盘上。

在你的circle-ci_heroku项目旁边点击设置项目

CircleCI 检测项目中的config.yml文件。点击使用现有配置,然后开始建造。您的第一个工作流将开始运行,但会失败。这是可以预料的。

Failed build

部署过程失败,因为您没有提供 Heroku API 密钥。现在就去解决它。点击项目设置按钮,然后点击环境变量。添加两个新变量如下:

  • HEROKU_APP_NAME变量的值是 Heroku 中的应用程序名称:loopback-heroku-circleci
  • HEROKU_API_KEY是您从帐户设置页面获取的 Heroku API 密钥。

选择从失败的选项重新运行工作流,以重新运行 Heroku 部署。要确认工作流是否成功,您可以在浏览器中打开新部署的应用程序。您的应用程序的 URL 是:https://<HEROKU_APP_NAME>.herokuapp.com/

您将会看到您当前的索引页面。

Default homepage

现在我们已经有了一个管道,让我们添加 API 的问题特性。

构建问题模型

对于本教程,问题将包含这些属性的字段:

  • 困难
  • 问题
  • 回答正确

创建问题时,默认情况下会分配一个唯一的主键。

您可以使用lb4 model命令并回答提示来生成模型。按键,输入属性名为空的,生成模型。请遵循以下步骤:

lb4 model question 
? Please select the model base class Entity (A persisted model with an ID)
? Allow additional (free-form) properties? No
Model Question will be created in src/models/question.model.ts

Let's add a property to Question
Enter an empty property name when done

? Enter the property name: id
? Property type: number
? Is id the ID property? Yes
? Is id generated automatically? Yes

Let's add another property to Question
Enter an empty property name when done

? Enter the property name: difficulty
? Property type: string
? Is it required?: Yes

Let's add another property to Question
Enter an empty property name when done

? Enter the property name: question
? Property type: string
? Is it required?: Yes

Let's add another property to Question
Enter an empty property name when done

? Enter the property name: answer
? Property type: string
? Is it required?: Yes

Let's add another property to Question
Enter an empty property name when done

? Enter the property name: 

将在src/models/question.model.ts创建一个新模型。

构建数据源

接下来,创建一个数据源来保存 API 的问题。对于本教程,使用内存数据库。使用以下命令创建数据源:

lb4 datasource 

回应如下所示的提示:

? Datasource name: db
? Select the connector for db:  In-memory db (supported by StrongLoop)
? window.localStorage key to use for persistence (browser only):
? Full path to file for persistence (server only): ./data/db.json 

接下来,在项目的根目录下创建一个名为data的文件夹。在data目录中,创建一个名为db.json的文件,并将其添加到:

{
  "ids": {
    "Question": 9
  },
  "models": {
    "Question": {
      "1": "{\"difficulty\":\"medium\",\"question\":\"The HTML5 standard was published in 2014.\",\"answer\":\"True\",\"id\":1}",
      "2": "{\"difficulty\":\"medium\",\"question\":\"Which computer hardware device provides an interface for all other connected devices to communicate?\",\"answer\":\"Motherboard\",\"id\":2}",
      "3": "{\"difficulty\":\"medium\",\"question\":\"On which day did the World Wide Web go online?\",\"answer\":\"December 20, 1990\",\"id\":3}",
      "4": "{\"difficulty\":\"medium\",\"question\":\"Android versions are named in alphabetical order.\",\"answer\":\"True\",\"id\":4}",
      "5": "{\"difficulty\":\"medium\",\"question\":\"What was the first Android version specifically optimized for tablets?\",\"answer\":\"Honeycomb\",\"id\":5}",
      "6": "{\"difficulty\":\"medium\",\"question\":\"Which programming language shares its name with an island in Indonesia?\",\"answer\":\"Java\",\"id\":6}",
      "7": "{\"difficulty\":\"medium\",\"question\":\"What does RAID stand for?\",\"answer\":\"Redundant Array of Independent Disks\",\"id\":7}",
      "8": "{\"difficulty\":\"medium\",\"question\":\"Which of the following computer components can be built using only NAND gates?\",\"answer\":\"ALU\",\"id\":8}"
    }
  }
} 

JSON 文件的ids键让数据库知道分配新问题的下一个 ID。在models部分,我们提供了每个型号的数据。还指定了Question模型和数据库中的基本问题。

创建存储库

对于本教程,您将使用存储库在数据库和问题模型之间提供一个抽象层。使用以下命令创建新的存储库:

lb4 repository 

回应如下所示的提示:

? Please select the datasource DbDatasource
? Select the model(s) you want to generate a repository for Question
? Please select the repository base class DefaultCrudRepository (Juggler bridge) 

新创建的类(位于src/repositories/question.repository.ts)拥有为您的模型执行 CRUD 操作所需的连接。

创建控制器

使用以下命令创建新的控制器:

lb4 controller 

响应 CLI 提示,如下所示:

? Controller class name: question
Controller Question will be created in src/controllers/question.controller.ts

? What kind of controller would you like to generate? REST Controller with CRUD functions
? What is the name of the model to use with this CRUD repository? Question
? What is the name of your CRUD repository? QuestionRepository
? What is the name of ID property? id
? What is the type of your ID? number
? Is the id omitted when creating a new instance? Yes
? What is the base HTTP path name of the CRUD operations? /questions 

CLI 创建了一个能够处理所有 CRUD 操作的控制器,我们不需要做任何其他事情。很漂亮吧?

提交您的代码并将最新的更改推送到您的 GitHub 库。这触发了 circle cibuild-and-test, circle ci 成功运行并将您的新更改部署到 Heroku——就像之前做的一样。干得好!

导航至https://<HEROKU_APP_NAME>.herokuapp.com/questions。有包含问题的 API 响应!

API response

结论

在本教程中,我向您展示了如何使用 GitHub、CircleCI 和 Heroku 为 LoopBackJS API 设置 CI/CD 管道。通过自动化发布新功能的过程,大大降低了人为错误对生产环境造成负面影响的风险。

此外,您宝贵的开发时间可以更好地应用到更复杂的开发方面。这有助于更有效的软件管理过程,因为重复的、平凡的方面是自动化的,而您的团队专注于解决问题。

本教程的全部代码可以在 GitHub 上找到。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他解决问题的技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。由于精通技术,他的爱好包括尝试新的编程语言和框架。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

FeathersJS 应用程序的自动化测试| CircleCI

原文:https://circleci.com/blog/automate-feathersjs-testing/

本教程涵盖:

  1. 构建一个 FeathersJS API
  2. 为 FeathersJS 应用程序编写代码和测试
  3. 创建 CI 管道以自动化测试

这是由两部分组成的系列之一。您还可以学习如何自动将 FeathersJS 应用程序部署到 Heroku 。

在软件开发生命周期中,测试提供的好处远远超出了代码本身。测试向所有各方(开发人员、客户、项目经理等)保证,虽然应用程序可能不是完全没有错误,但它确实如预期的那样做了。通过突出任何引入的回归,测试还提供了对代码进行调整和改进的信心。

大多数开发团队从中央存储库管理代码,使用版本控制系统(VCS)来推送更新并部署到生产服务器。理想情况下,测试在推送到中央存储库之前和部署到生产服务器之后运行。这使得任何问题都能在用户遇到之前被发现和处理。过去,这种手动过程会带来瓶颈,因为更新无法尽快部署。

如果在测试部署时发现问题,还会有用户体验不佳的风险,导致团队在解决问题时临时禁用应用程序。这些只是在将解决方案部署到生产环境之前,通过自动化测试流程解决的两个问题。

在本文中,我将向您展示如何使用 CircleCI 对 FeathersJS 应用程序进行自动化测试。为了帮助编写测试,我们将使用摩卡

先决条件

开始之前,请确保您的系统上安装了以下项目:

  • 10.0.0 的最低 NodeJS 版本
  • 一个最新的 JavaScript 包管理器,如 NPM
  • FeathersJS CLI

您可以通过运行以下命令来安装 FeathersJS CLI:

npm install -g @feathersjs/cli 

对于存储库管理和持续集成/持续部署,您需要:

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

入门指南

为项目创建新文件夹:

mkdir auth-api-feathersjs

cd auth-api-feathersjs 

接下来,使用 Feathers CLI generate命令生成一个新应用程序:

feathers generate app 

对于这个项目,我们将使用 JavaScript 创建一个 REST API。回答 CLI 中的问题,如下所示:

? Do you want to use JavaScript or TypeScript? JavaScript
? Project name auth-api-feathersjs
? Description
? What folder should the source files live in? src
? Which package manager are you using (has to be installed globally)? npm
? What type of API are you making? REST
? Which testing framework do you prefer? Mocha + assert
? This app uses authentication Yes
? Which coding style do you want to use? ESLint
? What authentication strategies do you want to use? (See API docs for all 180+
supported oAuth providers) Username + Password (Local)
? What is the name of the user (entity) service? users
? What kind of service is it? NeDB
? What is the database connection string? nedb://../data 

一旦 CLI 完成了应用程序的搭建,您就可以在任何您喜欢的代码编辑器中打开项目。

FeathersJS 提供了一些基本的测试来确保项目正常运行。您可以在项目根目录下的test文件夹中找到这些文件。使用以下命令运行测试:

npm test 

预期该输出:

 Feathers application tests
    ✓ starts and shows the index page
    404
info: Page not found {"className":"not-found","code":404,"data":{"url":"/path/to/nowhere"},"errors":{},"name":"NotFound","type":"FeathersError"}
      ✓ shows a 404 HTML page
info: Page not found {"className":"not-found","code":404,"data":{"url":"/path/to/nowhere"},"errors":{},"name":"NotFound","type":"FeathersError"}
      ✓ shows a 404 JSON error without stack trace

  authentication
    ✓ registered the authentication service
    local strategy
      ✓ authenticates user and creates accessToken (79ms)

  'users' service
    ✓ registered the service

  6 passing (282ms) 

配置 CircleCI

接下来,为 CircleCI 添加管道配置。对于这个项目,管道将由一个步骤组成:

  1. 构建和测试——在这里,我们构建项目,安装项目依赖项,并运行项目测试。

在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在新创建的文件中,添加以下配置:

# Use the latest 2.1 version of CircleCI pipeline process engine.
version: 2.1

orbs:
  node: circleci/node@5.0.2

jobs:
  build-and-test:
    executor: node/default
    steps:
      - checkout
      - node/install-packages:
          cache-path: ~/project/node_modules
          override-ci-command: npm install
      - run: npm test

workflows:
  test-my-app:
    jobs:
      - build-and-test 

这个配置使用 Node.js orb circleci/node来安装默认启用缓存的包。它还使 npm 可供您运行测试。

对于要执行的节点 orbte,管道只有一个作业build-and-test。这项工作的第一步是从 GitHub 库中提取代码。接下来,它安装在package.json文件中指定的包。通过使用指定目录中的缓存来加速此过程。该配置通过使用override-ci-command覆盖安装包的默认命令。这确保传递了该项目的正确安装命令。

这项工作的最后一步是运行npm test命令。

在 GitHub 上设置项目

现在您需要将这个项目转换成 Git 存储库,然后在 GitHub 上设置它。看到这个帖子寻求帮助:把你的项目推到 GitHub

登录您的 CircleCI 帐户。如果你注册了你的 GitHub 账户,你所有的库都会显示在你的仪表盘上。

点击auth-api-feathersjs项目设置项目

输入您的代码在 GitHub 上所在的分支机构的名称。然后点击设置项目

CircleCI project set up

您的第一个构建过程将开始运行并成功完成!

CircleCI project successful

点击构建和测试查看工作步骤和每个工作的状态。

Pipeline build successful

向您的 FeathersJS 应用程序添加测试

FeathersJS 的主要卖点之一是,它可以在几分钟内轻松构建原型,并在几天内完成生产就绪的应用程序。无需编写一行代码,您就已经有了一个 API 端点来处理注册和认证。您还拥有获取所有用户、更新用户和删除用户的端点。API 包括以下端点:

  • GET /users逐页列出所有用户。
  • POST /users创建新用户。此端点将用于注册。
  • POST /authentication使用提供的策略验证用户。对于本教程,我们使用“本地”身份验证。该策略使用保存在本地数据库中的电子邮件地址和密码组合。
  • GET /users/123返回 id 为 123 的用户的详细信息。您还可以在这个请求中包含查询,比如/users/123?email=yemiwebby@circleci.com
  • PATCH /users/123PUT /users/123更新 id 为 123 的用户的详细信息。
  • DELETE /users/123删除 id 为 123 的用户。

您可以在 Postman 中测试这些端点。使用以下命令运行服务器:

npm run dev 

Create User

注意,新创建的用户的密码没有在 JSON 响应中返回。这是在用户服务挂钩(位于src/services/users/users.hooks.js)中现成指定的。

Authenticate User

用户服务挂钩还有助于确保在用户可以向任何端点(除了注册端点之外)发出请求之前,在请求的头中指定一个 JWT 令牌。

您已经完成了本教程中的一个重要步骤,但是还剩下一些步骤。假设您的应用程序有一个安全要求,即用户只能删除自己的帐户。尝试删除另一个用户的帐户应该会返回一个403错误响应。尝试更新或修补另一个用户的帐户也应该如此。

您的下一步是为这个安全需求编写一个测试套件。首先,您需要建立一个数据库,只用于测试。为此,用以下代码更新config/test.json中的测试环境配置:

{
  "nedb": "../test/data"
} 

您还需要确保在每次测试运行之前清理数据库。要跨平台实现这一点,首先运行:

npm install shx --save-dev 

接下来,将 package.json 文件的scripts部分更新为:

 "scripts": {
    "test": "npm run lint && npm run mocha",
    "lint": "eslint src/. test/. --config .eslintrc.json --fix",
    "dev": "nodemon src/",
    "start": "node src/",
    "clean": "shx rm -rf test/data/",
    "mocha": "npm run clean && NODE_ENV=test mocha test/ --recursive --exit"
  }, 

这将确保在每次测试运行之前删除test/data文件夹。

最后,更新test/services/users.test.js中的代码以匹配:

const axios = require('axios');
const assert = require('assert');
const url = require('url');
const app = require('../../src/app');

const port = app.get('port') || 8998;
const getUrl = (pathname) =>
  url.format({
    hostname: app.get('host') || 'localhost',
    protocol: 'http',
    port,
    pathname,
  });

describe('\'users\' service', () => {
  it('registered the service', () => {
    const service = app.service('users');
    assert.ok(service, 'Registered the service');
  });
});

describe('Additional security checks on user endpoints', () => {
  let alice = {
    email: 'alice@feathersjs.com',
    password: 'supersecret12',
  };

  let bob = {
    email: 'bob@feathersjs.com',
    password: 'supersecret1',
  };

  const getTokenForUser = async (user) => {
    const { accessToken } = await app.service('authentication').create({
      strategy: 'local',
      ...user,
    });
    return accessToken;
  };

  const setupUser = async (user) => {
    const { _id } = await app.service('users').create(user);
    user._id = _id;
    user.accessToken = await getTokenForUser(user);
  };

  let server;

  before(async () => {
    await setupUser(alice);
    await setupUser(bob);

    server = app.listen(port);
  });

  after(async () => {
    server.close();
  });

  it('should return 403 when user tries to delete another user', async () => {
    const { accessToken } = alice;
    const { _id: targetId } = bob;
    const config = { headers: { Authorization: `Bearer ${accessToken}` } };

    try {
      await axios.delete(getUrl(`/users/${targetId}`), config);
    } catch (error) {
      const { response } = error;

      assert.equal(response.status, 403);
      assert.equal(
        response.data.message,
        'You are not authorized to perform this operation on another user'
      );
    }
  });

  it('should return 403 when user tries to put another user', async () => {
    try {
      const { accessToken } = bob;
      const { _id: targetId } = alice;
      const config = { headers: { Authorization: `Bearer ${accessToken}` } };
      const testData = { password: bob.password };

      await axios.put(getUrl(`/users/${targetId}`), testData, config);
    } catch (error) {
      const { response } = error;

      assert.equal(response.status, 403);
      assert.equal(
        response.data.message,
        'You are not authorized to perform this operation on another user'
      );
    }
  });

  it('should return 403 when user tries to patch another user', async () => {
    try {
      const { accessToken } = alice;
      const { _id: targetId } = bob;
      const config = { headers: { Authorization: `Bearer ${accessToken}` } };
      const testData = { password: alice.password };

      await axios.patch(getUrl(`/users/${targetId}`), testData, config);
    } catch (error) {
      const { response } = error;

      assert.equal(response.status, 403);
      assert.equal(
        response.data.message,
        'You are not authorized to perform this operation on another user'
      );
    }
  });
}); 

在这个测试套件中,我们有两个角色(Alice 和 Bob)在我们的应用程序中注册。

在一个测试场景中,Alice 试图删除 Bob 的帐户。因为这是不允许的,所以您可以期待 API 返回一个403响应。

在第二个场景中,Bob 试图发出一个PUT请求来重置 Alice 的密码。如果这个请求被成功处理,Bob 将能够以 Alice 的身份登录,并按照自己的意愿使用她的帐户。您希望避免这种情况,并期望返回一个403响应。

第三个场景与第二个相似,只是这次 Alice 试图通过一个UPDATE请求重置 Bob 的密码。同样,您可以期待返回一个403响应。

很棒的东西!您已经有了预期场景的代码和测试。剩下要做的就是推送您的代码,让新特性生效。

git commit -am 'Additional security checks on user endpoints'
git push origin main 

回到 CircleCI 去看看你的新建筑运行得多好。一切都一帆风顺,直到我们有一个构建失败的消息。有什么问题吗?

Build fails

看起来应用程序没有通过新测试套件中的测试。测试报告显示,在第一个场景中,Alice 成功删除了 Bob 的帐户。更有趣的是,因为我们删除了 Bob 的帐户,API 在第二个和第三个场景中返回了404响应。

假设您仍然需要为这个特性编写代码,并且您忘记了在将测试推到中央存储库之前在本地运行测试。这个场景可能看起来有些做作,但事实是这样的错误时有发生。拥有自动化测试过程可以防止这种人为错误。因为在部署更新之前必须通过一层测试,所以这个噩梦将永远无法通过测试环境。

好吧,你应该在别人发现之前解决这个问题。通过向用户服务挂钩添加验证检查,可以防止用户对其他帐户执行某些操作。

打开src/services/users/users.hooks.js。就在以module.exports = {开头的那一行的上方,加上:

...
const {Forbidden} = require('@feathersjs/errors');

const verifyCanPerformOperation = async context => {
  const {_id: authenticatedUserId} = context.params.user;
  const {id: targetUserId} = context;
  if (authenticatedUserId !== targetUserId) {
    throw new Forbidden('You are not authorized to perform this operation on another user');
  }
};
... 

这个函数将钩子上下文作为一个参数,并从中获得两个值:通过身份验证的用户的id和目标用户的id。如果这些值不相同,那么抛出一个forbidden错误。这个错误由钩子的错误处理器处理,它返回一个403响应。

接下来,更新src/services/users/users.hooks.js中的before条目以匹配:

...
before: {
    all: [],
    find: [authenticate('jwt')],
    get: [authenticate('jwt')],
    create: [hashPassword('password')],
    update: [hashPassword('password'), authenticate('jwt'), verifyCanPerformOperation],
    patch: [hashPassword('password'), authenticate('jwt'), verifyCanPerformOperation],
    remove: [authenticate('jwt'), verifyCanPerformOperation]
  },
  ... 

随着这一改变,在对updatepatchremove服务方法的认证检查之后执行验证检查。这些方法分别映射到PUTPATCHDELETE端点。这确保了登录用户可以检索他们的 id。

剩下的就是在本地运行测试以确保一切正常,提交最新的更改,然后将这些更改推送到您的 GitHub 存储库。这触发了 CircleCI build-and-test管道。

Pipeline build successful

干得好!

结论

在本文中,您使用 FeathersJS 构建了一个身份验证和用户管理 API。您还设置了一个 CircleCI 管道,在将更改部署到生产服务器之前,自动测试对 repo 的更改。

这种方法的好处是人为错误不会危及应用程序的安全性。通过自动化测试过程,您消除了人为错误对生产环境造成意外破坏的风险。它还为维护的软件增加了额外的质量控制和保证。尝试持续集成,让代码库瓶颈成为团队的过去!

本教程的全部代码可以在 GitHub 上找到。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

自动化开源安全跟踪| WhiteSource 和 CircleCI

原文:https://circleci.com/blog/automate-open-source-security-tracking-with-the-whitesource-circleci-orb/

在软件开发的敏捷时代,速度是游戏的名字。这就是为什么像 CircleCI 这样的 CI/CD 工具在改变我们的工作方式方面发挥了如此重要的作用;以极快的速度通过管道发送我们的软件。

我们总是问这样的问题:我们多快能给我们的客户推出一个新版本?我们如何添加这些新功能并保持进度?我们怎样才能尽可能少地浪费时间和精力来做这些事情呢?

开源软件组件通过为我们的产品提供构建模块而扮演着重要的角色。这些自由软件组件通过向我们提供可靠的代码,允许我们跳过自己编写基本功能的单调工作。这使我们能够专注于我们的业务逻辑,而不是从零开始构建我们自己的框架和基础设施。然而,即使我们享受开源组件的好处,它们也不是没有挑战,尤其是在安全漏洞方面。

不要让易受攻击的组件阻碍您的发展

事实是,影响开源组件的安全漏洞的数量在最近几年呈上升趋势,根据我们最新的研究报告显示,达到了 51%的峰值。这导致了更多的警报,当然,也导致了耗时的“拆旧换新”操作。

当开发人员在寻找解决我们代码中存在的问题的解决方案时,在我们从 GitHub 上的回购中获取开源组件之前,我们的第一直觉通常不会深入探究它的来龙去脉。我们根本没有时间开始调查我们使用的每个开源库是否有任何已知的漏洞,或者是否有更新的版本可用。相反,我们会问它是否做了我们需要它做的工作。如果是的话,太好了!我们把它放入代码中。没有吗?好,继续下一个组件,直到我们找到一个工作。

这里的问题是,一旦一个易受攻击的组件进入我们的代码,它可能会对您的软件构成风险,直到它在最好的情况下被标记为稍后进行补救,或者在最坏的情况下被黑客利用。无论是哪种情况,都意味着花时间回到代码中并实现某种修复。

处理补救措施

修复可以像添加补丁一样简单。这当然是说起来容易做起来难。

虽然超过 97%的开源漏洞都有补丁,但并不是所有的补救措施都像补丁一样简单。在这些情况下,我们可能需要寻找一个新版本,并希望它是向后兼容的。如果所有其他方法都失败了,唯一的选择可能是寻找一个不同的组件来做我们易受攻击的组件所做的大部分事情,并希望重新配置不会破坏其他所有事情。

所有这些听起来都像是一场争论,因为事实就是如此。好消息是,这些问题中的大部分都可以通过一开始就聪明地工作来轻松避免。

自始至终保护您的 CI/CD 渠道

WhiteSource CircleCI orb 集成了 WhiteSource 的自动化、持续跟踪功能,以帮助开发人员在将易受攻击的开源组件添加到他们的产品之前发现它们。Whitesource 可以在软件开发生命周期(SDLC)的最早阶段参与进来,以识别开源组件,并标记出容易受到攻击的组件,这些组件可能会在以后引起麻烦。通过在将任何易受攻击的组件添加到 CI、试运行或生产服务器之前将其捕获,它为我们提供了一个机会来找到一个不同的、更安全的版本或组件,以满足我们的需求。

尽管我们更愿意在游戏的早期发现易受攻击的组件,但有时易受攻击的组件可能已经存在于已部署的产品中。在这种情况下,我们需要补救。对于开发人员来说,可取之处在于,漏洞描述中建议的修复可以为我们指出正确的方向,节省我们自己研究和确认问题的理想解决方案的时间。

top alerts

快速简单的实施

首先,WhiteSource 帐户持有人可以点击 Circle CI“设置”部分,并创建一个新的上下文。接下来,就是在 integrate 选项卡上添加 WhiteSource API 令牌了。

CircleCI contexts

完成后,用户可以创建一个配置文件,其中包含必要的参数引用,并开始接收有关其开源使用和风险的 WhiteSource 报告。

示例用法:

version: 2.1

executors:
  java:
    description: The docker container to run java commands
    docker:
      - image: circleci/openjdk:8-jdk

jobs:
  scan:
    executor: java
    parameters:
      api_key:
        description: Unique identifier of the organization. Can be retrieved from the admin page in your WhiteSource account.
        type: string
        default: ""
      directory:
        description: Comma separated list of directories and / or files to scan.
        type: string
        default: "."
      config_file_path:
        description: Configuration file name (including file path).
        type: string
        default: "./whitesource-fs-agent.config"
    steps:
      - checkout
      - run:
          command: |
            if [[ -z "<< parameters.api_key >>" ]]; then
              bash <(curl -s -L https://github.com/whitesource/fs-agent-distribution/raw/master/standAlone/wss_agent_orb.sh) -c << parameters.config_file_path >> -d << parameters.directory >>
            else
              bash <(curl -s -L https://github.com/whitesource/fs-agent-distribution/raw/master/standAlone/wss_agent_orb.sh) -apiKey << parameters.api_key >> -c << parameters.config_file_path >> -d << parameters.directory >>
            fi 

包扎

我们使用开源组件来节省时间并更快地到达目的地。我们需要确保使用正确的技术来管理这些组件的使用方式,最大限度地减少安全使用这些组件所需的维护工作量。通过链接您的 CircleCI 和 WhiteSource 帐户,我们可以更轻松地捕捉漏洞,同时还能轻松管理漏洞,让您深入了解您的软件,从而在使用开源组件时做出更明智的决策。


什里·伊夫桑是一名经验丰富的云解决方案架构师和产品经理,拥有工业工程和管理学士学位。在加入 WhiteSource 之前,什里曾在 R&D 的多家公司担任多个职位,包括解决方案架构师、R&D 团队领导和产品经理。

Kubernetes -基础设施代码- Kubernetes 教程| CircleCI

原文:https://circleci.com/blog/automate-releases-from-pipelines-using-infrastructure-as-code/

持续交付使开发人员、团队和组织能够毫不费力地更新代码并向他们的客户发布新特性。这一切都是可能的,因为团队和组织内最近的文化转变,他们开始接受 CI/CD 和 DevOps 实践。实施 CI/CD 和 DevOps 实践使这些团队能够更好地利用现代工具,以高度的信心和一致性来构建、测试和部署他们的软件。

基础设施即代码(IaC)使团队能够轻松管理他们的云资源,方法是在代码中静态定义和声明这些资源,然后通过代码部署和动态维护这些资源。在本文中,我将演示如何在 CI/CD 管道中实现 IaC。这篇文章还演示了如何在管道中实现我们合作伙伴的 Pulumi orb。这个 orb 定义并部署一个应用到一个 Google Kubernetes 引擎(GKE) 集群。Pulumi 将提供该职位的 IaC 部分。

假设

这篇文章假设您在 GitHub 上有一个现有的 Git 存储库。这里提供的代码示例需要存在于项目 repo 的目录中。

使用的技术

这篇文章还假设读者对以下内容有基本的了解;

启动装置

Pulumi 使开发人员能够用他们喜欢的语言(例如 JavaScript、Python、Go 等)编写代码。),轻松部署云应用和基础设施,无需学习专门的 DSL 或 YAML 模板解决方案。一流语言的使用使得抽象和重用成为可能。Pulumi 在所有主要云服务提供商(如 AWS、Azure、GCP 等)中提供高级别的云包和低级别的资源定义。)这样你就可以掌握一个系统交付给所有人。

在您选择的应用程序存储库中,创建一个 Pulumi 应用程序将驻留的新目录。

mkdir -p pulumi/gke
cd pulumi/gke 

接下来,注册 Pulumi 和 Google Cloud 账户,如果你以前没有注册过的话。

在创建了一个新的 Pulumi 项目之后,现在在pulumi/gke目录中有三个文件:

  • pulumi . YAML——指定关于项目的元数据。
  • 普鲁米。<栈名>。YAML——包含我们初始化的堆栈的配置值。 <栈名> 应该替换为创建新的 Pulumi 项目时定义的栈名。出于本教程的目的,我们将这个文件命名为Pulumi.k8s.yaml
  • main。py -定义堆栈资源的 Pulumi 程序。这就是 IaC 魔法发生的地方。

编辑Pulumi.<stack name>.yaml文件并将以下内容粘贴到其中:

config:
  gcp:credentials: ./cicd_demo_gcp_creds.json
  gcp:project: cicd-workshops
  gcp:region: us-east1
  gcp:zone: us-east1-d
  gke:name: k8s 

编辑__main__.py文件并将其内容替换为:

import os
import pulumi
import pulumi_kubernetes
from pulumi import ResourceOptions
from pulumi_kubernetes.apps.v1 import Deployment
from pulumi_kubernetes.core.v1 import Namespace, Pod, Service
from pulumi_gcp import container

conf = pulumi.Config('gke')
gcp_conf = pulumi.Config('gcp')

stack_name = conf.require('name')
gcp_project = gcp_conf.require('project')
gcp_zone = gcp_conf.require('zone')

app_name = 'cicd-app'
app_label = {'appClass':app_name}
cluster_name = app_name

image_tag = ''
if 'CIRCLE_SHA1' in os.environ:
    image_tag = os.environ['CIRCLE_SHA1']
else:
    image_tag = 'latest'

docker_image = 'ariv3ra/orb-pulumi-gcp:{0}'.format(image_tag)

machine_type = 'g1-small'

cluster = container.Cluster(
    cluster_name,
    initial_node_count=3,
    min_master_version='latest',
    node_version='latest',
    node_config={
        'machine_type': machine_type,
        'oauth_scopes': [
            "https://www.googleapis.com/auth/compute",
            "https://www.googleapis.com/auth/devstorage.read_only",
            "https://www.googleapis.com/auth/logging.write",
            "https://www.googleapis.com/auth/monitoring",
        ],
    }
)

# Set the Kubeconfig file values here
def generate_k8_config(master_auth, endpoint, context):
    config = '''apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: {masterAuth}
    server: https://{endpoint}
  name: {context}
contexts:
- context:
    cluster: {context}
    user: {context}
  name: {context}
current-context: {context}
kind: Config
preferences: {prefs}
users:
- name: {context}
  user:
    auth-provider:
      config:
        cmd-args: config config-helper --format=json
        cmd-path: gcloud
        expiry-key: '{expToken}'
        token-key: '{tokenKey}'
      name: gcp
    '''.format(masterAuth=master_auth, context=context, endpoint=endpoint, 
            prefs='{}', expToken = '{.credential.token_expiry}', tokenKey='{.credential.access_token}')

    return config

gke_masterAuth = cluster.master_auth['clusterCaCertificate']
gke_endpoint = cluster.endpoint
gke_context = gcp_project+'_'+gcp_zone+'_'+cluster_name

k8s_config = pulumi.Output.all(gke_masterAuth,gke_endpoint,gke_context).apply(lambda args: generate_k8_config(*args))

cluster_provider = pulumi_kubernetes.Provider(cluster_name, kubeconfig=k8s_config)
ns = Namespace(cluster_name, __opts__=ResourceOptions(provider=cluster_provider))

gke_deployment = Deployment(
    app_name,
    metadata={
        'namespace': ns,
        'labels': app_label,
    },
    spec={
        'replicas': 3,
        'selector':{'matchLabels': app_label},
        'template':{
            'metadata':{'labels': app_label},
            'spec':{
                'containers':[
                    {
                        'name': app_name,
                        'image': docker_image,
                        'ports':[{'name': 'port-5000', 'container_port': 5000}]
                    }
                ]
            }
        }
    },
    __opts__=ResourceOptions(provider=cluster_provider)
)

deploy_name = gke_deployment

gke_service = Service(
    app_name,
    metadata={
        'namespace': ns,
        'labels': app_label,
    },
    spec={
        'type': "LoadBalancer",
        'ports': [{'port': 80, 'target_port': 5000}],
        'selector': app_label,
    },
    __opts__=ResourceOptions(provider=cluster_provider)
)

pulumi.export("kubeconfig", k8s_config)
pulumi.export("app_endpoint_ip", gke_service.status['load_balancer']['ingress'][0]['ip']) 

__main__.py文件中的内容指定了您将从管道中部署的 GKE 集群和基础设施。这个 Pulumi 应用程序创建了一个三节点 Kubernetes 集群,它通过 Docker 容器在 pods 中运行应用程序。这段代码还创建了一个Load Balancer资源,它将流量均匀地路由到各个计算节点上的活动 Docker 容器。如果你想了解更多关于 Pulumi Python 应用的信息,请访问网站获取详细解释。

Google 云设置

在本节中,您将创建和生成必要的 GCP 凭证。这些凭证将为我们的 CI/CD 管道和 Pulumi 代码提供访问权限,以便在 GCP 上执行命令。

创建一个 GCP 项目

默认项目是为新帐户默认设置的。我建议您创建一个新项目,并保持它的独立性,以便以后可以轻松地拆除它。创建后,请务必将project id复制下来,因为它与project name不同。

How to find your project id.

获取项目凭据

接下来,设置一个服务帐户密钥,Pulumi 将使用它来创建和管理您的 GCP 项目中的资源。进入创建服务账户密钥页面。选择默认服务账户或新建一个,选择JSON作为密钥类型,点击创建。将这个.json文件保存在pulumi/gke文件夹中。

重要安全提示: 将文件重命名为cicd_demo_gcp_creds.json,以保护您的 Google Cloud 凭据不会在公共 GitHub 存储库中发布和暴露。此外,您可以在这个项目的.gitignore文件中添加凭证的.json文件名。您必须非常谨慎地使用该文件中的数据,因为一旦暴露,任何拥有该信息的人都可以登录您的帐户,并在您的 Google Cloud 帐户上创建资源和运行费用。

cicekci 设置

现在,我们需要配置 CircleCI 和我们的管道配置文件来将 Pulumi 集成到我们的 CI/CD 管道中。

对 Google 服务帐户文件进行编码

服务帐户文件必须编码成一个base64值,以便将该数据作为一个环境变量存储在 CircleCI 中。在终端中运行以下命令,对值进行编码并获得结果:

base64 cicd_demo_gcp_creds.json 

该命令的结果将类似于以下内容:

ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAiY2ljZC13b3Jrc2hvcHMiLAogICJwcml2YXRlX2tleV9pZCI6ICJiYTFmZDAwOThkNTE1ZTE0NzE3ZjE4NTVlOTY1NmViMTUwNDM4YTQ4IiwKICAicHJpdmF0ZV9rZXkiOiAiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tXG5NSUlFdlFJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLY3dnZ1NqQWdFQUFvSUJBUURjT1JuRXFla3F4WUlTXG5UcHFlYkxUbWdWT3VzQkY5NTE1YkhmYWNCVlcyZ2lYWjNQeFFBMFlhK2RrYjdOTFRXV1ZoRDZzcFFwWDBxY2l6XG5GdjFZekRJbXkxMCtHYnlUNWFNV2RjTWw3ZlI2TmhZay9FeXEwNlc3U0FhV0ZnSlJkZjU4U0xWcC8yS1pBbjZ6XG5BTVdHZjM5RWxSNlhDaENmZUNNWXorQmlZd29ya3Nob3BzLmlhbS5nc2VydmljZWFjY291bnQuY29tIgp9Cg== 

将结果复制到您的剪贴板,因为我们将在下一部分使用它。

创建项目变量

为了让这个 CI/CD 管道在 GCP 上执行命令,我们必须在 CircleCI 中配置项目级环境变量

使用 CircleCI 仪表板创建以下项目级环境变量:

  • $DOCKER_LOGIN =您的 Docker Hub 用户名
  • $DOCKER_PWD =您的 Docker Hub 密码
  • $GOOGLE_CLOUD_KEYS =上一节的 base64 编码结果
  • $PULUMI_ACCESS_TOKEN =从 PULUMI 仪表板生成访问令牌

集成了 Pulumi 的 CI/CD 管道

现在,您已经拥有了将 Pulumi 应用程序集成到 CircleCI config.yml文件中所需的所有元素。编辑您的config.yml并将以下配置粘贴到您的文件中。这个config.yaml的内容特定于我在演示和演讲中使用的一个示例 Python 项目,因此您的项目的config.yml将会不同。要查看完整的示例项目,请在 GitHub 点击查看回购。我将引导您并解释这个示例配置中重要的 Pulumi 集成部分,以便您对正在发生的事情有一个清晰的理解。

version: 2.1
orbs:
  pulumi: pulumi/pulumi@1.0.1
jobs:
  build_test:
    docker:
      - image: circleci/python:3.7.2
        environment:
          PIPENV_VENV_IN_PROJECT: 'true'
    steps:
      - checkout
      - run:
          name: Install Python Dependencies
          command: |
            pipenv install --skip-lock
      - run:
          name: Run Tests
          command: |
            pipenv run pytest
  build_push_image:
    docker:
      - image: circleci/python:3.7.2
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: false
      - run:
          name: Build and push Docker image
          command: |       
            pipenv install --skip-lock
            pipenv run pyinstaller -F hello_world.py
            echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
            echo 'export IMAGE_NAME=orb-pulumi-gcp' >> $BASH_ENV
            source $BASH_ENV
            docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
            echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
            docker push $DOCKER_LOGIN/$IMAGE_NAME
  deploy_to_gcp:
    docker:
      - image: circleci/python:3.7.2
        environment:
          CLOUDSDK_PYTHON: '/usr/bin/python2.7'
          GOOGLE_SDK_PATH: '~/google-cloud-sdk/'
    steps:
      - checkout
      - pulumi/login:
          access-token: ${PULUMI_ACCESS_TOKEN}
      - run:
          name: Install dependecies
          command: |
            cd ~/
            sudo pip install --upgrade pip==18.0 && pip install --user pulumi pulumi-gcp pulumi-kubernetes
            curl -o gcp-cli.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz
            tar -xzvf gcp-cli.tar.gz
            echo ${GOOGLE_CLOUD_KEYS} | base64 --decode --ignore-garbage > ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
            ./google-cloud-sdk/install.sh  --quiet 
            echo 'export PATH=$PATH:~/google-cloud-sdk/bin' >> $BASH_ENV
            source $BASH_ENV
            gcloud auth activate-service-account --key-file ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
      - pulumi/update:
          stack: k8s
          working_directory: ${HOME}/project/pulumi/gcp/gke/
workflows:
  build_test_deploy:
    jobs:
      - build_test
      - build_push_image:
          requires:
            - build_test
      - deploy_to_gcp:
          requires:
          - build_push_image 

下面的代码片段指定将在这个管道中使用 Pulumi orb

version: 2.1
orbs:
  pulumi: pulumi/pulumi@1.0.1 

示例管道中的jobs:键定义了三个单独的作业:

  • 这个任务运行应用程序单元测试

  • build_push_image:该作业基于通常共存于项目报告中的 Docker 文件构建新的 Docker 映像

  • 这个任务通过 Pulumi orb 部署到 GKE 集群

我将专注于build_push_imagedeploy_to_gcp工作。

构建 _ 推送 _ 映像:

 build_push_image:
    docker:
      - image: circleci/python:3.7.2
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: false
      - run:
          name: Build and push Docker image
          command: |       
            pipenv install --skip-lock
            pipenv run pyinstaller -F hello_world.py
            echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
            echo 'export IMAGE_NAME=orb-pulumi-gcp' >> $BASH_ENV
            source $BASH_ENV
            docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
            echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
            docker push $DOCKER_LOGIN/$IMAGE_NAME 

在应用程序经过测试并成功通过后,build_push_image作业将应用程序打包成一个可执行的二进制文件。然后它启动了docker build命令,该命令基于项目回购中的Dockerfile构建了一个新的 Docker 映像。该作业还使用现有的环境变量,并定义一些新的环境变量,用于指定唯一的 Docker 图像名称。以下是该项目的Dockerfile:

FROM python:3.7.2
RUN mkdir /opt/hello_world/
WORKDIR /opt/hello_world/
COPY dist/hello_world /opt/hello_world/
EXPOSE 80
CMD [ "./hello_world" ] 

docker push命令将我们新建的 Docker 映像上传到 Docker Hub,以便将来存储和检索。

部署到 gcp:

 deploy_to_gcp:
    docker:
      - image: circleci/python:3.7.2
        environment:
          CLOUDSDK_PYTHON: '/usr/bin/python2.7'
          GOOGLE_SDK_PATH: '~/google-cloud-sdk/'
    steps:
      - checkout
      - pulumi/login:
          access-token: ${PULUMI_ACCESS_TOKEN}
      - run:
          name: Install dependencies
          command: |
            cd ~/
            sudo pip install --upgrade pip==18.0 && pip install --user pulumi pulumi-gcp pulumi-kubernetes
            curl -o gcp-cli.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz
            tar -xzvf gcp-cli.tar.gz
            echo ${GOOGLE_CLOUD_KEYS} | base64 --decode --ignore-garbage > ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
            ./google-cloud-sdk/install.sh  --quiet 
            echo 'export PATH=$PATH:~/google-cloud-sdk/bin' >> $BASH_ENV
            source $BASH_ENV
            gcloud auth activate-service-account --key-file ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
      - pulumi/update:
          stack: k8s
          working_directory: ${HOME}/project/pulumi/gcp/gke/ 

上面指定的deploy_to_gcp:作业是管道的一部分,它利用 Pulumi 应用程序和 orb 在 GCP 上实际建立新的 GKE 集群。下面,我将简要介绍一下deploy_to_gcp:工作。

 - pulumi/login:
          access-token: ${PULUMI_ACCESS_TOKEN} 

上面的代码演示了 Pulumi orb 的login:命令的规范和执行。参数access-token:通过您在 CircleCI 仪表板中设置的${PULUMI_ACCESS_TOKEN}环境变量传递。

 curl -o gcp-cli.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz
            tar -xzvf gcp-cli.tar.gz
            echo ${GOOGLE_CLOUD_KEYS} | base64 --decode --ignore-garbage > ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
            ./google-cloud-sdk/install.sh  --quiet 
            echo 'export PATH=$PATH:~/google-cloud-sdk/bin' >> $BASH_ENV
            source $BASH_ENV
            gcloud auth activate-service-account --key-file ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json 

以上命令下载并安装 Google Cloud SDK 。此 SDK 是在 GCP 上创建和修改 GKE 集群所必需的。前两行下载并解包 SDK。echo ${GOOGLE_CLOUD_KEYS} | base64 --decode...命令解码${GOOGLE_CLOUD_KEYS}环境变量,然后用解码后的内容填充cicd_gcp_creds.json。这个文件必须存在于 Pulumi 应用程序项目的目录中。这个特定的run:块中的其余命令安装 SDK,最后一行授权服务帐户通过cicd_demo_gcp_creds.json文件访问 GCP。

 - pulumi/update:
      stack: k8s
      working_directory: ${HOME}/project/pulumi/gcp/gke/ 

上面的代码利用 Pulumi orb 的update:命令将应用程序部署到 GCP 的一个新的 GKE 集群上。pulumi/update:命令显示了stack:working_directory:参数,它们代表了 Pulumi 堆栈的名称和初始化为 Pulumi 项目的目录的文件路径。您的working_directory:将不同于上面的代码示例。

结论

在本文中,我展示了如何将基础设施作为代码解决方案集成到 CI/Cd 管道中。我还演示了如何在 CI/CD 管道中声明和执行 CircleCI orb 技术。这些示例提供了对使用 CI/CD 自动化来构建、测试和部署使用 IaC 解决方案的代码的坚实理解。

使用 CircleCI orbs 通过简化我们编写 CircleCI 配置的方式来提高生产率。orb 也可以共享,这通过在我们的配置文件中使用预构建的命令、作业和执行器来节省时间。orb 并不局限于 CircleCI + GKE 部署。您可以浏览 Orb 注册表中的可用 Orb 列表,找到符合您选择的云平台、编程语言、testng 工具等的 Orb。

要查看完整的示例项目,请查看 GitHub 上的 repo这里

软件交付自动化合规| CircleCI

原文:https://circleci.com/blog/automate-software-delivery-compliance/

软件开发团队面临着越来越多的障碍:不断变化的设计需求、组织障碍、紧迫的期限、复杂的技术栈和软件供应链。开发人员和 IT 领导者面临的一个新挑战是需要遵守规定全面数据安全、事件响应以及监控和报告要求的法规和控制框架。

法规遵从性要求会给组织增加大量开销。幸运的是,可以使用持续集成和第三方工具来自动化与法规遵从性相关的活动。在本文中,您将回顾常见的法规遵从性框架的例子,作为软件交付组织实现法规遵从性的最佳实践,以及您如何自动化 CI/CD 的法规遵从性。

使用新的触发器和权限控制来管理您的生态系统中的变化

Learn More

软件合规性要求的示例

不同行业中的许多监管标准都要求遵守软件开发指南。这些标准的快速增长源于数字技术的爆炸式增长及其带来的新兴网络威胁。虽然法规遵从性不同于安全性,但它确实建立了组织的安全性必须满足的最低综合基准,如果没有超过的话。

例如,健康保险便携性和责任法案(HIPAA) 规定了美国医疗保健提供商和从业者使用的个人可识别健康信息的隐私和安全保护。另一个标准是支付卡行业数据安全标准(PCI-DSS) 。它提供了保护消费者隐私的卡交易护栏。具体来说,它禁止存储和未加密传输和处理个人卡详细信息,如卡验证值(CVV2s)、整个磁条和 pin。

对于服务提供商而言,服务组织控制 2 (SOC2) 报告是一份有价值的合规性认可,它确认了组织的服务交付流程和控制的信任服务标准

甚至政府在与承包商开展业务之前也有合规要求。例如,联邦风险和授权管理计划(FedRAMP) 是一项认证,政府承包商必须证明他们的云产品足够安全,可以存储国家数据。

在实践中,大多数企业的目标是获得多个合规标准的认证,以瞄准不同地区的市场,并让客户放心。因此,许多总部位于美国的云服务提供商希望获得欧盟(EU) 通用数据保护法规 (GDPR)合规性批准,以便为总部位于 EU-的客户提供服务。

软件合规性最佳实践

实施众所周知的遵从性最佳实践是衡量您在内部公司治理、风险管理流程、组织监督政策、供应商管理和一般安全意识方面的能力的一个很好的方式。常见软件合规性实践的示例包括:

  • 严格的访问控制和权限
  • 全面的测试和变更管理
  • 供应链漏洞扫描
  • 定期合规审计

下一节将探讨其中的一些实践,并描述您的组织如何通过自动化和简化您的法规遵从性实践来减轻其法规遵从性负担。

访问控制、角色和权限

访问控制是一套针对用户和基础设施访问的指导原则和程序。它确保您已经封锁了私人和专有信息,防止未经授权的访问,并且在发生违规事件时爆炸半径很小。

访问控制始于身份验证,包括在授权访问敏感数据之前确认用户的身份。至少,用户身份验证包括验证唯一的用户名和密码。它还可以包括面部、指纹或眼睛的扫描,或者加密证书。例如,双因素认证通常需要用户名和密码组合生物特征扫描。

身份验证后,您可以实施基于角色的访问控制(RBAC)策略。这些策略基于用户在组织中的分配角色来限制对信息的访问。RBAC 策略应该基于最低特权原则,也就是说用户应该只能访问他们工作所需的资源。除了 RBAC,您还可以使用基于属性的访问控制(ABAC)来提供基于用户或对象特征、操作类型等的细粒度访问。例如,您可以使用 ABAC 来实施公司在转换时间或周末停止工作的策略。

访问、角色和权限应该在固定时间内有效,并在到期或不再使用时撤销。这意味着持续监控,您可以通过计划清理扫描或策略即代码软件(如开放策略代理)来评估和执行声明性代码编写的访问策略,从而实现自动化。

综合测试

软件测试包括功能测试和非功能测试。功能测试证明,与一组需求或规范相比,应用程序运行良好。非功能性测试评估应用程序的大规模行为、它对最终用户体验的贡献以及它遵守内部和外部安全要求的程度。

即使您的软件和基础设施通过了功能、性能、可靠性和安全性测试,它们仍然可能不符合标准。不合规的代码更容易受到攻击,可能导致不合规的状态,对声誉产生负面影响,并招致巨额罚款。

组织可以进行全面的合规性测试,以检查用户访问权限、个人身份信息的传输和存储以及程序更改控制程序中的应用程序和基础架构漏洞。合规性检查还会验证组织的文档和程序、活动日志以及软件许可证。

虽然许多合规性测试可以自动化,但有些组织需要自动化和手动检查的结合。例如:

  • 自动化功能测试确认应用程序的登录功能正常。
  • 手动非功能性测试确认密码是不可见的,并且按照开发代码中的规定在数据库上进行了加密。

类似地,自动化的安全测试可以确保应用程序免受不安全的直接对象引用(IDOR) 的影响。通过手工检查,符合性测试可能集中在编码风格上,并在定义用户和对象模型时确认通用唯一标识符(UUIDs)的使用。它还可能证实:

  • 在执行任务之前,应用程序会检查有效的对象和登录的用户
  • URL 参数模糊不清
  • 错误消息是描述性的,但不够模糊,无法正确猜测软件架构和漏洞

供应链漏洞扫描

为了避免重新发明轮子,软件开发人员依赖于预先构建的外部组件。一个组织继承了其软件组件的软件供应链。这种继承带来了可能产生深远后果的风险。

减轻这种风险的一个很好的方法是在您的代码库中加入自动化的安全漏洞扫描和咨询。至少,大多数安全漏洞扫描器可以扫描十大开放 Web 应用安全项目(OWASP)漏洞。漏洞也是从安全公告、聊天和独立研究中整理出来的。

符合性审计

合规性审计审查您在记录中对成文政策的实施与官方政策的符合程度。审计可能从您的代码库开始,检查错误配置和暴露的秘密。然后,审计可以继续审查您的构建、部署的工件和基础设施。

确保为资源提供最低权限,并通过身份验证和适当的角色进行访问。当不再使用时,应该对它们进行逻辑分割和移交。其他检查应验证数据存储生命周期,并确保个人身份信息不以明文形式存储或根本不存储。此外,必须有写得很好的回切和故障转移过程文档,并且定期严格地演示这些技术。

您可以通过使用能够提供端到端跟踪、管理和分析的工具来审核您所执行的测试,从而进一步保证产品的合规性。确保捕获所有系统活动的日志可用,以提供可观察性并简化系统审计练习。

如何自动遵守 CI/CD

在您的软件开发组织中自动化遵循的最好方法是实现一个全面的持续集成和持续交付(CI/CD) 实践。使用自动化的 CI/CD 管道可以加速团队的发展。您还可以将 DevSecOps 和合规最佳实践整合到您的工作流程中,以实现持续合规。

许多 CI/CD 提供商鼓励使用配置-代码方法来定义管道。这种方法有助于团队

您的团队还可以为 CI/CD 管道实施基于角色的访问控制策略,以及分支和标记过滤器,以限制谁可以更改管道配置或触发发布以及在什么条件下发布。大多数服务还提供全面的审计日志,允许组织检索系统事件的详细记录以用于报告目的。

除了可追溯性和访问控制的好处,持续集成使团队能够将一系列第三方安全和漏洞扫描工具整合到他们管道的构建和测试阶段。您可以使用 CircleCI 的 orbs 将流行的安全和漏洞扫描工具整合到您管道的所有阶段,只需几行代码。

例如,orbs 使得集成 SAST 和 DAST 扫描进行漏洞和合规性管理变得非常简单。

在管道的构建阶段,您可以使用 Snyk orb 运行静态应用程序安全测试(SAST)作业,以检测依赖漏洞、合规性和许可问题。

在部署阶段,您可以运行动态应用程序安全测试(DAST)作业来捕获生产中的运行时漏洞。像 DeepfactorStackHawk 这样的工具可以根据实际的应用程序行为,提供对应用程序代码、包依赖关系、web APIs 以及对常见漏洞和暴露( CVEs )的遵从性的优先洞察。

对于数据保护要求严格的高度管制行业的团队,您可以选择在防火墙后的场所安装持续集成工具,以增加安全性。或者,如果你更喜欢混合方法的灵活性,你可以建立自托管的运行器来在私有基础设施上运行特定的工作。借助此选项,您可以自动执行冗余步骤,例如在高峰需求期间自动扩展,或者在发生区域性故障时切换到其他数据中心。

结论

软件交付的法规遵从性要求定义了组织的安全性和业务实践必须满足或超过的最低要求。不同的合规性法规适用于不同的行业,但通常提倡最低特权原则、基于角色的访问控制、个人身份数据的隐私和保护、敏捷和全面的测试,以及对可观察性的定期审计。

通过持续的集成和交付,您可以减少软件开发和交付中的遵从性开销。CircleCI 促进了合规性最佳实践,如分配基于角色的精细权限、自动化安全和漏洞扫描、在私有基础架构上运行作业,以及为系统中的事件和 CI/CD 管道中的作业生成审计日志。作为一家 SOC-2 认证的FedRAMP 授权的云提供商,CircleCI 为高度监管行业中的组织提供了快速移动并保持安全性和合规性所需的信心。

为了开始自动化您的合规实践并消除开发流程中的瓶颈,立即注册免费的 CircleCI 计划

Vue.js 应用程序| CircleCI 的自动化测试

原文:https://circleci.com/blog/automate-testing-of-vue-apps/

本教程涵盖:

  1. 创建和设置 Vue.js 应用程序
  2. 为 Vue 组件编写自动化测试
  3. 建立持续集成管道

作为 JavaScript 社区中领先的框架之一,Vue.js 是一个为 web 用户界面构建可重用组件的渐进式框架。其直观的 API 和处理前端逻辑的强大灵活性只是 Vue 被全球开发者采用的两个原因。

在本教程中,我将引导您构建一个简单的列表应用程序,显示用户的姓名和角色。我将向您展示如何为应用程序编写测试。最后,您将为自动化测试配置一个持续集成管道。

先决条件

对于本教程,您需要:

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

入门指南

您将使用 Vue CLI 创建一个新的 Vue.js 项目。从终端运行以下命令:

vue create vue-user-app 

系统会提示您回答几个问题。对于本教程,请使用此处显示的答案:

  • 请选择一个预置:手动选择特性
  • 检查你的项目所需的特性: Babel,Linter,Unit
  • 选择一个您想要用 3.x 启动项目的 Vue.js 版本
  • 选择一个 linter / formatter 配置:仅带有错误预防的 ESLint
  • 挑选附加皮棉特性:皮棉保存时
  • 挑选一个单元测试解决方案: Jest
  • 你更喜欢把 Babel,ESLint 等的配置放在哪里??在专用配置文件中
  • 将此存储为未来项目的预置?

Vue CLI 将按照您的指定安装 Vue 应用程序及其依赖项。

转到新项目,使用以下命令运行它:

cd vue-user-app

npm run serve 

您可以在http://localhost:8080在浏览器中查看应用程序。

Vue homepage

这将呈现新 Vue.js 应用程序的默认主页。在本教程的下一节中,您将通过创建新的可重用组件来改变这一点。

使用 CTRL + C 停止应用程序运行。

创建可重用组件

Vue.js 组件包含用于构建 web 应用程序的三个不同部分。它们是:

  • <template></template>
  • <script></script>
  • <style></style>

这些部分有助于为视图、业务逻辑和样式创建适当的结构。

创建用户组件

您将添加到应用程序的第一个组件是用于创建和列出用户。该组件包含一个带有输入字段的表单,用于接受特定用户的姓名。当提交表单时,来自输入字段的细节被推送到一个为测试目的而创建的虚拟users数组中。

首先,在./src/components文件夹中创建一个名为UserList.vue的新文件。打开此新文件并粘贴此内容:

<template>
  <div class="container">
    <div class="page-title">
      <h3>{{ message }}</h3>
    </div>
    <div class="row">
      <div
        class="col-md-4"
        v-for="user in users"
        :key="user.id"
        data-user="user"
      >
        <div class="m-portlet m-portlet--full-height">
          <div class="m-portlet__body">
            <div class="tab-content">
              <div class="tab-pane active" id="m_widget4_tab1_content">
                <div class="m-widget4 m-widget4--progress">
                  <div class="m-widget4__item">
                    <div class="m-widget4__img m-widget4__img--pic">
                      <img
                        src="https://bootdey.com/img/Content/avatar/avatar1.png"
                        alt=""
                      />
                    </div>
                    <div class="m-widget4__info">
                      <span class="m-widget4__title"> {{ user.name }} </span>
                      <br />
                      <span class="m-widget4__sub">
                        {{ user.title }}
                      </span>
                    </div>
                    <div class="m-widget4__ext">
                      <button
                        @click="deleteUser(user)"
                        class="btn btn-primary"
                        data-cy="taskDelete"
                        id="deleteForm"
                      >
                        Delete
                      </button>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="row">
      <form id="form" @submit.prevent="createUser">
        <input id="new-user" v-model="newUser" class="form-control" />
      </form>
    </div>
  </div>
</template> 

这是将用户列表呈现给视图的<template></template>部分。它还包含一个输入字段来发布新用户的名字。

接下来,将这段代码粘贴在</template>标签的后面:

<script>
export default {
  props: ["message"],
  data() {
    return {
      newUser: "",
      users: [
        {
          id: 1,
          name: "Anna Strong",
          title: "Software Engineer",
        },
        {
          id: 2,
          name: "John Doe",
          title: "Technical Writer",
        },
      ],
    };
  },
  methods: {
    createUser() {
      this.users.push({
        id: 3,
        name: this.newUser,
        title: "Crypto Expert",
      });
      this.newUser = "";
    },

    deleteUser(user) {
      const newList = this.users.filter((u) => user.id !== u.id);
      this.users = newList;
    },
  },
};
</script> 

这定义了一个users数组,其中包含要在页面上呈现的虚拟数据。createUser()方法通过输入字段接收新用户的详细信息,并将其推送到users数组。您还定义了一个名为deleteUser()的方法,它接受一个user对象作为参数,并在调用时将其从用户列表中移除。

创建标题组件

要为视图的标题部分创建一个组件,请转到./src/components文件夹并创建一个名为NavBar.vue的新文件。将以下代码粘贴到其中:

<template>
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div
      class="collapse navbar-collapse justify-content-md-center"
      id="navbarsExample08"
    >
      <ul class="navbar-nav">
        <li class="nav-item active">
          <a class="nav-link" href="#">
            Users Listing App <span class="sr-only">(current)</span></a
          >
        </li>
      </ul>
    </div>
  </nav>
</template> 

更新应用程序组件

打开应用程序的AppComponent,并通过包含到NavBarUserList组件的链接来更新它。将其内容替换为:

<template>
  <div>
    <NavBar />
    <UserList />
    <div class="container"></div>
  </div>
</template>

<script>
import NavBar from "./components/NavBar.vue";
import UserList from "./components/UserList.vue";

export default {
  name: "App",
  components: { NavBar, UserList },
};
</script>

<style>
body {
  background: #eee;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
.page-title {
  margin: 15px 15px;
}
.m-portlet {
  margin-bottom: 2.2rem;
}
.m-portlet {
  -webkit-box-shadow: 0 1px 15px 1px rgba(113, 106, 202, 0.08);
  -moz-box-shadow: 0 1px 15px 1px rgba(113, 106, 202, 0.08);
  box-shadow: 0 1px 15px 1px rgba(113, 106, 202, 0.08);
  background-color: #fff;
}
.m-portlet .m-portlet__head {
  border-bottom: 1px solid #ebedf2;
}
.m-widget4 .m-widget4__item {
  display: table;
  padding-top: 1.15rem;
  padding-bottom: 1.25rem;
}
.m-widget4 .m-widget4__item .m-widget4__img {
  display: table-cell;
  vertical-align: middle;
}
.m-widget4 .m-widget4__item .m-widget4__img.m-widget4__img--logo img {
  width: 3.5rem;
  border-radius: 50%;
}
.m-widget4 .m-widget4__item .m-widget4__img.m-widget4__img--pic img {
  width: 4rem;
  border-radius: 50%;
}
.m-widget4 .m-widget4__item .m-widget4__img.m-widget4__img--icon img {
  width: 2.1rem;
}
.m-widget4 .m-widget4__item .m-widget4__info {
  display: table-cell;
  width: 100%;
  padding-left: 1.2rem;
  padding-right: 1.2rem;
  font-size: 1rem;
  vertical-align: middle;
}
.m-widget4 .m-widget4__item .m-widget4__info .m-widget4__title {
  font-size: 1rem;
  font-weight: bold;
}
.m-widget4.m-widget4--progress .m-widget4__info {
  width: 50%;
}
</style> 

这包括一个<style></style>部分,包括应用程序的样式。

包括引导

打开公共文件夹中的index.html文件,并包含用于引导的 CDN 文件。这只是给页面一些默认的样式。

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css"
      integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn"
      crossorigin="anonymous"
    />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
        properly without JavaScript enabled. Please enable it to
        continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html> 

现在,用npm run serve再次运行应用程序。拜访https://localhost:8080User Listing App

现在您的应用程序已经启动并运行,您可以开始对UserList组件进行单元测试了。

Vue.js 组件的单元测试

JavaScript 应用程序的测试框架有很多很多。Jest 是最受欢迎的笑话之一。对于 Vue.js, Vue Test Utils (VTU) 是首选的测试库。这是有道理的,因为 VTU 测试库是建立在 Jest 之上的。它旨在通过提供实用函数来简化 Vue.js 组件的测试。

当您创建这个项目并选择 Jest 作为单元测试解决方案时,Vue CLI 安装了vue-test-utilsjest和其他测试库。还创建了以下目录:

  • 这个目录将包含所有的单元测试。一旦发出测试命令,Jest 将在这里搜索您的单元测试文件。

为应用程序编写测试

在这一节中,我们将为UserList组件编写单元测试。在该组件中,我们希望:

  • 挂载组件并检查它是否可以渲染传递给它的道具。
  • 在组件中查找元素并呈现用户列表。
  • 提交一个表单,然后创建一个新用户并将其添加到现有用户列表中。

首先,转到test/unit文件夹,将example.spec.js文件重命名为user.spec.js。打开文件并用以下内容替换其内容:

import { mount } from "@vue/test-utils";
import UserList from "@/components/UserList.vue";

describe("User List component unit tests: ", () => {
  it("renders props when passed", () => {
    const message = "new message";
    const wrapper = mount(UserList, {
      props: { message },
    });
    expect(wrapper.text()).toMatch(message);
  });

  test("Renders the list", () => {
    const wrapper = mount(UserList);
    const name = "Anna Strong";
    const user = wrapper.get('[data-user="user"]');
    expect(user.text()).toContain(name);
    expect(wrapper.findAll('[data-user="user"]')).toHaveLength(2);
  });

  test("creates a user", async () => {
    const wrapper = mount(UserList);
    const newName = "John Doe";
    await wrapper.get('[id="new-user"]').setValue(newName);
    await wrapper.get('[id="form"]').trigger("submit");
    expect(wrapper.findAll('[data-user="user"]')).toHaveLength(3);
  });
}); 

在这个文件中,我们从vue-test-utils库中导入了一个名为mount的函数来帮助我们挂载UserList组件的一个实例。

我们首先编写一个测试来断言组件可以呈现从父组件传入的道具。接下来,我们将数据属性定位在UserList组件的视图中,确保它包含一个用户的特定名称,并呈现来自users数组的默认长度。

最后,我们创建了一个测试函数来确保可以在组件中创建新用户。

在本地运行测试

要确认定义的测试是否通过,请从终端输入以下命令:

npm run test:unit 

这是终端输出:

> vue-user-app@0.1.0 test:unit
> vue-cli-service test:unit

 PASS  tests/unit/user.spec.js
  User List component unit tests:
    ✓ renders props when passed (33 ms)
    ✓ Renders the list (19 ms)
    ✓ creates a user (27 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.924 s, estimated 1 s
Ran all test suites. 

Terminal test result

在下一节中,您将使用 CircleCI 自动化这个测试。

自动化测试

在本节中,您将为项目的持续集成管道创建一个配置文件。

在项目的根目录下创建一个.circleci文件夹,并在其中创建一个名为config.yml的新文件。添加此内容:

version: 2.1
jobs:
  build-and-test:
    working_directory: ~/project
    docker:
      - image: cimg/node:17.4.0
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install --legacy-peer-deps
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run test for the application
          command: npm run test:unit
workflows:
  build-and-test:
    jobs:
      - build-and-test 

该文件定义了为项目构建和运行测试命令的作业。这份工作:

  • 从 CircleCI Docker 映像注册表中获取cimg/node:17.4.0 Docker 映像
  • 使用映像安装项目的所有依赖项
  • 运行测试

现在配置文件已经设置好了,您需要在 GitHub 上设置一个存储库,并将项目链接到 CircleCI。查看将您的项目推至 GitHub 以获得逐步说明。

在 CircleCI 建立项目

使用链接的 GitHub 帐户登录到您的 CircleCI 帐户,在项目的仪表板上查看您的所有存储库。搜索vue-user-app项目,点击设置项目继续。

Select project

系统将提示您选择一个配置文件。在 repo 中选择.circleci/config.yml文件,并输入存储代码的分支的名称。点击设置项目启动工作流程。

Select config file

这将初始化工作流并为您的项目运行测试。

Successful workflow

结论

在本教程中,我们用 Vue.js 构建了一个列表应用程序,并介绍了为其组件编写单元测试所需的步骤。然后,我们使用 CircleCI 基础设施来配置一个持续集成管道,以自动化测试。

我希望本教程对你有所帮助。点击 GitHub 查看本指南中项目的完整源代码。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

自动化您的 Nuxt.js 应用部署| CircleCI

原文:https://circleci.com/blog/automate-your-nuxt-js-app-deployment/

DevOps 实践包括持续集成和持续部署,是现代软件开发的重要组成部分。 Vue.jsNuxt.js 构建服务器端渲染应用的开发者应该利用这些现代流程来部署他们的应用。本文将向使用 Nuxt.js 的 Vue.js 开发人员展示如何使用 CircleCI 构建、测试和部署他们的应用程序到 GitHub 页面。

先决条件

要跟进这篇文章,你需要一些东西:

创建一个 Nuxt.js 项目并将其连接到 GitHub

第一个任务是创建一个新的 Nuxt.js 应用程序,并将其连接到您的 GitHub 帐户。继续,通过运行以下命令创建一个新的 Nuxt.js 应用程序:

npx create-nuxt-app my-nuxt-app 

接受默认的项目名称和项目描述。然后,交互式项目创建过程继续询问以下问题。选择与本文相应的答案:

  • 使用自定义服务器框架?(答案:)
  • 选择要安装的功能?(回答:点击回车跳过全部)
  • 使用自定义 UI 框架?(答案:)
  • 使用测试框架?(回答: Jest )
  • 选择渲染模式?(答案:万能)
  • 作者?(回答:点击回车选择默认)
  • 选择一个包管理器?(答案: npm )
  • 选择 Nuxt.js 模块?(回答:点击回车跳过全部)
  • 选择林挺工具?(回答:点击回车跳过全部)
  • 选择开发工具?(回答:点击回车跳过全部)

注意: 您的 CLI 显示问题的方式可能不同,但目的是一样的。你会发现把问题和上面的答案匹配起来很容易。

选择完所有这些参数后,create-nuxt-app开始搭建新的 Nuxt.js 应用程序。一旦搭建完成,下一步就是将应用程序连接到 GitHub 存储库。转到您的 GitHub 帐户并创建一个新的存储库。为了便于理解本教程,将 repo 命名为与项目相同的名称,即my-nuxt-app。现在,将项目连接到您刚刚创建的 GitHub repo。转到 Nuxt.js 项目的根目录,运行以下命令进行初始提交:

git add .
git commit -m “First Commit” 

然后通过运行以下命令将项目连接到 repo:

git remote add origin https://github.com/coderonfleek/my-nuxt-app.git
git push -u origin master 

注意: 上述存储库的 GitHub URL 将与您的不同,请用您的 URL 替换它,以便在本地和远程主分支机构之间进行首次推送和设置跟踪。

就这样,您现在已经将您的项目连接到您的 GitHub repo 了。

添加测试

根据您对 Jest 作为测试框架的选择,create-nuxt-app设置测试应用程序所需的一切(根据所需的包和默认测试配置,这些都可以在jest.config.js文件中找到)。测试可以在应用程序根目录下的test文件夹中找到。在test文件夹中,我们看到了一个Logo.spec.js测试文件,它包含了一个对 Logo 组件的简单测试,简单地检查该组件是否是一个 Vue 实例。我们将添加另一个测试来检查组件是否正确呈现。继续用下面的代码替换Logo.spec.js中的代码:

import { mount, shallowMount } from "@vue/test-utils";
import Logo from "@/components/Logo.vue";

const factory = () => {
  return shallowMount(Logo, {});
};

describe("Logo", () => {
  test("is a Vue instance", () => {
    const wrapper = mount(Logo);
    expect(wrapper.isVueInstance()).toBeTruthy();
  });

  test("renders properly", () => {
    const wrapper = factory();
    expect(wrapper.html()).toMatchSnapshot();
  });
}); 

现在,保存该文件,并通过运行以下命令进行测试:

npm run test 

这将显示一个类似于下图的屏幕:

设置构建脚本

为了将服务器端呈现的 Nuxt.js 应用程序部署到 GitHub Pages 之类的静态托管服务,我们需要运行nuxt generate命令,该命令在我们的项目根目录下生成一个dist文件夹,其中包含我们的应用程序的生产版本。这个生成的版本只包含静态文件,这使得它适合静态主机。

现在,关于部署到 GitHub 页面的应用程序,需要认识的一点是,它们的基本 URL 是该应用程序的存储库的名称。例如,GitHub 页面上的my-nuxt-app项目的结果主页应该是 https://[YOUR _ GitHub _ USERNAME]. GitHub . io/my-nuxt-app。

因此,我们需要找到一种方法来指示我们的 Nuxt.js 应用程序在部署到 GitHub 页面时使用适当的路由器库生成发行版。如果不这样做,我们所有的路线都会失败。

我们需要做的第一件事是编写一个专门针对 GitHub 页面部署的nuxt generate脚本。将下面一行作为额外的脚本添加到您的package.json文件的scripts部分。

 scripts : {
    ...
    "generate:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt generate --fail-on-page-error"
} 

generate:gh-pages脚本将DEPLOY_ENV环境变量(我们将在后面引用)设置为GH_PAGES(我们将为 GitHub 页面使用的标识),并且还包含了fail-on-page-error标志,确保如果出现页面错误,构建会失败。

接下来,我们进入nuxt.config.js配置文件并设置我们的路由器基础。就在module.exports行之前,添加以下内容:

const routerBase =
  process.env.DEPLOY_ENV === "GH_PAGES"
    ? {
        router: {
          base: "/my-nuxt-app/"
        }
      }
    : {}; 

在这里,我们创建一个routerBase对象,如果我们部署到 GitHub 页面,它根据变量DEPLOY_ENV的值被设置为一个以 GitHub 页面为目标的路由器基本对象,否则,默认为一个空对象。这将确保为我们的项目适当地设置路由器基础。如果你使用不同的 slug,确保将my-nuxt-app值改为你的回购价。

为了完成我们的配置,在nuxt.config.js文件中的导出对象内添加以下行:

 ...
  module.exports =  {

    ...routerBase,

	/* Other parts of module.exports*/
} 

这将把routerBase对象扩展到配置中。

注意: 您的nuxt.config.js文件可能包含不同的导出格式,格式为:

 export default {
	...
} 

这完全有效,routerBase将包含在导出的对象中,因为它包含在上面的module.exports格式中。

编写 CircleCI 部署脚本

现在来看主要的操作,我们可以设置部署脚本来部署到 GitHub 页面。

这是我们希望我们的脚本实现的目标:

  • 安装必要的软件包
  • 运行测试
  • 构建项目
  • 部署到 GitHub 页面

首先,让我们创建我们的 CircleCI 配置文件。进入项目的根目录,创建一个名为.circleci的文件夹,并在该文件夹中添加一个名为config.yml的文件。

安装必要的软件包

文件夹node_modules没有被推送到项目仓库,所以我们需要为 CircleCI 构建安装必要的包。在config.yml文件中,输入以下配置:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: update-npm
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install-packages
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules 

在上面的配置中,我们创建了一个使用来自 CircleCI 注册表节点 Docker 映像build作业。然后我们检查我们的代码并更新 npm。接下来,我们通过运行npm install命令安装我们的依赖项,并使用save_cache步骤创建我们的node_modules文件夹的缓存。我们还添加了一个restore_cache步骤来恢复我们的缓存资产,以便在上次运行中保存缓存后使用。

运行测试

接下来要添加到配置中的是运行测试的步骤。我们通过添加另一个run步骤来实现,如下所示:

 ...
      - run:
          name: test
          command: npm run test 

这一步运行我们之前在本地运行的npm run test命令,以使用 Jest 运行我们的测试。

构建项目

我们现在继续,通过运行nuxt generate命令和如下所示的另一个步骤来构建我们的项目:

 ...
      - run:
          name: build-project
          command: npm run generate:gh-pages 

注意: 这一步特别调用了generate:gh-pages脚本,该脚本是为部署到 GitHub 页面而设计的。

部署到 GitHub 页面

现在,我们的最终任务是部署到 GitHub 页面。为了保持整洁,我们希望让我们的部署分支远离master分支。该部署分支将作为孤立分支维护,仅用于部署到 GitHub 页面。这可能是一个非常繁琐的过程,但幸运的是,我们有一个名为 gh-pages 的 Node.js 包可以帮助我们实现这一点。这个包将帮助我们将文件推送到回购协议上的一个特殊的gh-pages分支,然后部署到 GitHub 页面。

将以下部署步骤添加到您的config.yml文件中:

 - run:
          name: Install and configure dependencies
          command: |
            npm install gh-pages --save-dev
            git config user.email "fikfikky@gmail.com"
            git config user.name "coderonfleek"
      - run:
          name: Deploy docs to gh-pages branch
          command: './node_modules/.bin/gh-pages --dotfiles --message "[skip ci] Updates" -d dist'
      - store_artifacts:
          path: test-results.xml
          prefix: tests
      - store_test_results:
          path: test-results.xml 

在上面的第一步中,我们安装了gh-pages包,并在我们的容器中为 GitHub 设置了git配置。请确保将user.emailuser.name的值更改为您的 GitHub 帐户详细信息。

在下一步中,我们使用本地安装的gh-pages包将前面构建步骤中生成的dist文件夹中的所有内容发布到我们的gh-pages分支。

注意,我们在gh-pages命令中添加了一些参数。我们提供了一个包含[skip ci]的特殊提交消息(通过–message 传递)。这指示 CircleCI 在我们将这些内容推送到gh-pages分支时不要启动新的构建。我们还添加了--dotfiles,这样gh-pages命令将会忽略所有的点文件。

完整的配置文件如下所示:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: update-npm
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install-packages
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: test
          command: npm run test
      - run:
          name: build-project
          command: npm run generate:gh-pages
      - run:
          name: Install and configure dependencies
          command: |
            npm install gh-pages --save-dev
            git config user.email "fikfikky@gmail.com"
            git config user.name "coderonfleek"
      - run:
          name: Deploy docs to gh-pages branch
          command: './node_modules/.bin/gh-pages --dotfiles --message "[skip ci] Updates" -d dist'
      - store_artifacts:
          path: test-results.xml
          prefix: tests
      - store_test_results:
          path: test-results.xml 

提交您的更改并将它们推送到主分支。

我们的第一次部署

现在,让我们对部署进行一次测试。

首先,我们需要将我们的项目连接到 CircleCI。因此,请转到您的 CircleCI 仪表板,在添加项目部分添加项目。在你的项目旁边,点击设置项目

这将带您进入一个类似于下图的页面:

点击开始构建开始构建您的项目。

CircleCI 将运行您的配置来构建、测试和部署您的项目。

你应该已经注意到构建失败了!

为什么会这样?如果您单击 process 选项卡并检查构建过程的每一步,您会注意到部署在gh-pages将我们的文件推送到部署分支的时候失败了。

好吧,我坦白。我知道它会失败。我很抱歉。😃

失败的原因是我们与 GitHub 的连接,需要推送我们的文件,没有被认证。

要建立到 GitHub 的认证连接,我们需要一个私有/公共 SSH 密钥对形式的部署密钥,它保存在我们的 GitHub 帐户中,并被授予写访问权限。

设置 GitHub 的认证

要在本地机器上创建 SSH 密钥对,只需运行以下命令:

ssh-keygen -t rsa -b 4096 -C “YOUR_GITHUB_EMAIL” 

确保将YOUR_GITHUB_EMAIL替换为您 GitHub 帐户的电子邮件地址。

选择一个像my_deploy_key这样的目的地名称,并接受默认的无密码,在提示符下点击输入

这应该会在选定的目的地自动为您生成一个名为my_deploy_key(私钥)和my_deploy_key.pub(公钥)的公钥/私钥对。

私钥保存在 CircleCI 中。进入你的 CircleCI Pipelines 页面,点击带有 cog 图标的按钮进入设置页面。

在设置页面的侧菜单上,向下滚动并点击 SSH 权限:

然后点击添加 SSH 密钥添加新密钥:

在主机名字段中,只需输入“github.com”并将您的私钥内容粘贴到私钥字段中,然后单击添加 SSH 密钥来添加密钥。

在文档的中了解更多关于添加您的私钥到 CircleCI 的信息。

添加后,您将看到新添加的密钥的指纹。CircleCI 在其脚本中使用指纹来识别私钥,因为这比公开实际的私钥更安全。

我们还需要确保“高级设置”页面上的“将秘密传递给来自分叉拉取请求的构建”选项被设置为关闭。在我们的项目设置页面的侧边菜单上可以找到高级设置链接。

为了完成我们的安全检查,请确保从本地系统中删除私钥。

接下来我们需要做的是将我们的公钥提交到我们的 GitHub 帐户。为此,请转到 GitHub 项目 repo 的“设置”选项卡。然后点击侧面菜单上的部署键。在部署密钥页面,点击添加部署密钥,出现如下表单。

在 Title 字段中,为它取一个方便的名称,例如“My CircleCI Deployment Key”。在密钥字段中,粘贴您的公钥(my_deploy_key.pub)的内容,并选中允许写访问复选框。然后点击添加键

GitHub 可能会要求您确认您的密码,确认后您会看到您的密钥显示在部署密钥列表中。

我们需要做的最后一件事是将来自 CircleCI 的私钥的指纹添加到我们的配置文件中。我们的部署步骤现在如下所示:

 - run:
          name: Install and configure dependencies
          command: |
            npm install gh-pages --save-dev
            git config user.email "fikfikky@gmail.com"
            git config user.name "coderonfleek"
      - add_ssh_keys:
          fingerprints:
            - "3a:9b:c5:67:6f:06:50:55:dd:67:c9:ed:0c:9e:1f:fa"
      - run:
          name: Deploy docs to gh-pages branch
          command: './node_modules/.bin/gh-pages --dotfiles --message "[skip ci] Updates" -d dist'
      - store_artifacts:
          path: test-results.xml
          prefix: tests
      - store_test_results:
          path: test-results.xml 

注意: 把这里用的指纹改成自己的指纹。

整个配置文件现在应该如下所示:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: update-npm
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install-packages
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: test
          command: npm run test
      - run:
          name: build-project
          command: npm run generate:gh-pages
      - run:
          name: Install and configure dependencies
          command: |
            npm install gh-pages --save-dev
            git config user.email "fikfikky@gmail.com"
            git config user.name "coderonfleek"
      - add_ssh_keys:
          fingerprints:
            - "3a:9b:c5:67:6f:06:50:55:dd:67:c9:ed:0c:9e:1f:fa"
      - run:
          name: Deploy docs to gh-pages branch
          command: './node_modules/.bin/gh-pages --dotfiles --message "[skip ci] Updates" -d dist'
      - store_artifacts:
          path: test-results.xml
          prefix: tests
      - store_test_results:
          path: test-results.xml 

成功部署我们的 Nuxt.js 应用程序

现在,让我们提交我们的更改并再次推送到主分支来触发部署。

一旦你推,一个新的构建过程将被触发,这一次将会成功。

单击 deployment 选项卡并检查 test 部分。你会注意到我们所有的测试都运行良好。

要确认我们的应用程序已部署,请访问 URL https://[YOUR _ GITHUB _ USERNAME]. GITHUB . io/my-nuxt-app 并查看已部署的应用程序。您应该会看到默认的 Nuxt.js 索引页面,如下所示:

结论

在本文中,我们看到了如何使用持续集成和持续交付来成功地将 Nuxt.js 应用程序部署到 GitHub 页面。有了 CircleCI 详尽的配置选项列表,我们可以微调我们的配置,以满足我们的特定需求,并部署到其他托管服务。

我希望你在这个项目上和我一样开心。快乐编码:)


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

自动测试| circleci

原文:https://circleci.com/blog/automatic-testing-for-laravel-projects/

本教程涵盖:

  1. 克隆样本 Laravel 应用程序
  2. 将 Laravel 测试添加到配置文件中
  3. 自动化测试

Laravel 附带了一个强大的测试套件,允许开发人员在浏览器中执行单元测试、测试 API 端点和运行自动化功能测试。

在这篇文章中,我们将把 Laravel 框架中可用的测试过程添加到我们的 CircleCI 配置文件中,以便在每次推出新代码时自动执行这些测试。

先决条件

要跟进这篇文章,你需要做几件事:

  • PHP > = 7.1 安装在你的系统上。您可以通过在终端上运行命令php -v来确认这一点。
  • 作曲全球安装。通过在终端上运行composer命令来确认这一点。
  • 一个 GitHub 账户。
  • 一个 CircleCI 账户

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

一旦你有了这些并开始运行,你就可以开始跟进了。

克隆示例项目

首先,克隆这个 Laravel 项目。它已经包含了一些我们将用 CircleCI 自动化的测试。

git clone https://github.com/yemiwebby/laravel-api-test 

一旦您克隆了项目,创建一个新的 GitHub 存储库,并将示例项目推送到其中。要熟悉您刚刚克隆的项目,请通过运行以下命令来安装项目的依赖项:

cd laravel-api-test

composer install 

接下来,在项目的根目录下创建一个名为.env的文件,并将.env.example的内容复制到其中(如果您使用的是 Linux,只需运行cp .env.example .env)。这个文件应该在你的.gitignore文件中被忽略(这已经在克隆项目的.gitignore中被忽略了)。

运行以下命令为 Laravel 项目生成应用程序密钥:

php artisan key:generate 

现在,通过运行以下命令在浏览器中运行项目:

php artisan serve 

通过访问 URL http://127.0.0.1:8000,在浏览器中进入项目主页。

Laravel app homepage

这是一个简单的主页,类似于每个新的 Laravel 项目的默认页面。在这种情况下,页面包含一个链接,上面写着点击我。点击后,链接会将您重定向到http://127.0.0.1:8000/myresponse

/myresponse路线显示以下是我的回应

Laravel App Response Page

我们稍后将测试这种行为。

该项目还包含一个.env.testing环境文件,仅用于我们的测试用例。应用程序测试可以在tests文件夹中找到。

tests文件夹中设置了三种类型的测试。一个单元测试文件在Unit文件夹(ExampleUnitTest.php),另一个包含 HTTP 测试的文件在Feature文件夹(ExampleHttpTest.php),最后一个浏览器测试文件在Browser文件夹(ExampleBrowserTest.php)。

在本文中,我们将通过将这些测试添加到我们的 CircleCI 配置中来实现自动化。

向 CircleCI 配置中添加单元测试

添加到 CircleCI 配置中的第一组测试是包含在tests/Unit/ExampleUnitTest.php文件中的单元测试。

这个文件包含两个测试用例:

public function testBasicTest()
{
    $this->assertTrue(true);
}

public function testUserCreation()
{
    $user = new User([
        'name' => "Test User",
        'email' => "test@mail.com",
        'password' => bcrypt("testpassword")
    ]);   

    $this->assertEquals('Test User', $user->name);
} 

默认情况下,任何 Laravel 项目都会进行第一次测试。它断言布尔值true

第二个测试用User模型创建了一个新的用户实例。然后,它根据预期值检查新创建的用户实例的名称,以断言匹配。

您可以在每次推送到您的存储库时自动运行这个测试。

进入项目的根目录,创建一个名为.circleci的文件夹。在这个文件夹中,创建一个名为config.yml的文件。

下面是自动化单元测试需要做的事情:

  1. 启动所需的环境
  2. 使用 composer 安装依赖项
  3. 缓存依赖关系
  4. 为项目建立一个.env环境文件
  5. 运行单元测试

将这段代码粘贴到 config.yml 文件中:

version: 2.1
orbs:
  browser-tools: circleci/browser-tools@1.1
jobs:
  build:
    docker:
      # Specify the version you desire here
      - image: cimg/php:7.4.14-browsers

    steps:
      - browser-tools/install-browser-tools    
      - checkout

      - run:
          name: "Prepare Environment"
          command: |
            sudo apt update
            sudo docker-php-ext-install zip

      - run:
          name: "Create Environment file"
          command: |
            mv .env.testing .env

      # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

      - run:
          name: "Generate App key"
          command: php artisan key:generate

      # run tests with phpunit
      - run:
          name: "Run Unit Tests"
          command: ./vendor/bin/phpunit tests/Unit 

这个配置获取一个 PHP 映像,并将您的代码签出到环境中。环境文件从.env.testing开始创建。

jobs:
  build:
    docker:
      # Specify the version you desire here
      - image: circleci/php:7.4-node-browsers

    steps:
      - checkout

      - run:
          name: "Prepare Environment"
          command: |
            sudo apt update
            sudo docker-php-ext-install zip

      - run:
          name: "Create Environment file"
          command: |
            mv .env.testing .env 

artisan命令安装依赖项。现在您可以生成 Laravel 所需的应用程序密钥。

 # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

      - run:
          name: "Generate App key"
          command: php artisan key:generate 

准备好所有需要的资产后,运行tests/Unit文件夹中的单元测试。

 # run tests with phpunit
      - run:
          name: "Run Unit Tests"
          command: ./vendor/bin/phpunit tests/Unit 

很好。

现在将你的更新推送到你的 GitHub 库

将 API 项目连接到 CircleCI

你的下一个任务是在 CircleCI 建立项目。去你的 CircleCI 控制台。如果您已经注册了 GitHub 帐户,那么您的所有存储库都可以在项目的仪表板上看到。

接下来,找到你的laravel-api-test项目,点击设置项目

Select project

系统将提示您编写新的配置文件,或者在项目中使用现有的配置文件。选择现有选项。在 GitHub 上输入您的代码所在的分支的名称,然后点击设置项目

Select main branch

点击开始建造

Pipeline Successful build 1

向 CircleCI 配置添加 http 测试

下一个要自动化的任务是 http 测试。该项目包含一个 API 路径,该路径包含在routes/api.php文件中。

Route::post('createuser', 'UserController@createUser'); 

注意: 别忘了加上导入use App\Http\Controllers\UserController

这条路线映射到UserController控制器中的一个createUser方法。该方法在数据库中创建一个新用户,并返回一条成功消息:“成功创建用户”。它还返回状态代码:201(Created)

已经为此端点编写了一个测试,以断言成功创建用户的状态代码和成功消息。该测试包含在文件tests/Feature/ExampleHttpTest.php中。

public function testUserCreationEndpointTest()
{
    $name = $this->faker->name();
    $email = $this->faker->email();
    $password = "mypassword";

    $response = $this->postJson('/api/createuser', [
        'name' => $name, 
        'email' => $email,
        'password' => $password,
        'password_confirmation' => $password
    ]); 

    $response
        ->assertStatus(201)
        ->assertExactJson([
            'message' => "Successfully created user!",
        ]);
} 

这个测试通过调用createuser端点来创建一个新的虚拟用户。然后,它检查响应代码和响应体,看它是否与预期的相匹配。

现在,您可以在 CircleCI 配置中自动化这个测试。以下是步骤:

  1. 在数据库文件夹中创建一个名为database.sqlite的 sqlite 数据库。.env.testing使用 sqlite 数据库进行测试。config/database.php中的 sqlite 配置也默认使用这个数据库。
  2. 运行迁移
  3. 用 PHPUnit 运行 Http 测试

将这些步骤添加到 CircleCI 配置文件中:

 - run:
        name: "Install Sqlite"
        command: sudo apt-get install php7.4-sqlite3      

    - run:
        name: "Create database and run migration"
        command: |
          touch database/database.sqlite
          php artisan migrate --env=testing

    - run:
        name: "Run Http Tests"
        command: ./vendor/bin/phpunit tests/Feature 

这些命令安装 Sqlite,引用本教程的特定版本(7.4),然后在database文件夹中创建数据库文件。然后,他们运行迁移,指定测试环境。

在第二步中,运行包含在tests/Feature文件夹中的 http 测试。

保存文件并将您的更改推送到您的存储库中。这应该会触发 CI/CD 管道,并且测试应该会成功运行。

Pipeline successful 2

向 CircleCI 配置添加浏览器测试

您将添加到配置中的最后一组测试是浏览器测试。

Laravel 使用 laravel/dusk 包直接在浏览器中运行测试。默认情况下,laravel/dusk 使用谷歌 Chrome 和一个独立的 ChromeDriver 安装来运行你的浏览器测试。如果您设置了自己的 Selenium 服务器,则可以使用其他浏览器。

Chrome 驱动已经和 laravel/dusk 包一起安装了,所以你不需要担心单独安装它。

默认情况下,当运行dusk命令启动浏览器测试时,laravel/dusk 会尝试启动 Chrome 驱动程序。对于本教程,我们需要手动启动驱动程序来完全控制这个过程。注释掉tests/DuskTestCase.php中自动启动 Chrome 浏览器的那一行:

public static function prepare()
{
    // static::startChromeDriver();
} 

这已经在克隆的项目中完成了,所以您不需要自己去做。

默认情况下,新的 Laravel 项目不会附带DuskTestCase.php文件和tests/Browser目录。您可以在安装 laravel/dusk 时通过运行以下命令来创建它们:

composer require --dev laravel/dusk
php artisan dusk:install 

关于 laravel/dusk 及其操作的更多细节可以在该包的 Laravel 文档页面中找到。

我们已经知道我们的应用程序有一个主页,主页上有一个标记为 Click Me 的链接。当单击它时,它会重定向到一个显示字符串“这是我的响应”的路由。

tests/Browser文件夹中的ExampleBrowserTest.php浏览器测试文件中,有一个测试断言了这个行为。它还会检查大 Laravel 标签是否能在你的主页上找到。

public function testBasicExample()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
                ->assertSee('Laravel');
    });
}

public function testLink()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
                ->clickLink('Click Me')->assertSee("Here is my response");
    });
} 

要运行您的浏览器测试,请将其添加到您的 CircleCI 配置中:

  1. 安装 laravel/dusk 包。
  2. 安装 staudenmeir/dusk-updater 软件包。这个包将你的 Chrome 驱动更新到 Docker 镜像上的 Chrome 浏览器版本。
  3. 为您的映像启动 Chrome 驱动程序(在本例中是 Linux)。
  4. 为应用服务。
  5. 运行浏览器测试。

将这些步骤添加到 CircleCI 配置中:

 - run:
        name: "Install Dusk and Update Chrome Drivers"
        command: |
          composer require --dev laravel/dusk
          composer require --dev staudenmeir/dusk-updater
          php artisan dusk:update --detect

    - run:
        name: Start Chrome Driver
        command: ./vendor/laravel/dusk/bin/chromedriver-linux
        background: true

    - run:
        name: Run Laravel Server
        command: php artisan serve
        background: true

    - run:
        name: Run Browser Tests Tests
        command: php artisan dusk 

这个片段从安装 laravel/dusk 开始。然后安装 staudenmeir/dusk-updater 包,让你可以更新我们的 chrome 驱动程序。

接下来,它在后台启动 Chrome 驱动程序和 Laravel 应用服务器,确保它们在后台运行。background: true被添加到两个步骤中。

最后一步是使用dusk命令运行浏览器测试。

以下是完整的config.yml文件:

version: 2.1
orbs:
  browser-tools: circleci/browser-tools@1.1
jobs:
  build:
    docker:
      # Specify the version you desire here
      - image: cimg/php:7.4.14-browsers

    steps:
      - browser-tools/install-browser-tools    
      - checkout

      - run:
          name: "Create Environment file"
          command: |
            mv .env.testing .env

      # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

      - run:
          name: "Generate App key"
          command: php artisan key:generate

      # run tests with phpunit
      - run:
          name: "Run Unit Tests"
          command: ./vendor/bin/phpunit tests/Unit

      - run:
          name: "Install sqlite"
          command: sudo apt-get install php7.4-sqlite3

      - run:
          name: "Create database and run migration"
          command: |
            touch database/database.sqlite
            php artisan migrate --env=testing

      - run:
          name: "Run Http Tests"
          command: ./vendor/bin/phpunit tests/Feature          

      - run:
          name: "Install Dusk and Update Chrome Drivers"
          command: |
            composer require --dev laravel/dusk
            composer require --dev staudenmeir/dusk-updater
            php artisan dusk:update --detect

      - run:
          name: Start Chrome Driver
          command: ./vendor/laravel/dusk/bin/chromedriver-linux
          background: true

      - run:
          name: Run Laravel Server
          command: php artisan serve
          background: true

      - run:
          name: Run Browser Tests Tests
          command: php artisan dusk 

准备好之后,将您的更改推送到存储库。测试将成功运行。

Pipeline successful 3

注意到启动 Chrome 驱动运行 Laravel 服务器旁边的圆形图标是“灰色的”。这表明这些进程正在配置中指定的后台运行。

结论

在本文中,您了解了如何将不同类型的 Laravel 测试添加到您的 CircleCI 配置中,以及 CircleCI 如何使运行这些测试变得轻而易举。如果您的一些测试没有成功运行,请尝试再次浏览 post,看看您是否错过了任何步骤。我相信你能找到丢失的部分。完整的源代码可以在 GitHub 的这里找到。

祝你度过一个没有 bug 的编码周。



Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

Symfony 应用的自动测试| CircleCI

原文:https://circleci.com/blog/automatic-testing-for-symfony-applications/

必须实现新的 web 或移动应用程序功能是不可避免的,而且通常非常重要。这些新代码对构建无错误应用程序的可能性构成了威胁,如果不小心的话,可能会破坏已经实现的功能。构建更好、更可靠的应用程序的方法之一是使用单元测试和功能测试来测试您的代码。

测试是推荐的最佳实践,因为它可以确保新版本满足质量和性能目标。不管应用程序是用什么编程语言或框架构建的,测试驱动的开发方法都是相似的。

在本教程中,我们将创建一个新的 Symfony 应用程序项目来管理客户详细信息。虽然这不是一个具有完整 CRUD 功能的应用程序,但我们将创建两个方法和一个控制器,然后引入单元测试和功能测试,以确保每个函数都按照预期返回适当的响应。最重要的是,我们将通过利用 CircleCI 来自动化我们的测试。

先决条件

为了最大限度地利用这篇文章,请确保您:

入门指南

首先使用 Composer 创建一个新的 Symfony 应用程序,使用以下命令:

composer create-project symfony/website-skeleton new-symfony-app 

前面的命令将在运行该命令的根文件夹中创建一个名为new-symfony-app的新文件夹。它还将安装所有必需的依赖项。

接下来,进入新创建的项目,安装一个 web 服务器来运行应用程序:

// move into project
cd new-symfony-app

// installl web server
composer require symfony/web-server-bundle --dev ^4.4.2 

现在您已经建立了一个新项目,接下来在本地用 Git 初始化该项目,并将其与 GitHub 连接起来。但在此之前,请前往 GitHub 并为您的项目创建一个资源库。

GitHub create new repository

在 GitHub 上可以随意使用相同的项目名称。现在,在您的项目文件夹中本地初始化 Git,使用:

git init 

这将把项目设置为 Git 存储库。接下来,使用以下命令在本地提交您的更改:

git add .

git commit -m "Initial commit" 

在项目文件夹内的终端上使用git remote add命令注册远程存储库。该命令有两个参数:

  • 远程名称,例如origin
  • 远程 URL,例如https://github.com/<your_username>/<your_repo>.git

就我而言:

// add remote origin
git remote add origin https://github.com/yemiwebby/new-symfony-app.git 

接下来,使用以下命令将本地项目推送到在 GitHub 帐户上创建的主分支:

// push to the repo
git push -u origin master 

现在我们已经建立了一个新的 Symfony 项目,并将其推送到远程存储库,在下一节中,我们将为客户创建一个控制器和一个实体类。

创建控制器

控制器负责处理 HTTP 请求并返回适当的响应。要为该应用程序自动生成控制器,请通过运行以下命令使用 Symfony 附带的 maker bundle :

php bin/console make:controller CustomerController 

这将为您创建两个新文件:

  • 位于src/Controller/CustomerController.php的控制器
  • templates/customer/index.html.twig中查看页面

打开CustomerController.php文件,将内容替换为:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class CustomerController extends AbstractController
{
    /**
     * @Route("/", name="customer", methods={"GET"})
     */
    public function index()
    {
        $customerName  = "John Doe";
        return $this->render('customer/index.html.twig', [
            'name' => $customerName,
        ]);
    }
} 

Symfony 在为应用程序定义路由时,将路由注释作为最首选的选项。这里,我们定义了一条名为customer的路由/。这将指示 Symfony 将任何对主页的请求指向此处定义的方法。因此,当用户的请求到达这个路径时,index()将呈现一个视图,该视图将在上面的文件中包含一个硬编码的$customerName

接下来,打开位于templates/customer/index.html.twig的视图文件,将内容替换为以下内容:

{% extends 'base.html.twig' %}
{% block title %} Customer Page {% endblock %}
{% block body %}
<div class="example-wrapper">
  <h1>Hello, Welcome to the customer page</h1>

  <h2>Customer name is : {{ name }}</h2>
</div>
{% endblock %} 

我们更新了视图,以动态呈现代表客户默认姓名的name变量。您可以使用以下命令在本地运行该应用程序:

php bin/console server:run 

现在,导航到 http://localhost:8000 查看主页。

Application homepage

该页面在控制器中呈现硬编码的客户姓名,并将其传递给视图。这是一个类似于在 MVC 结构的应用程序视图中动态呈现内容的过程。我们稍后将为这种行为编写一个功能测试。

创建实体

这里,我们将创建一个模型来表示一个Customer对象。为此,运行以下命令:

php bin/console make:entity Customer 

系统会提示您向src/Entity/Customer.php文件添加一些字段。如果您愿意,可以手动执行此操作。否则,输入firstNamelastName作为Customer类的字段,Symfony MakerBundle 将处理其余部分。查看下图。

Creating entity using makerbundle

为了确认,下面是Customer.php文件的样子。更新您的以匹配此处显示的内容:

<?php

namespace App\Entity;

use App\Repository\CustomerRepository;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass=CustomerRepository::class)
 */
class Customer
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $firstName;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $lastName;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getFirstName(): ?string
    {
        return $this->firstName;
    }

    public function setFirstName(string $firstName): self
    {
        $this->firstName = $firstName;

        return $this;
    }

    public function getLastName(): ?string
    {
        return $this->lastName;
    }

    public function setLastName(string $lastName): self
    {
        $this->lastName = $lastName;

        return $this;
    }

    public function getCustomerFullName(): string
    {
        return $this->getFirstName() . '' . $this->getLastName();
    }
} 

创建的getterssetters是 Symfony 自动生成的。我们包含了一个新方法来返回名为getCustomerFullName()的客户的全名。在本教程的后面部分,我们将为这里定义的单个函数编写单元测试。

使用 PHPUnit 创建测试

Symfony 与 PHP 测试框架 PHPUnit 集成。在编写任何测试之前,通过运行以下命令来执行 PHPUnit:

./bin/phpunit 

因为我们是第一次运行上面的命令,它将为我们的应用程序安装 PHPUnit 及其所有必需的依赖项。

编写单元测试

根据定义,单元测试通常被称为对代码的单个组件(单元)的测试。为 Symfony 应用程序编写单元测试类似于标准的 PHPUnit 单元测试。这里,我们将为在客户实体文件中创建的方法编写测试。

首先,在tests文件夹中创建一个名为Entity的新文件夹。在新创建的文件夹中,创建一个新文件并命名为CustomerTest.php。打开文件,并使用以下内容:

<?php
namespace App\Tests\Entity;

use App\Entity\Customer;
use PHPUnit\Framework\TestCase;

class CustomerTest extends TestCase
{
    public function testSettingCustomerFirstName()
    {
        $customer = new Customer();
        $firstName = "John";

        $customer->setFirstName($firstName);

        $this->assertEquals($firstName, $customer->getFirstName());
    }

    public function testSettingCustomerLastName()
    {
        $customer = new Customer();
        $lastName = "Doe";

        $customer->setLastName($lastName);

        $this->assertEquals($lastName, $customer->getLastName());
    }

    public function testReturnsCustomerFullName()
    {
        $customer = new Customer();
        $customer->setFirstName("John");
        $customer->setLastName("Deo");

        $fullName = $customer->getFirstName() . '' . $customer->getLastName();

        $this->assertSame($fullName, $customer->getCustomerFullName());
    }
} 

在上面的文件中,我们编写了三个不同的测试:

  • testSettingCustomerFirstName():这里我们创建了一个Customer类的实例,并设置了一个表示客户名字的虚拟值。然后,我们继续使用 PHPUnit 的断言方法来确认名称。
  • testSettingCustomerLastName():与这里定义的第一个方法类似,我们测试了为客户姓氏创建的 getter 和 setter 方法。
  • testReturnsCustomerFullName():最后,这个测试断言客户的firstNamelastName都可以被成功检索。

在本地运行测试

之前我们从终端使用./bin/phpunit命令运行了我们的测试。虽然这仍然是相关的和适当的,我们将稍微重组一下,并通过将它添加到composer.json文件的scripts部分来自动化这个命令。打开composer.json,将测试命令添加到scripts部分:

{
    ...
    "scripts": {
        ...,
        "test": [
            "./bin/phpunit"
        ]
    }
} 

此后,测试命令将作为composer test可用。

Run test unit

在我们将项目添加到 CircleCI 之前,让我们也编写一个非常简单的功能测试。

编写功能测试

通过功能测试,你可以命令浏览器浏览你的网站,点击一些链接,填写表格,并断言它在页面上看到的东西。首先,在tests文件夹中创建另一个文件夹并命名为Controller,它将存放CustomerController的测试脚本。继续在新创建的文件夹中创建一个新文件,并将其命名为CustomerControllerTest.php。打开文件,并使用以下内容:

 <?php

namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class CustomerControllerTest extends WebTestCase
{
    public function testShowCustomer()
    {
        $client = static::createClient();

        $client->request('GET', '/');

        $this->assertResponseStatusCodeSame(200, $client->getResponse()->getStatusCode());

        $this->assertStringContainsString('Customer name is : John Doe', $client->getResponse()->getContent());
    }
} 

在上面的代码片段中,我们验证了 HTTP 响应是成功的,因为它返回了状态代码200。接下来,我们验证了页面包含了本教程前面显示的预期内容。

您可以使用以下命令再次运行测试:

composer test 

Run test functional

用 CircleCI 自动化测试

既然我们所有的测试都在本地被证明是成功的,我们需要确保一旦我们将代码的更新推送到 GitHub 库,测试就会自动运行。

要创建 CircleCI 将使用的配置文件,导航到项目的根目录,并创建一个名为.circleci的文件夹。在这个文件夹中,创建一个名为config.yml的文件,并使用以下内容:

version: 2
jobs:
  build:
    docker:
      # Specify the version you desire here
      - image: circleci/php:7.4-node-browsers

    steps:
      - checkout

      - run: sudo apt update # PHP CircleCI 2.0 Configuration File# PHP CircleCI 2.0 Configuration File sudo apt install zlib1g-dev libsqlite3-dev
      - run: sudo docker-php-ext-install zip

      # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

      # run tests with phpunit
      - run:
          name: "Run tests"
          command: composer test 

我们在这里所做的是指定 PHP 的版本,该版本需要作为 Docker 映像的一部分进行安装,该映像将从 CircleCI image registry 中获取。接下来,我们指定为我们的项目安装所有的依赖项,然后最后包含一个运行测试的命令。

现在,您可以提交到目前为止所做的所有更改,并将项目推送到 GitHub 。

在 CircleCI 上建立项目

这里我们将把我们的 Symfony 应用程序连接到 CircleCI。导航到 CircleCI 网站并使用您的帐户登录。在 CircleCI 控制台上,进入添加项目页面,点击设置项目

CircleCI project dashboard

这将显示下面的页面。

CircleCI project configuration page

现在,点击开始构建,您将看到一个提示,要求您将提供的 CircleCI 配置添加到新分支上的项目中,或者手动设置配置。继续选择手动添加

Start building prompt

最后,点击开始构建,因为在推送到 GitHub 之前,我们已经在项目中包含了配置文件。这将基于配置文件运行一个持续集成 (CI)管道,并向您显示管道状态。

它在跑。

Build running

这是成功运行后的情况。

Build successful

Build results

这就是了。所有创建的测试都正常运行。

结论

Symfony 被认为是最古老、最健壮的 PHP 框架之一,以其优雅的结构和可重用的组件而闻名。在本教程中,我们创建了一个新的 Symfony 应用程序,并学习了如何利用 PHPUnit 为我们定义的一些方法和控制器编写单元和功能测试。然后,我们继续使用 CircleCI 设置自动化 CI 管道。

从这篇文章中获得的知识不会让你成为测试专家,除非你个人努力去获得更多的知识,因为我们在这里仅仅触及了表面。然而,你在这篇文章中学到的东西将会给你在典型的 Symfony 应用程序中如何构造测试以及如何自动化测试的基本知识。在 GitHub 上随意探索源代码并查看 CircleCI 的官方文档以了解更多信息。


Oluyemi 是一个技术爱好者、编程狂和热爱新技术的网络开发迷。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

Firebase Hosting -部署到 Firebase Hosting -部署 Gatsby 站点| CircleCI

原文:https://circleci.com/blog/automatically-deploy-a-gatsby-site-to-firebase-hosting/

Firebase Hosting 是 Google 的一个 web 应用托管平台。通过这项服务,你可以在谷歌的基础设施上托管你的网络应用。它支持简单的一步部署,并具有其他很酷的功能,如从 cdn 快速托管和回滚。在 Firebase 托管文档中有一个很好的服务概述。

Gatsby 是一个框架,可以让你快速创建基于 React 的应用和网站。它允许你用从各种来源获取的数据建立这些网站,包括降价文件、API,甚至 CMS。然后,它将这些数据与基于 React 的前端架构相结合,构建速度极快的交互式网站。Gatsby 将 web 应用程序编译成优化的静态文件,我们将把这些文件部署到 Firebase 主机上。我觉得很神奇,很高兴和大家分享!

在本文中,我们将设置一个简单的 Gatsby 站点,将代码托管在 GitHub 存储库中,并使用 CircleCI 将 web 应用程序自动部署到 Firebase 主机。

先决条件

为了完成本教程,您需要安装以下软件:

  1. Node.js

注意:你还需要有一个谷歌账户才能使用 Firebase 主机。

为什么是盖茨比?

我选择盖茨比只是因为它能让我们专注于高层次的细节。例如,我们不会从头开始构建页面、找出路径、添加 404 页面等等,而是将所有这些都内置到我们即将生成的 starter 项目中。Gatsby 为我们提供了这些现成的优势,但托管的概念仍然适用于任何其他类型的 web 应用程序,这些应用程序可以编译为静态文件,包括 Vue 和 Angular 应用程序,甚至是由静态站点生成器生成的网站。

Gatsby 项目设置

首先,我们需要在本地开发环境中安装 Gatsby。我们可以通过运行以下命令来实现:

npm install --global gatsby-cli 

安装完成后,我们将可以使用gatsby命令。现在,让我们使用 Gatsby CLI 来生成一个新站点:

gatsby new gatsby-site 

接下来,我们需要将目录更改为新创建的gatsby-site文件夹:

cd gatsby-site 

最后,我们可以通过启动开发服务器来浏览我们生成的站点:

gatsby develop 

您的新网站现在可以在http://localhost:8000访问。

如果一切运行成功,您现在就有了一个在本地运行的 Gatsby 站点。继续探索这个网站。看起来是这样的:

Gatsby starter page

如果您浏览一下生成的文件,您会发现 Gatsby 的文件夹结构很容易理解。例如,主页的代码可以在src/pages/index.js中找到。还要注意,不同页面之间的链接工作正常,我们还设置了一个 404 页面。你可以通过去一个不存在的路线来测试 404 页面。

Gatsby 提供了这些底层细节,比如路由,并为我们提供了一个功能性的 web 应用程序,我们现在可以将它部署到 Firebase 主机上。

推送至 GitHub

此时,让我们初始化一个新的 Git 存储库,并将代码推送到 GitHub。继续在gatsby-site文件夹中初始化一个新的 Git 存储库,并使用以下代码行创建一个初始提交:

git init
git add -all
git commit -m "Generate Gatsby site" 

之后,继续在 GitHub 上创建一个新的存储库,并将代码推送到存储库。

如果你不熟悉 GitHub,这个指南是一个很好的参考资料。

Firebase 设置

现在,我们有了一个功能性的网站,可以部署到 Firebase 主机上了。在此之前,我们需要使用以下三个简单的步骤在 Firebase 上创建一个新项目:

  • 在弹出的模式中给你的项目命名,然后点击创建项目

一旦创建了项目,我们需要在本地设置 Firebase,以便将我们的本地存储库链接到 Firebase 项目。通过运行以下命令安装 Firebase 命令行工具:

npm install -g firebase-tools 

我们还需要在本地将firebase-tools包作为devDependency安装到我们的项目中。这将在以后与 CircleCI 集成时派上用场,circle ci 默认不允许全局安装包。所以让我们现在就安装它:

npm install -D firebase-tools 

之后,我们需要登录 Firebase,将 CLI 连接到在线 Firebase 帐户。我们可以通过运行以下命令来实现:

firebase login 

一旦您登录,我们现在可以初始化我们的项目:

firebase init 

此操作将产生此提示,我们将在其中选择托管的:

Firebase init prompt

对于其余的提示,选择如下一个屏幕截图所示的选项:

提示完成后,Firebase CLI 会生成两个文件:

  • .firebaserc
  • firebase.json

注:firebase.json文件支持配置自定义托管行为。要了解这方面的更多信息,请访问 Firebase 全配置文档。

如果 Firebase CLI 没有加载您的项目,您可以在生成的.firebaserc文件中手动添加项目 ID:

{
  "projects": {
    "default": "gatsby-site-43ac5"
  }
} 

这也是将新文件提交到我们的存储库并将代码推送到 GitHub 的好时机。

这样,我们就将代码连接到了 Firebase 项目,现在我们可以在开发环境中尝试手动部署了。

手动部署到 Firebase

手动部署的第一步是生成优化的生产版本。在我们的例子中,gatsby包含了我们,因为它默认包含了这个。要生成它,请运行以下命令:

gatsby build 

这将在public目录中生成一个优化的静态站点。这是我们将部署到 Firebase 主机的目录。要手动将public目录部署到 Firebase 主机,只需要一个命令:

firebase deploy 

如果一切按预期运行,Firebase 将部署我们的站点,并给我们一个已部署站点的 URL 链接。

您还会注意到 Firebase 创建了一个新的.firebase文件夹来存储它的缓存。由于我们不希望这个文件夹出现在我们的存储库中,我们可以将文件夹名添加到.gitignore文件中,这样 Git 就会忽略它。

在下一步中,我们将使用 CircleCI 自动化部署,这样我们就可以立即部署新的变更到存储库中。

圆形构型

要用 CircleCI 构建我们的项目,我们需要添加一个配置文件,指示 CircleCI 构建我们的 web 应用程序,并在每次修改代码时自动将其部署到 Firebase。

在我们项目的根文件夹中,创建一个名为.circleci的文件夹,并在其中创建一个config.yml文件。CircleCI 要求配置文件位于此处。

下面是我们将在项目中使用的配置文件:

# CircleCI Firebase Deployment Config
version: 2
jobs:
  build:
    docker:
      - image: circleci/node:10
    working_directory: ~/gatsby-site
    steps:
      - checkout
      - restore_cache:
          keys:
            # Find a cache corresponding to this specific package-lock.json
            - v1-npm-deps-{{ checksum "package-lock.json" }}
            # Fallback cache to be used
            - v1-npm-deps-
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: v1-npm-deps-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Gatsby Build
          command: npm run build
      - run:
          name: Firebase Deploy
          command: ./node_modules/.bin/firebase deploy --token "$FIREBASE_TOKEN" 

让我们快速回顾一下配置文件。

  • 首先,version键使我们能够指定我们正在使用 CircleCI 2.0。
  • 接下来,我们指定代码将在其中运行的基本 Docker 映像。在这种情况下,是一个基于Node 10的容器,这是撰写本文时的当前版本。如果有更新的版本,您可以使用。
  • working_directory选项指定了我们的代码将被克隆的位置。
  • 接下来是restore_cache部分,它指示 CircleCI 恢复任何以前安装的依赖项。这里我们使用package-lock.json文件的校验和来检测是重新安装依赖项还是使用缓存来恢复之前下载的包。
  • 下一步是通过npm install命令安装依赖项。
  • save_cache部分指示 CircleCI 在安装完依赖项后保存它们。
  • 然后我们运行Gatsby Build命令。这将构建站点的优化生产版本,并准备好进行部署。
  • 最后,我们运行Firebase Deploy命令,将我们的代码部署到 Firebase 主机。在这一步中,您会注意到我们需要一个 Firebase 令牌来允许将应用程序部署到我们的帐户。该命令指定应该从环境变量FIREBASE_TOKEN中获取令牌。我们一会儿会得到这个代币。

此外,请注意我们如何从本地安装的依赖项运行firebase命令的变化,而不是作为一个全局命令。如前所述,用 CircleCI 全局安装软件包可能是一个问题,所以我们在项目中本地安装我们需要的所有软件包。

集成 CircleCI 和 GitHub

我们现在有了一个配置文件,可以继续将 CircleCI 与我们之前创建的 GitHub 存储库集成。

  • 如果您还没有,请在 CircleCI 上创建一个帐户。
  • 登录后,确保在左上角选择您的帐户。

  • 点击左侧工具条上的添加项目
  • 在下一页,搜索您的 GitHub 库的名称,然后点击它旁边的Set Up Project

  • 在下一页,有一个构建我们的项目所需的步骤列表,其中最重要的一个是添加 CircleCI 配置文件。因为我们的回购中已经有了这个文件,所以让我们一直滚动到底部,然后单击Start Building

我们的构建将最终开始运行,但是它在 Firebase 部署步骤中不出所料地失败了。😢

幸运的是,我知道部署失败的原因。这是因为我们还没有将 Firebase 部署令牌添加到 CircleCI 中。让我们在下一部分解决这个问题。

获取用于部署的 Firebase 登录令牌

在最后一步,我们将需要生成一个 Firebase 令牌,我们将使用它来允许访问我们的帐户。这个令牌将使 CircleCI 能够代表我们部署到 Firebase,因为我们不能在 CI 环境中使用 Firebase 的交互提示符登录。

在我们的本地开发环境中,让我们运行以下命令来生成令牌:

firebase login:ci 

这将打开一个浏览器窗口,提示您登录 Firebase 帐户。登录后,将会生成一个令牌。通过 web 浏览器进行身份验证后,您应该会得到类似以下的结果。

Obtaining a Firebase token

现在我们有了令牌,剩下的就是在 CircleCI 中将令牌作为一个环境变量添加,这样我们就可以在我们的项目中使用它了。我们的部署命令期望在FIREBASE_TOKEN环境变量中找到值。

将 Firebase 令牌添加到 CircleCI

我们需要采取以下步骤来添加令牌:

  • 通过点按项目旁边的齿轮图标,转到项目的设置。
  • 构建设置部分下,点击环境变量
  • 点击添加变量
  • 在出现的模态中,在name字段中输入 FIREBASE_TOKEN ,在value字段中添加我们从 FIREBASE 获得的令牌,最后点击添加变量完成变量的添加。

Adding an Environment variable to CircleCI

  • 完成这一步后,我们现在可以通过点击 CircleCI 项目页面右侧的重新运行工作流来重新运行我们的构建。

我们现在已经使用 CircleCI 成功地将我们的 web 应用程序部署到 Firebase 主机上了!🎉

Firebase deployment successful

结论

我们对使用 CircleCI 将 web 应用程序部署到 Firebase 的探索到此结束。从现在开始,当我们对 Gatsby 站点进行更新并将这些更改推送到 GitHub 时,它们将自动部署到 Firebase 主机上。这真是一个伟大的组合。

这种方法适用于任何其他前端项目,并不是 Gatsby 特有的。Firebase 为 web 应用程序提供主机服务,CircleCI 帮助实现自动化并简化流程。前进并展开!🚀

有关这些技术的更多信息,请参见以下资源:


Kevin Ndung'u 是一名软件开发人员和开源爱好者,目前在 Andela 担任软件工程师。他热衷于通过博客帖子和开源代码分享他的知识。当不构建 web 应用程序时,您可以发现他在看足球比赛。

阅读凯文·恩东乌的更多帖子

自动将私有 Docker 映像部署到 Amazon ECR - CircleCI

原文:https://circleci.com/blog/automatically-deploy-private-docker-images-to-aws-ecr/

使用 CircleCI orbs 构建 Docker 映像并将其推送到 Amazon ECR

本文介绍了如何使用 CircleCI orbs 构建生产级 Docker 映像并将其推送到 Amazon 弹性容器注册中心(ECR)进行存储。熟悉本文中使用的工具和技术是首选,但不是必需的。以下是常用术语和技术的简要总结:

  • Docker: 基于容器构建应用的软件平台。
  • 容器:一种虚拟化方法,将应用程序的代码、配置和依赖项打包成构建块,以实现一致性、效率、生产力和版本控制。
  • 容器映像:一个自包含的软件,其中包含运行应用程序所需的一切:代码、工具和资源。
  • 容器映像库:命名和相关容器映像的集合,通常提供相同应用程序或服务的不同版本,由它们的标签标识。
  • Docker image registry: 是一种存储集装箱图像的服务,由第三方托管,或者作为公共/私人注册中心,如 Docker Hub、AWS (ECR)、GCP (GCR)、Quay 等。它们简化了从开发到生产的工作流程。
  • 容器编排:容器编排就是管理容器的生命周期,尤其是在大型的动态环境中。

让我们深入了解一下 Amazon ECR,它是什么,以及 CircleCI 如何帮助您充分利用这项服务。

什么是亚马逊 ECR?

亚马逊 ECR 是一个完全托管的私有 Docker 容器注册中心,开发者可以很容易地存储、管理和部署 Docker 容器映像。亚马逊 ECR 与亚马逊弹性容器服务(亚马逊 ECSe )和亚马逊弹性 Kubernetes 服务(亚马逊 EKS )无缝集成。亚马逊 ECR 也可以和其他云厂商一起使用。

你为什么会想使用像亚马逊 ECR 这样的私有 Docker 注册中心?

  • 在许多情况下,分离开发、测试和生产注册是可取的。
  • 它是完全托管的,因此无需运营自己的容器存储库或担心底层基础架构的扩展。
  • 很安全。具有扫描功能和基于角色的访问控制的私有容器注册表提供了更多的安全性、治理(IAM)和高效管理。它通过 HTTPS 传输您的容器图像,并自动加密您的静态图像。
  • 在运行容器的系统附近运行注册中心可以减少部署延迟并减少网络中断的风险。

开发人员需要使用注册表来存储应用程序开发过程中创建的图像。持续集成管道连接构建容器图像,并将这些工件推入亚马逊 ECR。想象一个管道,其中您推送一个 commit,该 commit 触发 CircleCI 上的一个构建,该构建将一个新图像推送到 Amazon ECR 中。然后,您的注册中心可以启动 webhook 并触发部署。所有这些都不需要人工做任何事情。注册中心使得这种完全自动化的管道更加容易。放在注册表中的容器映像可以在开发的不同阶段使用。在几个步骤中,使用 CircleCI orbs ,我们将封装一个简单的 Node.js 应用程序,构建映像,并将其推送到 Amazon ECR。

设置 Amazon ECR

从 AWS 管理控制台中,选择 IAM。此服务允许您管理对 AWS 资源的访问。

创建角色/用户。在这个例子中,我把我的名字叫做ci-cd-ecr,但是任何任意的名字都可以。

接下来,我们需要使用一个AWS_ACCESS_KEY和一个AWS_SECRET_KEY以编程方式访问我们的 Amazon ECR 服务。点击新创建的用户,进入你的安全凭证页面。在这里,您将能够创建新的访问密钥。请妥善保存密钥,因为我们将使用它从 CircleCI 访问 Amazon ECR 服务。密钥的格式如下:

AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 

接下来,从策略部分创建一个策略,并附加前面创建的ci-cd-ecr角色。该政策赋予了亚马逊 ECR 的完全访问权限。CircleCI orb 使用我们新创建的ci-cd-ecr角色,将完全访问我们的 Amazon ECR 服务,包括创建图像存储库(如果它们不存在的话)。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:*",
                "cloudtrail:LookupEvents"
            ],
            "Resource": "*"
        }
    ]
} 

为用户生成访问密钥和秘密密钥,并将它们存储在安全的地方。在设置 CircleCI 管道示例时,我们将需要它们。

使用 aws-ecr orb 设置 CircleCI 管道

这个演示的代码可以在 GitHub 这里找到。CircleCI 配置文件可以在.circleci/文件夹中找到。正如您将看到的,使用 orbs,我们仅使用 18 行配置就完成了构建一个映像并将其推送到 Amazon ECR!要使用 orbs,我们需要使用 CircleCI 版。它是支持球体的版本。aws-ecr orb 预装了以下命令:

  • 建立形象
  • 标记图像(使用HEAD == CIRCLE_SHA1的 Git 提交散列)
  • 登录亚马逊 ECR
  • 创建一个 Amazon ECR repo(如果不存在)
  • 将图片推送到亚马逊 ECR

以下是我们管道的完整配置:

version: 2.1

orbs:
  aws-ecr: circleci/aws-ecr@6.7.0

workflows:
  build_and_push_image:
    jobs:
      - aws-ecr/build-and-push-image:
          account-url: AWS_ECR_ACCOUNT_URL
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          create-repo: true
          dockerfile: Dockerfile
          path: .
          region: AWS_REGION
          repo: circleci-ecr-orb-demo
          tag: "$CIRCLE_SHA1" 

为 Amazon ECR 设置环境变量

您可以在配置中看到几个需要设置的环境变量:

  • AWS_ECR_ACCOUNT_URL -存储映射到 AWS 帐户的 Amazon ECR 帐户 URL 的环境变量,例如{ awsAccountNum } . dkr . ECR . us-west-2 . Amazon AWS . com
  • AWS_ACCESS_KEY_ID -我们之前创建的ci-cd-ecr IAM 角色的 AWS 访问键 id。
  • AWS_SECRET_ACCESS_KEY -我们之前创建的 ci-cd-ecr IAM 角色的 AWS 密钥。将此设置为您将设置来保存此值的环境变量的名称,即 AWS_SECRET_ACCESS_KEY
  • AWS_REGION -您的 ECR 资源将位于的 AWS 区域。

注意: 你不必设置$CIRCLE_SHA1,因为它是所有 CircleCI 项目中可用的默认变量。这是当前构建的最后一次提交的SHA1散列。使用 Git 提交散列让您能够跟踪容器中的内容。理想情况下,它允许我们将容器追溯到其 Docker 映像,然后追溯到映像中包含的 Docker 文件和代码。在自动化执行环境中,这将带我们回到导致 Docker 映像构建的提交。

上面的配置示例使用了$CIRCLE_SHA1来设置tag:。或者,您可以在您的构建中使用 CircleCI 构建号($CIRCLE_BUILD_NUM)作为标签。

上面的dockerfile:命令指定了 docker 文件的路径。我们的演示报告使用以下 docker 文件:

# Set the base image to use for subsequent instructions
FROM node:alpine

# Add metadata to an image 
LABEL app="simple-node-application"
# Directive to set environmental variables key to value pair
ENV NPM_CONFIG_LOGLEVEL warn

# Set the working directory for any subsequent ADD, COPY, CMD, ENTRYPOINT, 
# or RUN instructions that follow it in the Dockerfile
WORKDIR /usr/src/app

# Copy files or folders from source to the dest path in the image's filesystem.
COPY package.json /usr/src/app/
COPY . /usr/src/app/

# Execute any commands on top of the current image as a new layer and commit the results.
RUN npm install --production

# Define the network ports that this container will listen on at runtime.
EXPOSE 3000

# Configure the container to be run as an executable.
ENTRYPOINT ["npm", "start"] 

CircleCI 和 Amazon ECR 都配置好之后,您就可以开始构建映像并将它们推送到存储库了。

仅用 18 行配置就将 Docker 图像推送到 Amazon ECR

仅用 18 行配置,我们就能够构建 Docker 映像并将其推送到 Amazon ECR。CircleCI orbs 通过将预构建的命令、作业和执行器导入 CircleCI 配置文件来节省时间,并提供与云服务和其他工具的轻松集成。


Dominic Motuka 是 Andela 的 DevOps 工程师,在 AWS 和 GCP 支持、自动化和优化生产就绪部署方面拥有 4 年多的实践经验,利用配置管理、CI/CD 和 DevOps 流程。

阅读多米尼克·莫图卡的更多帖子

自动识别哪些代码更改导致了错误

原文:https://circleci.com/blog/automatically-identify-which-code-changes-caused-errors/

这是关于检测和纠正 Rollbar 和 CircleCI 错误的两部分博客文章的第二部分,由特约撰稿人 Jason Skowronski 撰写。在这里阅读第一部分。

当您在实践连续交付时,监控您的应用程序是非常重要的,这样您就知道它在部署后运行良好。如果出现问题或用户体验不佳,您需要立即得到通知,以便您可以快速解决问题。当您的监控解决方案还可以告诉您哪些代码更改导致了错误时,您可以节省宝贵的故障排除时间。在我们的博客系列的第一部分中,我们展示了如何使用 Rollbar 来跟踪 CircleCI 中每次部署后出现的错误。滚动条显示哪些新的错误发生,哪些重新激活。对于任何给定的错误,它还可以显示错误首次发生后的可疑部署。

在第二部分中,我们将向您展示 Rollbar 如何自动识别可能导致错误的代码更改。这对于由外部因素(如第三方 API 或基础设施问题)引起的错误没有帮助,但它将帮助您更快地缩小由代码错误引起的错误。这将提高您的开发速度,并帮助您向客户交付更好的体验。

rollbar2_1.png

我们将继续我们的例子,展示 Rollbar 如何与 CircleCI 和 GitHub 集成来实现这一点。

跟踪 CircleCI 的部署

要将错误链接到源代码,首先需要在应用程序中设置 CircleCI。我们将假设您已经拥有 CircleCI 的帐户。如果没有,注册,然后配置 Git、Bitbucket 等源代码库。然后,按照我们博客系列第一部分中的说明,在进行部署时通知滚动条

rollbar2_2.png

将您的源地图发送到滚动条

如果使用 JavaScript,Rollbar 可以使用源代码映射将错误消息映射回原始源代码。如果没有,您可以跳过这一部分。

源代码映射对于调试生产代码是必不可少的。它们将浏览器的调试输出链接回被缩小或传输前的原始源代码。为了显示原始代码的堆栈跟踪,Rollbar 需要访问您的小型 JavaScript 的源映射。

为了上传源地图,您需要在 CircleCI 配置文件中的部署脚本之前添加 Rollbar 源地图 API 调用。

- run:
     name: Upload sourcemap to Rollbar 
     command: |
     curl https://api.rollbar.com/api/1/sourcemap/ \
     -F access_token=2a208f30fa1b4f0183adb694c4432038 \
     -F version=$CIRCLE_SHA1 \
     -F minified_url=https://s3.us-east-2.amazonaws.com/rollbar-example/
      main.[hash].bundle.js \
     -F source_map=main.[hash].bundle.js.map \
     -F main.js=main.[hash].js
    # Deployment script 

access_token: 滚动条上的目标项目令牌。这个令牌是在滚动条上创建项目时生成的。
环境:部署服务的部署环境。我们可以配置不同的环境,例如开发、试运行和生产。
版本:部署的应用版本。这应该是存储库提交 ID。如果提供的版本是提交 ID,滚动条将创建一个到存储库提交源代码的链接。
minified_url: 缩小文件的完整 url。应该以 http:或 https:开头,我们将去掉它们。
source_map: 源地图的内容,作为多部分文件上传。

识别代码版本

为了让 Rollbar 识别错误的可疑部署,它必须知道当前部署的是哪个版本。跟踪转移的一个简便方法是使用 git SHA 号,因为它在某个时间点唯一地标识了您的代码。因为这个版本是在提交代码时设置的,所以不能在应用程序中硬编码。相反,我们可以在构建时插入版本。

我们希望我们的包构建器 npm 自动插入代码版本,因此我们将插入一个简短的脚本来完成这项工作。让我们创建一个名为 git.version.ts 的定制 ts 文件来获取源代码的最新 SHA 修订号。

var fs = require('fs');
import { Observable } from 'rxjs';

let exec = require('child_process').exec;

const revision = new Observable<string>(s => {
   exec('git rev-parse HEAD',
       function (error: Error, stdout: Buffer, stderr: Buffer) {
           if (error !== null) {
               console.log('git error: ' + error + stderr);
           }
           s.next(stdout.toString().trim());
           s.complete();
       });
});
const branch = new Observable<string>(s => {
   exec('git rev-parse --abbrev-ref HEAD',
       function (error: Error, stdout: Buffer, stderr: Buffer) {
           if (error !== null) {
               console.log('git error: ' + error + stderr);
           }
           s.next(stdout.toString().trim());
           s.complete();
       });
});

Observable
   .combineLatest(revision, branch)
   .subscribe(([revision, branch]) => {
       console.log(`version: '${process.env.npm_package_version}', revision: '${revision}', branch: '${branch}'`);

       const content = '// this file is automatically generated by git.version.ts script\n' +
           `export const versions = {version: '${process.env.npm_package_version}', revision: '${revision}', branch: '${branch}'};\n`;

       fs.writeFileSync(
           'src/environments/versions.ts',
           content,
           {encoding: 'utf8'}
       );
   }); 

接下来,在 package.json 中添加预构建脚本,预构建脚本将在构建应用程序时自动执行,它将创建一个 version.ts 文件,其中包含分支、修订号等 git 修订信息。您可以在整个项目中直接访问 version.ts 文件。

"scripts": {
   "prebuild.prod": "ts-node git.version.ts",
   "build.prod": "ng build --prod --aot false --sourcemaps "
 } 

告诉滚动条代码版本

现在我们可以配置 Rollbar 的code_version变量,这样它就可以跟踪版本号。我们将使用 git.version.ts 文件读取修订号。我们将在 app.module.ts 文件中初始化 Rollbar,如下所示。

const versions = require('../environments/versions');
const rollbarConfig = {
  accessToken: 'f627d5e044a24b9987a23e54c5df352e',
  captureUncaught: true,
  captureUnhandledRejections: true,
  enabled: true,
  source_map_enabled: true,
  environment: 'production',
  payload: {
	server: {
  	   branch: 'master',
  	   root: 'webpack:///./'
	},
	client: {
  	   javascript: {
    	       code_version: versions.versions.revision
  	   }
	}
  }
};

export function rollbarFactory() {
 return new Rollbar(rollbarConfig);
}

@NgModule({
 declarations: [
   AppComponent
 ],
 imports: [
   BrowserModule,
   FormsModule
 ],
 providers: [
   { provide: ErrorHandler, useClass: RollbarErrorHandler },
   { provide: Rollbar,  useFactory: rollbarFactory }
 ],
 bootstrap: [AppComponent]
})
export class AppModule { } 

添加源代码管理集成

我们还希望 Rollbar 与我们的源代码控制库集成,这样当错误发生时,它可以自动将我们链接到正确的代码行。它还能告诉我们何时有代码变更破坏了部署。

下面,你可以看到我们已经配置了与 GitHub 的集成。确保在滚动条中配置项目根目录,如下所示;它是放置根的源代码位置。如果没有正确配置,Rollbar 将无法将源代码链接到错误。在这个例子中,项目根是/

rollbar2_3.png

运行测试用例以生成错误

现在,我们将测试刚刚组装好的所有部件。我们将在 CircleCI 上执行构建,运行应用程序,然后生成一个测试错误。这将向 Rollbar 发送一个测试错误,这样我们就可以看到它接下来如何识别部署和源代码。

找到错误的根本原因

菜单选项卡上检查滚动条仪表板的错误记录,它将显示错误细节以及导致错误的源代码文件的链接。该错误将显示可疑的部署版本以及导致错误的代码。

单击列表中的错误项目,我们会看到项目详细信息页面。在回溯的旁边,我们可以看到导致错误的源代码的链接。

rollbar2_4.png

当您点击链接时,它将直接打开 GitHub 存储库中的类,并突出显示更改的代码片段,如下图所示:

rollbar2_5.png

调查错误原因的第二种方法是使用 Suspect Deploy 选项卡。它显示了首次发现错误的修订,以及自上次部署以来对代码所做的更改。我们可以看到提交消息,表明引入了一个错误代码。

rollbar2_6.png

现在,只需点击几下鼠标,您就可以从滚动条中报告的错误直接找到可能导致问题的确切部署和源代码。不再需要在嘈杂的日志文件中搜寻,也不再需要在源代码中寻找负责的代码行。这将为您排除故障节省时间,并帮助您让生产系统更快地工作。

将 Docker 化的 Python 应用程序自动部署到 Docker Hub | CircleCI

原文:https://circleci.com/blog/automating-deployment-dockerized-python-app/

本教程涵盖:

  1. 将 Python 应用程序“Dockerizing 化”
  2. 设置管道以构建和测试映像
  3. 将 Docker 映像部署到 Docker Hub

CI/CD 系统遵循多层环境模式:开发、测试、登台和产品发布都是这个过程的一部分。这个循环中的每个设置可以有多种设置和配置。因此,不得不为不同的环境设置单独的配置可能会带来不便和负担。

在本教程中,我们将看看 Docker 是什么,以及它如何将开发人员从设置问题和端口冲突中解放出来。我将介绍如何“Dockerize”一个 Python 应用程序。然后,我将指导您建立一个管道来构建一个映像,并在测试通过后将其推送到 Docker Hub。

先决条件

完成本教程需要以下内容:

  • Python 安装在你的系统上。
  • 一个 CircleCI 账户。
  • 一个 GitHub 的账号,了解一些 GitHub 的动作比如commitpush
  • A 坞站枢纽账号。
  • 对管道工作原理的基本理解。有了这些,你就可以开始了。

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

为什么要用 Docker?

假设你是一个测试项目的开发人员。当您在项目中工作时,您正在使用Python 2.7,但是由于某种原因,您将在生产中使用Python 3.9。此外,Devops 团队目前正在使用Python 3.6为组织托管应用程序。

这些版本不一致可能会使开发过程有些痛苦。他们还可能让 Devops 团队头疼,因为他们要努力跟上需要托管的每个应用程序的不同版本。幸运的是, Docker 提供了一个解决方案。首先,让我澄清一些与 Docker 的使用相关的概念。

Docker 是一个使用容器化技术的平台,允许开发者快速创建、共享和运行处于创建状态的应用程序。

一个 Docker container 是一个应用程序的运行时环境,由打包的代码及其所有依赖项组成。要旋转 Docker 容器,可以使用 Docker 图像。

一个 Docker image 是一个可执行文件(模板),它包含了应用程序正常运行所需的一切,包括代码、依赖关系和虚拟环境。

Docker 图像是使用一个名为Dockerfile的特殊文件创建的。Docker 文件是包含 Docker 在创建图像时要遵循的一组说明的文档。

Docker process

Docker 消除了担心机器配置的需要,因为它在容器本身中引导所有的配置。这确保了应用程序在所有安装了 Docker 的机器上一致地运行。

与虚拟机不同,使用 Docker 可以轻松管理微服务架构的开发和部署。这不仅导致了精益组织,也导致了系统的分离。解耦系统最小化了故障发生的机会,使它们比单一的应用程序风险更小。

Docker 促进了跨平台的兼容性和可维护性,以及简单性、快速部署和安全性。

将您的 Python 应用程序归档

“归档”应用程序包括以下步骤:

  • 设置一个 API 来构建一个映像
  • 创建 Dockerfile 文件

设置 API 以构建映像

要在本节教程中演示 Docker 过程,请在本地使用 FastAPI RestAPI。已经为您创建了 API。使用以下命令克隆存储库:

git clone https://github.com/CIRCLECI-GWP/dockerhub-automated-circleci-deployments.git;

cd dockerhub-automated-circleci-deployments; 

这将 GitHub 存储库克隆到一个名为dockerhub-automated-circleci-deployments的目录中,然后访问它。

dockerhub-automated-circleci-deployments目录中,创建一个虚拟环境,并使用为您的操作系统提供的命令激活它。

##  Windows OS ##

# create a venv
python -m venv venv
# activate venv
venv\Scripts\activate 
##  Linux/Mac OS ##

#create a venv
python3 -m venv venv
# activate venv
source venv/bin/activate 

创建虚拟环境后,您可以使用以下命令安装依赖项:

pip install -r requirements.txt 

运行应用程序:

uvicorn app.main:app --reload 

现在您已经验证了您的 API 运行成功,接下来为您的应用程序创建一个Dockerfile

创建 Dockerfile 文件

如前所述,Dockerfile是一本创建图像的食谱。当您运行命令docker build时,Docker 将从 docker 文件中读取指令并构造一个映像。

在这种情况下,您需要 Docker 文件来:

  • 下载Python 3.10.2版本
  • 在应用程序中查找您的包
  • 安装成功后,您需要这个映像来启动容器中的应用程序。

将此添加到您的 docker 文件中:

FROM python:3.10.2

WORKDIR /usr/src/app

COPY requirements.txt ./

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] 

在这个 Docker 配置中,WORKDIR命令指定了工作目录,这是到您的目录的绝对路径。COPY命令将requirements.txt文件复制到应用程序所在的目录中。RUN命令安装运行应用程序所需的包。最后,CMD指定了运行映像时将执行的命令。

您可能想知道主机和端口在应用程序RUN命令中的用途。Docker 将使用uvicorn作为服务器和您的应用程序入口。主机和端口将确保您可以访问容器外部的应用程序。

如果您想在本地构建映像来验证 docker 文件是否正常工作,您可以运行以下命令:

docker build -t fastapi-app . 

接下来,您将需要在 GitHub 上创建一个新的存储库,提交并首先将所有更改推送到存储库。然后,您可以开始使用 CircleCI 将 Docker 容器部署到 Docker Hub 的过程。

使用 CircleCI 将 Docker 映像部署到 Docker Hub

Docker Hub 是 Docker 图片的储存库,类似于 GitHub。它使得搜索和与其他开发者共享 Docker 图像变得更加容易。

设置 CircleCI

为了确保您的应用程序与 CircleCI 的集成,创建一个配置文件,告诉 CircleCI 如何初始化您的存储库和运行测试。从根目录中,创建一个名为.circleci/的新目录。在这个新目录中,创建一个名为config.yml的新文件。将此添加到您的config.yml文件中:

version: 2
jobs:
  build:
    docker:
      - image: cimg/python:3.10.2
    steps:
      - checkout
      - run:
          name: Install pip packages
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install -r requirements.txt

      - run:
          name: Test with pytest
          command: |
            . venv/bin/activate
            pytest

workflows:
  version: 2
  build-master:
    jobs:
      - build 

这个配置定义了 Python 3.10.2图像。然后,一旦下载了映像,配置将设置虚拟环境,安装所有的应用程序依赖项,然后运行您的测试。

添加 CircleCI 配置文件后,提交并将您的更改推送到您的远程 GitHub 存储库。

现在您可以从仪表板中创建一个 CircleCI 项目。

Setting up project on CircleCI

点击设置项目开始建造。系统将提示您使用项目存储库中的配置文件。输入配置文件所在分支的名称。在本例中,它是main分支。

Existing configuration file

点击设置项目完成该过程。

First build

现在您已经在 CircleCI 上运行了测试,您的工作已经完成了一半!接下来,您需要将应用程序部署到 Docker Hub。您需要添加一个选项,首先登录 Docker Hub,然后在每次测试通过时构建一个映像。

将任何图像推送到 Docker Hub 总是需要认证。您首先需要在 CirclecI 仪表板上配置 Docker Hub USER_IDPASSWORD和图像名称。转到 CircleCI 仪表板。从设置部分,选择环境变量

使用以下格式添加环境变量:

  • DOCKER_HUB_USER_ID是您的 Docker Hub ID。
  • DOCKER_HUB_PASSWORD是您的 Docker Hub 密码。

用环境名IMAGE_NAME添加 Docker 图像名作为环境变量。为其指定一个唯一的名称作为值。

 Docker hub configuration variables

目前,您的 CircleCI 配置只运行您的测试。您需要修改它,以便它首先运行测试,然后构建一个 Docker 映像,如果所有测试都通过了,就把它推送到 Docker Hub。

向您的config.yml文件添加一个deploy任务,如下所示:

version: 2
jobs:
  build:
    docker:
      - image: cimg/python:3.10.2
    steps:
      - checkout
      - run:
          name: Install pip packages
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install -r requirements.txt

      - run:
          name: Test with pytest
          command: |
            . venv/bin/activate
            pytest

  deploy:
    docker:
      - image: cimg/base:2022.06
    steps:
      - checkout
      - setup_remote_docker:
          version: 19.03.13
      - run:
          name: Build and push to Docker Hub
          command: |
            docker build -t $DOCKER_HUB_USER_ID/$IMAGE_NAME:latest .
            echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_USER_ID" --password-stdin
            docker push $DOCKER_HUB_USER_ID/$IMAGE_NAME:latest

workflows:
  version: 2
  build-master:
    jobs:
      - build
      - deploy:
          requires:
            - build 

该配置文件包含一个带有两个任务的workflow:

  • build任务构建并测试代码。
  • deploy作业构建并把你的 Docker 映像推送到 Docker Hub。

在添加的配置部署步骤中,deploy作业在启动远程 Docker 引擎之前检索代码。当创建 Docker 映像进行部署时,您必须使用setup_remote_docker选项。出于安全目的,该选项为每个构建创建一个单独的远程环境。这个环境是专门为运行 Docker 命令而设置的。

完成后,您可以开始运行 Docker 命令来构建和标记您的图像:

docker build -t $DOCKER_HUB_USER_ID/$IMAGE_NAME:latest . 

该命令构建了一个 Docker 图像,并用:latest标签对其进行标记。图像名称的前缀是您在 CircleCI 仪表板中设置为环境变量的 Docker Hub 用户名,后面是图像的实际名称(也是您设置的环境变量)。

一旦您有了构建的映像,使用 CircleCI 和存储的环境变量登录到您的 Docker Hub 帐户。

echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_USER_ID" --password-stdin 

这就是配置文件中的命令的作用。现在您有了一个映像并登录到 Docker Hub,您的映像将使用配置文件中定义的以下命令被推送到 Docker Hub:

docker push $DOCKER_HUB_USER_ID/$IMAGE_NAME:latest 

为了确保管道的完整性,有一个条件要求您的构建和测试作业在部署到 Docker Hub 之前通过。这发生在workflow下的最终配置块中:

workflows:
  version: 2
  build-master:
    jobs:
      - build
      - deploy:
          requires:
            - build 

最后,我们需要将所有的更改提交到 GitHub。一旦我们这样做了,我们就可以继续检查 CircleCI 仪表板,以查看我们的测试和部署的进度。

瞧啊。我们的deploybuild步骤都是绿色的,并且检查deploy作业,我们的映像被成功构建并被推送到 Docker hub。

 Successful Docker hub push

为了验证我们的图像被推送到 Docker Hub,我们可以访问 Docker Hub 网站并检查图像的状态。在我们的例子中,我们的图像被命名为circleci-automated-dockerhub-image,我们可以在 Docker hub 仪表板上看到它,如下所示:

 Pushed Docker hub image

这样,我们不仅验证了我们的 CircleCI 配置工作正常,还验证了我们的映像被成功地推送到 Docker Hub。虽然感觉很多,但我们已经能够使用 CircleCI 和 Docker Hub 创建 CI/CD 流程,从而节省了大量时间,否则这些时间将花费在手工部署 Docker Hub 上。

结论

在本教程中,我们学习了如何使用 CirclecI 构建 Docker 映像并将其部署到 Docker Hub。我们还学习了如何在项目中使用定义的 Docker 文件构建 Docker 映像。

在本教程的最后一节,我们还介绍了如何创建一个管道来构建一个映像,并仅在测试通过时将它推送到 Docker Hub。我希望你喜欢阅读本教程,因为我们创造了它。直到下一个,保持敏锐,不断学习!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

使用 PythonAnywhere | CircleCI 自动化烧瓶部署

原文:https://circleci.com/blog/automating-flask-deployments-with-pythonanywhere/

本教程涵盖:

  1. 设置 PythonAnywhere
  2. 在 CircleCI 建立一个烧瓶项目
  3. 使用 CircleCI 自动将 Flask 部署到 PythonAnywhere

既然开发团队已经了解了 CI/CD,那么部署就没有理由成为一个耗时而繁琐的过程。CI/CD 可能从持续测试开始,但是添加自动化部署会将您的 CI/CD 实践带到下一个级别。

持续部署缩短了发布时间,因此您可以花更多时间来提高应用程序的质量。在本教程中,我将指导您使用自动连续部署将 Flask 应用程序部署到 PythonAnywhere。我希望在我们结束本教程时,你能感觉像一个“摇滚明星”级别的 CI/CD 从业者。

先决条件

开始之前,请确保这些项目已准备就绪:

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

我在本教程中使用的示例应用程序是一个 Flask API。Flask 应用程序公开了一个 Swagger API,我们可以用它来创建和检索书籍。通过克隆这个 GitHub 库获得应用程序的源代码:git clone git@github.com:mwaz/automating-flask-deployments-with-pythonanywhere.git

您将只为 PythonAnywhere 配置使用克隆的存储库,这将在本教程的后面部分介绍。

什么是 PythonAnywhere?

PythonAnywhere 是一个托管平台,是 AWS、Azure、Google Cloud 或 Heroku 等平台的替代平台。PythonAnywhere 平台为在线开发和部署提供了一个基于云的、易于使用的工具。

自动化部署到 PythonAnywhere

自动化持续部署到 PythonAnywhere 为开发人员提供了许多好处:

  • 设置简单快捷
  • 专为性能打造
  • 开源项目免费

我创建了一个流程图(以 CircleCI 为例),展示了如何自动连续部署到 PythonAnywhere。该过程可以适用于任何 CI/CD 工具和任何其他托管平台。

CircleCI and PythonAnywhere deployment steps

该图显示了如何通过 CircleCI web 界面和配置文件使用 CircleCI API 来配置部署作业。该图还显示了从高级视图执行所有 CircleCI 步骤和服务的过程。请注意从应用程序克隆到准备部署再到部署应用程序之间的上下文分离。

设置 PythonAnywhere

PythonAnywhere 使入门和设置变得简单。PythonAnywhere 平台:

  • 附带预安装的 Python 库
  • 允许您为应用程序设置不同的环境
  • 支持调度任务,如常规数据库清理或使用 cron 作业执行重复性任务

登录到您注册的 PythonAnywhere 黑客帐户后,打开web选项卡并创建一个新的webapp。对于这个项目,使用手动配置选项。这允许您设置一个虚拟环境并安装特定于项目的附加库。

PythonAnywhere creating a new web app

安装完成后,您将被重定向到 web 应用程序的仪表板。您可以使用仪表板,通过控制台界面进一步配置您的应用程序。

Application dashboard PythonAnywhere

从控制台,您可以使用特定的 python 版本声明版本:3.9为您的应用程序创建一个虚拟环境。运行以下命令:

mkvirtualenv flask-venv --python=’/usr/bin/python3.9 

将应用程序克隆到 PythonAnywhere 控制台

接下来,将应用程序从 GitHub 克隆到 PythonAnywhere 控制台。这是重要的一步,因为它还设置了远程pushpull GitHub 链接,我们稍后将使用这些链接从 GitHub 中提取更改。在控制台上运行以下命令:

git clone https://github.com/mwaz/automating-flask-deployments-with-pythonanywhere.git

安装依赖项

更改目录:cd automating-flask-deployments-with-pythonanywhere使用:pip install pipenv安装 pipenv 软件包管理器

安装所有依赖项:pipenv install

克隆 Flask 应用程序后,您需要告诉 PythonAnywhere 您的项目所在的位置。为此,使用 Web 选项卡配置位置。不关闭 bash 控制台(在最右边的 hamburger 菜单上),打开 Web 选项卡,设置项目的源代码路径、虚拟环境路径和静态文件,包括 Swagger UI YAML 文件。

Configuring Code repository

将源代码路径设置为:/home/<username>/<project-name>

创建虚拟环境后,您可以用它打开控制台。现在你可以忽略这个选项。

注意: 要获取工作目录路径,使用 bash $pwd命令查找父目录。

恭喜你!您的 Flask 项目已经在 PythonAnywhere 上设置并成功托管。打开 web 应用程序的链接,查看默认的 PythonAnywhere 屏幕。

Default PythonAnywhere apps page

到目前为止做得很好,但是还有一些步骤要做。

应用程序的页面显示 PythonAnywhere 的默认欢迎消息。要打开 Flask 应用程序,需要对 PythonAnywhere 的配置文件进行一些更改。

从仪表板打开 WSGI 配置文件,并修改配置。

WSGI location

WSGI (Web 服务器网关接口)是 Web 服务器的 Python 接口。它允许您编写可以在服务器上运行的 Python 代码,并用于配置 web 服务器。使用 WSGI,您可以将请求从 web 服务器转发到 Flask 后端,并从 web 服务器转发回请求者。

要执行 Flask 应用程序,请删除配置文件中的hello world代码。

此外,配置项目目录的路径和应用程序的入口点:

import sys

# add your project directory to the sys.path
project_home = '/home/waweru/automating-flask-deployments-with-pythonanywhere'
if project_home not in sys.path:
    sys.path = [project_home] + sys.path

# import flask app but need to call it "application" for WSGI to work
from run import app as application 

保存更改并关闭文件。接下来,您需要重新加载应用程序,并在 URL 后面添加后缀api-docs。加载您的 Flask Swagger 文档页面。

 Swagger page PythonAnywhere

您已经成功地将 Flask 应用程序部署到 PythonAnywhere。

接下来,您需要配置您的应用程序,以便在您每次向主分支进行部署时,CircleCI 会自动进行部署。

设置 CircleCI 集成

要在这个项目中设置 CI/CD,使用ssh连接到 PythonAnywhere 服务器。这确保了一旦 CircleCI 执行,如果管道运行成功,您可以从主 GitHub 分支获取最新的更改。

注: SSH 也称为安全 Shell 或安全套接字 Shell,是一种用于操作网络服务以在不安全的网络上安全连接的网络协议。ssh 的应用包括:远程命令行登录和远程命令执行。

下图显示了如何使用 ssh 将 CI/CD 应用于 CircleCI 和 PythonAnywhere。

 SSH with CircleCI

该图显示,当您将代码推送到 GitHub 时,它会启动一个进程将代码部署到 PythonAnywhere。一旦部署完成,您就可以连接到 PythonAnywhere 服务器,并从主分支获取最新的代码。现在您已经知道了它是如何工作的,下一步是编写一个 CircleCI 配置文件来实现它。

初始化仪表板上的 CircleCI

要在 CircleCI 设置任何项目,请从 CircleCI 仪表板开始。转到项目部分。列出了与您的 GitHub 用户名或组织相关的所有 GitHub 存储库。对于本教程,您想要设置的存储库是automating-flask-deployments-with-pythonanywhere

在“项目”面板上,选择设置所选项目的选项。选择使用现有配置的选项。第一个配置步骤已经完成。

注意: 如果您已经克隆了这个库,这不是一个必须的步骤,但是对于您学习如何在您自己的项目中设置它来说,这是一个重要的步骤。

首先,在您的根目录中创建一个.circleci目录,并添加一个config.yml文件。配置文件将包含每个项目的 CircleCI 配置。设置好之后,您可以在配置中使用 CircleCI orbs 来执行 Python API 测试。

设置 CircleCI

您的 CircleCI 配置文件将执行您的测试并将您的应用程序部署到 PythonAnywhere。在.circleci/config.yaml文件中,输入:

version: 2.1
orbs:
  python: circleci/python@1.2

workflows:
  build-app-with-test:
    jobs:
      - build-and-test
      - deploy:
          requires:
            - build-and-test
jobs:
  build-and-test:
    docker:
      - image: cimg/python:3.9
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: |
            pipenv --three
            pipenv install
      - run:
          name: Run tests
          command: pipenv run pytest
  deploy:
    docker:
      - image: cimg/python:3.9
    steps:
      - checkout
      - run:
          name: Deploy Over SSH
          command: |
            ssh-keyscan -H ssh.pythonanywhere.com >> ~/.ssh/known_hosts
            ssh $SSH_USER@$SSH_HOST "cd automating-flask-deployments-with-pythonanywhere; git pull"; 

在这个 CircleCI 配置中,您将创建两个作业。第一个作业是构建作业,负责安装依赖项和运行测试。部署作业只负责将应用程序部署到 PythonAnywhere。运行构建和测试作业,然后运行部署作业,确保只有在测试成功后才进行部署。

jobs:
  - build-and-test
  - deploy:
      requires:
        - build-and-test 

测试通过后,您可以使用部署作业将应用程序部署到 PythonAnywhere。要允许部署应用程序,您需要在 PythonAnywhere 帐户中生成 SSH 密钥,然后将该私钥添加到 GitHub 帐户中。

生成 SSH 密钥

您不希望总是使用密码来连接到 PythonAnywhere。要允许连接到 PythonAnywhere 而不需要每次都输入密码,您可以在 PythonAnywhere 控制台上生成 SSH 密钥。从 PythonAnywhere 控制台,运行以下命令:

ssh-keygen -t rsa -b 2048 

系统将提示您输入密码以增强安全性。您可以添加此项,也可以留空。

 Generating public/private keys

太好了!您已经生成了您的密钥。现在您可以在 CircleCI 中使用它们来自动连接到 PythonAnywhere。我将在下一节讨论这个问题。

首先,您需要 PythonAnywhere 知道您的公钥是一个授权的密钥。使用以下命令将公钥添加到 PythonAnywhere 控制台中的~/.ssh/authorized_keys:

ssh-copy-id waweru@ssh.pythonanywhere.com 

系统将提示您输入密码。一旦获得授权,您就可以连接到 PythonAnywhere,再也不用使用密码了。酷吧?通过在 PythonAnywhere 控制台中运行以下命令进行测试:

ssh -i ~/.ssh/id_rsa_pub waweru@ssh.pythonanywhere.com 

这个命令应该可以让您从控制台登录到 PythonAnywhere 服务器,而无需输入密码。下一步是向 CircleCI 添加 SSH 密钥。

向 CircleCI 添加 SSH 密钥

按照上一节中的步骤,您能够为 PythonAnywhere 帐户生成私钥和公钥。现在您可以将您的私钥复制到 CircleCI。

为了避免增加服务器的开销,您可以只显示私钥,然后将其复制到剪贴板。在 PythonAnywhere 控制台中,输入以下命令:

 cat ~/.ssh/id_rsa 

 Generating public/private keys

将您的私钥复制到剪贴板后,导航到您项目的 CircleCI 项目设置,并在那里添加私钥。

 Adding a private key

使用 CircleCI 部署

部署到 CircleCI 的基本设置已经完成。返回到 CircleCI config.yml文件中的Deploy Over SSH配置步骤。使用键扫描步骤ssh-keyscan -H ssh.pythonanywhere.com >> ~/.ssh/known_hosts。这将获取 PythonAnywhere 帐户的 SSH 密钥,并将它们添加到部署作业的 runners ~/.ssh/known_hosts文件中。

然后使用 SSH 进行身份验证,并使用以下命令从 GitHub 获取代码:

ssh $SSH_USER@$SSH_HOST "cd automating-flask-deployments-with-pythonanywhere; git pull"; 

注意,我们使用$SSH_USER$SSH_HOST变量来进行认证,同时屏蔽 SSH 用户和主机名。我们通过在环境变量下的 CircleCI 设置页面中添加SSH_USERSSH_HOST来实现这一点。你可以在这里阅读更多关于添加变量的内容。

 Declaring host and user

通过 SSH 访问 PythonAnywhere 的SSH_HOSTssh.pythonanywhere.comSSH_USER是您的 PythonAnywhere 帐户的用户名。

现在您应该能够使用 CircleCI 将您的应用程序部署到 PythonAnywhere 了。

 Sequential runs

如前所述,部署作业在构建和测试作业之后运行。这是因为部署作业要求构建和测试作业通过,如果构建和测试作业失败,部署作业也会失败。

 Successful deployment to PythonAnywhere

在 PythonAnywhere 上重新加载应用程序

要验证您的应用程序是否成功部署到 PythonAnywhere,您需要使用 PythonAnywhere 仪表板手动重新加载。您可以通过在 PythonAnywhere 帐户中创建一个 bash 脚本来避免这一步。该脚本会在部署后自动重新加载应用程序。调用这个脚本reload.sh,并添加命令来重新创建或更新<username>_pythonanywhere_com_wsgi.py文件。该命令控制应用程序的重新加载。

#!/bin/bash
touch var/www/<username>_pythonanywhere_com_wsgi.py 

将脚本添加到根文件夹中的reload.sh后,使用 PythonAnywhere 控制台使其可执行。在控制台中,运行以下命令:

chmod +x reload.sh 

每当在 PythonAnywhere 中检测到更改时,该命令使脚本在 bash 中可执行。现在,每次启动部署时,脚本都将被执行,应用程序也将被重新加载。祝贺您实现了我们使用 PythonAnywhere 实现自动化部署的目标!

结论

通过本教程,您已经能够学习如何为部署准备应用程序、配置 PythonAnywhere 环境以及使用 SSH 自动化部署。本教程还解释了 SSH 密钥在部署到 PythonAnywhere 中的重要性。如果满足所有参数(包括通过测试和 CircleCI 作业),您可以设置应用程序进行部署,从而丰富您的 CI/CD 实践知识。您了解了如何在每次部署后在 PythonAnywhere 上自动重新加载。我希望你喜欢这个教程,可以和你的团队一起使用它来提高你的 CI/CD 实践水平。直到下次,继续编码!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

将 Adonis.js API 自动部署到 Heroku | CircleCI

原文:https://circleci.com/blog/automating-the-deploy-of-an-adonis-api-to-heroku/

凭借 GitHub 上的 8000 多颗星和 Node.js 网站上的顶级 Node.js web 框架之一, Adonis.js 正在成为 Node.js 社区的主要参与者,成为构建 web 应用程序的可靠框架。它圆滑简洁的 API 使得它对开发人员非常友好,易于扩展,并且具有很高的性能。在本教程中,我们将使用 Adonis.js 构建一个简单的 API,并自动将其部署到 Heroku

先决条件

要遵循本教程,需要做一些事情:

  1. JavaScript 的基础知识
  2. 安装在您系统上的node . js(>= 8.0)
  3. 全局安装 Adonis.js CLI(运行npm i -g @adonisjs/cli进行安装)
  4. 一个英雄的账户
  5. 一个的账户
  6. GitHub 的一个账户

所有这些安装和设置,让我们开始教程。

创建 Adonis.js API 项目

首先,通过运行下面的命令,创建一个新的仅支持 API 的 Adonis.js 应用程序:

adonis new my-adonis-api --api-only 

这将搭建一个 Adonis.js 项目,该项目仅用于构建 API 端点,而非网页。该项目将被放置在adonis new命令后指定的my-adonis-api文件夹中。一旦搭建过程完成,进入项目的根目录(cd my-adonis-api)并运行以下命令来启动应用程序:

adonis serve --dev 

这将在http://localhost:3333启动一个本地服务器,您可以通过浏览器或在 CLI 上使用curl来访问它。点击这个根端点将返回下面的 JSON 数据:

{ "greeting": "Hello world in JSON" } 

为本地开发设置 SQLite

在开始编写 API 代码之前,我们需要一个本地开发数据库。默认情况下,我们的 Adonis.js 项目被配置为使用一个带有设置的SQLite数据库来连接到在./config/database.js中找到的数据库。

 module.exports = {

  connection: Env.get("DB_CONNECTION", "sqlite"),

  sqlite: {
    client: "sqlite3",
    connection: {
      filename: Helpers.databasePath(
        `${Env.get("DB_DATABASE", "development")}.sqlite`
      ),
    },
    useNullAsDefault: true,
    debug: Env.get("DB_DEBUG", false),
  },

  .....
} 

并且在项目根目录下的环境配置文件(.env)中,我们也有一些要使用的数据库的配置。

DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=adonis
HASH_DRIVER=bcrypt 

该配置定义了要使用的数据库连接(DB_CONNECTION)和数据库名称(DB_DATABASE)。这里的连接设置为sqlite,数据库名设置为adonis。那部分可以走了。

现在我们已经有了数据库,下一步是安装sqlite3 Node.js 包。这是连接到我们的SQLite数据库的驱动程序。通过运行以下命令安装软件包:

npm install sqlite3 --save 

安装了这个包之后,接下来我们将运行必要的迁移来设置我们的数据库模式。Adonis.js 使用迁移以编程方式设置和更新数据库模式,以便在不同的环境中轻松复制模式,并在开发团队和部署环境中保持一致性。

迁移文件可以在./database/migrations中找到。默认情况下,这个文件夹包含两个迁移,一个是针对users表的,另一个是针对tokens表的。要运行这些迁移并设置我们的数据库模式,请在项目的根目录下运行以下命令:

adonis migration:run 

上面的命令将在./database文件夹中创建一个新的adonis.sqlite数据库文件(因为它还不存在)并运行我们的迁移。

Migrations Run

我们现在已经为使用我们的SQLite数据库做好了一切准备。接下来,我们将为一个简单的User API 编写代码,该 API 允许我们创建和获取用户记录。

创建 API 的用户模型

为了开始开发我们的User API,我们需要定义我们的User模型。Adonis.js 模型可以在./app/Models文件夹中找到。在该目录中找到User.js文件(该文件是在搭建过程中创建的),并输入以下代码:

"use strict";

const Model = use("Model");

const Hash = use("Hash");

class User extends Model {
  static get table() {
    return "users";
  }

  static boot() {
    super.boot();

    this.addHook("beforeSave", async (userInstance) => {
      if (userInstance.dirty.password) {
        userInstance.password = await Hash.make(userInstance.password);
      }
    });
  }

  tokens() {
    return this.hasMany("App/Models/Token");
  }

  static async createUser(data) {
    const newUser = await this.create(data);

    return newUser;
  }

  static async getUsers(filter) {
    const allUsers = await this.query().where(filter).fetch();

    return allUsers;
  }
}

module.exports = User; 

在上面的代码中,我们创建了一个扩展基类ModelUser模型。然后我们定义一个table getter 来返回这个模型引用的表的名称。接下来,我们添加一个beforeSave钩子,确保我们的明文密码在用户创建时被保存到数据库之前被加密。

创建一个tokens关系函数来引用Token模型,它也存在于/app/Models目录中。这种关系允许我们在登录时获取每个用户的访问令牌。

最后,我们再创建两个模型函数,一个创建新用户(createUser),另一个基于查询过滤器获取用户列表(getUsers)。

创建 API 用户控制器

我们的下一个任务是创建一个User控制器。js 是一个模型视图控制器(MVC)框架,所以我们需要控制器来处理我们的 API 请求。

我们将在我们的控制器方法中做一些验证。这意味着我们需要用 Adonis.js 验证包来设置我们的项目。要在 Adonis.js 中实现验证,请使用以下命令安装验证程序包:

adonis install @adonisjs/validator 

一旦软件包安装完毕,将以下项目添加到./start/app.js中的providers数组:

 const providers = [
    ....,
    '@adonisjs/validator/providers/ValidatorProvider'
] 

通过运行以下命令创建新的控制器:

adonis make:controller User --type http 

该命令将在./app/Controllers中创建一个名为Http的新文件夹,并在该文件夹中创建一个新文件UserController.js。在新创建的控制器中,用下面的代码替换文件中的代码:

"use strict";

const Logger = use("Logger");
const { validate } = use("Validator");
const User = use("App/Models/User");

class UserController {
  async create({ request, response }) {
    const data = request.post();

    const rules = {
      username: `required|unique:${User.table}`,
      email: `required|unique:${User.table}`,
      password: `required`
    };

    const messages = {
      "username.required": "A username is required",
      "username.unique": "This username is taken. Try another.",
      "email.required": "An Email is required",
      "email.unique": "Email already exists",
      "password.required": "A password for the user"
    };

    const validation = await validate(data, rules, messages);

    if (validation.fails()) {
      const validation_messages = validation.messages().map((msgObject) => {
        return msgObject.message;
      });

      return response.status(400).send({
        success: false,
        message: validation_messages
      });
    }

    try {
      let create_user = await User.createUser(data);

      let return_body = {
        success: true,
        details: create_user,
        message: "User Successfully created"
      };

      response.send(return_body);
    } catch (error) {
      Logger.error("Error : ", error);
      return response.status(500).send({
        success: false,
        message: error.toString()
      });
    }
  } //create

  async fetch({ request, response }) {
    const data = request.all();

    try {
      const users = await User.getUsers(data);

      response.send(users);
    } catch (error) {
      Logger.error("Error : ", error);
      return response.status(500).send({
        success: false,
        message: error.toString()
      });
    }
  } //fetch
}

module.exports = UserController; 

在上面的代码中,我们创建了两个控制器函数(createfetch),它们分别创建一个新用户和获取一个用户列表。在create函数中,我们使用我们的Validator模块来验证request数据,以确保创建新用户的每个必选项都存在。我们还为验证设置了适当的错误消息。

在 API 上注册路线

现在是我们注册路线的时候了,这是开发 API 的最后一步。打开文件./start/routes.js,用以下代码替换其中的代码:

"use strict";

const Route = use("Route");

Route.get("/", () => {
  return { greeting: "Welcome to the Adonis API tutorial" };
});

//User routes
Route.group(() => {
  Route.post("create", "UserController.create");

  Route.route("get", "UserController.fetch", ["GET", "POST"]);
}).prefix("user"); 

在上面的文件中,我们将/路由中的默认greeting消息更改为Welcome to the Adonis API tutorial。接下来,我们注册分别映射到UserControllercreatefetch函数的/create/get路线。我们给这两个端点加上前缀/user来增加一些路由命名空间。

在 Postman 中测试端点

现在让我们通过调用我们的端点来测试我们的 API。我们将使用邮递员来测试我们的端点。确保您的应用程序正在运行。如果没有,运行adonis serve --dev再次启动。

下面是用户创建和获取用户的测试:

用户创建-验证失败

User Creation - Validation Failed

用户创建-成功

User Creation - Successful

用户获取

User Fetch

为生产部署设置 Heroku 和 MySQL

到目前为止,我们所做的一切都可以在本地机器上完美地工作,但是本教程的目的是让我们的 API 托管在生产环境中,并自动化部署过程。因此,我们将把 API 托管在 Heroku 平台上,而不是在我们的机器上运行。此外,我们将使用MySQL,而不是使用SQLite,这是一个更健壮的关系数据库管理系统,适用于生产环境。

我们还想确保当我们在本地机器上运行时,使用SQLite,当在生产中运行时,我们的代码自动切换到MySQL

为了在 Heroku 上托管我们的 API,我们需要创建一个 Heroku 应用程序。登录您的 Heroku 帐户并创建一个新的应用程序。

Heroku App

接下来,我们需要创建一个远程MySQL实例。幸运的是,我们能够访问 Heroku 上的附加组件。其中一个附加组件是通过 ClearDBMySQL实例。

注意 : 要在你的 Heroku 应用程序上安装插件,你需要在 Heroku 上设置账单。请确保在您的帐户设置中添加一张付费卡。

要添加一个MySQL插件,进入应用程序的Resources选项卡,搜索MySQL,如下所示。

Add MySQL

选择 ClearDB 选项来设置MySQL实例。在附加弹出屏幕上,选择免费的Ignite计划。

Add MySQL

点击 Provision 设置数据库。一旦完成,它将被添加到您的附加组件列表中,并且一个新的CLEARDB_DATABASE_URL环境变量将被添加到您的应用程序中。它可以在你的申请的Settings页面的Config Vars部分找到。

在该页面上,单击显示配置变量来显示您的环境变量。现在,将另外两个环境变量添加到该列表中:

  • APP_KEY:在.env文件中找到您的应用程序的 API 密钥
  • DB_CONNECTION:为了确保MySQL用于生产而不是SQlite,将此设置为mysql

设置我们的生产数据库的最后一步是在./config/database.js中配置我们的mysql连接。我们需要url-parse包来帮助我们正确解析 ClearDB 上的MySQL数据库的连接字符串。我们还需要mysql包作为我们的驱动程序来连接到我们的生产数据库。使用以下命令安装这些软件包:

npm install url-parse mysql --save 

现在,用下面的代码替换./config/database.js中的所有内容:

"use strict";

const Env = use("Env");

const Helpers = use("Helpers");
const URL = require("url-parse");
const PROD_MYSQL_DB = new URL(Env.get("CLEARDB_DATABASE_URL"));

module.exports = {
  connection: Env.get("DB_CONNECTION", "sqlite"),

  sqlite: {
    client: "sqlite3",
    connection: {
      filename: Helpers.databasePath(
        `${Env.get("DB_DATABASE", "adonis")}.sqlite`
      )
    },
    useNullAsDefault: true,
    debug: Env.get("DB_DEBUG", false)
  },

  mysql: {
    client: "mysql",
    connection: {
      host: Env.get("DB_HOST", PROD_MYSQL_DB.host),
      port: Env.get("DB_PORT", ""),
      user: Env.get("DB_USER", PROD_MYSQL_DB.username),
      password: Env.get("DB_PASSWORD", PROD_MYSQL_DB.password),
      database: Env.get("DB_DATABASE", PROD_MYSQL_DB.pathname.substr(1))
    },
    debug: Env.get("DB_DEBUG", false)
  }
}; 

在上面的文件中,我们已经配置了我们的mysql连接,以便在生产中使用我们的 ClearDB 实例,并且sqlite连接将在我们的本地机器上用作后备。

在 CircleCI 上配置项目

我们的下一个任务是在 CircleCI 建立我们的项目。从将你的项目推送到 GitHub 开始。

接下来,转到 CircleCI 仪表板上的添加项目页面。

Add Project - CircleCI

点击设置项目开始。这将加载下一个屏幕。

Add Config - CircleCI

在设置页面上,单击手动添加以指示 CircleCI 我们将手动添加配置文件,而不使用显示的示例。接下来,您会得到提示,要么下载管道的配置文件,要么开始构建。

Build Prompt - CircleCI

点击开始构建开始构建。这个构建将会失败,因为我们还没有设置配置文件。我们稍后会做这件事。

我们需要在 CircleCI 控制台上做的最后一件事是为我们刚刚添加的项目设置环境变量。这将使它能够对我们的 Heroku 应用程序进行身份验证访问,以进行部署。

点击管道页面上的项目设置进入您的项目设置(确保您的项目是当前选择的项目)。

Project settings - CircleCI

在这个页面上,点击侧面菜单上的环境变量

Environment variables - CircleCI

点击添加环境变量添加新的环境变量。

Add Environment variable - CircleCI

添加以下环境变量:

  • HEROKU_APP_NAME:这是您的 Heroku 应用程序的名称(在本例中为my-adonis-api-app)
  • HEROKU_API_KEY:在账户设置下你的 Heroku 账户的账户标签下找到你的 Heroku 账户 API key。

添加完成后,您现在已经在 CircleCI 控制台上为部署到 Heroku 做好了一切准备。

自动化 API 的部署

我们现在正处于将 Adonis.js API 自动部署到 Heroku 主机平台的最后阶段。在我看来,这是最简单的一步。

我们需要创建一个 Heroku Procfile来为 Heroku 提供关于如何部署我们的应用程序的指令。在项目的根目录下,创建一个名为Procfile(没有文件扩展名)的文件。将以下命令粘贴到其中:

release: ENV_SILENT=true node ace migration:run --force
web: ENV_SILENT=true npm start 

在上面的文件中,我们指示 Heroku 使用node ace migration:run --force运行我们的迁移。这是对adonis migration:run命令的替代。这是有意使用的,因为 Heroku 环境没有像我们在本地计算机上那样全局安装 Adonis CLI。这一步是在 Heroku 的release阶段完成的。

接下来,我们使用npm start命令指示 Heroku 运行我们的应用程序。

我们在两个命令前都添加了前缀ENV_SILENT=true,以抑制一些 Adonis.js 警告,因为它试图查找一个.env文件,该文件的用途已经被我们之前在 Heroku 应用程序中设置的环境变量所替代。

现在我们可以编写部署脚本了。在项目的根目录下,创建一个名为.circleci的文件夹和一个名为config.yml的文件。在config.yml文件中,输入以下代码:

version: 2.1
orbs:
  heroku: circleci/heroku@0.0.10
workflows:
  heroku_deploy:
    jobs:
      - heroku/deploy-via-git 

在上面的配置中,我们引入了 Heroku orb ( circleci/heroku@0.0.10),它自动为我们提供了一组强大的 Heroku 任务和命令。其中一个任务是heroku/deploy-via-git,它直接从你的 GitHub repo 将你的应用程序部署到你的 Heroku 账户。这项工作已经负责安装 Heroku CLI、安装项目依赖项和部署应用程序。它还获取我们的 CircleCI 环境变量,以便顺利部署到我们的 Heroku 应用程序。

提交对我们的项目所做的所有更改,并推送到您的 repo 来触发部署。如果遵循了所有的说明,您就已经构建了一个成功的部署管道。

Build Successful - CircleCI

要查看部署的幕后操作,请单击 build

Build Details - CircleCI

如果您仔细观察,上面的屏幕显示我们的迁移运行成功。要完全确认我们的应用程序已经成功部署,请访问站点https://[APP_NAME].herokuapp.com的默认 Heroku 地址。在我们的例子中,这是https://my-adonis-api-app.herokuapp.com/

API Live - Heroku

注意 : 我在浏览器上使用的是 JSON 格式化程序,所以你的显示格式可能会有所不同。

现在,您可以从 Postman 对您的生产 API 运行一些测试。

API Live - Heroku

API Live - Heroku

厉害!

从上面的屏幕可以看出,我们现在已经有了一个部署到 Heroku 的工作生产 API。

结论

使用 Adonis.js 构建 API 很有趣。在本教程中,我们学习了如何使用 CircleCI 创建一个自动连续部署(CD)管道,以便在每次将代码推送到我们的存储库时,自动将 Adonis.js API 部署到 Heroku。我们还学习了为我们的开发和生产环境配置不同的数据库系统。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

自动扩展 AWS 中的自托管跑步者以满足需求| CircleCI

原文:https://circleci.com/blog/autoscale-self-hosted-runners-aws/

自托管运行器允许您在您的私有云或内部托管您自己的可扩展执行环境,让您能够更加灵活地定制和控制您的 CI/CD 基础架构。具有独特安全或计算需求的团队可以在五分钟内设置并开始使用自托管跑步者。设置完成后,您的团队可以访问 CircleCI 云平台上可用的一系列流行功能,包括并行性和测试分割、使用 SSH 进行调试,以及直接在 CircleCI UI 中管理自托管跑步者。

大多数团队在整个工作日都会经历资源需求的波动,维护未使用的计算能力会导致不必要的成本。为了防止这种情况,您可以实施一个扩展解决方案来根据队列中的作业数量自动启动和关闭自托管运行程序,使您能够按需访问所需的计算能力,而不会有在闲置资源上浪费资金的风险。在本教程中,您将学习如何使用 AWS 自动缩放组(ASG)为 CircleCI 的自托管跑步者设置基本的自动缩放解决方案。如果您对 Kubernetes 集群中的自动伸缩运行器感兴趣,您还可以查看我们的容器运行器 /)选项。

自托管运行程序—完全由您控制的执行环境

自托管运行器提供了一个完全可定制的执行环境。使用自托管运行程序时,CircleCI 会将作业发送到您的计算机,以执行所需的 CI/CD 步骤。如果您的应用程序需要访问内部数据库或敏感资源以进行适当的测试,您可以将它部署到防火墙后的自托管运行程序。

Self-hosted runners diagram

跑步者被设计成尽可能易于配置、管理和部署。下图提供了与您将在本教程中实现的解决方案相关的转轮行为的一些详细信息。更多关于自助跑步者的信息可以在常见问题跑步者概念页面上找到。

  • 资源类:自主运行者被分组为唯一命名的resource classes,用于识别和分配任务。所有自托管运行程序资源类必须有一个唯一的名称,以确保可以从 CircleCI web 界面中正确地识别和管理运行程序。
  • 计算一致性:保持自托管运行程序的底层计算在资源类中的一致性是一个最佳实践,每台机器都应该配置相同的架构和环境。这可以防止任何意外行为,并使故障排除更加容易。在本教程中,我们将使用 EC2 模板来确保 runner 代理得到统一配置。
  • 作业排队:如果没有可用的运行程序,CircleCI 作业将等待,直到所需资源类的自托管运行程序可用。如果您已经实现了自动缩放解决方案,这将为您的自托管运行程序提供启动时间
  • 连接需求:跑步者为新工作轮询 CircleCI,因此不需要来自互联网的连接。这意味着您不需要配置任何传入防火墙规则或端口转发,并且运行者不需要静态公共 IP 地址。

既然您已经熟悉了使用 CircleCI 自托管运行器的基础知识,那么就让我们开始学习教程吧。

通过 AWS 自动缩放组自动缩放自托管跑步者

这个示例实现演示了如何使用 AWS 自动伸缩功能来伸缩自托管运行程序,从而根据需求增加或减少可用的自托管运行程序的数量。

示例库包括一个基本的 Node.js 应用程序和一个 CircleCI 管道配置来测试它。为了执行这个 CircleCI 管道,您将设置一个自托管的 runner 作为基于 Ubuntu 的 AWS EC2 启动模板。launch template 和 Auto Scaling 组将用于根据由给定运行程序资源类的运行程序 API 提供的队列深度(队列中的作业数量)值启动实例——所有这些都由定期检查 API 的 Lambda 函数触发。

Runners with AWS Auto Scaling

在 CircleCI 中设置 runner 资源类

第一步是创建一个资源类。您可以在 CircleCI UI 中点击一次来完成此操作。

Create custom resource class

一旦创建了资源类,记下为它生成的认证令牌不会再播了。在下一步中,您将需要此令牌向 CircleCI 认证自托管跑步者。

准备自托管 runner 安装脚本

接下来,您需要创建一个安装脚本,以便在 AWS 中自动安装和配置自托管运行器。当从下一步中创建的模板启动 AWS 实例时,这个 Bash 脚本将在完成引导后作为 root 用户被调用。您可以在示例存储库中的文件AWS _ config/install _ runner _ Ubuntu . sh中找到该脚本的模板。

使用上一步中的资源类和令牌,更新脚本模板中的以下变量:

  • RUNNER_NAME:可以是您希望为您的跑步者指定的任何字母数字名称,因为它将出现在 CircleCI UI 中。
  • AUTH_TOKEN:应该替换为在创建资源类期间出现在 UI 中的资源类令牌。

然后,您需要添加一些步骤,以便在作业运行时安装任何您希望作为执行环境一部分的依赖项或包。这个必须在启用和启动 runner 服务之前在脚本中完成。

例如,如果您正在开发和测试一个 Node.js 应用程序,您将希望向脚本中添加安装 Node.js 的步骤。

# aws_config/install_runner_ubuntu.sh

#------------------------------------------------------------------------------
# Configure your runner environment
# This script must be able to run unattended - without user input
#------------------------------------------------------------------------------
apt install -y nodejs npm 

由于该脚本将在启动时为自动缩放组中创建的每个实例执行,因此它必须能够无人值守地运行(无需用户输入)。

在安装脚本中,注意 CircleCI runner 的短暂(1m ) idle_timeout 时间。这有助于缩减不再需要的自托管运行程序和实例。

# aws_config/install_runner_ubuntu.sh

#------------------------------------------------------------------------------
# Install the CircleCI runner configuration
# CircleCI Runner will be executing as the configured $USERNAME
# Note the short idle timeout - this script is designed for auto-scaling scenarios - if a runner is unclaimed, it will quit and the system will shut down as  defined in the below service definition
#------------------------------------------------------------------------------

cat << EOF >$CONFIG_PATH
api:
 auth_token: $AUTH_TOKEN
runner:
 name: $UNIQUE_RUNNER_NAME
 command_prefix: ["sudo", "-niHu", "$USERNAME", "--"]
 working_directory: /opt/circleci/workdir/%s
 cleanup_working_directory: true
 idle_timeout: 1m
 max_run_time: 5h
 mode: single-task
EOF 

并注意相关服务ExecStopPost设置中的关机命令。

# aws_config/install_runner_ubuntu.sh

#------------------------------------------------------------------------------
# Create the service
# The service will shut down the instance when it exits - that is, the runner  has completed with a success or error
#------------------------------------------------------------------------------

cat << EOF >$SERVICE_PATH
[Unit]
Description=CircleCI Runner
After=network.target
[Service]
ExecStart=$prefix/circleci-launch-agent --config $CONFIG_PATH
ExecStopPost=shutdown now -h
Restart=no
User=root
NotifyAccess=exec
TimeoutStopSec=18300
[Install]
WantedBy = multi-user.target
EOF 

这确保了任何没有请求作业的空闲运行者和任何已经完成任务的运行者将被快速终止,以避免浪费资源。

在配置您的服务时,如果您需要进行更改,请参考 systemd 文档。如果服务运行时间超过 5 小时(18300 秒),前面的示例将终止服务,这与跑步者的max_run_time相匹配。

创建启动模板

登录 AWS 管理控制台并导航至管理 EC2 的服务页面。您需要创建一个启动模板,并填写如下字段:

  • 给你的新模板起一个合理的名字,比如 CCI-转轮-模板。
  • 选择“提供指导以帮助我设置一个可用于 EC2 自动缩放的模板”复选框
  • 对于启动模板内容 AMI,选择快速启动,然后选择 Ubuntu 22.04 LTS
  • 选择一个实例类型——您需要根据您的需求选择一个。
  • 选择一个Key pair进行登录。当您需要通过 SSH 登录来对实例进行故障排除时,这很有帮助。
  • 对于网络设置和安全组,选择一个现有的安全组或创建一个。明智的做法是只允许来自可信 IP 地址的 SSH,并阻止所有其他传入流量。
    • 自托管 runner 轮询 CircleCI 的新工作,不需要任何传入连接。
  • 对于高级网络配置,点击添加网络接口并启用为该接口自动分配公共 IP
  • 配置存储—如果您认为需要,可以增加每个实例的硬盘大小。
  • 对于高级细节,将 install_runner_ubuntu.sh 的内容全部复制粘贴到用户数据字段中。当实例启动时,该字段的内容将作为 shell 脚本执行
  • 其他一切都可以保留默认值。

请注意,资源类认证令牌存储在启动模板中(作为 runner 安装脚本的一部分),所以不要共享它!

创建自动缩放组

接下来,您需要创建一个自动缩放组。为每个部分输入这些值:

  • 第一步:选择启动模板或配置。
    • 给你的团队起一个合理的名字,比如cci-runner-auto-scaling-group
    • 确保您创建的模板被设置为Launch template
    • 其他一切都保持原样。
  • 步骤 2: 选择实例启动选项。
    • 对于实例启动选项,请选择一个可用性区域和子网。如果您的实例需要与其他 AWS 资产通信,请将它们分配到适当的区域/子网。
    • 其他一切都保持原样。
  • 第三步:配置高级选项。
    • 让一切保持原样。
  • 步骤 4: 配置组大小和扩展策略。
    • 将所需容量、最小容量和最大容量设置为 0。在后续步骤中创建的 Lambda 函数将更新这些值,以满足您的缩放要求。
    • 选中“启用扩展保护”复选框,以防止实例过早终止。作业可能会不按提交顺序完成,从而降低队列深度,并导致自动扩展组终止较旧的实例,即使它们可能不是已完成任务的自托管运行程序。
    • 其他一切都保持原样。
  • 跳过步骤 5 和 6。
  • 第七步:复习。
    • 检查您的配置并保存。

一旦启动,实例将负责自己的生命周期。自托管运行程序将在基于idle_timeout flag的短暂空闲时间后终止。因为运行程序处于单任务模式,所以自托管运行程序也将在作业完成时优雅地终止(成功或失败)。我们还将服务配置为在实例退出时关闭实例。

创建 IAM 策略和角色

Lambda 函数需要权限来监控队列和更改自动缩放参数。您需要设置一个身份和访问管理(IAM)策略和相关角色来授予这些权限。

创建一个具有所需权限的策略。您可以从我们的示例存储库中的文件AWS _ config/lambda _ iam . JSON或者从下面的代码块中复制并粘贴策略。该策略将授予更新自动缩放组和从 AWS 机密管理器读取机密的权限,这是 Lambda 函数所需的两个权限。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "autoscaling:UpdateAutoScalingGroup",
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": "*"
        }
    ]
} 

一旦建立了策略,为您的 Lambda 函数创建一个新角色,并将新策略分配给该角色。

同样,保持命名的一致性,以便您可以在以后轻松地找到和识别 AWS 组件。给 IAM 组件起一个合理的名字,比如:cci-runner-lambda-iam-policycci-runner-lambda-iam-role

创造(和保持)秘密

AWS secrets manager 提供了一种安全的方式来存储 API 密钥和其他敏感信息,以便在 Lambda 函数中使用。机密以键/值对的形式存储。用这些选项创建一个秘密:

  • 第一步:选择密件类型。
    • 选择其他类型的密码
    • 添加以下键/值对:
      • resource_class_:CCI 跑步者的资源类,格式为用户名/类名。
      • 这将是一个用于轮询 runner API 的 CircleCI 个人令牌——它不是上面安装脚本中使用的 runner 令牌。
    • 将加密密钥设置为aws/secretsmanager
  • 第二步:配置你的秘密。
    • 给你的秘密取一个合理的名字,比如 CCI-朗纳-拉姆达-秘密。
    • 让其余的保持原样。
  • 第三步:配置旋转—可选。
  • 第四步:复习。
    • 查看并保存您的秘密。
    • 此时不需要复制和粘贴生成的代码——它已经包含在存储库中的示例 Lambda 函数中。请务必记下机密名称和区域。

创建 Lambda 函数

AWS Lambda 函数是执行代码的无服务器函数。此示例使用一个按计划触发的 Lambda 函数来运行一个 Python 脚本,该脚本检查 CircleCI runner API 并改变自动缩放组以增加或减少运行实例的数量。从头开始创建一个 Lambda 函数,配置如下:

  • 给你的函数起一个合理的名字,比如cci-runner-lambda-function
  • 将运行时间设置为Python 3.8
  • 将架构设置为x64_64
  • 点击执行角色,然后使用已有的角色。选择您之前创建的 IAM 角色。

aws _ config/lambda _ function . py文件的内容复制并粘贴到 AWS 控制台的函数源中。

# aws_config/lambda_function.py

import json, urllib3, boto3, base64, os

# This script polls the number of unclaimed tasks for a Circle CI runner class, and sets the parameters for an AWS Auto Scaling group 
# It uses the CircleCI runner API https://circleci.com/docs/2.0/runner-api/
# It requires the included IAM role and should be triggered every minute using an EventBridge Cron event

# Retrieve environment variables
secret_name   = os.environ['SECRET_NAME'] 
secret_region = os.environ['SECRET_REGION']
auto_scaling_max = os.environ['AUTO_SCALING_MAX'] 
auto_scaling_group_name = os.environ['AUTO_SCALING_GROUP_NAME']
auto_scaling_group_region = os.environ['AUTO_SCALING_GROUP_REGION']

# Function to retrieve secrets from AWS Secrets manager
# https://aws.amazon.com/secrets-manager/
def get_secret(secret_name, region_name):
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region_name
    )

    get_secret_value_response = client.get_secret_value(
        SecretId=secret_name
    )

    if 'SecretString' in get_secret_value_response:
        secret = get_secret_value_response['SecretString']
        return(secret)
    else:
        decoded_binary_secret = base64.b64decode(get_secret_value_response['SecretBinary'])
        return(decoded_binary_secret)

# Make a HTTP GET request with the given URL and headers
def get_request(url, headers):
    http = urllib3.PoolManager()
    r = http.request('GET', url, headers=headers)
    r_json = json.loads(r.data.decode("utf-8"))
    return(r_json)

# The handler function - is executed every time the Lambda function is triggered
def lambda_handler(event, context):
    # Get secrets
    secrets = json.loads(get_secret(secret_name, secret_region))

    # Configure Runner API endpoint https://circleci.com/docs/2.0/runner-api/#endpoints
    endpoint_url = 'https://runner.circleci.com/api/v2/tasks?resource-class=' + secrets['resource_class']
    headers = {'Circle-Token': secrets['circle_token']}

    # Get result from API endpoint
    result = get_request(endpoint_url, headers)

    # Configure Runner API endpoint https://circleci.com/docs/2.0/runner-api/#endpoints
    endpoint_url = 'https://runner.circleci.com/api/v2/tasks/running?resource-class=' + secrets['resource_class']
    headers = {'Circle-Token': secrets['circle_token']}

    # Get result from API endpoint
    result_running = get_request(endpoint_url, headers)

    total_desired = int(result["unclaimed_task_count"]) + int(result_running["running_runner_tasks"]) 

    # Update the auto scaling group with a desired number of instances set to the number of jobs in the queue, or the maximum, whichever is smallest
    instances_min = 0
    instances_max = int(auto_scaling_max)
    instances_desired = min(total_desired, int(auto_scaling_max))
    # Set the Auto Scaling group configuration
    client = boto3.client('autoscaling', region_name=auto_scaling_group_region)
    client.update_auto_scaling_group(
        AutoScalingGroupName=auto_scaling_group_name,
        MinSize=instances_min,
        MaxSize=instances_max,
        DesiredCapacity=instances_desired
    )  

    # Lambda functions should return a result, even if it isn't used
    return result["unclaimed_task_count"] 

在 Lambda 函数的 Configuration 选项卡下,导航到环境变量,然后编辑并添加以下键/值对:

  • SECRET_NAME:上面创建的秘密的名称。
  • SECRET_REGION:上面那个秘密的区域。
  • AUTO_SCALING_MAX:要加速旋转的最大实例数,为整数。
    • 我们建议将最大值设置为 CircleCI 计划中自托管运行程序的并发限制。
  • AUTO_SCALING_GROUP_NAME:自动缩放组的名称。
  • AUTO_SCALING_GROUP_REGION:自动缩放组的区域。

其他一切都保留默认值。

按计划触发 Lambda 功能

Lambda 函数可以通过多种方式触发。在这种情况下,它将按计划执行。我们建议每分钟调用一次该函数来检查队列深度,并及时做出适当的调整。

要进行设置,进入 Lambda 函数编辑屏幕,点击添加触发器。搜索并选择event bridge(cloud watch Events),然后选择创建新规则。填写以下详细信息:

  • 给你的规则起个合理的名字,比如cci-runner-scheduled-trigger
  • 将规则类型设置为调度表达式
  • 输入值cron(0/1 * * * ? *)每分钟触发一次功能。

点击添加完成定时触发的设置。

测试和部署

这样,您的自动缩放流道解决方案的所有移动部件都就位了。现在您可以将它添加到您的 CircleCI 配置中并开始使用它了!

在 Lambda 函数编辑屏幕中,返回到代码选项卡并单击 Deploy 。就是这样!一切都在运行,随时可以使用。

要进行测试,请转到测试选项卡。让一切保持原样(防止测试被保存)。点击测试

结果将是成功或失败,如果需要,您将能够调试您的函数代码。如果一切恢复正常,您就可以开始运行了。

如果您希望监控您的函数,您可以使用 Lambda 中的 monitor 选项卡来确保您的函数按照您在上一节中设置的时间表运行。

在自动缩放的自托管运行程序上运行 CircleCI 作业

要在新的自动缩放资源类上运行 CircleCI 作业,首先需要将该资源类添加到您的 CircleCI 配置文件中。

。示例存储库中的 circleci/config.yml 文件使用了machineresource_class选项。

# .circleci/config.yml

version: 2.1

workflows:
  testing:
    jobs:
      - runner-test

jobs:
  runner-test:
    machine: true
    resource_class: NAMESPACE/-NAMENAME # Update this to reflect your self-hosted runner resource class details 
    steps:
      - run: echo "Hi I'm on Runners!"
      - run: node --version
      - checkout
      - run:
          command: npm install
          name: Install Node.js app dependencies
      - run:
          command: npm run test
          name: Test app 

一旦您运行了一个作业,自托管的运行程序将在启动时出现在 CircleCI web 界面中。当作业出现在队列中时,AWS Lambda 功能将触发自动缩放组来增加其容量。当实例准备好时,作业将从 CircleCI 发送到 runner 来执行。

New runners resource class

您可以通过 CircleCI web 界面监控自托管跑步者的状态。完成后,运行器将终止它正在运行的实例,自动缩放组将减少所需的实例数量,以匹配新的队列长度。

node.js-circleci-runner

您的管道结果将与您的 CircleCI 云作业一起发送回 CircleCI UI。

Successful parallel test runs

在 AWS 端,您将能够看到随着队列深度的变化和作业的完成,Lambda 函数正在调整自动缩放组。

AWS - auto-scaling groups

CircleCI 是一个灵活的 CI/CD 平台,以您的方式工作

使用自托管运行程序,您可以完全控制 CI/CD 管道,包括执行环境以及存储和处理数据的位置。

CircleCI 鼓励 DevOps 最佳实践——但它并没有规定你应该如何做事。您需要能够发挥团队的优势,利用您的工具链所允许的全部灵活性,同时保持合规性和安全性。您可以从使用预构建的执行环境开始,随着您的需求变得更加专业化,部署您自己定制的、可扩展的自托管运行程序,并通过简单的配置更改开始使用它们,而不必彻底检查您的整个 CI/CD 工具链。

今天你可以通过注册一个免费计划开始使用 CircleCI。您的免费计划提供了开始构建您自己的自动化 CI/CD 管道来测试和部署您的代码所需的一切。所有计划中都包含自托管跑步者,当您准备开始尝试 CircleCI 提供的高级功能时,您可以使用这些跑步者。

您可以通过以下资源了解有关在 CircleCI 管道中使用自托管运行器的更多信息:

AWS EC2 - AWS ECR | CircleCI

原文:https://circleci.com/blog/aws-ecr-auth-support/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


TL:DR; CircleCI 2.0 现在支持直接从 Docker executor 向 AWS EC2 容器注册中心(ECR)认证。这意味着您可以使用 ECR 中的私有 Docker 映像作为您的构建映像。查看文件

CircleCI 2.0 带来了原生 Docker 支持。一个项目可以构建在 2.0 之上,使用一个公共 Docker 映像作为执行环境。精心制作一个轻量级的 CI 环境,根据您的项目的确切需求进行定制,并且可以对其进行快照以便反复使用,这已经变得很平常了。用户喜欢拥有这种能力,但表示他们也希望支持私有图像。不久之后,auth键被引入来支持登录到 Docker 注册表,以获取私有映像作为您的执行环境:

jobs:
  build:
    docker:
      - image: acme-private/private-image:321
        auth:
          username: mydockerhub-user  # can specify string literal values
          password: $DOCKERHUB_PASSWORD  # or project UI environment variable reference 

这对于支持标准docker login凭证的注册管理机构(即 Docker Hub)来说非常有效。如果您有 Docker 注册中心的用户名和密码,并且这是您所需要的,那么您就很好。输入 AWS 的 ECR。

AWS ECR 提供 Docker 注册服务,但不提供适当的docker login凭证。相反,根据 AWS CLI 文档,您需要运行aws ecr get-login,这将生成一个带有临时登录凭证的docker login shell 命令。这些凭据仅持续 12 小时,因此不适合在 CI 环境中使用。

我们现在增加了专门针对 AWS ECR 的内置支持。您可以通过以下三种方式之一从 ECR 开始使用私有映像:

  1. 使用 CircleCI AWS 集成设置您的 AWS 凭证。
  2. 使用标准的 CircleCI 私有环境变量设置您的 AWS 凭证。
  3. 使用aws_auth.circleci/config.yml中指定您的 AWS 凭证:
version: 2
jobs:
  build:
    docker:
      - image: account-id.dkr.ecr.us-east-1.amazonaws.com/org/repo:0.1
        aws_auth:
          aws_access_key_id: AKIAQWERVA  # can specify string literal values
          aws_secret_access_key: $ECR_AWS_SECRET_ACCESS_KEY  # or project UI envar reference 

选项 2 和 3 实际上是相同的,只是选项 3 允许您为凭证指定任何想要的变量名。当您对不同的基础设施有不同的 AWS 凭证时,这就很方便了。例如,假设您的 SaaS 应用运行速度更快的测试,并在每次提交时部署到暂存基础架构,而对于 Git 标签推送,我们在部署到生产之前运行全面的测试套件:

version: 2
jobs:
  build:
    docker:
      - image: account-id.dkr.ecr.us-east-1.amazonaws.com/org/repo:0.1
        aws_auth:
          aws_access_key_id: $AWS_ACCESS_KEY_ID_STAGING
          aws_secret_access_key: $AWS_SECRET_ACCESS_KEY_STAGING
    steps:
      - run:
          name: "Every Day Tests"
          command: "testing...."
      - run:
          name: "Deploy to Staging Infrastructure"
          command: "something something darkside.... cli"
  deploy:
    docker:
      - image: account-id.dkr.ecr.us-east-1.amazonaws.com/org/repo:0.1
        aws_auth:
          aws_access_key_id: $AWS_ACCESS_KEY_ID_STAGING
          aws_secret_access_key: $AWS_SECRET_ACCESS_KEY_STAGING
    steps:
      - run:
          name: "Full Test Suite"
          command: "testing...."
      - run:
          name: "Deploy to Production Infrastructure"
          command: "something something darkside.... cli"

workflows:
  version: 2
  main:
    jobs:
      - build:
          filters:
            tags:
              only: /^\d{4}\.\d+$/
      - deploy:
          requires:
            - build
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^\d{4}\.\d+$/ 

在 CircleCI 2.0 上享受使用来自 AWS ECR 的私人图像。始终注意验证您没有从映像或私有 envars 的构建输出中泄漏任何敏感信息。

将自定义映像持续部署到 Azure 容器注册表| CircleCI

原文:https://circleci.com/blog/azure-custom-images/

Azure 容器注册中心是微软自己的用于 Docker 图片的托管平台。这是一个私有注册表,您可以在其中存储和管理私有的docker容器图像和其他相关工件。然后,这些映像可以被拉出并在本地运行,或者用于托管平台的基于容器的部署。

在本教程中,您将学习如何创建一个定制的docker映像,并不断地将其部署到 Azure 容器注册中心。

先决条件

要遵循本教程,需要做一些事情:

  1. 您系统上安装的 Node.js (版本> = 10.3)
  2. 一个蓝色的账户
  3. 一个的账户
  4. GitHub 的一个账户
  5. 安装在您系统上的 Azure CLI
  6. Docker 安装在您的系统上

安装并设置好所有这些之后,您就可以开始本教程了。

在 Azure 上创建容器注册中心

首先,您需要在 Azure 上创建一个容器注册中心来存储和构建您的 Docker 容器。点击 Azure 门户主页上的创建按钮,进入容器 - > 容器注册中心

Create registry - Azure

在注册表创建页面上,填写适当的信息,包括注册表的名称。

Create registry Page - Azure

点击审核+创建按钮。在查看页面中,确认您的注册表信息,点击创建触发注册表创建过程。

如果您更喜欢使用 Azure CLI 来创建您的资源,您也可以使用此命令来创建注册表:

az acr create --name circlecigwpregistry --resource-group Demos-Group --sku standard --admin-enabled true 

注意,在这个过程中,我已经将我的注册表命名为circlecigwpregistry。如前所述,您可能希望使用不同的名称。请确保在本教程后面的所有步骤中替换您的注册表名称。

克隆演示项目

下一步是获取 Node.js 项目,该项目将用于构建一个 Docker 容器,并将其托管在 Azure 容器注册表中。运行以下命令来克隆项目:

git clone --single-branch --branch base-project https://github.com/CIRCLECI-GWP/azure-custom-images.git 

这个项目是一个基本的 Node.js API,有两个端点:

  • 根端点(/)简单地打印出一条欢迎消息
  • /todos端点返回一组todo任务对象,还包含一个用于测试/todos端点的测试套件

编写和构建自定义 Docker 映像

下一步是编写一个自定义 Docker 映像来托管应用程序。进入项目文件夹(cd azure-custom-images)的根目录,创建Dockerfile文件。将以下代码输入到文件中:

FROM node:current-alpine

WORKDIR /app

COPY ./package.json ./

RUN npm install

COPY ./ ./

CMD ["npm", "start"] 

docker文件使用node:current-alpine作为其基础映像,并将容器中的工作目录(将基于该映像创建)设置为/app。首先使用npm installpackage.json文件拉入/app来安装依赖项。最后,项目的剩余内容被复制到映像中,应用程序将在运行时使用npm start启动。参考本页了解更多关于 Docker 图像和容器的上下文。

通过在项目的根目录下运行以下命令并使用名称customnodeimage对其进行标记来构建映像。

docker build -t customnodeimage . 

注意命令末尾的点(.)。不要忘记加上这一点;它将构建上下文定义为当前目录。

构建完成后,您可以运行下面的命令,将 Node.js 应用程序作为基于customnodeimage Docker 映像的容器来运行。

docker run -p 1337:1337 customnodeimage 

CLI 中该命令的输出应该包含:Server running on localhost:1337行,这表示应用程序正在运行。

前面的命令将容器(1337)中的应用程序端口转发到您机器上的端口1337。您可以导航到 http://localhost:1337 查看应用基础端点。

App Local Run - Browser

你也可以访问/todos路线来装载todo对象。

App Local Run - Browser

要将这个映像放到您的注册表中,运行下面的命令从 docker 文件中在ACR上构建映像。用您创建的注册表的名称替换<container_registry_name>

az acr build --registry <container_registry_name> --image customnodeimage . 

您现在可以在注册表上查看此图像。在 Azure 资源页面上单击您的注册表。应该会列在 Azure 门户首页最近资源下。进入服务->-仓库。

Image - ACR

为容器设置构建任务

当对应用程序代码或Dockerfile进行任何更新时,我们希望在ACR上推送和构建更新,以便在ACR上始终有一个更新的映像。这就是持续部署。

幸运的是,ACR可以被配置为监视远程存储库上的一个分支,并根据该分支的更新触发构建。持续部署映像的策略是在远程存储库上为映像创建一个分支,ACR将监视该分支以触发构建。一旦应用程序测试通过,更新就会自动推送到被监视的分支,从而使用更新的内容触发映像的重建。

首先,运行项目根目录下的命令rm -rf .git删除任何现有的git历史,然后将项目推送到 GitHub

接下来,在 GitHub 存储库中,从主分支创建一个buildimage分支。

Create branch - GitHub

要查看刚刚创建的分支,您需要一个 ACR 任务。要创建一个ACR任务,你需要一个 GitHub 个人访问令牌

要创建 GitHub 令牌,请在您的 GitHub 页面上进入设置->-开发者设置。在侧边菜单上,点击个人访问令牌,然后点击生成新令牌按钮。确认您的密码以继续,您将被带到令牌创建页面。

输入密钥的描述。对于这个演示,给它所有的repo权限(除了delete_repo权限)。也给了它webhook的许可。webhook许可是最重要的一个,因为它有能力在你的回购中创建一个 webhook,用来通知ACR触发一个新的Docker映像构建。

GitHub token - GitHub

点击生成令牌按钮。生成令牌后复制它。它将不会再次显示。

要创建一个ACR任务,使用您的存储库地址、您的注册表名称和 GitHub 访问令牌在您的项目的根目录下运行以下命令:

az acr task create --registry <container_registry_name> --name buildcustomimage --image customnodeimage --context <your_github_repo>#buildimage --file Dockerfile --git-access-token <access_token> 

对于本教程中的注册中心和存储库,这将是:

az acr task create --registry circlecigwpregistry --name buildcustomimage --image customnodeimage --context https://github.com/CIRCLECI-GWP/azure-custom-images.git#buildimage --file Dockerfile --git-access-token [My_TOKEN] 

这将创建一个buildcustomimage任务,它将在每次更新被推送到buildimage分支时重建customnodeimage映像。要查看您新创建的任务,请进入注册页面,然后进入服务->-任务

ACR task - Azure

创建 CircleCI 项目

现在,进入 CircleCI 仪表板上的项目页面。选择您在本教程中使用的 GitHub 帐户来添加项目。

Add Project - CircleCI

点击设置项目按钮开始添加信息。

Add Config - CircleCI

在设置页面上,点击 Use Existing Config 表示您正在手动添加一个配置文件,而不是使用样本。系统会提示您下载管道的配置文件或开始构建。

Build Prompt - CircleCI

点击开始构建开始构建。此构建将失败,因为您尚未设置配置文件。我们将在本教程的后面完成这项任务。

设置 GitHub 认证

持续部署策略的一部分是从将为该项目编写的管道配置推送到buildimage分支,因此 CircleCI 需要经过身份验证的访问才能访问 GitHub 上的存储库。

幸运的是,CircleCI 提供了一种添加User API Key来实现这一点的方法。在你的项目上,进入项目设置->-SSH 密钥,进入用户密钥部分。点击授权 GitHub 按钮,连接 GitHub。

一旦完成,一个添加用户密钥按钮出现在用户 API 密钥部分。

Add User key - CircleCI

将生成一个指纹,稍后将在部署管道中使用。复制指纹,并将其保存在安全的地方。

因为指纹将与您的 GitHub 电子邮件和用户名一起在管道脚本中使用,所以将它们放在环境变量中是安全的。在项目设置的侧菜单上,点击环境变量并添加这些变量:

  • GITHUB_EMAIL是您连接的 GitHub 帐户的电子邮件
  • 你的 GitHub 用户名是
  • GITHUB_FINGERPRINT是生成的认证指纹

编写管道脚本

本教程的最后一个任务是为定制的docker映像编写连续部署管道脚本。

为了使管道脚本能够将更新推送到 GitHub repo 的buildimage分支,我们将使用节点包 gh-pages 。这个包是可配置的,可以将文件从一个分支推到任何给定存储库中的另一个分支。

在项目根目录下使用以下命令将软件包作为开发依赖项进行安装:

npm install gh-pages --save-dev 

接下来,在package.json文件中添加一个pushtobuild脚本:

"scripts" : {
  .....,
  "pushtobuild": "npx gh-pages -b buildimage --message '[skip ci] Updates' -d ./"
} 

这个脚本调用gh-pages,使用npx将文件从main分支推送到buildimage分支。添加了[skip ci] Updates--message参数,以便在将更改推送到该分支时,CircleCI 不会重新运行管道。

现在,您可以开始编写管道脚本了。在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在config.yml里面,输入:

version: 2
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: update-npm
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install-packages
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run tests
          command: npm run test

  deploy:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: Configure Github credentials
          command: |
            git config user.email $GITHUB_EMAIL
            git config user.name $GITHUB_USERNAME
      - add_ssh_keys:
          fingerprints:
            - $GITHUB_FINGERPRINT
      - run:
          name: Build Image on Azure Container Registry Task
          command: npm run pushtobuild

workflows:
  version: 2
  build:
    jobs:
      - build
      - deploy:
          requires:
            - build # only deploy once build job has completed
          filters:
            branches:
              only: main # only deploy on the main branch 

这个文件用两个jobs定义了一个工作流。

build作业从远程存储库中检出代码,安装所需的依赖项,并运行测试以确保代码中没有错误。一旦build任务完成,deploy任务接管并签出一份干净的代码副本(我们不希望将node_modules文件夹从之前的任务推送到被监视的分支)。然后,它配置对 GitHub 的访问,使管道脚本能够将更新推送到被监视的分支(buildimage)。最后,deploy作业运行 Node.js 脚本来推送更新并触发docker映像的新构建。

当您提交您的更新并推送到 GitHub 存储库时,您将拥有一个成功的工作流!

Build Successful - CircleCI

您可以点击build任务查看成功的测试。

Build Details - CircleCI

要查看流程的详细信息,请单击deploy作业(在浏览器中,返回上一页)。

Deploy Details - CircleCI

服务->-任务进入你的注册表任务,点击运行标签。您将看到构建正在运行。

Image build running - Azure

过一会儿,构建将完成并标记为成功

当您在Services->-Repositories中找到您的映像并单击该映像时,您会注意到Last updated date已经更改,这表明新的构建现在是最新的。

结论

Azure Container Registry 为您提供了存储容器映像的能力,支持快速和可扩展的容器工作负载检索。在本教程中,您已经构建了一个定制的docker映像,并在每次您的应用程序代码或Dockerfile发生变化时不断地将它部署到ACR。要开始将你所学到的应用到你自己的工作中,今天就注册参加你的 CircleCI 免费试用

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

Azure 功能的持续部署| CircleCI

原文:https://circleci.com/blog/azure-functions-cd/

无服务器计算,一种由提供商管理服务器的模式,让开发者专注于编写专用的应用程序逻辑。无服务器计算已经被许多开发团队采用,因为它可以自动伸缩。自动伸缩将开发人员从分配管理任务中解放出来,因此他们不必担心服务器资源的分配,也不必为他们没有消耗的资源付费。另一个巨大的好处是,无服务器计算非常适合传统和微服务架构。

几乎每个大型云计算服务提供商都有自己版本的无服务器产品。几个例子是:

先决条件

要遵循本教程,需要做一些事情:

  1. 安装在您系统上的 Node.js (版本> = 12)
  2. 一个蓝色的账户
  3. 一个的账户
  4. GitHub 的一个账户
  5. 安装了 Azure CLI
  6. Azure 功能核心工具已安装

安装并设置好所有这些之后,您就可以开始本教程了。

创建 Azure 服务主体帐户

首先,您需要创建一个 Azure 服务主帐户。这种类型的账户是 Azure 专门为自动化流程设计的,比如连续部署管道。您将需要在本教程稍后构建的管道中访问您的 Azure 帐户,因此这是必需的。

在确保您登录到您的控制台后,(如果没有,运行az login),使用 Azure CLI 创建一个服务主体帐户,并给它一个唯一的名称。运行命令:

az ad sp create-for-rbac --name [ServicePrincipalName] 

用您想要使用的名称替换占位符ServicePrincipalName。例如:

az ad sp create-for-rbac --name MyServicePrincipal 

这个命令的输出是一个带有name键的json字符串。管道脚本中的身份验证需要这三(3)个详细信息。

  • 该名称的格式为http://[ServicePrincipalName]
  • 租户 id 为tenant
  • password是自动生成的账户密码

创建函数项目

下一步是使用 Azure core tools CLI 实用程序在本地创建 Azure functions 项目。运行以下命令创建一个基于 JavaScript 的 Azure functions 项目。

func init MySampleFunctionProject --javascript 

该命令在MySampleFunctionProject文件夹中创建一个新项目。该项目将包含两个文件,host.jsonlocal.settings.json。这些文件包含全局和本地环境的配置。

进入新创建项目的根目录。输入:

cd MySampleFunctionProject 

向项目中添加新函数

接下来,您将向您的项目添加一个新函数。我们将在本教程中使用的函数将由一个简单响应请求客户端的 HTTP 调用触发。

通过运行以下命令,将函数MyHttpExample添加到您的项目中:

func new --name MyHttpExample --template "HTTP trigger" --authlevel "anonymous" 

该函数使用HTTP trigger模板来搭建一个基本的index.js入口点。

module.exports = async function (context, req) {
  context.log("JavaScript HTTP trigger function processed a request.");

  const name = req.query.name || (req.body && req.body.name);
  const responseMessage = name
    ? "Hello, " + name + ". This HTTP triggered function executed successfully."
    : "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.";

  context.res = {
    // status: 200, /* Defaults to 200 */
    body: responseMessage
  };
}; 

这段代码导出一个接收 Node.js 请求对象(req)和一个函数(context)的函数。然后检查请求中是否设置了一个name查询字符串参数。如果设置了该参数,代码将在响应体中用字符串Hello [name]. This HTTP triggered function executed successfully.进行响应。否则,响应正文中将返回一条默认消息。

这个function.json文件也被创建:

{
  "bindings": [
    {
      "authLevel": "Anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get", "post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
} 

这将功能配置为响应HTTP触发,并允许GETPOST请求。该函数的输出被路由到HTTP请求的响应流。authLevel键被设置为Anonymous,允许对功能端点的非认证访问。

现在,通过运行以下命令,在项目(非函数)文件夹的根目录下本地启动函数:

func start 

注意:如果第一次没有运行或者返回一个关于丢失参数的错误,重新运行该命令。确保您位于项目文件夹的根目录:MySampleFunctionProject

该功能将在本地启动,并在您的 CLI 中显示端点。请耐心等待,显示localhost URL 可能需要一段时间。

Azure Functions Core Tools
Core Tools Version:       3.0.3388 Commit hash: fb42a4e0b7fdc85fbd0bcfc8d743ff7d509122ae 
Function Runtime Version: 3.0.15371.0

Functions:

        MyHttpExample: [GET,POST] http://localhost:7071/api/MyHttpExample

For detailed output, run func with --verbose flag.
[2021-03-18T08:45:44.954Z] Worker process started and initialized.
[2021-03-18T08:45:49.427Z] Host lock lease acquired by instance ID '0000000000000000000000003EA6BE15'. 

通过在浏览器中向其传递查询参数name来访问该端点。

Function Run - Browser

创建存储帐户

Azure 函数需要一个存储帐户来维护项目的状态和其他信息。

如果没有创建资源组,请使用 Azure CLI 工具创建一个。使用您喜欢的resource_group_nameregion并运行:

az group create --name <resource_group_name> --location <region> 

例如,我在本教程中使用了以下命令:

az group create --name Demos-Group --location westeurope 

接下来,使用您的资源组创建一个通用存储帐户。您需要为您的storage_account_name取一个全球唯一的名称。该名称只能包含数字和小写字母。使用以下命令创建存储帐户:

az storage account create --name <storage_account_name> --location <region> --resource-group <resource_group_name> --sku Standard_LRS 

例如,我在本教程中使用了以下命令:

az storage account create --name <storage_account_name> --location westeurope --resource-group Demos-Group --sku Standard_LRS 

Standard_LRS指定通用账户,由函数支持。

创建 Azure 函数应用程序

您当前使用的函数项目仅存在于您的本地计算机上。现在是时候在 Azure 上创建函数了。通过运行以下命令创建新的 Azure 函数:

az functionapp create --resource-group <resource_group_name> --consumption-plan-location <region> --runtime node --runtime-version 12 --functions-version 3 --name <app_name> --storage-account <storage_name> 

您需要用之前选择的信息替换占位符。将resource_group_name替换为您的资源组,将region替换为您目前使用的区域(在本例中为westeurope),将app_name替换为您的函数的全局唯一名称,将storage_name替换为您刚刚在最后一个 CLI 命令中创建的存储帐户的名称(<storage_account_name>)。

这个命令基于 Node.js 版本 12 在 Azure 上创建一个函数。如果您使用的是不同的 Node.js 版本(也支持版本 10 ),请使用--runtime-version参数切换版本。

app_name将是您的功能应用的默认 DNS 域,因此它必须是全球唯一的。还将为此功能自动创建一个应用洞察资源,用于监控。

在 CircleCI 建立项目

在这一步中,我们将自动化部署过程。从项目文件夹的根目录,将项目推送到 GitHub

现在,进入 CircleCI 仪表板上的项目页面。选择关联的 GitHub 帐户以添加项目。

Add Project - CircleCI

点击设置项目按钮开始设置项目。

Add Config - CircleCI

在 setup 页面上,点击 Use Existing Config 以指示 CircleCI 您正在手动添加一个配置文件,而不是使用显示的示例。接下来,系统会提示您下载管道的配置文件或开始构建。

Build Prompt - CircleCI

点击开始建造。此构建将失败,因为您尚未设置配置文件。我们将在教程的后面完成这一步。

您将需要从部署脚本访问您的 Azure 帐户,这就是我们在教程开始时创建 Azure 服务主体帐户的原因。我们可以使用 azure-cli orb 登录并在部署脚本中使用 Azure CLI。这个 orb 需要在项目上设置一些环境变量。

这些是:

  • AZURE_SP,这是您的服务主体名称(格式为http://[ServicePrincipalName])
  • AZURE_SP_PASSWORD 是您的服务主体帐户创建响应中的password密钥
  • AZURE_SP_TENANT 是您的服务主体帐户创建响应中的tenant
  • FUNCTION_APP_NAME 是使用 Azure CLI 创建的 Azure function 应用的名称

注意: FUNCTION_APP_NAME 不是azure-cli orb 所必需的,但最好将其设置为一个环境变量。

转到 CircleCI 项目上的Project Settings然后Environment Variables,点击添加环境变量

Add Environment Variable - CircleCI

使用该对话框,添加前面描述的环境变量。

Environment Variables - CircleCI

编写部署配置

该过程的最后一步是编写持续部署管道脚本,该脚本将部署 function app,并在更新被推送到 GitHub 存储库时持续部署它。

在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在config.yml里面,输入这个代码:

jobs:
  deploy:
    working_directory: ~/repo
    executor: node/default
    steps:
      - checkout
      - azure-cli/install
      - azure-cli/login-with-service-principal
      - run:
          name: Install Azure functions tools
          command: sudo npm i -g azure-functions-core-tools@3 --unsafe-perm true
      - run:
          name: Deploy to Azure function App
          command: func azure functionapp publish $FUNCTION_APP_NAME --javascript

orbs:
  azure-cli: circleci/azure-cli@1.0.0
  node: circleci/node@4.1.0
version: 2.1
workflows:
  login-workflow:
    jobs:
      - deploy 

在该文件中,定义了一个deploy任务。这个文件使用了两个圆球,azure-cli圆球和node圆球。node/default用作executor提供 Node.js 环境,而azure-cli用于安装 Azure CLI 并使用服务主体帐户登录。接下来,使用npm安装azure-functions-core-tools。它用于使用func azure functionapp publish命令以及函数应用程序名称和将项目定义为 Node.js/JavaScript 项目的--javascript标志来部署函数应用程序。

提交您的更改并推送到 GitHub 存储库。你会有一个成功的部署。

Deploy Successful - CircleCI

点击进入deploy工单查看详情。

Deploy Details - CircleCI

功能终点显示在Deploy to Azure function App步骤细节中。如果 URL 的一部分被屏蔽,从 Azure 获取链接。用查询参数name在您的浏览器中加载这个端点来测试它。

Function Live - Browser

厉害!

结论

无服务器架构提供了许多可扩展性和成本节约优势。您可以轻松地将这些无服务器功能插入到您的应用程序流程中,并通过存储服务、邮件服务和发布/订阅系统等服务来触发它们。这些优点使得无服务器功能成为一种在每个应用程序架构中都有用的技术。

在本教程中,您已经使用 CircleCI 创建并部署了一个 Azure 函数,以建立一个连续的部署管道来无缝地部署对该函数的更新。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

Bazel Android 项目的持续集成| CircleCI

原文:https://circleci.com/blog/bazel-for-android/

Bazel(发音像美味的药草:“bay-zell”)是由 Google 开发的通用构建工具。一些著名的公司,如 Twitter 和 Android 开源项目已经迁移到 Bazel。在本教程中,您将学习如何构建一个 Bazel Android 项目,并设置它与 CircleCI 的持续集成。我们将通过自动运行测试并生成一个二进制 APK 文件来结束本文。

除了书面指南,还有一个工作样本项目。样本项目也可以在 CircleCI 上的查看。

关于示例项目

本教程的示例项目是一个用 Kotlin 编写的最小的 Android 应用程序,带有 Bazel 构建配置。项目应用程序具有应用程序二进制代码- //app/src:app以及使用 Robolectric - //app/src:unit_tests进行单元测试的构建目标。

先决条件

要完成本教程,您应该对现代 Android 开发、Kotlin、Gradle 和 Git 有所了解。你不需要对巴泽尔有任何经验。

为巴泽尔建立一个项目

要开始,您需要转到 GitHub,克隆示例项目,并检查设置。

示例项目是一个标准的、最小的 Android Gradle 应用程序,使用 Kotlin。它有一个项目文件和一个带有自己的build.gradleapp模块。在app目录中有一个公共的文件层级,有maintestandroidTest子目录,用于各种构建类型。

注意: 我在 Mac OS Catalina 上安装家酿软件时遇到了问题,因此您的里程数可能会有所不同。

使用工作空间、构建和规则

开始使用 Bazel 需要两个文件- WORKSPACEBUILDWORKSPACE文件描述的正是你的工作空间。

WORKSPACE应该在引用所有其他资源的顶级目录中。它相当于顶层的build.gradle,在这里您可以指定在哪里找到依赖关系的存储库。

您在WORKSPACE文件中看到的第一行是:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 

load方法允许您访问该位置的其他脚本;在我们的例子中http_archive。使用这个方法从 web 上获取其他远程资源,比如来自 GitHub 的库版本。Bazel 的另一个很好的特性是用于验证下载文件完整性的sha256参数。

http_archive(
    name = "rules_android",
    urls = ["https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip"],
    sha256 = "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806",
    strip_prefix = "rules_android-0.1.1",
) 

当然,因为 Skylark 是有效的 Python,变量和常量的工作方式与您预期的一样。

 RULES_JVM_EXTERNAL_TAG = "2.2"
RULES_JVM_EXTERNAL_SHA = "f1203ce04e232ab6fdd81897cf0ff76f2c04c0741424d192f28e65ae752ce2d6"

http_archive(
    name = "rules_jvm_external",
    strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
    sha256 = RULES_JVM_EXTERNAL_SHA,
    url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)

load("@rules_jvm_external//:defs.bzl", "maven_install") 

您已经看到了如何使用http_archive加载新脚本,然后使用它们加载名为maven_install的新脚本。

获取依赖关系

在 Android 和 JVM 项目中,您通常从 Maven 存储库中获取依赖项。两个最常见的开源依赖库是 Maven Central 或 JCenter。对于特定于 Android 的依赖项,还有谷歌自己的 Maven 知识库。

Bazel 采用了一种熟悉的maven_install方法:

 maven_install(
    artifacts = [
        "androidx.core:core-ktx:1.2.0",
        "androidx.appcompat:appcompat:1.1.0",
        "androidx.fragment:fragment:1.0.0",
        "androidx.core:core:1.0.1",
        "androidx.lifecycle:lifecycle-runtime:2.0.0",
        "androidx.lifecycle:lifecycle-viewmodel:2.0.0",
        "androidx.lifecycle:lifecycle-common:2.0.0",
        "androidx.drawerlayout:drawerlayout:1.0.0",
        "androidx.constraintlayout:constraintlayout:1.1.3",
        "com.google.android.material:material:1.0.0",
        "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1",
        "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1",
        "junit:junit:4.+",

    ],
    repositories = [
        "https://maven.google.com",
        "https://jcenter.bintray.com",
    ],
    fetch_sources = True,
) 

artifacts参数包含所有的依赖项和它们的版本,repositories指定它们来自哪里。

为整个工作空间下载依赖项,但还不包括在应用程序中。在本教程的稍后部分,您会发现它们已经包含在内。

合并科特林

通读 Bazel 文档时,您会了解到 Kotlin 并没有得到该工具的官方支持。幸运的是,Kotlin 有一个官方的社区规则,它带来了编译特性。

目前,Bazel 插件还不完全支持 Kotlin 1.4。您可以引入 1.3.0 稳定版或 1.4.0 候选版。

 rules_kotlin_version = "legacy-1.4.0-rc4"
rules_kotlin_sha = "9cc0e4031bcb7e8508fd9569a81e7042bbf380604a0157f796d06d511cff2769"

http_archive(
    name = "io_bazel_rules_kotlin",
    urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/%s/rules_kotlin_release.tgz" % rules_kotlin_version],
    sha256 = rules_kotlin_sha,
)
load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories", "kt_register_toolchains")

kotlin_version = "1.4.20"
kotlin_release_sha = "11db93a4d6789e3406c7f60b9f267eba26d6483dcd771eff9f85bb7e9837011f"
rules_kotlin_compiler_release = {
    "urls": [
    "https://github.com/JetBrains/kotlin/releases/download/v{v}/kotlin-compiler-{v}.zip".format(v = kotlin_version),
    ],
    "sha256": kotlin_release_sha,
}
kotlin_repositories(compiler_release = rules_kotlin_compiler_release)
kt_register_toolchains() 

您可以使用 Kotlin Bazel 规则附带的 Kotlin 编译器的捆绑版本。如果你喜欢使用其他的东西,你可以从 GitHub 上的 Kotlin 发布页面下载任何其他的编译器版本。

现在我们已经完成了你的 Kotlin Android 应用程序的 Bazel WORKSPACE,我们可以专注于单独的包和它的BUILD文件。

什么是 Bazel 包?

Bazel 应用程序称为目标,它们位于 Bazel 软件包中。Bazel 包是任何具有BUILD文件及其子目录的目录。也就是说,除非一个子目录包含自己的BUILD文件。在这种情况下,该特定的子目录将成为其自己的包。

Bazel 中的包在工作区内用双斜线//和它们的目录结构来寻址。我们的应用程序有一个单独的包://app/src。那就是BUILD文件所在的地方。

在 Bazel 应用程序中使用目标

BUILD文件包含我们前面提到的load方法调用,以及kt_android_binaryandroid_testkt_android_library调用。这些元素是 Bazel 应用程序中的目标。目标可以是接受输入并产生构建输出的任何东西。在我们的例子中,这可以是源代码,也可以是另一个目标。每个 Bazel 应用程序可以包含多个目标。

对于本教程,我们将使用testandroid_binary目标。Android 二进制文件输出您的。apk 文件,并且test做测试。你可以在巴泽尔文档中找到这两者的文档。对于每个 Android 目标,您必须包括 Android 清单文件。

 android_binary(
    name = "my_bazel_app",
    manifest = MANIFEST,
    custom_package = PACKAGE,
    manifest_values = {
        "minSdkVersion": "21",
        "versionCode" : "2",
        "versionName" : "0.2",
        "targetSdkVersion": "29",
    },
    deps = [
        ":bazel_res",
        ":bazel_kt",
        artifact("androidx.appcompat:appcompat"),
    ],
) 

使用 Bazel 命令构建项目

要构建,使用bazel build [target][target]是您工作空间中完全合格的 Bazel 目标。对于本教程中的示例应用程序,目标是://app/src:app,因此命令将是bazel build //app/src:app

第一次构建可能需要一些时间,但是 Bazel 将缓存大多数依赖项和临时工件,因此未来的构建将会更快。

安装示例应用程序

所有最终构建工件都存储在bazel-bin/app/src/main/app.apk中。为了安装应用程序,Bazel 有一个方便的mobile-install命令:

bazel mobile-install //app/src:app 

该命令使用与您连接的设备相关的参数调用adb install

与 CircleCI 一起建立 Bazel 项目

CircleCI 有许多 Android Docker 映像,这些映像提供了构建 Android 应用程序所需的一切。也就是几乎一切。Bazel 不是默认安装的,所以这将是我们的第一步。

Docker 镜像是基于 Debian Linux 的,所以你可以使用 Bazel 文档中的 Ubuntu 安装说明。有两个步骤:

  1. 安装 Bazel apt 库
  2. apt install安装 Bazel 本身

一个简单的setup-bazel CircleCI 命令就可以完成这项工作。

commands:
  setup-bazel:
    description: |
      Setup the Bazel build system used for building Android projects
    steps:
      - run:
          name: Add Bazel Apt repository
          command: |
            sudo apt install curl gnupg
            curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg
            sudo mv bazel.gpg /etc/apt/trusted.gpg.d/
            echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
      - run:
          name: Install Bazel from Apt
          command: sudo apt update && sudo apt install bazel 

这个片段大部分是可复制、可粘贴和可重用的。您可能只是想为更加确定性的构建固定一个特定版本的 Bazel。我将在接下来的步骤和最终的示例项目中向您展示原因。

测试和构建 Bazel 目标

为了测试和构建 Bazel 目标,您需要分别使用bazel testbazel build命令,为正确的目标传递合格的包和名称。在我们的例子中,这些是用于测试的//app/src:unit_tests,以及用于应用程序二进制文件的//app/src:app

在本例中,我们在setup-bazel步骤之后立即构建它们。

jobs:
  build:
    docker:
      - image: circleci/android:api-29
    steps:
      - checkout
      - android/accept-licenses
      - setup-bazel
      - run:
          name: Run tests
          command: bazel test //app/src:unit_tests # Depending on your Bazel package and target
      - run:
          name: Run build
          command: bazel build //app/src:app # Depending on your Bazel package and target 

存储测试和构建工件

Bazel for Android 将所有测试输出存储在项目的bazel-testlogs目录中,将所有二进制输出存储在项目的bazel-bin目录中。

在我们的例子中,输出将采用与 Bazel targets - src/app相同的包结构。当您添加这些节时,CircleCI 会存储所有有用的输出:

 - store_test_results:
          path: ~/project/bazel-testlogs/app/src/unit_tests
      - store_artifacts:
          path: ~/project/bazel-testlogs/app/src/unit_tests
      - store_artifacts:
          path: ~/project/bazel-bin/app/src/app.apk
      - store_artifacts:
          path: ~/project/bazel-bin/app/src/app_unsigned.apk 

我们还存储了app_unsigned.apk,因为如果你想发布一个发布版本,你需要自己签名。你可以在安卓开发者门户上阅读更多关于手动签名的信息。

安装和使用特定的 Bazel 版本,以实现更具确定性的构建

当使用apt install bazel安装 Bazel 时,您安装的是最新稳定的版本。在本地机器上总是使用最新和最好的可能没问题,但是在 CI/CD 环境中,您可能希望在您的构建中有更多的确定性。

通过修改您的apt install bazel行来使用一个特定的版本,您可以确保一致地使用最新的版本:apt install bazel-3.7.2。您需要确保在所有后续调用中使用该特定版本。例如,bazel-3.7.2 build ...

使用 Bazel 特定版本的一种方法是在您的config.yml中使用 CircleCI 可重用参数。示例项目使用了build作业中的参数:

jobs:
  build:
    parameters:
      bazel-version:
        description: "Pinned Bazel version Replace with your one"
        default: "bazel-3.7.2"
        type: string
    ...
    steps:
      - checkout
      - android/accept-licenses
      - setup-bazel:
          bazel-version: <<parameters.bazel-version>>
      - run:
          name: Run tests
          command: << parameters.bazel-version >> test //app/src:unit_tests # Depending on your Bazel package and target 

结论和下一步措施

我希望这篇教程能让你了解如何在你的 CI/CD 管道中运行和构建 Bazel Android 应用程序。下一步可能是通过自动部署到测试服务来进一步扩展管道,甚至直接在应用商店分发应用。

在您的防火墙后面:CircleCI Enterprise 现已上市

原文:https://circleci.com/blog/behind-your-firewall-circleci-enterprise-available-today/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


circle-gh-ent_2x

在过去的四年里,我们已经帮助数千个开发团队更快地发布了更好的应用。在听到客户想要在他们自己的防火墙后使用 CircleCI 的方法后,我们开始着手使之成为可能。我们在几十个客户中测试了 CircleCI Enterprise ,我们很兴奋地宣布,从今天起, CircleCI Enterprise 正式上市

CircleCI 企业用户可以访问 CircleCI Insights,这是一个交互式可视化仪表板,有助于清晰、实时地理解构建数据。

如何知道 CircleCI 企业是否适合你

选择 CircleCI Enterprise 而不是 CircleCI.com 的四个最常见的原因是:

  • GitHub 企业支持。使用 GitHub Enterprise 的组织是完美的候选人,因为我们提供与 GitHub Enterprise 的现成集成。

  • 定制的执行环境。 CircleCI Enterprise 允许您定制运行您的构建的容器,创建您的团队需要的确切环境

  • 调优构建资源。使用 CircleCI Enterprise,您可以控制您的构建车队,并可以调整分配给容器的 CPU 和 RAM。

  • 监管和政策考虑。组织可能会面临阻止代码脱离其直接控制的法规要求。其他组织出于各种原因制定政策,阻止他们的团队使用由第三方管理的基于云的服务。有了 CircleCI,企业可以测试、部署和管理构建,所有这些都在他们自己的防火墙后面。

到目前为止的故事…

我们将继续我们的使命,让软件团队更有生产力,我们一直在寻找新的方法来帮助团队将代码转化为可靠的产品。CircleCI Enterprise 是我们产品套件的最新成员,为团队利用自动化测试和部署的能力提供了另一种选择。

我们喜欢听软件团队是如何工作的,以及我们可以做些什么来帮助您提高流程的可靠性和吞吐量,所以请在sayhi@circleci.com给我们留言,分享您的想法或与我们讨论 CircleCI 如何帮助您的团队。

CI/CD 管道中的 Android 应用基准测试| CircleCI

原文:https://circleci.com/blog/benchmarking-android/

加载速度快、交互流畅的高性能应用已经成为必需。这些品质是用户所期望的,而不仅仅是一个好东西。确保顺利交互的方法是将性能验证作为发布流程的一部分。

谷歌最近发布了对他们的 Jetpack 基准库的更新。一个值得注意的新增功能是 Macrobenchmark 库。这让你可以测试你的应用在启动和滚动等方面的性能。在本教程中,您将从 Jetpack 基准库开始,并学习如何将其作为 CI/CD 流的一部分来实现。

先决条件

要从本教程中获得最大收益,您需要几样东西:

  • 有 Android 开发和测试经验,包括仪器测试
  • 与格拉德的经历
  • 一个免费的 CircleCI 账户
  • Firebase 和谷歌云平台账户
  • 安卓工作室北极狐

注: 我写这个教程用的版本是 2020.3.1 Beta 4。在撰写本文时(2021 年 7 月),Android Studio 的当前稳定版本不支持 Macrobenchmark。

关于项目

该项目基于我为一篇关于在 CI/CD 管道中测试 Android 应用的博客文章创建的早期测试样本。

我已经扩展了示例项目的,将benchmark任务作为 CI/CD 构建的一部分。这个新作业使用 Jetpack Macrobenchmark 库运行应用程序启动基准。

Jetpack 基准测试

Android Jetpack 提供了两种基准测试:微基准测试和宏基准测试。自 2019 年以来一直存在的微基准测试允许对应用程序代码进行性能测量(想想可能需要一段时间处理的缓存或类似过程)。

Macrobenchmark 是 Jetpack 的新成员。它允许你在应用程序启动和滚动等容易注意到的地方测量应用程序的整体性能。示例应用程序使用这些宏基准测试来测量应用程序的启动。

基准测试库和方法都与运行在连接设备和仿真器上的熟悉的 Android 工具框架一起工作。

建立图书馆

在官方的 Jetpack 网站上有详细的库设置文档-macro benchmark 设置。注意:我们不会详细介绍所有步骤,因为它们可能会在未来的预览版中发生变化。相反,下面是该过程的概述:

  • 创建一个名为macrobenchmark的新测试模块。在 Android Studio 中创建一个 Android 库模块,并将build.gradle改为使用com.android.test作为插件。新模块需要最低 SDK 级别的 API 29: Android 10 (Q)。
  • 对新模块的build.gradle文件进行一些修改。将测试实现更改为 implementation,指向您想要测试的 app 模块,并使发布构建类型为debuggable
  • 将 profileable 标签添加到您的应用程序的AndroidManifest
  • 指定本地版本签名配置。您可以使用现有的debug配置。

完整的分步说明请参考官方 Macrobenchmark 库文档中的指南:https://developer . Android . com/studio/profile/macro benchmark # setup。

编写和执行宏基准测试

Macrobenchmark 库引入了一些新的 JUnit 规则和指标。

我们使用的StartupTimingMetric取自 GitHub 上的性能样本项目。

const val TARGET_PACKAGE = "com.circleci.samples.todoapp"

fun MacrobenchmarkRule.measureStartup(
    profileCompiled: Boolean,
    startupMode: StartupMode,
    iterations: Int = 3,
    setupIntent: Intent.() -> Unit = {}
) = measureRepeated(
    packageName = TARGET_PACKAGE,
    metrics = listOf(StartupTimingMetric()),
    compilationMode = if (profileCompiled) {
        CompilationMode.SpeedProfile(warmupIterations = 3)
    } else {
        CompilationMode.None
    },
    iterations = iterations,
    startupMode = startupMode
) {
    pressHome()
    val intent = Intent()
    intent.setPackage(TARGET_PACKAGE)
    setupIntent(intent)
    startActivityAndWait(intent)
}

@LargeTest
@RunWith(Parameterized::class)
class StartupBenchmarks(private val startupMode: StartupMode) {

    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startupMultiple() = benchmarkRule.measureStartup(
        profileCompiled = false,
        startupMode = startupMode,
        iterations = 5
    ) {
        action = "com.circleci.samples.target.STARTUP_ACTIVITY"
    }

    companion object {
        @Parameterized.Parameters(name = "mode={0}")
        @JvmStatic
        fun parameters(): List<Array<Any>> {
            return listOf(StartupMode.COLD, StartupMode.WARM, StartupMode.HOT)
                .map { arrayOf(it) }
        }
    }
} 

上面的代码将 3 种类型的启动度量作为参数化选项:热启动、热启动和冷启动。使用MacroBenchmarkRule将这些选项传递给测试(意味着应用程序最近运行了多久,以及它是否被保存在内存中)。

在 CI/CD 管道中运行基准测试

你可以在 Android Studio 中运行这些样本,它会给你一个很好的应用程序性能指标的打印输出,但不会做任何事情来确保你的应用程序总是高性能的。为此,您需要在 CI/CD 流程中集成基准。

所需的步骤是:

  • 构建 app 和 Macrobenchmark 模块的发布版本
  • 在 Firebase 测试实验室(FTL)或类似工具上运行测试
  • 下载基准测试结果
  • 将基准存储为工件
  • 处理基准测试结果以获得计时数据
  • 基于结果通过或不通过构建

我创建了一个新的benchmark-ftl作业来运行我的测试:

orbs:
  android: circleci/android@1.0.3
  gcp-cli: circleci/gcp-cli@2.2.0

...

jobs:
  ...
    benchmarks-ftl:
    executor:
      name: android/android
      sdk-version: "30"
      variant: node
    steps:
      - checkout
      - android/restore-gradle-cache
      - android/restore-build-cache
      - run:
          name: Build app and test app
          command: ./gradlew app:assembleRelease macrobenchmark:assemble
      - gcp-cli/initialize:
          gcloud-service-key: GCP_SA_KEY
          google-project-id: GCP_PROJECT_ID
      - run:
          name: run on FTL
          command: |
            gcloud firebase test android run \
              --type instrumentation \
              --app app/build/outputs/apk/release/app-release.apk \
              --test macrobenchmark/build/outputs/apk/release/macrobenchmark-release.apk \
              --device model=flame,version=30,locale=en,orientation=portrait \
              --directories-to-pull /sdcard/Download \
              --results-bucket gs://android-sample-benchmarks \
              --results-dir macrobenchmark \
              --environment-variables clearPackageData=true,additionalTestOutputDir=/sdcard/Download,no-isolated-storage=true
      - run:
          name: Download benchmark data
          command: |
            mkdir ~/benchmarks
            gsutil cp -r 'gs://android-sample-benchmarks/macrobenchmark/**/artifacts/sdcard/Download/*'  ~/benchmarks
            gsutil rm -r gs://android-sample-benchmarks/macrobenchmark
      - store_artifacts:
            path: ~/benchmarks
      - run:
          name: Evaluate benchmark results
          command: node scripts/eval_startup_benchmark_output.js 

这个片段代表了在 Firebase 测试实验室的真实设备上运行宏基准测试的作业。我们将 Docker executor 与 Android orb 提供的图像一起使用,它安装了 Android SDK 30 并包含 NodeJS。我们将在评估结果的脚本中使用 Node。

在构建了 app 和 macrobenchmark 模块 APKs 之后,我们需要使用 orb 初始化 Google Cloud CLI。这是我们在 CircleCI 中为 Google Cloud 提供环境变量的地方。然后,我们在 Firebase 测试实验室中运行测试:

- run:
    name: run on FTL
    command: |
      gcloud firebase test android run \
        --type instrumentation \
        --app app/build/outputs/apk/release/app-release.apk \
        --test macrobenchmark/build/outputs/apk/release/macrobenchmark-release.apk \
        --device model=flame,version=30,locale=en,orientation=portrait \
        --directories-to-pull /sdcard/Download \
        --results-bucket gs://android-sample-benchmarks \
        --results-dir macrobenchmark \
        --environment-variables clearPackageData=true,additionalTestOutputDir=/sdcard/Download,no-isolated-storage=true 

这将 apk 上传到 Firebase,指定设备(在我们的例子中是 Pixel 4),为测试提供环境变量,并指定存储结果的云存储桶。我们总是把它放在macrobenchmark目录中,以便于获取。我们不需要安装用于运行测试的gcloud工具;它与 CircleCI Android Docker 映像捆绑在一起。

一旦作业终止,我们需要使用与 GCP CLI 工具捆绑在一起的gsutil工具下载基准数据:

- run:
    name: Download benchmark data
    command: |
      mkdir ~/benchmarks
      gsutil cp -r 'gs://android-sample-benchmarks/macrobenchmark/**/artifacts/sdcard/Download/*'  ~/benchmarks
      gsutil rm -r gs://android-sample-benchmarks/macrobenchmark
- store_artifacts:
    path: ~/benchmarks 

这将创建一个benchmarks目录,并将基准从云存储中复制到其中。复制完文件后,我们还删除了存储桶中的macrobenchmark目录,以避免它与之前的跟踪文件混淆。例如,您也可以将文件保留在 Google Cloud Storage 中,而不是根据作业 ID 构造目录名。

评估基准结果

如果基准测试已经完成,基准测试命令将成功终止。不过,它不会给你任何迹象表明他们实际表现如何。为此,您需要分析基准测试的结果,并自己决定测试是失败还是通过。

结果被导出到一个 JSON 文件中,该文件包含每个测试的最小、最大和中间计时。我编写了一个简短的 Node.js 脚本来比较这些基准,如果它们中的任何一个超出了我的预期时间,就会失败。Node.js 非常适合处理 JSON 文件,并且在 CircleCI 机器映像上很容易获得。

运行该脚本只是一个命令:

- run:
    name: Evaluate benchmark results
    command: node scripts/eval_startup_benchmark_output.js 
const benchmarkData = require('/home/circleci/benchmarks/com.circleci.samples.todoapp.macrobenchmark-benchmarkData.json')

const COLD_STARTUP_MEDIAN_THRESHOLD_MILIS = YOUR_COLD_THRESHOLD
const WARM_STARTUP_MEDIAN_THRESHOLD_MILIS = YOUR_WARM_THRESHOLD
const HOT_STARTUP_MEDIAN_THRESHOLD_MILIS = YOUR_HOT_THRESHOLD

const coldMetrics = benchmarkData.benchmarks.find(element => element.params.mode === "COLD").metrics.startupMs
const warmMetrics = benchmarkData.benchmarks.find(element => element.params.mode === "WARM").metrics.startupMs
const hotMetrics = benchmarkData.benchmarks.find(element => element.params.mode === "HOT").metrics.startupMs

let err = 0
let coldMsg = `Cold metrics median time - ${coldMetrics.median}ms `
let warmMsg = `Warm metrics median time - ${warmMetrics.median}ms `
let hotMsg = `Hot metrics median time - ${hotMetrics.median}ms `

if(coldMetrics.median > COLD_STARTUP_MEDIAN_THRESHOLD_MILIS){
    err = 1
    console.error(`${coldMsg} ❌ - OVER THRESHOLD ${COLD_STARTUP_MEDIAN_THRESHOLD_MILIS}ms`)
} else {
    console.log(`${coldMsg} ✅`)
}

if(warmMetrics.median > WARM_STARTUP_MEDIAN_THRESHOLD_MILIS){
    err = 1
    console.error(`${warmMsg} ❌ - OVER THRESHOLD ${WARM_STARTUP_MEDIAN_THRESHOLD_MILIS}ms`)
} else {
    console.log(`${warmMsg} ✅`)
}

if(hotMetrics.median > HOT_STARTUP_MEDIAN_THRESHOLD_MILIS){
    err = 1
    console.error(`${hotMsg} ❌ - OVER THRESHOLD ${HOT_STARTUP_MEDIAN_THRESHOLD_MILIS}ms`)
} else {
    console.log(`${hotMsg} ✅`)
}

process.exit(err) 

为了建立计时的阈值,我将我的预期计时建立在之前的基准运行结果的基础上。如果我们引入一些减慢启动速度的代码,比如一个冗长的网络调用,基准评估将会在阈值之上运行,并且构建失败。

要使一个构建失败,我可以调用process.exit(err),它从一个脚本中返回一个非零的状态代码,这导致基准评估工作失败。

这是一个非常简单的基准测试示例。从事 Jetpack 基准测试库工作的人已经写了一些文章,将基准测试与最近的构建进行比较,使用逐步拟合的方法来检测任何回归。你可以在这篇博文中读到。

使用 CircleCI 的开发人员可以通过使用 circle ci API:https://circle ci . com/docs/API/v2/# operation/getJobArtifacts 从以前的构建中获取历史作业数据来实现类似的分步拟合方法。

警告

根据定义,端到端基准测试是不可靠的测试,需要在真实设备上运行。在模拟器上运行不会产生接近用户所看到的结果。为了说明这一点,我创建了一个benchmarks-emulator作业来展示计时的不同。

撰写本文时(2021 年 7 月),基准测试工具仍处于测试阶段。要使用它们,您需要预览 Android Studio(北极狐)版本。插件和库正在积极开发,所以更新一切可能不会像描述的那样工作。

结论

在本文中,我介绍了如何将 Android 应用程序性能基准测试与其他测试一起包含在 CI/CD 管道中。这可以防止当您添加新功能和其他改进时,性能退化影响到您的用户。

我们使用了新的 Android Jetpack 宏基准库,并展示了将其与 Firebase Test Lab 集成的方法,以在真实设备上运行基准。我们展示了如何分析结果,如果应用程序启动时间超过我们允许的阈值,则通过构建。

如果你对我接下来应该报道的话题有任何反馈或建议,请通过 Twitter - @zmarkan 联系我。

资源

在 CI 中运行基准

集装箱化的好处

原文:https://circleci.com/blog/benefits-of-containerization/

随着对技术需求的增长,我们的应用程序的规模和复杂性也在增长。

今天的开发人员经常面临管理大型应用程序的困难任务。更复杂的是底层基础设施,它通常像其应用程序一样庞大、多样和复杂。

现代应用程序的复杂性带来了许多挑战。随着应用程序和基础架构的规模和复杂性的增长,部署新功能、更新代码和简化开发工作流程变得越来越棘手。

应对这些挑战的一个解决方案是被称为容器化的虚拟化技术。在本文中,我们将探讨什么是容器化,以及它如何使测试和部署您的应用程序更快、更有效。

什么是容器?

一个容器是一个便携式计算环境。它包含了应用程序运行所需的一切,从二进制文件到依赖关系到配置文件。

容器在底层主机操作系统之上的抽象层上运行。像虚拟机(VM)一样,它们是隔离的,并且小心地限制对系统资源的访问。

与依赖虚拟化操作系统和虚拟机管理程序软件层的虚拟机不同,容器化为应用程序提供了对计算资源的直接访问,而无需额外的软件层。

对于容器,虚拟化发生在主机操作系统级别。这意味着没有虚拟硬件、虚拟内核或虚拟操作系统消耗资源来运行应用程序。因此,容器化是一种更精简、更高效的虚拟化方法。

容器是隔离的和独立的,一个主机可以同时运行一个或多个容器。多少?主机上容器的数量仅受计算资源可用性的限制。

集装箱化的好处

每天,开发人员都在寻找新的方法来使用容器化来解决他们的挑战。使用容器化的方法很多,每个应用程序都可能产生独特的好处。以下是开发人员决定容器化的一些最常见的原因:

  • 轻便
  • 效率
  • 灵活
  • 更快的交货
  • 提高安全性
  • 更快的应用启动
  • 更简单的管理
  • 灵活性

在应用程序开发中应该使用容器吗?查看以下部分,更详细地了解使用容器的好处。

轻便

没有一个关于容器化的讨论是完整的,至少有一句格言是这样的:“写一次,运行到任何地方。”因为容器捆绑了所有的依赖项,所以您可以将应用程序带到任何地方,而无需重新构建它来适应新的环境。

此外,容器化提供的抽象确保了无论您将容器部署在哪里,它都以相同的方式工作。这意味着你可以把你的应用带到云中,在虚拟机上运行,或者直接进入裸机。只要主机操作系统支持您的容器化工具,您就可以毫不费力地进行部署。

效率

容器化是开发人员可以使用的最有效的虚拟化方法之一。容器以两种方式提高效率:它们使用所有可用的资源,它们最小化开销。

正确配置后,容器允许主机利用几乎所有可用的资源。独立容器可以在不干扰其他容器的情况下执行它们的操作,从而允许单个主机执行许多功能。

容器还消除了对虚拟化操作系统、管理程序和其他通常由虚拟化技术引入的瓶颈的需求。与依赖虚拟内核的虚拟机不同,容器使用主机操作系统的内核。这大大降低了开销,并最大限度地减少了资源使用。

灵活

集装箱化是简化工作流程的重要工具。您可以快速创建容器,将它们部署到任何环境中,在那里它们可以用于解决许多不同的 DevOps 挑战。

当任务出现时,您可以快速开发一个容器来处理该任务。如果不再需要它,您可以自动关闭它,直到再次需要它。这是一种被称为编排的技术。像 Kubernetes 这样的技术自动化了协调、管理、缩放和移除容器的过程。

你可以把 Kubernetes 想象成你的集装箱管弦乐队的指挥。在 Kubernetes-coordinated containers 的帮助下,开发人员可以快速响应问题并提出新的解决方案,而无需担心冗长复杂的部署。

更快的交货

升级从概念到实现需要多长时间?通常,应用程序越大,实现任何改进所需的时间就越长。容器化通过划分应用程序解决了这个问题。使用微服务,您甚至可以将应用程序中最庞大的部分分割成独立的部分。

微服务通过将碎片分割成容器来拆分更大的应用。这种划分使得开发人员更容易实现更改和部署新代码。您可以更改应用程序的孤立区域,而不会影响整体。

提高安全性

集装箱化带来的隔离也提供了额外的安全层。因为容器是相互隔离的,所以您可以确信您的应用程序正在它们自己的自包含环境中运行。这意味着,即使一个容器的安全性受到损害,同一主机上的其他容器仍然是安全的。

除了彼此隔离之外,容器还与主机操作系统隔离,并且只能最低限度地与计算资源交互。所有这些等同于一种本质上更安全的部署应用程序的方式。

更快的应用启动

与虚拟机等其他虚拟化方法相比,容器是非常轻量级的。轻量级的众多好处之一是启动速度快。因为容器不依赖管理程序或虚拟化操作系统来访问计算资源,所以启动时间几乎是瞬时的。

唯一的限制因素是应用程序本身。由于没有实质性的开销需要等待,唯一的启动延迟来自于您的代码。快速启动是频繁更新和改进的一大原因。

灵活性

容器化允许开发人员灵活地在虚拟化或裸机环境中操作他们的代码。无论部署的需求是什么,集装箱化都可以满足这些需求。如果突然需要将您的环境从金属环境转换到虚拟环境,或者相反,您的容器化应用程序已经准备好进行转换。

使用微服务的容器化应用变得如此灵活,以至于您可以在裸机上托管某些元素,并将其他元素部署到虚拟云环境中。

用容器思考允许开发人员重新构思他们可用的资源。这可能意味着从最大容量的机器中挤出额外的处理量。或者这可能意味着发现以前似乎是资源限制的东西仅仅是创新的机会。

更简单的管理

作为平台的一部分,Kubernetes 提供了各种简化容器管理的工具,如回滚和升级。它还处理安装。您可以使用自我修复功能来尝试恢复失败的容器,终止未通过健康检查的容器,并持续监控容器的健康和状态。

Kubernetes 还自动化了资源管理。您可以为每个容器分配一定数量的 CPU 和 RAM 来处理其任务。最终,在 Kubernetes 这样的工具的帮助下管理容器比传统的应用程序管理方法要容易得多。

结论

集装箱化是一项多用途的技术,有多种多样的应用。如果应用得当,集装箱化可以提高开发工作的效率;加速部署、简化工作流程并最大限度地减少基础架构冲突。它还允许开发人员更好地利用可用资源。容器可以被配置为利用几乎所有可用的计算资源,并且几乎不需要额外开销。

集装箱化的概念起源于几十年前。Kubernetes 和 Docker engine 等现代工具的引入为容器创造了某种复兴,将它们推到了许多开发人员工作流的前沿。随着应用程序越来越复杂,我们很可能会看到容器化的更多应用。

如果您还没有这样做,现在是开始使用容器进行开发的好时机。

持续集成中计划管道的优势

原文:https://circleci.com/blog/benefits-of-scheduling-ci-pipelines/

调度是软件开发实践中不可或缺的一部分。用于调度作业的工具可以帮助开发团队节省时间,方法是将周期性任务(如修改数据库或定期发送电子邮件)安排在指定的时间执行。有许多可供选择,包括用于 Linux 的 cron、用于 Windows 的计划任务、用于 macOS 的 launchd、Jobber 和 anacron。在大多数情况下,用户使用设置指令编写脚本,命令在指定的时间执行脚本。

持续集成和持续交付(CI/CD)是另一个重要的工具,它允许开发人员自动化循环过程,如构建、测试、部署和发布软件。 CI/CD 管道通常由特定事件触发,比如对代码库的新提交。但是在许多情况下,团队可以从根据设定的时间表运行构建、测试甚至部署中受益,而不是作为对变更事件的响应。

调度管道为您提供了常规 CI/CD 管道的所有功能,以及调度任务执行的能力。在这篇文章中,我将解释在持续集成管道中调度作业的一些好处。我还将描述一些常见的用例,您可以实现这些用例来改进您的工作流并优化您团队的开发速度。

在持续集成管道中调度作业

自动化是 CI/CD 的基础。许多 CI/CD 管道包含自动将代码从提交到发布的所有必要步骤:构建、标准化、测试和验证、打包、发布、部署和监控。

CI/CD 最大的好处是提高了开发速度和协作。但是如果没有自动化,您的团队将无法实现这些好处。相反,他们将手动标准化构建,以确保只有一个“最新”的软件版本,以防止同时处理多个版本。您必须手动维护所有工件、依赖项和软件性能报告的当前文档。

CI/CD 管道由一个作业队列组成,通常被称为工作流,需要执行这些作业来保持您的软件安全、有效和最新。因为自动化是至关重要的,所以如何以及何时触发管道变得同样重要。只要能为您和您的团队带来最大价值,就能触发您的渠道,这对于运营成功至关重要。

对于最佳的流水线触发,传统的链式反应执行方式是不够的。在许多情况下,团队需要根据设定的时间表灵活地执行作业,而不是响应事件触发。因此,计划作业是 CI/CD 管道中的一个自然选择。

有三类工作最常受益于计划管道:

  1. 部署工件
  2. 测试
  3. 维护让我们进一步探讨这三种类型的定期 CI/CD 工作。

部署工件

部署工件是 CI/CD 工作的一部分,它受益于自动化和调度。当您手动部署工件时,您负责一致地建立和维护部署时间表。

自动调度使您能够一致且准时地部署您的工件。当您需要按时交付一个构建,以便用户在他们期望的时候得到软件时,这一点尤其重要。

例如,您可以为内部或外部测试人员安排一个每天晚上自动发布的版本——或者为那些想要尝试最新版本并且不介意遇到错误的 beta 测试人员。

有关计划应用程序夜间构建的更多信息,请查看计划管道入门。

测试

自动化测试新构建的过程至少与自动化其部署同等重要。手动测试浪费了大量的资源,因为它很少有足够的效率来彻底和一致地验证每一个构建。

您可以使用调度作业来运行任何测试,从 QA 到整个测试套件。第二天你就可以对你的软件的功能充满信心。通过调度测试作业,您可以持续运行耗时的测试,这些测试对于每次新提交来说都是低效的。

通过计划测试,您可以在一致的时间运行它们,最适合您的团队的需要。

维护

维护在很大程度上依赖于用户生成的数据和软件性能指标。要获得这些,你必须仔细监控你的应用程序,并运行定期扫描。即使您的 CI/CD 工作流没有持续部署,更新发布的频率也意味着您必须找到一种方法来自动监控生产中的软件,尤其是在发布的早期阶段。

通过安排维护,您可以定期、及时地运行必要的性能和安全扫描。这个系统化的过程为您提供了一个整体的工作流程,并提供了您的项目的完全可见性。

预定管道的用例

有许多不同的用例受益于调度管道,包括:

  • 正常提交-推送周期之外的安全检查
  • 刷新和重置资源
  • 对构建运行常规 QA 测试
  • 数据清理
  • 负载测试
  • 发送报告和通知

正常提交-推送周期之外的安全检查

随着日益频繁和严重的网络威胁,保护您的应用程序免受不良分子的攻击已经成为软件开发的一个重要组成部分。这个过程从软件被构建时就开始了,并且会无休止地继续下去。

大多数 CI 管道只有在代码库收到新提交时才会被触发运行。对于大多数组织来说,这些提交往往相隔几个小时发生。随着漏洞被发现的频率——无论是在您的软件中发现的还是在您的软件供应链的一个组件中发现的——这已经不够了。您必须定期运行频繁、彻底的安全扫描。

一致性测试通常需要运行自动化的基于安全性的测试。在 CI/CD 中,最佳实践是将这些安全性测试构建到您的 CI/CD 工作流中。

但是,您可能更喜欢将这些安全扫描与其他自动性能和功能测试分开运行。此外,考虑到必须执行的无数安全测试,扫描可能需要比新版本触发的扫描更频繁地运行。在具有严格的软件合规性要求的行业中,以及在管理关键应用程序(如支付平台或收集个人信息的应用程序)时,尤其如此。

在这些情况下,按设定的时间或间隔安排扫描,而不是使用通常用于 CI/CD 管道执行的传统 domino 风格的方法,这一点至关重要。有关如何使用调度管道调度安全扫描的示例,请查看使用调度管道运行常规安全扫描的

刷新/重置资源

重置和刷新资源也受益于自动化调度。作为 CI/CD 管道的一部分,您可以选择一个时间将服务和环境重置为默认状态、归档日志以及重新部署应用程序。因为您的 CI/CD 管道已经可以访问您的环境,所以使用这个功能没有真正的缺点。

对构建运行常规 QA 测试

可以说,没有自动化测试就没有 CI/CD。虽然手动测试很重要,但是自动化测试是您的测试团队能够跟上 CI/CD 开发步伐的唯一方法。

通常,组织使用专用于多个项目的 QA 测试的服务器来自动化定期测试。为了使这种方法有效,必须在设定的时间将构建部署到这些服务器上。对于手动测试,在构建可以部署到测试服务器之前,必须有授权的团队成员来触发测试。

有了预定的管道,您可以将此流程纳入 CI/CD 管道。您可以计划在指定的时间将构建部署到您的测试服务器,这将自动触发测试脚本运行,而不会损害您已建立的访问控制,也不需要团队成员的干预。

数据清理

如果您的工作流涉及数据处理,您通常会执行数据排序、清理、转换、合并、建模、数据库备份和批处理等任务。尽管这些任务可能不是构建,但它们对您的工作流仍然至关重要,并且在您的管道中占有一席之地。对于非调度管道,将这些任务添加到工作流是一项挑战,因为它们通常必须定期运行,而不是等待事件触发。

输入计划的管道。当您可以将任务设置为在预定义的时间运行时,您的数据工作流将成为项目管道的一部分。关于如何使用调度管道来维护和更新数据库的示例,请查看使用调度管道自动清理数据库

负载测试

团队依靠负载测试来证明他们的应用程序符合预期的性能标准。负载测试是了解产品在特定现实条件下的行为的最可靠的方法。

在 CI/CD 中,负载测试是每个构建的验证标准的一部分。让这些测试成为你的管道的一部分是有意义的。不幸的是,手动负载测试很难协调和测量,并且实现起来很昂贵。相比之下,自动化负载测试允许您安排这些测试在最适合您的团队的时候运行,从而更有效地利用您的资源。

例如,您可以将负载测试安排在非高峰工作时段,以节省资源。或者,您可以将测试安排在高峰使用时间运行,以获得真实世界或现场测试结果。您可以在使用 k6 调度负载测试和持久化输出中了解如何调度重复的 API 负载测试。

发送报告和通知

计划管道的另一个主要好处是能够让团队成员和其他利益相关者了解应用程序和其他重要资源的最新状态。通过按计划运行您的管道,您不仅可以将构建工件部署到准备区域进行审查,还可以定期发送电子邮件、更新共享仪表板、发布文档以及执行许多其他类型的报告工作,这些工作可以帮助您的团队的其他成员对您正在进行的工作充满信心。关于如何使用 CI/CD 和调度管道定期自动生成和交付发票的示例,请查看构建自动发票生成器应用程序

预定管道的好处

正如上一节中的用例所强调的,调度管道为标准的基于事件的 CI/CD 管道提供了理想的补充。使用调度管道,您可以避免不重要或资源密集型工作的作业队列,并安排它们在最合适的时间运行。

调度管道还有助于在整个开发过程中维护应用程序的安全性。应用程序安全性至关重要,及早检测漏洞可以减少平均恢复时间(MTTR)。要检测正常周期和部署周期之间出现的漏洞,您必须持续运行安全扫描并关注其结果。除了资源管理之外,调度管道还提供了改进的保护。

最后,预定的管道有助于让经理、团队成员和其他涉众知道在哪里可以找到最新的构建、性能数据、文档和其他工件。

结论

向用户交付最佳软件体验从根本上依赖于管理您的构建和生产过程的 CI/CD 管道的效率。在的情况下,您可能需要按计划执行某些任务,比如定期安全检查和 QA 测试,而不是在事件发生时执行。在这些情况下,您的管道必须做的不仅仅是等待触发器。

CircleCI 的调度管道允许您设计能够从事件触发器执行任务的管道,或者在最符合您的操作模型和开发团队需求的时间运行它们。

当您准备好开始转变您的 CI/CD 工作流程时,您可以注册一个免费的 CircleCI 帐户并了解有关如何使用预定管道启动的更多信息。

CircleCI 博客内容许可

原文:https://circleci.com/blog/blog-content-licensing-at-circleci/

我们不时会收到来自贡献者和读者的问题,询问我们博客上的内容是如何获得许可的。我们的目的一直是让读者在有限的限制下使用我们博客上的内容。今天,我们更新了我们的博客许可条款,以明确说明在此发布的内容如何获得许可,以便我们的读者可以更好地使用它。

除了 CircleCI 商标之外,CircleCI 博客上的所有内容都获得了知识共享署名 4.0 国际版(CCY 4.0 版)许可的许可,除非另有说明。除非另有说明,CircleCI 博客上发布的所有软件代码,包括代码片段,均根据 Apache 许可证 2.0 版进行许可。

我们向读者提供代码示例,以帮助他们解决常见的瓶颈,突出新颖的解决方案,并介绍新功能的集成。我们完全期待它们被我们的读者复制和使用。例如,下面是一个项目的配置文件片段,展示了如何在管道中的作业中使用cURL与 Bintray REST API 通信。它确保服务是可操作的,如果不是,它使用逻辑失败并退出作业。

...
      - run:
          name: Get Bintray API status
          command: |
                    BT_STATUS=$(curl -s https://status.bintray.com/api/v2/status.json |  jq --raw-output '.status.description')
                    echo “Bintray API Status: $BT_STATUS”
                    if [ “$BT_STATUS” != “All Systems Operational” ]; then
                      echo “Error [Bintray API Status: $BT_STATUS]”
                      exit 1
                    fi
... 

请随意使用这段代码,并根据您的需要进行调整!

我们希望这些许可条款能够阐明如何使用我们博客上的内容,但如果有任何问题,请随时联系我们。

CircleCI 博客- CircleCI

原文:https://circleci.com/blog/

纽约早餐圆桌会议:德沃普斯-切尔莱西的变脸

原文:https://circleci.com/blog/breakfast-roundtable-in-nyc-the-changing-face-of-devops/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


11 月 15 日(星期二)上午 8 点在纽约市与我共进早餐。

11 月 15 日星期二,我将与联合创始人兼首席执行官 Edith Harbaugh 共同主持早餐会,届时我们将讨论 DevOps 如何转变软件交付方式。

我希望你能加入我们的谈话。我们将邀请一小群工程领导者讨论他们在向自己的公司介绍 DevOps 时面临的挑战和机遇,我们将从与会者提交的主题中抽取大量内容。

人们已经开始提交讨论主题和问题:

  • 我们如何利用自助式基础架构提高开发人员的工作效率?
  • DevOps 作为一种文化 vs. DevOps 作为一个团队是什么样子的?
  • 既然测试环境永远不会与生产环境完美匹配,我们如何在生产环境中测试连接的系统呢?
  • 随着我们工程团队的成长,我们如何安全地扩展开发人员拥有的部署?
  • 为什么我们在企业中需要 DevOps?
  • 培根(认真地)

立即回复,并将您的话题和问题添加到列表中。期待很快见到你。

议程

上午 8:30-9:00-网络和早餐(培根!)

上午 9:00-10:00-圆桌讨论

位置

纽约第五大道 110 号工作台(16 街和 17 街之间)

为企业带来数字化转型:为什么大型组织现在关注 CI - CircleCI 如此重要

原文:https://circleci.com/blog/bringing-digital-transformation-to-the-enterprise-why-it-s-important-that-big-organizations-are-now-paying-attention-to-ci/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


本周,领先的研究公司 Forrester 发布了他们的第一个用于持续集成的 Forrester Wave。我们在 CircleCI 的团队花了大量时间与 Forrester 的研究团队合作,我们很自豪被评为该领域的领导者。我们认为有一个更大的问题值得探讨:为什么现在对持续集成有如此多的关注?为什么 Forrester 的客户要求他们提供关于哪些 CI 工具和公司值得关注的研究和分析?

这一切都与数字化转型有关。

是的,“数字化转型”现在是一个热门词汇。但是,在嗡嗡声背后是什么呢?对于硅谷的人来说,很容易对网络成为主流几十年后发生的数字“变革”的概念嗤之以鼻。初创公司是数字原生企业,因此转型的想法可能看起来只不过是出售昂贵咨询项目的一种方式。但是这个短语的趋势本质实际上指向了一个更大的故事和行业的一个重要转变。

词是时代的产物

与创业公司不同,创业公司可以很容易地在整个团队中测试新工具和流程,大型组织不能抓住每一个热门的新趋势,实施结构性变革,只是为了进行尝试。当他们推出变更时,他们必须清楚地了解投资回报和风险控制。当您在多个业务部门拥有数千名工程师时,所有这些工程师都受到安全控制和任意数量的政策和法规约束,企业中发生的任何变化都不会是简单的,也不会在几天或几周内执行。

五年前,硅谷投资者马克·安德森宣称软件正在“吞噬世界”新技术和微服务到处涌现,包括持续集成和交付工具类别的第一批进入者。

五年前,选择采用 CI/CD 实践的公司在如何配置和维护必要的工具分类方面开辟了自己的道路。许多人看到了围绕开源工具建立的草根努力,但在过去的 18 个月左右,我们与几十家世界上最大的公司进行了交谈,他们看到了这些草根努力的极限。他们正在努力实现规模化,并消除数十甚至数百个完全独立安装的此类工具所带来的瓶颈,其中每个工具都需要小心翼翼地供应机器、配置围墙花园内的作业、管理安全性以及处理插件。

五年前,没有任何供应商准备支持在大型企业中在全公司范围内推广现代 CI/CD 实践。但是在软件领域,五年内可以发生很多事情。在过去的五年中,这一产品类别的优质产品数量以及这些产品的抛光都有了巨大的增长。Forrester 报告代表了该类别整体成熟度的一个里程碑,它显然正在成为所有高性能软件组织(无论规模大小)的一流组成部分。

谁不是软件公司?

运营,客户互动…几乎没有一个企业可以免于对高质量软件开发的需求。构建高质量软件并快速交付的行为,已经成为各行各业公司创造价值的核心引擎。这就是我们说现在每个公司都是软件公司的意思。

结果,今天的软件组织的性能已经成为价值创造的关键瓶颈;我们大客户的管理团队意识到了这一点,并有改进的任务。工具和工程过程的问题不再是工程团队中的孤儿,不再是开发人员吹毛求疵的话题。他们已经超越了服务器机房,进入了会议室。随着它们与整个组织的价值创造联系越来越紧密,它们的重要性将会继续增长。

不再仅仅是开发工具

随着软件继续蚕食世界,更多的商业注意力转移到软件开发的细节和后勤上。“DevOps”已经成为你的软件组织整体性能的简写,这是有充分理由的。如果一家公司能够缩短从创意到工作软件的距离,他们就能缩小创新管道的一项基本成本,并能比竞争对手更好更快地创造价值:绩效的本质。相对于几年前我们构建软件的方式,我们生活在未来。因此,在你的运输管道的任何一个环节进行改进,对于保持你在市场上的整体竞争地位都是至关重要的。

Forrester Wave 报告的发布表明 CI 和软件开发本身已经达到了一个新的里程碑。这些年来,我们已经看到这种变化在我们的客户群中得到了反映。我们已经看到它从大多数天生具有优化吞吐量 DNA 的初创公司转变为大型企业,这些企业意识到他们软件输出的速度和质量是他们底线的一部分。我们的客户现在是市场领导者,因为他们早期投资。每天,越来越多的大公司意识到需要新的基础设施来使他们的表现符合市场标准。

成为现代软件组织的赌注已经改变了。如果你想保持竞争力,领先的 DevOps 实践不再是一个不错的选择,这就是为什么每个行业的 C 级高管都在谈论“数字化转型”。

Clojure web 框架管道-构建一个 Clojure web 应用程序| CircleCI

原文:https://circleci.com/blog/build-a-clojure-web-app-using-duct/

在这篇博文中,我们将使用 Clojure 和一个名为 Duct 的框架来构建一个服务器端 web 应用程序。为什么是管道?大多数 Clojure web 应用程序都是以定制的方式构建的,使用的是由开发人员挑选并组合在一起的库集合。Duct 提供了一个模块化框架,减少了搜索这些库的工作量,使您能够更快地启动和运行一个基本的服务器端 web 应用程序。还有其他 Clojure web 框架,但是 Duct 有很好的默认组合,不需要太多的学习曲线。

我假设你有一定的 Clojure 基础知识。如果你是 Clojure 的新手,看看clo jure for the Brave and Trueclo jure from the ground,或者伦敦 ClojureBridge 网站上的大量有用资源。

如果你想看完整的代码,我已经把它提交到 GitHub 这里

先决条件

为了构建这个 web 应用程序,您需要安装以下软件:

  1. Java JDK 8 或更高版本 - Clojure 运行在 Java 虚拟机上,事实上,它只是一个 Java 库(JAR)。我用版本 8 构建了这个,但是一个更好的版本应该也可以。
  2. Leiningen - Leiningen,通常简称为 lein(读作‘line’),是最常用的 Clojure 构建工具。
  3. Git -无处不在的分布式版本控制工具。

获得基本的 web 应用程序

关于 Duct 的一个好处是,你可以使用它的 lein 模板给你一个现成的 web 应用程序框架。我们将使用它为您的 web 应用程序提供一个起点,允许您输入并列出每部电影的描述和评级。对于这个入门应用程序,我们将使用 SQLite 作为开发的数据库引擎。在以后的博客文章中,我们将对此进行重构以使用 PostgreSQL。以下命令将为您提供一个具有我们所需的基本结构的初始项目:

$ lein new duct film-ratings +site +ataraxy +sqlite +example 

这将创建一个名为“电影分级”的新目录,其结构如下:

.
├── db
├── dev
│   ├── resources
│   │   └── dev.edn
│   └── src
│       ├── dev.clj
│       └── user.clj
├── project.clj
├── README.md
├── resources
│   └── film_ratings
│       ├── config.edn
│       ├── handler
│       │   └── example
│       │       └── example.html
│       └── public
├── src
│   └── film_ratings
│       ├── boundary
│       ├── handler
│       │   └── example.clj
│       └── main.clj
└── test
    └── film_ratings
        ├── boundary
        └── handler
            └── example_test.clj 

我们稍后将研究这些文件,但是现在,您需要为 Duct 生成本地配置。这个配置只是为您的机器准备的,并且会使用在.gitignore文件中为您生成的条目自动从 Git 存储库中排除。要生成此本地配置,请输入:

$ lein duct setup 

让我们检查一下它是否运行

此时,我们应该运行应用程序来检查我们的设置是否正常。为此,请输入以下命令:

$ lein repl
nREPL server started on port 37347 on host 127.0.0.1 - nrepl://127.0.0.1:37347
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.9.0
...
user=> (dev)
:loaded
dev=> (go)
:duct.server.http.jetty/starting-server {:port 3000}
:initiated
dev=> 

这将启动应用程序监听端口3000。打开浏览器,输入网址http://localhost:3000/,你会看到:

这可能看起来很奇怪,但是服务器正在工作。只是你没有路径/的路线。让我们看一下应用程序的配置。在您最喜欢的文本编辑器或 IDE 中打开文件film-ratings/resources/film_ratings/config.edn,您会看到:

{:duct.core/project-ns  film-ratings
 :duct.core/environment :production

 :duct.module/logging {}
 :duct.module.web/site {}
 :duct.module/sql {}

 :duct.module/ataraxy
 {[:get "/example"] [:example]}

 :film-ratings.handler/example
 {:db #ig/ref :duct.database/sql}} 

这是管道的基本配置文件。它指定了项目名称空间、目标环境(在本例中是生产环境,尽管您将在后面查看开发配置),以及日志、站点和 SQL 配置的一些占位符。

该文件中最有趣的行是:

 :duct.module/ataraxy
 {[:get "/example"] [:example]}

 :film-ratings.handler/example
 {:db #ig/ref :duct.database/sql}} 

它们指定如何将 URL 映射到处理 URL 的 HTTP 请求的处理函数。在本例中,我们使用一个名为ataraxy的库来指定从 URL 到函数的路径。您可以在:duct.module/ataraxy映射条目中看到,模板生成了一个路由,将对 URL /example的 HTTP GET 请求映射到关键字:example

由于已经为/example定义了一条路线,您可以在浏览器中键入http://localhost:3000/example,您将会看到:

处理请求

{[:get "/example"] [:example]}路由如何服务于这个示例处理程序页面?

默认情况下,Duct 假设路由值(:example)中的关键字将以{{project-ns}}.handler为前缀,它将查找该关键字来确定处理程序配置。在这个例子中,film-ratings.handler/example在它的选项值中定义了一个对:duct.database/sql键的集成引用。 Integrant 是一个微框架,它构建一个配置,然后通过以正确的顺序启动配置中定义的组件来构建一个运行系统。稍后我们将再次讨论数据库选项。现在,只需注意,Duct 寻找一个整合键来确定哪个函数充当处理程序。

您将在文件film-ratings/src/film_ratings/handler/example.clj中找到处理函数:

(defmethod ig/init-key :film-ratings.handler/example [_ options]
  (fn [{[_] :ataraxy/result}]
    [::response/ok (io/resource "film_ratings/handler/example/example.html")])) 

处理程序是一个函数,它返回另一个接受 HTTP 请求并返回响应的函数。这里的内部函数只是返回一个向量,告诉 ataraxy 返回一个状态 200 (OK)和作为响应主体的example.html文件。

Integrant 根据配置为处理程序提供初始化选项。在这种情况下,不使用这些选项,但这是一种处理数据库等资源的方式。

设置持续集成

在我们继续之前,让我们提交到目前为止我们所拥有的 Git 并建立我们的持续集成构建。

首先,让我们在本地创建 Git 存储库。打开一个新的终端会话(保持运行的终端会话lein打开),并从film-handler根目录输入以下命令:

$ git init
$ git add .
$ git commit -m "Duct app generated w/ +site +ataraxy +sqlite +example" 

此时,您将需要一个 GitHub 帐户。如果你没有,在 GitHub 注册。登录您的帐户,添加一个名为film-handler的新存储库。复制存储库的 URL,并在以下命令中使用它:

$ git remote add origin <github repo url>
$ git push --set-upstream origin master 

如果您的 URL 是https版本,您将被提示输入您的 GitHub 用户名和密码(如果您启用了多重身份验证,您将需要在 GitHub 中生成一个个人访问令牌以用作密码)。

您现在在 GitHub 中有了自己的代码。我们将使用 CircleCI 来运行持续集成构建。去https://circleci.com/创建一个账户,用你的 GitHub 证书注册。

在告诉 CircleCI 如何运行一个构建之前,有必要手动运行一个:

$ lein do test, uberjar

lein test film-ratings.handler.example-test

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
Compiling film-ratings.main
Compiling film-ratings.handler.example
Created .../film-ratings/target/film-ratings-0.1.0-SNAPSHOT.jar
Created .../film-ratings/target/film-ratings-0.1.0-SNAPSHOT-standalone.jar 

您可以看到,这运行了一个测试,然后将应用程序打包为一个独立的 Uber jar(Uber jar 是一个包含所有所需库的单一归档文件)。让我们通过编辑project.clj文件并添加一个 uberjar-name 键值来简化 uberjar 的名称:

...
  :main ^:skip-aot film-ratings.main
  :uberjar-name "film-ratings.jar"
  :resource-paths ["resources" "target/resources"]
... 

如果您重新运行lein do test, uberjar,您将看到 jar 名称被更改为film-ratings.jar。现在是时候给根film-handler目录添加一个.circleci目录和一个config.yml目录了。

$ mkdir .circleci
$ cd .circleci
$ touch config.yml
$ cd .. 

现在编辑空的config.yml得到下面几行 YAML 代码。

version: 2
jobs:
  build:
    working_directory: ~/cci-film-ratings # directory where steps will run
    docker:
      - image: circleci/clojure:lein-2.8.1
    environment:
      LEIN_ROOT: nbd
      JVM_OPTS: -Xmx3200m # limit the maximum heap size to prevent out of memory errors
    steps:
      - checkout
      - restore_cache:
          key: film-ratings-{{ checksum "project.clj" }}
      - run: lein deps
      - save_cache:
          paths:
            - ~/.m2
          key: film-ratings-{{ checksum "project.clj" }}
      - run: lein do test, uberjar 

然后我们需要将编辑好的project.clj文件和 CircleCI 配置添加到 GitHub 中。

$ git add .
$ git commit -m "Added circleci"
$ git push origin 

接下来,进入你的 CircleCI 账户,选择添加项目。从列表中选择您的回购,然后点击设置项目。默认情况下,操作系统和语言应该预先选择为LinuxClojure,所以只需点击开始构建。如果您随后单击 Building 并进入构建作业,您将看到构建正在运行,最终您将看到一个成功的构建。

添加索引页

您并不真的需要这个示例页面,所以让我们做一些更改来删除它,并为/路线添加一个索引页面。为此,您需要编辑config.edn来删除示例路由并添加新的索引路由。配置应该如下所示:

{:duct.core/project-ns  film-ratings
 :duct.core/environment :production

 :duct.module/logging {}
 :duct.module.web/site {}
 :duct.module/sql {}

 :duct.module/ataraxy
 {[:get "/"] [:index]}

 :film-ratings.handler/index {}
} 

您现在需要为索引页面创建一个处理程序,因此在film-ratings/src/film_ratings/handler目录中创建一个名为index.clj的文件。将以下内容添加到索引文件中:

(ns film-ratings.handler.index
  (:require [ataraxy.core :as ataraxy]
            [ataraxy.response :as response]
            [film-ratings.views.index :as views.index]
            [integrant.core :as ig]))

(defmethod ig/init-key :film-ratings.handler/index [_ options]
  (fn [{[_] :ataraxy/result}]
    [::response/ok (views.index/list-options)])) 

这个defmethod用一个处理函数初始化:film-ratings.handler/index键,这个处理函数接受一个请求并解结构 ataraxy 路由的结果。在这种情况下,我们没有命名它,因为它没有被使用。您还可以看到defmethod的两个参数,第一个将是关键字 key,:film-ratings.handler/index,在本例中,第二个是在 config 中定义的任何初始化的选项,在本例中也没有使用。

这个处理程序现在引用了一个不存在的film-ratings.views.index名称空间中的list-options函数,所以让我们创建这个文件。添加一个名为film-ratings/src/film_ratings/views的新目录,并创建一个新的index.clj文件,其中包含:

(ns film-ratings.views.index
  (:require [film-ratings.views.template :refer [page]]))

(defn list-options []
  (page
    [:div.container.jumbotron.bg-white.text-center
     [:row
      [:p
       [:a.btn.btn-primary {:href "/add-film"} "Add a Film"]]]
     [:row
      [:p
       [:a.btn.btn-primary {:href "/list-films"} "List Films"]]]])) 

您可以看到,list-options函数返回了一个类似 HTML 的数据结构,该结构封装在对page函数的函数调用中;再说一遍,那还不存在。使用一个名为 hiccup 的库将这种类似 HTML 的数据结构转换成真正的 HTML。为了使用 hiccup,您需要将它作为一个依赖项添加到project.clj文件中:

 :dependencies [[org.clojure/clojure "1.9.0"]
                 [duct/core "0.6.2"]
                 [duct/module.logging "0.3.1"]
                 [duct/module.web "0.6.4"]
                 [duct/module.ataraxy "0.2.0"]
                 [duct/module.sql "0.4.2"]
                 [org.xerial/sqlite-jdbc "3.21.0.1"]
                 [hiccup "1.0.5"]] 

现在让我们添加索引视图名称空间中引用的page函数。创建一个film-ratings/src/film_ratings/views/template.clj文件:

(ns film-ratings.views.template
  (:require [hiccup.page :refer [html5 include-css include-js]]
            [hiccup.element :refer [link-to]]
            [hiccup.form :as form]))

(defn page
  [content]
  (html5
    [:head
     [:meta {:name "viewport" :content "width=device-width, initial-scale=1, shrink-to-fit=no"}]
     [:title "Film Ratings"]
     (include-css "https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css")
     (include-js
       "https://code.jquery.com/jquery-3.3.1.slim.min.js"
       "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
       "https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js")
     [:body
      [:div.container-fluid
       [:div.navbar.navbar-dark.bg-dark.shadow-sm
        [:div.container.d-flex.justify-content-between
         [:h1.navbar-brand.align-items-center.text-light "Film Ratings"]
         (link-to {:class "py-2 text-light"} "/" "Home")]]
       [:section
        content]]]]))

(defn labeled-radio [group]
  (fn [checked? label]
    [:div.form-check.col
     (form/radio-button {:class "form-check-input"} group checked? label)
     (form/label {:class "form-check-label"} (str "label-" label) (str label))])) 

您还可以看到另一个函数labeled-radio,它返回单选按钮的 hiccup。你以后会需要这个的。您现在可以删除film-ratings/src/film_ratings/handler/example.cljfilm-ratings/test/film_ratings/handler/example_test.clj文件以及film-ratings/resources/handler/example目录和内容。

运行新的索引页面

让我们检查一下索引页面是否正确呈现。

回到您正在运行的lein repl终端。通常,您可以通过在 repl 中运行(reset)来刷新应用程序的状态,但是在这种情况下,您添加了一个新的依赖项(hiccup)。这需要通过重新启动 repl 来重新加载,如下所示:

user=> (quit)
Bye for now!
$ lein repl
...
user=> (dev)
:loaded
dev=> (go)
:duct.server.http.jetty/starting-server {:port 3000}
:initiated
dev=> 

接下来,打开浏览器进入http://localhost:3000/ URL,您应该会看到:

在提交这些更改之前,我们应该为索引页面添加一个测试,以防我们在某个时候意外破坏它。创建一个包含以下内容的文件film-ratings/test/film_ratings/handler/index_test.clj:

(ns film-ratings.handler.index-test
  (:require [film-ratings.handler.index]
            [clojure.test :refer [deftest testing is]]
            [ring.mock.request :as mock]
            [integrant.core :as ig]))

(deftest check-index-handler
  (testing "Ensure that the index handler returns two links for add and list films"
    (let [handler (ig/init-key :film-ratings.handler/index {})
          response (handler (mock/request :get "/"))]
      (is (= :ataraxy.response/ok (first response)))
      (is (= "href=\"/add-film\""
            (re-find #"href=\"/add-film\"" (second response))))
      (is (= "href=\"/list-films\""
            (re-find #"href=\"/list-films\"" (second response))))))) 

然后运行测试:

$ lein test

lein test film-ratings.handler.index-test

Ran 1 tests containing 3 assertions.
0 failures, 0 errors. 

现在提交这些更改并将其推送到 GitHub。

$ git add .
$ git commit -m "Added index page"
$ git push 

这将启动 CircleCI 上的一个构建,你可以在你的 CircleCI 控制台上看到。

添加电影

到目前为止,我们有一个应用程序,它只显示了一个索引页面,这个页面上有一些按钮,但没有链接到任何东西。接下来,我们需要添加一个处理程序来添加电影和一个处理程序来列出电影。我们还需要把这个输入数据库。

让我们从加入我们的数据库开始。如果你查看film-ratings/dev/resources,你会看到一个dev.edn文件。在开发模式下启动时(这是您目前运行应用程序的方式),在 Integrant 启动应用程序之前,Duct 会将该开发配置文件与生产配置文件合并。

{:duct.core/environment :development
 :duct.core/include ["film_ratings/config"]

 :duct.module/sql
 {:database-url "jdbc:sqlite:db/dev.sqlite"}} 

如您所见,这将把:duct.module/sql设置为一个 SQLite 数据库的引用,Integrant 在您启动应用程序时启动该数据库。在启动时,这将是对包含正在运行的数据库连接的映射的引用。

目前,这引用了一个空数据库。但是,您可以使用 Duct 的 Ragtime 模块来使用数据库迁移库 Ragtime ,用电影表填充数据库。将以下几行添加到film-ratings/resources/film_ratings/config.edn:

 :film-ratings.handler/index {}

 :duct.migrator/ragtime
 {:migrations [#ig/ref :film-ratings.migrations/create-film]}

 [:duct.migrator.ragtime/sql :film-ratings.migrations/create-film]
 {:up ["CREATE TABLE film (id INTEGER PRIMARY KEY, name TEXT UNIQUE, description TEXT, rating INTEGER)"]
  :down ["DROP TABLE film"]}

 } 

让我们为 add films 表单视图和 post 请求添加处理程序,以便将电影添加到数据库中。首先,将路线添加到film-ratings/resources/film_ratings/config.edn文件中:

 :duct.module/ataraxy
 {[:get "/"] [:index]
  "/add-film"
  {:get [:film/show-create]
   [:post {film-form :form-params}] [:film/create film-form]}} 

新的add-film URL 现在被映射到两个处理程序,一个用于 GET 方法,一个用于 POST 方法。注意,post route 使用 Clojure 析构从请求中提取表单参数,并将它们作为参数传递给:film/create处理程序。

此时,我们还没有定义:film/create或:film/show-create Integrant 键及其选项。为此,在config.edn中添加这些行:

 :film-ratings.handler/index {}
 :film-ratings.handler.film/show-create {}
 :film-ratings.handler.film/create {:db #ig/ref :duct.database/sql} 

create键有一个对数据库的集成引用,它将被传递给选项映射中的处理程序。接下来,我们需要为这两个键创建处理程序。这些键的命名空间为film-ratings.handler.film。这是命名空间管道,Integrant 将寻找它来找到初始化处理程序键的函数。我们需要为这个名称空间创建一个新文件,film-ratings/src/film_ratings/handler/film.clj:

(ns film-ratings.handler.film
  (:require [ataraxy.core :as ataraxy]
            [ataraxy.response :as response]
            [film-ratings.boundary.film :as boundary.film]
            [film-ratings.views.film :as views.film]
            [integrant.core :as ig]))

(defmethod ig/init-key :film-ratings.handler.film/show-create [_ _]
  (fn [_]
    [::response/ok (views.film/create-film-view)]))

(defmethod ig/init-key :film-ratings.handler.film/create [_ {:keys [db]}]
  (fn [{[_ film-form] :ataraxy/result :as request}]
    (let [film (reduce-kv (fn [m k v] (assoc m (keyword k) v))
                          {}
                          (dissoc film-form "__anti-forgery-token"))
          result (boundary.film/create-film db film)
          alerts (if (:id result)
                   {:messages ["Film added"]}
                   result)]
      [::response/ok (views.film/film-view film alerts)]))) 

:film-ratings.handler.film/show-create defmethod 返回一个处理程序,该处理程序简单地返回调用create-film-view的结果,该结果被包装在一个带有::response/ok关键字的向量中,由ataraxy呈现为一个状态为 200 的 HTTP 响应。

:film-ratings.handler.film/create defmethod 将数据库作为一个选项,其包含的处理函数从ataraxy结果中分解出胶片格式。电影表单将是 HTML 表单的映射(我们还没有创建)。该映射的键是字符串形式的表单字段的名称,此外它还有一个防伪标记。为了方便起见,let的第一行删除了防伪标记,将字符串密钥改为关键字。然后调用一个create-film函数,将数据库和电影形式作为参数。这将返回一个应该有一个:id:messages键值对的结果映射。然后,处理函数返回对film-view调用的响应。

此时,views 函数和create-film函数都不存在。我们需要创建一个新的视图文件,film-rating/src/film_ratings/views/film.clj:

(ns film-ratings.views.film
  (:require [film-ratings.views.template :refer [page labeled-radio]]
            [hiccup.form :refer [form-to label text-field text-area submit-button]]
            [ring.util.anti-forgery :refer [anti-forgery-field]]))

(defn create-film-view
  []
  (page
   [:div.container.jumbotron.bg-light
    [:div.row
     [:h2 "Add a film"]]
    [:div
     (form-to [:post "/add-film"]
              (anti-forgery-field)
              [:div.form-group.col-12
               (label :name "Name:")
               (text-field {:class "mb-3 form-control" :placeholder "Enter film name"} :name)]
              [:div.form-group.col-12
              (label :description "Description:")
               (text-area {:class "mb-3 form-control" :placeholder "Enter film description"} :description)]
              [:div.form-group.col-12
               (label :ratings "Rating (1-5):")]
              [:div.form-group.btn-group.col-12
               (map (labeled-radio "rating") (repeat 5 false) (range 1 6))]
              [:div.form-group.col-12.text-center
               (submit-button {:class "btn btn-primary text-center"} "Add")])]]))

(defn- film-attributes-view
  [name description rating]
  [:div
   [:div.row
    [:div.col-2 "Name:"]
    [:div.col-10 name]]
   (when description
     [:div.row
      [:div.col-2 "Description:"]
       [:div.col-10 description]])
   (when rating
     [:div.row
      [:div.col-2 "Rating:"]
      [:div.col-10 rating]])])

(defn film-view
  [{:keys [name description rating]} {:keys [errors messages]}]
  (page
   [:div.container.jumbotron.bg-light
    [:div.row
     [:h2 "Film"]]
    (film-attributes-view name description rating)
    (when errors
      (for [error (doall errors)]
       [:div.row.alert.alert-danger
        [:div.col error]]))
    (when messages
      (for [message (doall messages)]
       [:div.row.alert.alert-success
        [:div.col message]]))])) 

create-film-view返回 hiccup,显示一个表单来输入电影名称、描述和 1 到 5 之间的评分。注意来自film-ratings.view.template名称空间的page函数被用来将来自create-film-view的 hiccup 封装到提供导航条等的 hiccup 中。film-view函数获取一个表示电影的地图和一个包含错误或消息的地图,并渲染 hiccup 以显示电影以及错误和消息警报。渲染电影属性的打嗝声已经被提取到它自己的函数film-attributes-view中,因为您将在电影列表中重用它。

添加数据库函数作为边界

到目前为止,我们一直在实现主要处理 HTTP 请求和响应的函数和配置。我们已经添加了迁移以在开发 SQLite 数据库中创建一个Film表和对该数据库的 Integrant 引用,但是我们实际上并没有对该数据库做任何事情。

是时候添加一个边界名称空间来处理数据库交互了。边界是一个管道概念,用于将外部依赖项与代码的其余部分隔离开来。边界是 Clojure 协议和相关的实现。Clojure 协议是与其他语言中的接口类似的概念。

当前配置将传递给:film-ratings.handler.film/create处理程序的选项映射到:duct.database/sql键,Integrant 将通过引用一个duct.database.sql.Boundary记录来初始化这个键。在处理程序中,我们有一个对现在需要实现的函数的引用。

这个函数是在film-ratings.boundary.film名称空间中的create-film,并且这个函数需要在一个协议中定义,该协议使用我们的create-film函数的实现来扩展管道框架中的duct.database.sql.Boundary记录。

让我们继续创建那个film-ratings/src/film_ratings/boundary/film.clj文件:

(ns film-ratings.boundary.film
  (:require [clojure.java.jdbc :as jdbc]
            duct.database.sql)
  (:import java.sql.SQLException))

(defprotocol FilmDatabase
  (list-films [db])
  (create-film [db film]))

(extend-protocol FilmDatabase
  duct.database.sql.Boundary
  (list-films [{db :spec}]
    (jdbc/query db ["SELECT * FROM film"]))
  (create-film [{db :spec} film]
    (try
     (let [result (jdbc/insert! db :film film)]
       (if-let [id (val (ffirst result))]
         {:id id}
         {:errors ["Failed to add film."]}))
     (catch SQLException ex
       {:errors [(format "Film not added due to %s" (.getMessage ex))]})))) 

让我们专注于create-film函数。该功能在您的FilmDatabase协议中定义。然后,这个协议被实现为用您的create-film的实现来扩展duct.database.sql.Boundary记录。

不出所料,create-film函数引用了FilmDatabase(在启动时由 Integrant 通过实时数据库连接为您初始化)和一个来自处理程序解析的表单参数的电影地图引用。该函数使用clojure.java.jdbc/insert!函数将电影地图插入电影表。该函数返回一个带有新插入记录的 id 的映射,或者如果发生错误,返回一个错误的映射。

添加电影

我们现在有足够的实现来将电影添加到数据库中。如果您的 repl 仍然在终端会话中运行,那么切换回终端会话。如果没有,启动一个新的 repl。然后重置应用程序以重新加载新代码:

dev=> (reset)
:reloading (film-ratings.boundary.film film-ratings.handler.film film-ratings.main film-ratings.views.film film-ratings.handler.example dev user)
:duct.migrator.ragtime/applying :film-ratings.migrations/create-film#5fc9a814
:resumed
dev=> 

如果您现在转到http://localhost:3000/并点击Add Film按钮,您会看到:

继续填写表格并点击添加

我们添加了大量代码。让我们提交它并推送到 GitHub。

$ git add .
$ git commit -m "Add films functionality."
$ git push 

我们可以通过转到仪表板来检查 CircleCI,看看我们的构建是否已经正确运行。

列出电影

最后,我们只需要实现列出电影的配置、处理程序和视图。

更改 ataraxy 路由,并在 config.edn 中添加一个新的列表处理程序键:

 :duct.module/ataraxy
 {[:get "/"] [:index]
  "/add-film"
  {:get [:film/show-create]
   [:post {film-form :form-params}] [:film/create film-form]}
  [:get "/list-films"] [:film/list]}

 :film-ratings.handler/index {}
 :film-ratings.handler.film/show-create {}
 :film-ratings.handler.film/create {:db #ig/ref :duct.database/sql}
 :film-ratings.handler.film/list {:db #ig/ref :duct.database/sql} 

将以下处理函数添加到film-ratings/src/film_ratings/handler/film.clj文件:

(defmethod ig/init-key :film-ratings.handler.film/list [_ {:keys [db]}]
  (fn [_]
    (let [films-list (boundary.film/list-films db)]
      (if (seq films-list)
       [::response/ok (views.film/list-films-view films-list {})]
       [::response/ok (views.film/list-films-view [] {:messages ["No films found."]})])))) 

我们已经定义了在处理程序中调用的list-films函数,所以不需要添加它。

将新的list-film-view函数添加到film-ratings/src/film_ratings/views/film.clj文件中:

(defn list-films-view
  [films {:keys [messages]}]
  (page
   [:div.container.jumbotron.bg-light
    [:div.row [:h2 "Films"]]
    (for [{:keys [name description rating]} (doall films)]
      [:div
       (film-attributes-view name description rating)
       [:hr]])
    (when messages
      (for [message (doall messages)]
       [:div.row.alert.alert-success
        [:div.col message]]))])) 

因为这将为每部电影调用先前定义的film-attributes-view函数,所以我们需要确保文件中的list-films-viewfilm-attributes-view之后。

通过再次重置 repl 来测试这是否有效:

dev=> (reset)
:reloading (film-ratings.views.film film-ratings.handler.film)
:resumed
dev=> 

转到索引页面http://localhost:3000/并选择List Films按钮,您应该会看到您添加的所有电影的列表。

最后,向 Git 添加并提交您的更改。

摘要

恭喜您,您已经创建了一个功能性的管道网络应用程序!🎉目前,这个应用程序只有一个为开发配置文件定义的数据库,所以我们在构建中创建的 uberjar 实际上不会工作,因为生产 SQL 模块是一个空映射:duct.module/sql {}

我将把这作为一个练习留给读者,但如果你感觉不够自信,或者如果你在这方面有所挣扎,我将在随后的博客中详细介绍如何将生产数据库添加到应用程序中,如何使用 Docker 打包,以及如何让 circle ci将其部署到 AWS

阅读更多信息:


Chris Howe-Jones 是顾问 CTO、软件架构师、精益/敏捷蔻驰、开发人员和 DevCycle 的技术导航员。他主要从事 Clojure/ClojureScript、Java 和 Scala 方面的工作,客户从跨国组织到小型创业公司。

阅读更多克里斯·豪-琼斯的文章

构建自动发票生成器应用程序| CircleCI

原文:https://circleci.com/blog/build-automated-invoice-app/

本教程向您展示如何:

  1. 使用 API 生成发票并将其发送给客户
  2. 将计划管道添加到您的配置中
  3. 使用项目设置定义计划触发器

作为一名软件工程师和技术内容创作者,我与许多公司签订了许多不同的合同。为了获得工作报酬,大多数公司都要求我开发票。有时,他们希望每天都有,在周末,甚至在项目已经完成的时候。

给我的客户寄一张发票是至关重要的,因为它决定了我何时以及是否能按时拿到钱。如果这听起来像是一个重复的任务,会占用我大量的工作时间,那么你是对的。为了集中精力完成任务,我决定建立一个自动化的发票工作流程。

在本教程中,我将向您展示如何使用发票生成器 API调度管道有效地生成发票并将其发送给客户。

先决条件

对于本教程,您需要:

克隆演示应用程序

对于本教程,我已经建立了一个简单的 Node.js 项目,并在invoice.js文件中定义了一个现有的generateInvoice()函数。要开始,请运行以下命令:

git clone https://github.com/yemiwebby/automated-invoice-starter.git automated-invoice 

这会将项目克隆到开发目录中的一个automated-invoice文件夹中(或者当您从运行命令时)。

接下来,转到新创建的项目并安装它的依赖项:

cd automated-invoice

npm install 

在本地生成发票

要确认项目按预期运行,请在本地运行应用程序。通过运行以下命令生成发票:

node invoice.js 

输出应该显示一张发票已经生成并保存在您的项目目录中。

Saved invoice to invoice.pdf 

现在,您可以设置将发票作为附件发送。

将发票作为附件发送

从部署在互联网上的任何应用程序发送电子邮件都需要 SMTP 服务器和一些其他配置。对于本教程,我使用了 Mailtrap,它提供免费的 SMTP 服务来试验电子邮件发送功能。如果您还没有这样做,请在此处创建一个邮件陷阱帐户。

接下来,转到收件箱页面查看您的 SMTP 和 POP3 凭证。

Mailtrap dashboard

创建环境变量

使用以下命令在项目的根目录下创建一个.env文件:

cp .env.sample .env 

用 mailtrap.io 仪表板上的值替换占位符MAILTRAP_USERNAMEMAILTRAP_PASSWORD

将发票作为附件发送电子邮件

该应用程序使用 nodemailer 模块发送带有发票附件的电子邮件。该模块已经安装。你需要做的就是打开invoice.js文件,用下面的代码替换它的内容:

var https = require("https");
var fs = require("fs");
require("dotenv").config();
var nodemailer = require("nodemailer");

const generateInvoice = (invoice, filename, success, error) => {
  var postData = JSON.stringify(invoice);
  var options = {
    hostname: "invoice-generator.com",
    port: 443,
    path: "/",
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Content-Length": Buffer.byteLength(postData),
    },
  };
  var file = fs.createWriteStream(filename);
  var req = https.request(options, function (res) {
    res
      .on("data", function (chunk) {
        file.write(chunk);
      })
      .on("end", function () {
        file.end();
        if (typeof success === "function") {
          success();
          sendEmail(filename);
        }
      });
  });
  req.write(postData);
  req.end();
  if (typeof error === "function") {
    req.on("error", error);
  }
};

const sendEmail = (file) => {
  var transport = nodemailer.createTransport({
    host: "smtp.mailtrap.io",
    port: 2525,
    auth: {
      user: process.env.MAILTRAP_USERNAME,
      pass: process.env.MAILTRAP_PASSWORD,
    },
  });
  var mailOptions = {
    from: "invoice@me.com",
    to: "sample@me.com",
    subject: "Invoice for weekly payments via Node.js",
    text: "Find attached the weekly invoice from me. Thanks",
    attachments: [
      {
        path: file,
      },
    ],
  };
  transport.sendMail(mailOptions, function (error, info) {
    if (error) {
      console.log(error);
    } else {
      console.log("Email sent: " + info.response);
    }
  });
};

let invoice = {
  logo: "http://invoiced.com/img/logo-invoice.png",
  from: "Invoiced\n701 Brazos St\nAustin, TX 78748",
  to: "Awesome Company / Client",
  currency: "usd",
  number: "INV-0001",
  payment_terms: "Due for payment",
  items: [
    {
      name: "Weekly technical content",
      quantity: 1,
      unit_cost: 500,
    },
    {
      name: "Employee Portal Management",
      quantity: 1,
      unit_cost: 1000,
    },
  ],
  fields: {
    tax: "%",
  },
  tax: 5,
  notes: "Thanks for being an awesome customer!",
  terms: "Looking forward to the payments",
};

generateInvoice(
  invoice,
  "invoice.pdf",
  () => console.log("Saved invoice to invoice.pdf"),
  (err) => console.log(err)
); 

对这个代码块的更改通过要求nodemailer模块来修改文件。创建一个sendEmail()函数,它将生成的文件作为参数,并作为附件发送。附件被发送给用mailOptions变量定义的接收者。*generateInvoice()*函数调用sendEmail()方法并传递适当的参数给它。

现在,使用以下命令再次运行应用程序:

node invoice.js 

输出是:

Saved invoice to invoice.pdf
Email sent: 250 2.0.0 Ok: queued 

此输出显示发票已成功生成并发送。前往您的 Mailtrap 收件箱查看邮件。

Mailtrap inbox

点击附件打开发票。

Generate invoice

添加 CircleCI 配置文件

接下来,您需要为 CircleCI 添加管道配置,以自动创建和发送发票。

在项目的根目录下,创建一个名为.circleci的文件夹。在该文件夹中,创建一个名为config.yml的文件。在新创建的文件中,添加以下配置:

version: 2.1
jobs:
  build:
    working_directory: ~/project
    docker:
      - image: cimg/node:17.4.0
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run the application
          command: node invoice.js
          background: true 

该脚本获取 Node.js Docker 映像并安装项目的依赖项。最后一步运行命令来生成并发送发票。

接下来,在 GitHub 上建立一个存储库,并将项目链接到 CircleCI。查看将项目推送到 GitHub 以获得分步说明。

登录您的 CircleCI 帐户。如果你注册了你的 GitHub 账户,你所有的库都可以在你项目的仪表盘上看到。

点击为您的automated-invoice项目设置项目

Setup project on CircleCI

将提示您几个关于配置文件的选项。选择use the .circleci/config.yml in my repo选项。在 GitHub 上输入你的代码所在的分支名称,然后点击设置项目按钮。

Select configuration

您的第一个工作流将开始运行,但会失败。这是因为您没有提供邮件陷阱的配置详细信息。你现在可以弥补了。

点击项目设置按钮,然后点击环境变量。添加这两个新变量:

  • MAILTRAP_USERNAME
  • MAILTRAP_PASSWORD

重新运行工作流。将会生成发票并发送电子邮件。

Successful Pipeline

设置计划的管道

通常,一旦您将代码推送到存储库,CircleCI 工作流就会自动执行。但是,对于本教程,目标是以特定的时间间隔运行这个管道,最好是每周一次。使用 CircleCI 上的调度管道,您可以像配置一个cron作业一样配置您的管道,并按时间间隔运行它。有两种方法可以做到这一点:

  • 使用 API
  • 使用项目设置

对于本教程,我们将使用项目设置来配置管道触发器。从您的项目中,进入项目设置。从左侧工具条的菜单中点击触发器

在触发器页面上,点击添加预定触发器。填写触发器表单以配置管道。

Schedule Trigger

以下是这些字段的输入内容:

  • Trigger name是唯一的时间表名称。
  • Trigger Source表示将触发管道的源(在本教程中为scheduled)。
  • Trigger description是一个可选字段,可用于添加有关触发器的更多信息。
  • Timetable定义运行预定管道的时间和频率。
  • Pipeline Parameters是在参数键中声明的变量。它们可用于检查何时运行管道,但是本教程不需要它们。
  • Attribution指定与日程相关联的用户。它可以是中立演员的系统。或者它可以是当前的,这将获取您当前用户的权限(根据您正在使用的令牌)。

这些设置将管道配置为每天每五分钟运行一次。点击保存触发器将其激活。

您可以从“项目触发器”页面查看创建的触发器。

Project Triggers page

返回到管道页面,等待至少五分钟。你的管道会被 CircleCI 系统触发。

Pipeline schedule results

您也可以检查您的邮件陷阱收件箱,以查看生成的发票。

Mailtrap multiple inbox list

结论

在本教程中,您能够快速生成一张发票,并毫不费力地将其发送给特定的收件人。为了实现开票流程的自动化,我们使用了 CircleCI 的预定管道功能。

因为您在这里实现的内容很少,所以它可以很容易地包含在现有项目或新项目中。这种工作流程的另一个优点是,当 CircleCI 自动处理发票时,您可以保持项目的持续集成和部署。这一新增功能消除了繁琐的手动任务并实现了自动化,因此您可以将更多时间用于开发和部署应用程序。

我希望本教程对你有所帮助。完整的源代码可以在 GitHub 的这里找到。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他解决问题的技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。由于精通技术,他的爱好包括尝试新的编程语言和框架。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

如何用 Docker | CircleCI 构建 CI/CD 管道

原文:https://circleci.com/blog/build-cicd-piplines-using-docker/

一年中,我在会议和其他活动中与我的许多工程师同事交谈。我喜欢演示的一件事是他们如何毫不费力地将持续集成/持续部署(CI/CD)管道实现到代码库中。在这篇文章中,我将介绍一些演示代码和我在演示中使用的 CircleCI 配置。遵循这些步骤将向您展示如何将 CI/CD 管道实现到您的代码库中。

这篇文章将涵盖:

  • Python Flask 应用程序的简单单元测试
  • 如何在项目中使用 CircleCI 配置文件在代码库中实现 CI/CD 管道
  • 建立码头工人形象
  • 将 Docker 图像推送到 Docker Hub
  • 启动一个部署脚本,该脚本将在数字海洋服务器上运行 Docker 容器中的应用程序

先决条件

在我们开始之前,您需要:

完成所有先决条件后,您就可以开始下一部分了。

使用示例应用程序

在这篇文章中,我将使用一个简单的 Python Flask ,你可以在这里和本地git clone找到这个项目的完整源代码。该应用程序是一个简单的 web 服务器,当向它发出请求时,它会呈现 html。Flask 应用程序位于hello_world.py文件中:

from flask import Flask

app = Flask(__name__)

def wrap_html(message):
    html = """
        <html>
        <body>
            <div style='font-size:120px;'>
            <center>
                <image height="200" width="800" src="https://infosiftr.com/wp-content/uploads/2018/01/unnamed-2.png">
                <br>
                {0}<br>
            </center>
            </div>
        </body>
        </html>""".format(message)
    return html

@app.route('/')
def hello_world():
    message = 'Hello DockerCon 2018!'
    html = wrap_html(message)
    return html

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000) 

这段代码中的关键是去掉hello_world()函数中的message变量。此变量指定一个字符串值,并将在单元测试中测试此变量的值是否匹配。

测试代码

代码必须经过测试,以确保向公众发布高质量、稳定的代码。Python 附带了一个名为 unittest 的测试框架,我将在本教程中使用它。现在我们有了一个完整的 Flask 应用程序,它需要一个配套的单元测试来测试应用程序并确保它按照设计的那样运行。单元测试文件test_hello_world.py是我们 hello_world.py 应用程序的单元测试。现在,让我们浏览一下代码。

import hello_world
import unittest

class TestHelloWorld(unittest.TestCase):

    def setUp(self):
        self.app = hello_world.app.test_client()
        self.app.testing = True

    def test_status_code(self):
        response = self.app.get('/')
        self.assertEqual(response.status_code, 200)

    def test_message(self):
        response = self.app.get('/')
        message = hello_world.wrap_html('Hello DockerCon 2018!')
        self.assertEqual(response.data, message)

if __name__ == '__main__':
    unittest.main() 
import hello_world
import unittest 

使用import语句导入hello_world应用程序使得测试可以访问hello_world.py中的代码。接下来,导入unittest模块,并开始为应用程序定义测试覆盖。

class TestHelloWorld(unittest.TestCase):testhello world 从基类unittest.Test实例化而来,基类是测试的最小单元。它检查对一组特定输入的特定响应。unittest 框架提供了一个基类 TestCase,您将使用它来创建新的测试用例。

def setUp(self):
        self.app = hello_world.app.test_client()
        self.app.testing = True 

调用类级方法setUp()来准备测试夹具。在调用测试方法之前立即调用它。在这个例子中,我们创建并定义了一个名为app的变量,并从 hello_world.py 代码中将它实例化为app.test_client()对象。

def test_status_code(self):
    response = self.app.get('/')
    self.assertEqual(response.status_code, 200) 

方法test_status_code()在代码中指定了一个实际的测试用例。这个测试用例向 Flask 应用程序发出一个get请求,并在response变量中捕获应用程序的响应。self.assertEqual(response.status_code, 200)response.status_code结果的值与200的期望值进行比较,这表示get请求成功。如果服务器响应的 status_code 不是 200,测试将失败。

def test_message(self):
    response = self.app.get('/')
    message = hello_world.wrap_html('Hello DockerCon 2018!')
    self.assertEqual(response.data, message) 

另一个方法test_message(),指定了一个不同的测试用例。这个测试用例被设计来检查在 hello_world.py 代码的hello_world()方法中定义的message变量的值。像前面的测试一样,对应用程序进行了一个 get 调用,结果被捕获到一个response变量中:

message = hello_world.wrap_html('Hello DockerCon 2018!') 

按照 hello_world 应用程序中的定义,message变量被赋予从hello_world.wrap_html() helper 方法得到的 html。字符串Hello DockerCon 2018被提供给wrap_html()方法,然后在 html 中注入并返回。test_message()验证应用程序中的消息变量是否与测试用例中的预期字符串匹配。如果字符串不匹配,测试将失败。

实施 CI/CD 管道

既然我们已经清楚了应用程序及其单元测试,那么是时候将 CI/CD 管道实现到代码库中了。使用 CircleCI 实现 CI/CD 管道非常简单,但是在继续之前,请确保执行以下操作:

设置 CI/CD 管道

一旦您的项目在 CircleCI 平台中建立,任何向上游推送的提交都将被检测到,CircleCI 将执行您的config.yml文件中定义的作业。

在 repo 的根目录中创建一个新目录,并在这个新目录中添加一个 yaml 文件。新资产必须遵循项目的 git 存储库中的这些命名模式-目录:.circleci/文件:config.yml。这个目录和文件基本上定义了 CircleCI 平台的 CI/CD 管道和配置。

配置文件

config.yml 文件是所有 CI/CD 奇迹发生的地方。在显示示例文件的代码块之后,我将简要解释语法中发生了什么。

version: 2
jobs:
  build:
    docker:
      - image: circleci/python:2.7.14
        environment:
          FLASK_CONFIG: testing
    steps:
      - checkout
      - run:
          name: Setup VirtualEnv
          command: |
            echo 'export TAG=0.1.${CIRCLE_BUILD_NUM}' >> $BASH_ENV
            echo 'export IMAGE_NAME=python-circleci-docker' >> $BASH_ENV 
            virtualenv helloworld
            . helloworld/bin/activate
            pip install --no-cache-dir -r requirements.txt
      - run:
          name: Run Tests
          command: |
            . helloworld/bin/activate
            python test_hello_world.py
      - setup_remote_docker:
          docker_layer_caching: true
      - run:
          name: Build and push Docker image
          command: |
            . helloworld/bin/activate
            pyinstaller -F hello_world.py
            docker build -t ariv3ra/$IMAGE_NAME:$TAG .
            echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
            docker push ariv3ra/$IMAGE_NAME:$TAG
      - run:
          name: Deploy app to Digital Ocean Server via Docker
          command: |
            ssh -o StrictHostKeyChecking=no root@hello.dpunks.org "/bin/bash ./deploy_app.sh ariv3ra/$IMAGE_NAME:$TAG" 

jobs:键代表将要运行的任务列表。作业封装了要执行的操作。如果您只有一个作业要运行,那么您必须给它一个键名build:。这里有更多关于工作和建造的细节。

build:键由几个元素组成:

docker:键告诉 CircleCI 使用一个 Docker 执行器,这意味着我们的构建将使用 Docker 容器来执行。

image: circleci/python:2.7.14指定构建必须使用的 Docker 映像。

步骤:

steps:键是一个集合,它指定了将在这个构建中执行的所有命令。发生的第一个动作是- checkout命令,它在执行环境中执行代码的 git 克隆。

- run:键指定在构建中执行的命令。运行键有一个name:参数,您可以在这里标记一组命令。例如,name: Run Tests对测试相关的动作进行分组。这种分组有助于在 CircleCI 仪表板中组织和显示构建数据。

注: 每个run块相当于单独的、个体的壳或端子。已配置或已执行的命令将不会在以后的运行块中持续。使用文档的提示&技巧部分中的$BASH_ENV解决方法。

- run:
    name: Setup VirtualEnv
    command: |
      echo 'export TAG=0.1.${CIRCLE_BUILD_NUM}' >> $BASH_ENV
      echo 'export IMAGE_NAME=python-circleci-docker' >> $BASH_ENV 
      virtualenv helloworld
      . helloworld/bin/activate
      pip install --no-cache-dir -r requirements.txt 

这个运行块的command:键有一个要执行的命令列表。这些命令设置了$TAG & IMAGE_NAME自定义环境变量,这些变量将在整个构建过程中使用。剩下的命令设置 python virtualenv 并安装在requirements.txt文件中指定的 python 依赖项。

- run:
    name: Run Tests
    command: |
      . helloworld/bin/activate
      python test_hello_world.py 

在这个运行块中,命令对我们的应用程序执行测试。如果这些测试失败,整个构建将会失败。开发人员需要修改他们的代码并重新提交。

- setup_remote_docker:
    docker_layer_caching: true 

这个运行块指定了 setup_remote_docker: 键,这是一个支持从 docker executor 作业中构建、运行和推送映像到 Docker 注册表的特性。当 docker_layer_caching 设置为 true 时,CircleCI 将尝试重用在之前的作业或工作流中构建的 docker 图像(层)。也就是说,您在之前的作业中构建的每个图层都可以在远程环境中访问。但是,在某些情况下,即使配置指定 docker_layer_caching: true,您的作业也可能在干净的环境中运行。

setup_remote_docker:特性是必需的,因为我们正在为我们的应用程序构建 Docker 映像,并将该映像推送到 Docker Hub。

- run:
    name: Build and push Docker image
    command: |
      . helloworld/bin/activate
      pyinstaller -F hello_world.py
      docker build -t ariv3ra/$IMAGE_NAME:$TAG .
      echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
      docker push ariv3ra/$IMAGE_NAME:$TAG 

构建和推送 Docker 映像运行块指定了使用 pyinstaller 将应用程序打包成一个二进制文件的命令。然后继续进行 Docker 映像构建过程。

docker build -t ariv3ra/$IMAGE_NAME:$TAG .
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
docker push ariv3ra/$IMAGE_NAME:$TAG 

这些命令基于 repo 中包含的Dockerfile构建 docker 映像。构建 Docker 映像的说明可以在这里找到: Dockerfile

FROM python:2.7.14

RUN mkdir /opt/hello_word/
WORKDIR /opt/hello_word/

COPY requirements.txt .
COPY dist/hello_world /opt/hello_word/

EXPOSE 80

CMD [ "./hello_world" ] 

echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin命令使用 CircleCI 仪表板中设置的\(DOCKER_LOGIN 和\)DOCKER_PWD 环境变量作为登录凭证&将此映像推送到 Docker Hub。

- run:
    name: Deploy app to Digital Ocean Server via Docker
    command: |
      ssh -o StrictHostKeyChecking=no root@hello.dpunks.org "/bin/bash ./deploy_app.sh ariv3ra/$IMAGE_NAME:$TAG" 

最后一个运行块将我们的新代码部署到运行在数字海洋平台上的实时服务器上,确保您已经在远程服务器上创建了部署脚本。ssh 命令访问远程服务器并执行deploy_app.sh脚本,包括 ariv3ra/\(IMAGE_NAME:\)TAG ,它指定要从 Docker Hub 拉取和部署的映像。

作业成功完成后,新的应用程序应该在 config.yml 文件中指定的目标服务器上运行。

摘要

本教程的目标是指导您在代码中实现 CI/CD 管道。虽然这个示例是使用 Python 技术构建的,但是一般的构建、测试和部署概念可以使用您喜欢的任何语言或框架轻松实现。您可以扩展本教程中的简单示例,并根据您自己的管道进行定制。CircleCI 有很棒的文档请确保研究我们的文档网站。如果你真的遇到困难,你可以联系 https://discuss.circleci.com/社区/论坛网站上的 CircleCI 社区。

阅读更多信息:

停靠影像-停靠精灵|圆形

原文:https://circleci.com/blog/build-custom-docker-images-faster-and-more-easily-with-our-dockerfile-wizard/

有关我们最新的 Docker 内容,请参见为您的 CI/CD 管道使用 Docker 指南

在过去几年中,Docker 和其他容器化/虚拟化工具的崛起已经帮助我们重新关注 CircleCI 所珍视的一些软件开发价值——一致性、自动化和连续性;也就是说,在一个一致的执行环境中开发软件,以自动化的方式测试它,并在关注新代码的持续交付的情况下部署它。

我们在去年夏天发布了 CircleCI 2.0,将 Docker 置于我们的持续集成平台的核心,并允许客户使用 Docker 映像的任意组合作为执行环境,在 CircleCI 上构建项目。

然而,在实践中,用您需要的每种语言和依赖项的精确版本构建和定制您自己的 Docker 映像可能是棘手的、乏味的和令人生畏的——尤其是对于那些对 Docker 还不熟悉的软件开发人员。出于这个原因,我们发布了各种各样的便利 Docker 镜像,以及软件工具集和执行环境的常见组合。

但是,如果您使用的是旧版本的某某工具,而我们的便利图片没有涵盖,或者只是想要更多的选项,该怎么办呢?现在,我们开发了一个 Docker 工具,可能会有所帮助。我们新的 Dockerfile 向导允许您在 CircleCI 上构建和部署您自己的定制 Docker 映像——您所需要的只是一个 Docker Hub 帐户(当然,还有一个 CircleCI 帐户)。

DockerfileWizard.jpeg

要使用它,将docker file-wizard repository分支到您的 GitHub 帐户,并开始在 CircleCI 上构建您的项目分支。然后,按照自述文件中的说明,你可以在 CircleCI 上定制和构建你的 Docker 镜像,甚至不需要在你的本地计算机上安装 Docker。

完成后,您将拥有一个 Docker 图像,可以在 CircleCI 上构建的任何项目中使用——或者在任何其他地方使用。

当您能够维护一个一致的软件环境时,持续集成工作得最好,从您的本地开发机器到您的 CI 构建。Dockerfile 向导将帮助您做到这一点。

我们希望这个工具能让您更进一步,永远不必再说“它在我的本地机器上工作,但在 CircleCI 上不工作”。

构建映像更新计划

原文:https://circleci.com/blog/build-image-update-schedule/

TL;速度三角形定位法(dead reckoning)

图像类型 时间范围 更多信息
Docker 便利图片 小于 24 小时(自动) 了解更多信息
机器图像 需要时(手动) 了解更多信息
Xcode 图像 少于 7 天(手动) 了解更多信息

Docker 便利图片

CircleCI 上的大多数作业都是由docker执行程序运行的。这使得 Docker 映像能够用于作业的执行环境。虽然可以使用 Docker Hub、AWS 的 ECR 或 GCP 的 Container Registry 上的任何 Docker 映像,但我们提供了一组映像,如circleci/golang,其中预装了在 CI 环境中有用的工具。利用 Docker 的分层能力,我们的图像基于 Docker 和 Docker 社区 Docker Library 发布的上游图像。

更新

每当一个新的版本出现,例如最近的 Go v1.11 版本,Docker 库社区就会更新这个映像。在这种情况下,它们更新了golang图像。这需要多长时间完全由他们决定。有时,图像会在几小时内更新。如果有阻塞 GitHub 的问题,可能需要一两天。无论如何,我们的 Docker 便利图像构建系统将根据需要,使用预定工作流程每 24 小时从上游重建新图像。因此,一旦上游图像通过 Docker 库可用,我们将在 24 小时内拥有我们的版本。

关于文档页面的更多信息。

测试版和发布候选版

Docker 提供了很大的灵活性。使用 Docker 库映像以及 CircleCI 映像的一个好处是,许多软件开发人员会为不稳定的版本发布标签。例如,在 Go v1.11 问世和golang/circleci:1.11图像可用之前,几个发布候选(RC)标签已经可用了相当长一段时间。像circleci/golang:1.11rccircleci/golang:1.11-r2这样的标签在每个 RCs 之后都是可用的。这使您能够在发布日之前开始在即将到来的语言版本上测试您的软件。

机器图像

当需要由docker执行器提供的低级访问时,通常使用machine执行器。它是一个虚拟机(VM)映像,而不是 Docker/LXC 映像。

由于其极其普通的用例,图像几乎不经常更新。它只安装了一些基本的构建模块工具。machine镜像安装了 Docker 和 Docker Composed。这就是为什么这个映像会定期更新的主要原因:为了跟上 Docker 的步伐。

更新

没有设定更新此映像的时间表。它目前大约每月更新一次,但可能需要更长时间。提供此映像是为了让用户能够在虚拟机上安装他们需要的东西。可以在文档页面上找到关于machine可用图像版本的信息。

macOS 映像

执行人是最简单的解释。我们基于 Xcode 版本发布,而不是处理基于月份的版本号,甚至 macOS 版本号。我们有基于 Xcode 版本的语义版本化( SemVer )的独立图像,比如Xcode 9.4.1图像。

更新

当有新的 Xcode 版本时,我们会发布新的图像。节奏主要取决于苹果。一旦某个版本的 Xcode 发布,我们通常会在七天内为它准备一个映像。这一时间框架受版本变化的影响很大(例如,bugfix 版本花费的时间最少),也受苹果发布更新的周末时间的影响。可用图像/支持的 Xcode 版本可在文档页面上找到。

Snapcraft - Snapcraft ip | CircleCI

原文:https://circleci.com/blog/build-test-publish-snap-packages/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


Snapcraft 是一个软件包管理系统,它正在努力争取在 Linux 平台上的一席之地,它重新想象了你如何交付你的软件。一组新的跨发行版工具可以帮助您构建和发布“快照”。我们将介绍如何使用 CircleCI 2.0 来支持这个过程,以及在这个过程中可能遇到的一些问题。

什么是快照包?还有 Snapcraft?

快照是 Linux 发行版的软件包。它们的设计吸取了在 Android 以及物联网设备等移动平台上交付软件的经验教训。 Snapcraft 是一个包含快照和构建快照的命令行工具的名称,是一个网站,几乎是围绕实现这一点的技术的整个生态系统。

Snap 包旨在隔离和封装整个应用程序。这一概念实现了 Snapcraft 的目标,即提高软件的安全性、稳定性和可移植性,允许单个“snap”不仅可以安装在多个版本的 Ubuntu 上,还可以安装在 Debian、Fedora、Arch 等版本上。Snapcraft 网站上的描述:

为每个 Linux 桌面、服务器、云或设备打包任何应用程序,并直接提供更新。

在 CircleCI 2.0 上构建快照包

在 CircleCI 上构建快照与您的本地机器基本相同,用 CircleCI 2.0 语法包装。在这篇文章中,我们将浏览一个样本配置文件。如果你不熟悉 CircleCI 或者想了解更多关于 2.0 的入门知识,可以从这里开始

基本配置

version: 2
jobs:
  build:
    machine: true
    working_directory: ~/project
    steps:
      - checkout
      - run:
          command: |
            sudo apt update && sudo apt install -y snapd
            sudo snap install snapcraft --edge --classic
            /snap/bin/snapcraft 

这个例子使用machine执行器来安装snapd,这个可执行文件允许您管理快照并启用平台,以及snapcraft,这个工具用于创建快照。

使用了machine执行器而不是docker执行器,因为我们需要一个更新的内核用于构建过程。这里提供了 Linux 4.4,这对于我们的目的来说已经足够新了。

用户空间依赖性

上面的例子使用了machine执行器,它目前是一个带有 Ubuntu 14.04(可信)和 Linux v4.4 内核的虚拟机。如果您的项目/snap 需要可信任的存储库中可用的构建依赖项,这是没问题的。如果您需要不同版本的依赖项,比如 Ubuntu 16.04 (Xenial),该怎么办?我们仍然可以在machine执行器中使用 Docker 来构建我们的快照。

version: 2
jobs:
  build:
    machine: true
    working_directory: ~/project
    steps:
      - checkout
      - run:
          command: |
            sudo apt update && sudo apt install -y snapd
            docker run -v $(pwd):$(pwd) -t ubuntu:xenial sh -c "apt update -qq && apt install snapcraft -y && cd $(pwd) && snapcraft" 

在这个例子中,我们再次在machine executor 的 VM 中安装snapd,但是我们决定安装 Snapcraft,并在用 Ubuntu Xenial 映像构建的 Docker 容器中构建我们的 snap。所有在 Ubuntu 16.04 中可用的apt包将在构建期间对snapcraft可用。

测试

在我们的博客、我们的文档【我们的文档】以及互联网上,对你的软件代码进行单元测试已经有了广泛的报道。搜索你的语言/框架和单元测试或 CI 将会找到大量的信息。在 CircleCI 上构建快照意味着我们以一个.snap文件结束,除了创建它的代码之外,我们还可以测试这个文件。

工作流程

假设我们构建的快照是一个 webapp。我们可以构建一个测试套件来确保此快照正确安装和运行。我们可以试着安装卡扣。我们可以运行 Selenium 来确保正确的页面加载、登录、工作等。这里有一个问题,快照被设计成可以在多个 Linux 发行版上运行。这意味着我们需要能够在 Ubuntu 16.04、Fedora 25、Debian 9 等平台上运行这个测试套件。CircleCI 2.0 的工作流可以有效地解决这个问题。

CircleCI 2.0 测试版最近增加了一个功能,那就是工作流。(更新:CircleCI 2.0 现已上线,截止 2017 年 7 月 11 日。点击查看。)这允许我们在 CircleCI 中以一定的流程逻辑运行离散任务。在这种情况下,我们的 snap 构建完成后,这将是一个单独的作业,然后我们可以开始 snap 发行版测试作业,并行运行。我们想要测试的每个发行版都有一个。这些作业中的每一个都是该发行版的不同的 Docker 映像(或者在将来,会有额外的executors可用)。

下面是一个简单的例子:

workflows:
  version: 2
  build-test-and-deploy:
    jobs:
      - build
      - acceptance_test_xenial:
          requires:
            - build
      - acceptance_test_fedora_25:
          requires:
            - build
      - acceptance_test_arch:
          requires:
            - build
      - publish:
          requires:
            - acceptance_test_xenial
            - acceptance_test_fedora_25
            - acceptance_test_arch 

这个设置构建了 snap,然后用四个不同的发行版在其上运行验收测试。如果所有发行版构建都通过了,那么我们可以运行 publish job来完成任何剩余的 snap 任务,然后将它推送到 Snap Store。

持久化。快照包

为了在工作流示例中测试我们的.snap包,需要一种在构建之间持久保存该文件的方法。这里我提两种方式。

  1. 工件——我们可以在build任务期间将快照包存储为 CircleCI 工件。然后在以下作业中检索它。CircleCI Workflows 有自己处理共享工件的方式,可以在这里找到
  2. snap store 频道 -在 snap store 发布快照时,有不止一个channel可供选择。将 snap 的主分支发布到edge渠道进行内部和/或用户测试已经成为一种惯例。这可以在build作业中完成,以下作业从边缘通道安装 snap。

第一种方法完成速度更快,其优势是能够在快照进入快照商店和接触任何用户(甚至是测试用户)之前对其进行验收测试。第二种方法的优势在于,从快照存储安装是 CI 期间运行的测试之一。

向快照存储验证

脚本snap craft-config-generator . py可以生成商店凭证并将它们保存到.snapcraft/snapcraft.cfg(注意:在运行公共脚本之前,一定要检查它们)。您不希望在回购中以明文形式存储该文件(出于安全原因)。您可以对文件进行 base64 编码,并将其存储为一个私有环境变量,或者您可以[加密文件][加密文件]并将密钥存储在一个私有环境变量中。

下面是一个将商店凭证保存在加密文件中,并在deploy步骤中使用凭证发布到快照商店的示例:

- deploy:
    name: Push to Snap Store
    command: |
      openssl aes-256-cbc -d -in .snapcraft/snapcraft.encrypted -out .snapcraft/snapcraft.cfg -k $KEY
      /snap/bin/snapcraft push *.snap 

根据前面的工作流示例,这可以是一个仅在验收测试作业通过时运行的部署作业,而不是部署步骤。

更多信息

安全 Kubernetes -安全 CI/CD | CircleCI & Alcide

原文:https://circleci.com/blog/build-with-circleci-configure-securely-with-alcide/

DevOps 已经不是什么新概念了。许多公司已经将 DevOps 集成到他们的软件开发流程中,以改进和加速软件开发,并帮助推动他们的数字化转型。现在有完整的工具生态系统、方法和转换模型,以及无尽的资源,可用于指导公司走上 DevOps 之旅。

但是 DevOps 的成功有时很难衡量,因为它不是一个正式的框架;它更多的是一种文化和一套做法。你能得到的指导是有限的,以确保你做得正确,或者你能准确地衡量你的成功和失败。DevOps 在每个组织中看起来也不同,导致没有两个 DevOps 管道是相同的。

DevOps 最突出的目标之一是确保无摩擦、尽可能自动化的 CI/CD 管道。让我们从安全的角度来看看这意味着什么。

持续 Kubernetes 部署:Alcide 方式

确保安全性符合性的最简单的方法是在开发阶段向左移动并解决安全性问题。通常,安全性是在生产阶段应用的,这意味着它不是环境端到端流程的一部分。在开发级别保护应用程序和网络将使您更有信心,您的应用程序将在生产级别正确地互操作。

左移后,确保以安全的方式持续部署和监控集群、节点和单元。理想情况下,您应该有一个工具,它通过查看工作负载安全和治理检查、集群工作节点检查、集群入口控制器和等等来提供集群配置和安全状态的实时摘要,但是该工具最重要的特性是它会使未能通过策略检查的资源上的管道失败。

在最近我们在阿尔西德进行的一项分析中,我们查看了 5000 多份跟踪扫描,发现 DevOps 团队在遵循 Kubernetes 的最佳实践(如秘密处理和网络策略)时面临着巨大的挑战和差距。具体来说,我们发现 89%的部署扫描显示,公司没有使用 Kubernetes 的秘密资源,秘密写在公开。我们还发现,超过 75%的扫描部署使用装载高漏洞主机文件系统(如/proc)的工作负载,而没有一个被调查的环境显示使用 Kubernetes 网络策略的分段实施。

埃尔希德的 Kubernetes 顾问做的一切,甚至更多。旨在快速安全地增加 K8s 集群,使用 DevSecOps 策略进行开发的团队可以享受自动化的、无需人工干预的 Kubernetes 体验,并让 Alcide 来完成所有繁重的工作。

底线

DevSecOps 要么成功地提高您的团队的速度、敏捷性和安全性,要么您的组织将遭受损失。正确创建您的管道,并确保不要让错误配置转变为安全风险。必须通过在每个阶段集成安全性来保护整个应用程序管道。通过 CircleCI 和 Alcide 的本机集成,这使得整个过程变得容易得多。现在,您可以通过添加 Alcide Advisor orb ,只需几行配置就可以将 Alcide 与 CircleCI 一起使用。orb是 CircleCI config 的可重用、可共享的开源包,支持服务的即时集成。完整的 CircleCI 球体列表见球体注册表


这篇文章是我们制作的关于 DevSecOps 的系列文章的一部分。要阅读本系列的更多文章,请单击下面的链接之一。

在 CircleCI - CircleCI 为行为分析开发一致的分类法

原文:https://circleci.com/blog/building-a-consistent-taxonomy-for-behavioral-analytics-with-amplitude-segment-schema-and-om/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


这是 CircleCI 关于构建分析的第二篇也是最后一篇文章。这篇文章从上一篇文章停止的地方开始,并专门讨论了我为解决第 1 部分中的问题而提出的内部实现。

我们决定采用的第三方平台(细分、幅度和旁观者)解决了人口统计(持久)和行为(特定事件)数据的组合问题,以及创建组织渠道的问题。我们解决了数据完整性问题——该问题是由与我们之前的分析提供商的不良集成造成的——通过在切换到我们的新提供商 Segment 时重写我们的集成代码。这种平台组合没有帮助缓解的另一个主要问题是数据的内聚性和一致性。因此,这成为我在实施过程中的主要关注点。

没有重复事件的一致分类是强大的行为分析基础设施的基石。有了这个基础,就更容易理解,更容易理解和有效地使用数据,最终加快分析和采用率。

因此,我的工作是构建一个平台,确保开发者以一种不仅对用户清晰的方式命名事件,而且不会意外地创建类似或重复的事件。然而,同样重要的是,分类法必须使开发人员添加新事件变得简单。因为当事件创建变得太困难时,它被视为生产力的负担,并且不被添加到开发工作流中。

事件模式和分类

行为分析有两个主要部分,因此我需要关注两个地方来确保一致性:事件名称和事件数据。

事件名称

由于没有事件名称的权威模式,我们以前的实现已经发现了类似事件名称的问题,例如clicked-signupsignup-clicked。为了抵消这种事件名称的狂野西部,我解决了我的第一个挑战:设计一个既一致又直观的模式。

看看我们在网站上关心的用户交互,很明显大多数是由用户的行为触发的印象、点击和状态变化。在回顾了我们现有的许多事件名称之后,我将必要的结构浓缩成一个模式,可以处理所有这些情况。新约定变成: -

这种分解的一个例子可以在upgrade-button-impressionupgrade-button-clickedplan-upgraded的执行中看到。每一个都代表我们的计划升级漏斗的一部分:一个是当用户看到升级按钮时,另一个是当他们点击它时,另一个是当他们的计划在数据库中升级时。并且每个都属于该模式。

从事件名称中排除的和包含的一样重要。就拿这个美女来说:blue-signup-button-header-enterprise-page-clicked。CircleCI 并不是唯一一个反对简洁的人;我在我以前的许多公司都看到过这种冗长。一方面,这个名字极其直观。数据消费者几乎不可能误解这个事件的动作。然而,如果事件名称太细,它们就有污染分析名称空间的风险,成为分析的负担。

从数据分析的角度来看,这种激进的细分会造成统计上的噩梦。为了计算像注册按钮被点击的数量这样基本的事情,现在有必要汇总几个事件的信息。简化您的行为数据可以让您快速、轻松地获得一个高层视图,了解您的平台上的哪些功能正在执行哪些操作。

如果这个例子看起来有点夸张,那是因为它确实如此。通常,开发人员不会试图将关于一个事件的所有信息都强加到它的名称中。然而,有一条信息是开发人员一直热衷于放入名称中的:活动的地点。然而,该模式有意地从事件名称中排除了这一信息。事件名称应该是视图(和组件)不可知的;名字应该代表用户动作,而不包括动作发生的地方。

事件数据

现在,我不是说一个事件的地点不重要;绝对是。它不应该存储在事件名称中。如果你发现一周内注册人数增加了 30%,你肯定会想看看是哪个页面产生了这些注册人数。

输入事件数据的用途。

借助我们的行为分析平台(细分+幅度),事件数据对于细分事件至关重要。在我们的 SQL 仓库中,Segment 为每个惟一的事件名创建一个表,并将每个事件数据转换成该表中的一列。这使我们能够使用 WHERE 和 GROUP BY 等 SQL 语句来划分事件表,并获得更细粒度的洞察力。

在广度上,我们可以使用这些相同的事件数据片段来分割数据或创建用户群组。例如,我们可以根据用户如何跟踪他们的第一个项目(通过我们的“添加项目”页面或构建顶部的“跟踪项目”按钮)来将用户分组。这些群组(分别为project-followed.view = add-projectsproject-followed.view = build)随后可用于测量保持力。

firstimage2.png

与事件名称一样,拥有一致的事件数据模式也很重要。如果org在一个事件中引用组织名称,在另一个事件中引用组织 id,那么事件数据就会变得和我们过去的事件名称一样混乱和无效。

因为每个事件都可能需要定制数据,所以完全系统化事件数据分类是一项挑战。例如,我们的teammates-invited事件有一个num-teammates属性,表示有多少队友收到了邀请。其他事件很少使用这个num-teammates数据,所以将它添加到已定义的事件数据分类中是没有意义的。

然而,有四个事件数据在 CircleCI 的所有事件中普遍存在:orgviewuserrepo。这些数据通常表示用户在组织(org)环境下的页面(视图)上执行的操作,有时也表示在特定项目(repo)环境下执行的操作。给定事件数据的全局上下文,跟踪这四个属性似乎是全局事件数据模式最合理的起点。

将想法转化为代码

我的计划的关键是创建一个基础设施,它执行我提出的事件命名模式,并为全局事件数据模式创建一个基础。

为了加强这种一致性,我使用了 plumatic 模式库。对于那些不熟悉模式的人来说:“模式是描述数据形状的 Clojure(脚本)数据结构,可用于记录和验证函数和数据。”这个验证功能意味着我可以使用“模式检查”来确保传递到分析库中的数据来自一组预定义的好值。

注意 1 :因为我正在使用 plumatic 模式库来执行我的事件命名模式,所以在下面的部分中‘模式’是一个有点超载的术语。为了清楚起见,我将把这个库和这个库创建的对象称为专有名词(schema),而我的事件命名 schema 称为普通名词(Schema)。

注 2 :本文中的代码示例并不完全代表我们的产品代码。我这样做有两个原因。第一个,尝试并强调我所提出的概念的实现,第二个,让非 Clojure 开发者更容易理解。然而,代码片段是基于我们的开源前端的,所以如果你想看真实的东西,请随意查看

一致的名字

我使用 Schema 验证的第一件事是事件名称的中央存储库。正如你们中的一些人所知,创建事件命名模式是一回事,实施它是另一回事。

首先,我创建了一个包含所有事件的列表,这些事件当前都是从我们的 web 应用程序中触发的。然后,我使用 Schema 通过添加以下代码来验证传递给 track 函数的事件名称:

;; Below are the lists of our supported events.
;;     Events should NOT be view-specific.
;;     They should be view agnostic and include a view in the properties.
;; Add new events here and keep each list of event types sorted alphabetically
(def supported-events
	#{:account-settings-clicked
	   …
	   :web-notifications-permissions-set})

;; Create an enum from the set of supported events
(def SupportedEvents
(apply s/enum supported-events))

;; Create an AnalyticsEvent which is a merge of the CoreAnalyticsEvent
;; and a dictionary with a key :event-type, whose value must be found in the
;; enum of SupportedEvents
(def AnalyticsEvent
(merge CoreAnalyticsEvent {:event-type SupportedEvents}))

;; Create a track function, which expects a dictionary (event-data) of the
;; schema defined by AnalyticsEvent. That way, if the argument passed to track
;; does not have a key :event-data, with a value found in the SupportedEvents enum,
;; it will throw an Exception and not fire the event.
(defn track [event-data :- AnalyticsEvent]
	track stuff…) 

Plumatic Schema 确保开发人员在添加新事件时必须首先手动将其添加到受支持的事件集中,否则他们会在测试时在 JS 控制台中看到错误。这种强制实现了三件事,它:

描述事件命名模式( - ),开发人员应该使用它来添加事件,增加他们遵循命名模式的机会。提供了大量遵循命名模式的示例,鼓励正确使用命名法。通过保持事件按字母顺序排序,不鼓励创建类似的事件,为开发人员提供了查看某个特性的现有事件的机会。

一个额外的、个人的好处:作为我们分析的所有者,这给了我一个地方来检查模式之外的事件没有被添加。以前,事件分散在整个代码库中,因此很难枚举它们。这个事件名称列表已经允许我捕获事件命名模式之外的事件。

一致的数据

我需要确保一致性的下一个地方是事件数据,因为精心选择的事件数据提供了对用户参与的更细粒度细节的宝贵见解。

teammates-invited-caption.png

幸运的是,在 CircleCI,我们使用 Om (脸书 React 框架上的 clojurescript 包装器)作为我们的前端网络应用。这意味着我们的应用程序由保存在内存中的大型状态图提供支持,并通过用户交互或 API 调用的响应进行更新。这种结构的一个主要好处是,在任何给定的时间,我们的应用程序“知道”它的操作上下文,无论是当前页面、当前用户,还是特定的组织或存储库。

为了确保这种状态信息总是包含在分析调用的事件数据中,我做了几件事:

首先,我以一致的方式从状态中提取这些信息。其次,我确保状态总是被传递给分析函数。最后,在向我们的第三方提供者发出跟踪调用之前,我总是将这些键添加到事件数据中。考虑到这些目标,我最终编写了以下代码:

;; Define an `AnalyticsEvent` schema, which requires an `:event-type` key and 
;; a `:current-state` key. The `:event-type` value must come from our our enum of `supported-events` (described in the code block above).
;; The `:current-state` can be a map with any values (this is the app state).
(def AnalyticsEvent
{:event-type SupportedEvents
 :current-state {s/Any s/Any})

;; Given the `current-state`, return a dictionary of the properties that we want
;; to track with every event.
(defn- properties-to-track-from-state [current-state]
  "Get a map of the mutable properties we want to track out of the
  state. Also add a timestamp."
 	{:user (get-in current-state state/user-login-path)
 	 :view (get-in current-state state/current-view-path)
  	 :repo (get-in current-state state/navigation-repo-path)
  	 :org (get-in current-state state/navigation-org-path)})

;; A `track` function which takes a single argument `event-data`, a dictionary
;; in the shape described by the schema `AnalyticsEvent`.
;; If the input data is not in the correct shape, it throws an Exception.
(defn track [event-data :- AnalyticsEvent]
(let [{:keys [event-type current-state]} event-data]
		(segment/track-external-click event-type
(properties-to-track-from-state current-state)))) 

通过阅读上面的代码可以看到,我们的 track 函数有一个参数event-data,它有一个键current-state(事件触发时应用程序的状态)。然后,我们解析状态中的重要属性,并将该字典作为事件的数据发送。

但是:尽管这种实现确保了每个事件用一组一致的填充键来触发,但是它非常严格。如果开发人员需要添加额外的数据,比如 A/B 测试处理,该怎么办?或者更糟的是,如果出现自动填充数据出错的极端情况,该怎么办?

考虑我们的仪表板页面。虽然此页面既没有组织也没有存储库上下文(它显示所有组织和存储库,但不特定于任何组织和存储库),但它有特定于组织或存储库的链接。例如,页面左侧分支选择器组件上的任何链接都有一个关联的组织和存储库。

branch-selector.png

由于这些情况,我们需要能够修改属性字典。我通过向track函数签名添加一个新的properties参数来实现这一点,它优先于自动生成的映射。以下是更新后的代码:

;; Same as the AnalyticsEvent above, but now has an optional key `properties`
;; `properties` is a map that has Clojure keywords as keys, and allows custom user-set values.
(def AnalyticsEvent
  {:event-type (s/enum supported-api-response-events)
   :current-state {s/Any s/Any}
   (s/optional-key :properties) {s/Keyword s/Any}})

;; This function gets the data we want to track automatically out of the `current-state`,
;; and then merges the `properties` passed in. Because we pass in `properties` as the second
;; argument to the `merge` call, the `properties` take precedence.
;; So, if `properties` has an `:org` key, its value will overwrite the value of `:org` returned from
;; `properties-to-track-from-state`.
(defn- supplement-tracking-properties [{:keys [properties current-state]}]
  "Fill in any unsupplied property values with those supplied
  in the current app state."
  (-> current-state
      (properties-to-track-from-state)
      (merge properties)))

;; Same as the track call above, but can take a `properties` key in its input map.
;; This map of properties takes precedence over the map generated automatically
;; by parsing the app’s state.
(defn track :default [event-data :- AnalyticsEvent]
  (let [{:keys [event-type properties current-state]} event-data]
    (segment/track-event event-type
                                       (supplement-tracking-properties {:properties properties
                                                                                            :current-state current-state})))) 

代码现在允许开发人员根据需要向事件数据添加自定义属性。此外,在自动添加的属性是错误的情况下,它允许开发人员用正确的值覆盖它们。

就这样结束了!

三个第三方平台和一次大规模的事件命名分类检查之后,我们现在有一个分析库,可以确保我们的数据消费者的一致性,同时为我们的开发人员提供灵活性和易用性。

展望未来——需要优化的领域

在这个分析库首次推出以来的八个月里,它已经成为我们收集行为数据的主要方式。在这段时间里,我了解了我做得好的地方和做得差的地方。我不认为这是一篇诚实的博文,如果我不解决错误或伸缩问题的话,所以这里有一些八个月后我想到的事情。

关注点分离

在我们当前的架构中,CircleCI 前端分析的一部分和便携式分析库的实际部分之间没有分离。虽然这在从单一来源触发事件时并不重要,但当我们将行为分析扩展到多个服务时,该基础架构在验证这些事件及其数据时至关重要。拥有一个公共库可以鼓励不同代码库之间的最佳实践。

2016-12-06-building-consistent-taxonomy-frontendlibrary.png

我们已经感受到这种痛苦的地方是我们的服务器端事件。虽然前端被很好地模式化了,但是服务器端没有严格的模式化。结果是,在相似的事件中,有不同的键代表同一件事(即:不同事件上的orgorg-name)。拥有一个合适的库可以让我重用模式检查并防止这种情况发生。

事件名称的缩放

到目前为止,事件名称集已经非常成功,但是随着我们添加越来越多的事件,列表正在快速增长。在这一点上,我开始怀疑这个列表是否保持了它原来的有用性,或者是否有更好的方法来对它进行分层以便于使用。

如果您查看列表,您会注意到事件名称实际上只是功能和动作的组合。那么为什么要把它们放在一起呢?为什么不拥有一组有效的功能、一组有效的动作,然后让分析库根据客户端传递给它的功能/动作组合自动生成事件名称呢?这将更具可伸缩性,并使创建不属于分类法的事件名称变得更加困难。这是我将来想尝试的东西。

其余的事件数据

我们恰当地规划了四个事件数据键,但是其他的呢?我们运行 A/B 测试,所以那些处理也应该自动填充事件数据。数据越多越好,但我们的事件数据字典之间也越有可能出现分歧。

那么,我们如何确保随着行为分析复杂性的增加,我们的事件数据模式保持一致呢?这可能是最难解决的问题,但在扩大我们对行为数据的使用方面,这将是一个重要的问题。

等待下一个 Om

我们目前正在将我们现有的前端从 om 迁移到 Om(你可以在这里阅读)。这种大规模迁移令人兴奋的一点是,它允许开发人员解决第一个架构解决方案中可能遗漏的问题。

例如,当我们迁移到 Om 时,CircleCI 没有数据分析师或增长团队,也不太致力于利用数据。由于我们更加依赖数据,分析在此次迁移中受到了优先考虑,这将使我们能够解决我们当前实施中遇到的一些问题。

结论

再周密的计划和设计,事情总会被遗忘。这种事件分类和模式的实现将我们从一个不区分数据优先级的公司带到了一个根据数据做出决策的公司。也就是说,随着我们规模的扩大,它已经开始出现一些裂缝。我期待着花一些时间来解决这些问题,并希望写另一篇博文来分享未来的见解。

有兴趣了解更多关于这个话题的信息吗?12 月办公时间回复12 月 14 日星期三,Justin 将在 Heavybit 俱乐部会所介绍 all things data。空间有限。

使用 CircleCI - CircleCI 构建自动化网站发布管道

原文:https://circleci.com/blog/building-an-automated-website-publishing-pipeline-using-circleci/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


这是客户成功工程师 Luc Perkins 撰写的 Reflect 博客的转贴。 Reflect 是一个现代数据平台,结合了强大的 API、漂亮的可视化和易于使用的创作工具

我有一个坦白:我实际上有点喜欢手动部署和管理网站。我喝一杯咖啡,运行make publish或一些类似的命令,凝视 CLI 输出,等待静态资产生成并rsync发送到千里之外的服务器,刷新浏览器,直到我看到新的变化开始生效——这是生活中微妙的乐趣之一。

但是让我们面对现实吧,既然连续部署和类似的自动化模式已经成为我们行业的标准实践,通常就没有什么好的理由来这样做了。不久前,我们在 Reflect 决定使用 CI 工具(CircleCI)来自动化我们的网站部署流程,其好处显而易见。

我们的网站生成工具包

在我们进入网站部署之前,先简单介绍一下我们如何为上下文构建网站。

我们使用杰基尔作为我们的静态站点生成器和我们的静态资产管道的管理者。基本上,我们将一堆 MarkdownSass 和 JavaScript 放入 Jekyll,最终在_site目录中得到一个完整的站点。我们将在以后的文章中对此进行更多的讨论。

除此之外,我们还使用: Gulp.js 来管理本地开发的浏览器 syncRake 用于运行我们的部署脚本,这些脚本是高度参数化的,并从 Ruby 语法(而不是 Bash)中受益匪浅;当然还有老式的 Make T7,这是项目的主要入口点(我们的 CircleCI 作业只运行 Make 命令)。

我们网站背后的基础设施

那么我们的站点在哪里运行,我们如何到达那里?下面是我们设置的快速概述:

  • 我们有两个专用的数字海洋液滴,一个在纽约运行,另一个在旧金山运行。
  • 我们有网站的生产和暂存环境。目前,站点的试运行版本和生产版本在相同的机器上并行运行(考虑到我们的试运行站点的负载很轻,这很好)。
  • 所有资产都通过 nginx 提供服务。我们为暂存和生产站点提供了单独的 nginx 配置。

专业提示:将你的 nginx 配置存储在与网站相同的存储库中

最初,我们的 nginx 配置只是依赖于我们的液滴。如果我们需要更新那些配置,我们就把它们放进盒子里,进行特别的修改。但是这样做的缺点是非常明显的:很容易更新一个框而忘记更新另一个框,令人讨厌的是必须跳到另一个框才知道配置是什么,等等。因此,我们决定将 nginx 配置存储在 repo 中,与网站本身放在一起,这意味着它们现在受版本控制,易于查看。那么,我们如何确保 nginx 真正使用这些配置呢?我们将在下面详细讨论。

圆形构型

我们所有的代码都存在于 GitHub 上,我们可能略知一二。在 Reflect,我们使用 CircleCI 来处理与 CI 相关的所有事情。我们有大量的回复链接到它,我们在一个专用的空闲频道上从它那里得到稳定的信息流,到目前为止我们对它非常满意。

我们网站的 CircleCI 配置位于circle.yml file(按照惯例)中,目前非常少:

machine:
  timezone:
    America/Los_Angeles
  node:
    version: 7.4.0
  ruby:
    version: 2.3.1

dependencies:
  override:
    - make setup

deployment:
  production:
    branch: master
    commands:
      - make release
  staging:
    branch: development
    commands:
      - make release_staging 

该配置指定:

  • CI 输出的时间戳应该是针对America/Los_Angeles时区的,因为那是我们都生活在其中的时区(我们正在幕后游说一个America/Portland时区…我们会让你知道进展如何)
  • Node.js 版本 7.4.0 将被使用(比如我们的 Gulp.js 设置),而 Ruby 版本 2.3.1 将被用于 Jekyll
  • 在构建之前,将运行一个额外的make setup命令。这个命令在我们的Makefile中是这样的:
gem install bundler
bundle install
npm install 

我们的基本发展模式

尽管总会有例外和边缘情况,但在大多数情况下,Reflect 的所有项目都遵循一个工作流程,其中:

  • master分支被认为是最新的
  • 所有拉取请求都指向一个development分支,而不是直接指向master
  • development仅作为拉取请求的一部分合并到master中,这使我们能够在更新master之前查看许多拉取请求的收集结果。

网站也遵循这个流程。我们发现这个流程对我们的持续部署模式非常友好,因为我们可以自信地向development推送大胆的更改,并保证它们在被推送到master之前会被再次审查,这可能意味着从更新我们在 npm 中的库到更新 Debian 包的任何事情,在这种情况下,将网站推送到现场。

一旦 CI 环境设置完毕,接下来会发生什么取决于我们要合并到的分支:

  1. 每当变更被合并到master时,生产站点被更新。这意味着以下所有事件都发生在 CircleCI:

    • Jekyll 构建站点时将JEKYLL_ENV设置为production。当我们的 Jekyll 环境被设置为生产时,我们的模板为 DisqusGoogle Analytics 添加 JavaScript。更多关于 Jekyll 环境的信息将在下一篇文章中发布!
    • 运行make release命令,rsync_site文件夹的内容保存到两个数字 Ocean droplets,scp保存到我们的 nginx 配置文件,并使用 SSH 重启 nginx 并执行一些基本的清理工作(比如chown某些目录)。
  2. 每当变更被合并到development时,站点的临时版本被更新(这包括#1 的所有步骤)。

  3. 默认情况下,CircleCI 会在您推送到没有任何关联行为的分支时运行make test。在我们的例子中,这意味着每当我们推送到masterdevelopment之外的分支时,make test就会运行。如果make test返回 0,则构建通过;否则,构建会失败。

目前,我们的make test命令做一件事:make buil,它只是运行jekyll build。该过程以 0(成功)或其他值(失败)退出。如果我们将任何东西推到任何一个分支,而 Jekyll 不能构建它,那么 CircleCI 会立即通过 Slack 通知我们。在未来,我们希望通过在我们的网站构建中添加链接检查器和拼写检查器来更全面地“测试”我们的网站。

保持您的 CircleCI 配置最小化

一般来说,在您的circle.yml配置文件中只包含绝对必要的内容是一个好主意。您不希望您的各种commands部分看起来像整个 shell 脚本。每当您需要调用一个命令时,可以将一切委托给一个 shell 脚本或一个 Make 命令。YAML 是一种简单的键/值标记格式,而不是脚本引擎!

CircleCI 和 SSH

让我们的设置工作的唯一棘手的部分是使 CircleCI 能够通过 SSH 与我们的数字海洋水滴直接通信。我们是这样做的:

  1. 我们将网站的 GitHub repo 添加到我们组织的 CircleCI 项目中

  2. 我们在本地机器上创建了一个 SSH 密钥,并将密钥上传到我们当前的两个数字海洋水滴上的~/.ssh/authorized_keys。我们现在通过1 密码与任何需要的人分享这个密钥。

  3. 我们通过 CircleCI web UI 中的设置页面将生成的密钥上传到 circle ci。

如果你使用过 AWS 或 DigitalOcean 或类似的服务,你可能会遇到这种情况。对我们来说,棘手的部分是确保 CircleCI 我们的工程师能够执行相同的任务。毕竟,有时手动发布和部署任务仍然是必要的,例如,如果 CircleCI 在我们的站点完全崩溃时关闭。

我们学到的最重要的事情是,你应该在你的网站 repo 中本地保存你的 SSH 配置,这样你就可以确保每个用户(包括 CircleCI!)对所有的sshscprsync命令使用相同的配置。我们网站的 SSH 配置存储在一个config/ssh-config文件中,而-F标志使我们能够在调用命令时直接使用该配置。以下是一些例子:

# Establishes a basic SSH tunnel using the config within the repo
$ ssh -F config/ssh-config reflect.io-east

# Copies our local production nginx config onto a remote box
$ scp -F config/ssh-config \
  config/nginx/nginx-production.conf \
  reflect.io-west:/etc/nginx/conf.d/reflect.io.conf 

网站 repo 中的 SSH 配置如下所示:

Host reflect.io-west
  HostName 192.0.2.1
  IdentityFile ~/.ssh/id_reflect_website
  User root

Host reflect.io-east
  HostName 192.0.2.2
  IdentityFile ~/.ssh/id_reflect_website
  User root 

为了确保任何人都可以手动管理该站点,我们负责该站点的每个工程师都将密钥保存在本地~/.ssh/id_reflect_website。在我们的数字海洋水滴眼中,CircleCI 和我们的工程师是平等的。

结论:开始吧,但是要记住几件事

事后看来,建立一个自动化的网站发布流程的好处是显而易见的。设置好一切花费了几个小时,但毫无疑问,这为我们节省了很多时间,让我们安心。如果你为你的网站使用静态站点生成器,我们强烈建议你做同样的投资。但是我们发现了一些你可以从中学习的东西:

  • 确保在必要时仍然可以手动部署该站点。即使在高度自动化的设置中,也有很多地方可能出错,您需要为自动化框架出现问题的情况做好准备。在任何时候,您的团队中的几个成员都应该准备好安全地手动部署站点,并且只使用少量的(有良好文档记录的!)命令。我们有八名员工,目前其中一半可以通过运行git checkout master && make release在任何时间部署站点
  • 在多个环境中运行站点,这两个环境都连接到您的自动化管道中。对我们来说,这意味着master直接推向生产,而development直接推向产品化,但是您的实践可能会有所不同。您的网站是其他人进入您的组织的门户,应该像解决其他问题一样彻底地解决问题。
  • 实际上,在本地发布站点所需的一切都应该位于 repo 中,并接受版本控制(因此对协作开放)。

阅读更多由 Luc 和 Reflect 团队发布的关于此类主题的帖子

构建和测试 ASP.NET 核心应用程序

原文:https://circleci.com/blog/building-and-testing-an-asp-net-core-application/

本文探讨了如何使用 CircleCI 的 Windows 执行环境在 Windows 虚拟机上构建和运行一个简单的计算器应用程序,该应用程序是使用 ASP.NET 核心构建的。它包括应用程序的完整配置文件,并对相关部分进行了分解解释。

web 和应用程序开发中有许多框架,每一个都有不同的特性和优势。ASP.NET 是一个开源的服务器端 web 应用程序框架,由微软创建,运行在 Windows 上,允许开发者创建 web 应用程序、web 服务和动态内容驱动的网站。ASP.NET 核心是 ASP.NET 的一个改进的跨平台版本,可以在每一个主要的计算平台上运行,包括 Windows、macOS 和 Linux。

CircleCI 的 Windows 支持构建和测试您的应用程序的好处是:

  • 支持基于 Docker 的 Windows 工作流的 Docker 引擎企业版
  • Windows 环境作业基于虚拟机,并提供完全的构建隔离
  • PowerShell 是默认的 Shell,但是 Bash 和 cmd 可以通过声明获得
  • Windows 作业可以访问同一套强大的 CircleCI 功能,如缓存、工作区、审批作业和受限上下文,并且支持级别相同

先决条件

要跟进,您需要:

申请详情

本教程使用了一个简单的计算器应用程序,您可以在这里找到并派生出。以下是应用程序的目录结构:

.
├── Dockerfile
├── LICENSE
├── README.md
├── SimpleCalc
│   ├── Controllers
│   ├── Models
│   ├── Program.cs
│   ├── Services
│   ├── SimpleCalc.csproj
│   ├── Startup.cs
│   ├── Views
│   ├── appsettings.Development.json
│   ├── appsettings.json
│   └── wwwroot
├── SimpleCalc.sln
└── test
    └── SimpleCalc.Tests

8 directories, 9 files 

一旦你完成了这个项目,按照的说明在 CircleCI 上建立你的构建,把它连接到 CircleCI。

配置 CircleCI

为了配置 CircleCI ,我在项目根目录下的.circleci文件夹中添加了一个config.yml文件。以下是该项目的完整配置文件:

version: 2.1

orbs:
  windows: circleci/windows@2.2.0

jobs:
  test:
    description: Setup and run application tests
    executor:
      name: windows/default
    steps:
      - checkout
      - restore_cache:
          keys:
            - dotnet-packages-v1-{{ checksum "SimpleCalc/SimpleCalc.csproj" }}
      - run:
          name: "Install project dependencies"
          command: dotnet.exe restore
      - save_cache:
          paths:
            - C:\Users\circleci\.nuget\packages
          key: dotnet-packages-v1-{{ checksum "SimpleCalc/SimpleCalc.csproj" }}
      - run:
          name: "Run Application Tests"
          command: dotnet.exe test -v n --results-directory:test_coverage --collect:"Code Coverage"
      - run:
          name: "Print Working Directory"
          command: pwd
      - store_artifacts:
          path: C:\Users\circleci\project\test_coverage
  build:
    description: Build application with Release configuration
    executor:
      name: windows/default
    steps:
      - checkout
      - run:
          name: "Build Application according to some given configuration"
          command: dotnet.exe build --configuration Release
workflows:
  test_and_build:
    jobs:
      - test
      - build:
          requires:
            - test 

现在,我们来分解一下配置。

定义操作系统/环境和运行时

我们正在使用 CircleCI 版,它支持使用 orbs 。CircleCI orbs 是可共享的配置元素包,包括作业、命令和执行器。我们正在使用的是 Windows orb 。它为用户提供了构建 Windows 项目的工具,如通用 Windows 平台(UWP)应用程序、. NET 可执行文件或特定于 Windows 的项目(如。NET 框架)。

version: 2.1

orbs:
  windows: circleci/windows@2.2.0 

该项目有testbuild个作业。构建作业需要通过所有测试才能运行。为了配置这种排序,我们将使用 CircleCI 的工作流,这是一组用于定义作业集合及其运行顺序的规则。工作流声明位于示例配置文件的末尾。

workflows:
  test_and_build:
    jobs:
      - test
      - build:
          requires:
            - test 

设置和运行测试

Windows executor 预装了 Visual Studio 2019 以及许多其他开发工具。我们将使用默认的 PowerShell shell,所以它不需要声明。

 test:
    description: Setup and run application tests
    executor:
      name: windows/default 

步骤是在作业期间运行的可执行命令的集合。在接下来的步骤中,我们将检查我们的代码并恢复我们项目的依赖项。

 steps:
      - checkout
      - restore_cache:
          keys:
            - dotnet-packages-v1-{{ checksum "SimpleCalc/SimpleCalc.csproj" }}
      - run:
          name: "Install project dependencies"
          command: dotnet.exe restore
      - save_cache:
          paths:
            - C:\Users\circleci\.nuget\packages
          key: dotnet-packages-v1-{{ checksum "SimpleCalc/SimpleCalc.csproj" }} 

CircleCI 的缓存文档详细描述了可用的缓存选项以及如何使用它们。dotnet.exe restore命令使用 NuGet 来恢复依赖项以及项目文件中指定的项目特定工具。默认情况下,依赖项和工具的恢复是并行执行的。

单元测试对于现实世界的应用程序是必不可少的。您还可以从单元测试中收集代码覆盖率。单元测试是一种软件测试方法,通过这种方法对源代码的各个单元进行测试,以确定它们是否适合使用。单元是最小的可测试软件组件。“dotnet.exe 测试”是。用于执行单元测试的. NET 测试驱动程序。我们的项目使用了 xUnit 测试框架。如果所有测试都成功,测试运行程序返回 0 作为退出代码;否则,如果任何测试失败,它将返回 1。测试运行器和单元测试库被打包为 NuGet 包,并被恢复为项目的普通依赖项。

 - run:
         name: "Run Application Tests"
         command: dotnet.exe test -v n --results-directory:test_coverage --collect:"Code Coverage" 

我们将从运行测试中收集的覆盖率作为工件存储在test_coverage文件夹中。ASP.NET 核心项目的代码覆盖率报告不是现成的。

 - store_artifacts:
         path: C:\Users\circleci\project\test_coverage 

构建项目

在成功运行测试之后,我们想要构建我们的项目。为此,我们定义要使用的构建配置。此项目的可用选项有:

  • 释放;排放;发布
  • 调试下面的配置显示了我们选择的版本。
 build:
    description: Build application with Release configuration
    executor:
      name: windows/default
    steps:
      - checkout
      - run:
          name: "Build Application according to some given configuration"
          command: dotnet.exe build --configuration Release 

从……开始。NET Core 2.0 SDK,您不必运行“dotnet restore”,因为它是由所有需要进行恢复的命令隐式运行的,如“dotnet new”、“dotnet build”和“dotnet run”。

结论

在 CircleCI 上使用 Windows executor 使得构建和测试 ASP.NET 核心应用程序变得轻而易举。CircleCI certified Windows orb 是一个值得信赖的解决方案,因为它经过了 CircleCI 的测试和社区的验证。要了解更多关于您的配置中可用的 orb 的信息,请访问 orb 注册表

orb 节省了原本要花在以下方面的时间:

  • 研究、测试和设置 Windows 虚拟机
  • 安装开发工具,例如 vs2019、Nuget
  • 学习使用 Windows 工具,如 PowerShell

orb 的使用还减少了必须在配置文件中编写的代码行数。这个项目的配置文件有 42 行代码,更容易理解和维护。


Dominic Motuka 是 Andela 的 DevOps 工程师,在 AWS 和 GCP 支持、自动化和优化生产就绪部署方面拥有 4 年多的实践经验,利用配置管理、CI/CD 和 DevOps 流程。

阅读多米尼克·莫图卡的更多帖子

Android 项目的持续集成| CircleCI

原文:https://circleci.com/blog/building-android-on-circleci/

CircleCI 在 Android 开发人员中很受欢迎,原因有几个:它可以快速入门,快速执行具有高并行性的构建,(无论是本机、跨平台还是多平台),甚至支持使用我们的 Android 机器映像直接从 CircleCI 运行 Android 模拟器。

本文将向您展示如何在 CircleCI 平台上为一个示例项目构建和测试 Android 应用程序。完整的源代码可以在 GitHub-CircleCI-Public/Android-testing-circle ci-examples 上找到,你可以在相应的 circle ci 项目中找到工作管道

为了从本文中获得最大收益,您应该

  • Android 开发的工作知识
  • 熟悉 Android 工具和测试框架
  • 能够使用 Gradle 构建系统

CircleCI 为 Android 提供了什么

我们为您提供了一些构建 Android 的工具:

  • Docker 图像
  • Android 机器图像
  • 安卓 orb

图像对接器

Docker 图像是团队使用的主要执行者。它运行在 Docker 容器中,因此启动非常快,并且包含了开始构建 Android 项目所需的一切。预装工具的完整列表可以在文档中找到。这些图片还附带了预装 Node.js、Android NDK 和浏览器工具的变体。

机器图像

变化最大的是新安卓机镜像。这是一个虚拟机映像,所以比上面提到的 Docker 映像花费的时间多一点,但它包含了构建 Android 应用程序可能需要的所有工具,包括 SDK、Google Cloud tools for Firebase、 Python 、Node。JS 和浪子。然而最大的变化是对嵌套虚拟化的支持,它允许你在这个机器映像中运行 Android 模拟器。

Android 机器映像支持嵌套虚拟化,这允许在 CircleCI 作业中运行 x86 模拟器。如果你从平台早期就开始为 Android 开发,你可能记得模拟器非常慢。然后 x86 模拟器开始支持英特尔的 Hyper-V 虚拟机管理程序,这让模拟器直接使用主机的资源。像这样直接使用资源使得模拟器能够运行得更加流畅和快速。

很长一段时间,支持仅限于我们的本地硬件。对 CircleCI 的有限支持降低了模拟器的速度,并使其难以运行 UI 测试。嵌套虚拟化解决了这一问题,它为可大规模复制的 CI/CD 环境增加了对快速仿真的支持,使所有的好处都变得可用。

安卓 Orb

最后是 CircleCI Android orb,它提供了作为执行者对两个映像的轻松访问,以及安装 SDK、运行模拟器、缓存、测试等方便的任务和命令。你可以在 orb 文档的链接中阅读更多关于 orb 的内容。

介绍 Android CI 演示项目

我已经创建了一个演示项目,这是谷歌自己的 Android 测试 Codelab 源代码的一个分支。Codelab 是一个简单的待办事项列表管理器应用程序,包含单元测试和工具测试的组合,有和没有 UI。该项目还可以在 CircleCI 上观看。如果在 Android Studio 中打开,一定要切换到Project视图。项目视图允许您查看我们将要查看的.circleci/config.yml文件。

该项目有一个单独的app模块,带有默认的debugrelease变量。test目录包含单元测试,而androidTest目录包含仪器测试。包括了完整的 CircleCI 配置,我将在本文中引用它。配置位于.circleci/config.yml

为示例 Android 应用程序构建和测试 CI

我们的 CI 流程必须做到三点:

  1. 在虚拟机中运行单元测试
  2. 在模拟器上运行检测测试
  3. 构建应用程序的发布版本

我们将同时运行单元测试和仪器测试。如果两种类型的测试都成功了,我们就可以组装发布版本了。我们还将添加一个条件,如果新的工作已经提交到“主”分支,并且只有在从 23 到 30 的每个 Android 版本上成功的模拟器测试之后,才继续发布版本。当我们的构建满足这个条件时,我们将有坚实的保证,我们的应用程序将在许多不同的 Android 设备上工作。

*注意: 正如我之前提到的,从这一点开始的一切都将引用.circleci/config.yml中的 CircleCI 配置文件。

使用 CircleCI Android orb

创建我们的 CI 管道的第一步是使用 Android orb,它包含许多已经为您预先编写好的步骤。最新版本可在 orb 文档中获得。

定义 orb 后,脚本指定这些作业:

  • unit-test
  • android-test
  • release-build

还包括一个工作流程test-and-build

version: 2.1

orbs:
  android: "2.1.2"

jobs:
  unit-test:
    ...
  android-test:
    ...
  release-build:
    ...
    # We'll get to this later

workflows:
  test-and-build:
    ...
  # We'll get to this later 

运行单元测试

我们的unit-test作业包含一个来自 Android orb 的执行程序:android/android-docker。如上所述,它在 Docker 容器中运行,启动速度极快。

在第steps节中,有一些事情需要注意。首先,我们运行android/restore-gradle-cache Gradle cache 帮助存储我们的依赖项并构建工件。在运行测试命令之后,我们还运行android/save-gradle-cacheandroid/save-build-cache来确保后续的构建通过使用缓存运行得更快。这些都来自 Android orb,如前缀android/所示。

我们还可以通过将find-args参数传递给restore-gradle-cache来修改缓存的内容。在这个开源项目中可以找到一个很好的例子

主要的构建步骤发生在android/run-tests命令中,我们在test-command参数中调用./gradlew testDebug。这个命令运行debug构建变体的所有单元测试。另外,这个命令带有一个默认的重试值,可以帮助您避免测试失败。

jobs:
  unit-test:
    executor:
      name: android/android-docker
      tag: 2022.08.1
    steps:
      - checkout
      - android/restore-gradle-cache
      - android/run-tests:
          test-command: ./gradlew testDebug
      - android/save-gradle-cache
      ... 

在模拟器上运行 Android 测试

我们的工作是使用新的 Android 模拟器功能。该作业使用与build作业相同的 Android 机器执行器。所有后续作业都将使用同一个执行器,缓存恢复步骤也是一样的。

模拟器要求我们在机器映像上运行,所以我们指定android/android-machine作为执行器。为了缩短构建时间,我们将resource-class指定为 xlarge。您也可以在large执行器上运行它,但是我发现资源的缺乏会导致构建时间变慢。我建议你自己做一些实验,为你的项目找到合适的执行者。

下一步更短;我们只需要android/start-emulator-and-run-tests命令。这个命令确实做到了它所说的:它启动指定的模拟器并运行测试。我们再次传入test-command作为参数(它使用了android/run-tests命令)。system-image是完全合格的 Android 模拟器系统映像。我们目前将我们的 Android 机器映像捆绑回 SDK 版本 23。你可以使用sdkmanager工具安装更多。

jobs:
  android-test:
    executor:
      name: android/android-machine
      resource-class: xlarge
    steps:
      - checkout
      - android/start-emulator-and-run-tests:
          test-command: ./gradlew connectedDebugAndroidTest
          system-image: system-images;android-30;google_apis;x86
      ... 

您可以使用 Android orb 中指定的命令手工编写所有模拟器设置步骤的代码:create-avdstart-emulatorwait-for-emulatorrun-tests

存储测试结果

测试不仅仅是运行测试,因为我们可以从 CircleCI 仪表板中准确地看到什么失败了,从而获得很多价值。为此,我们可以使用 CircleCI 平台内置的store_test_results函数,它将显示我们通过(或失败)的构建。

单元测试和仪器测试的步骤略有不同,因为它们各自的 Gradle 任务将测试存储在不同的位置。对于单元测试,测试将在app/build/test-results中进行,对于仪器测试,测试将在app/build/outputs/androidTest-results中进行。

我们建议您创建一个新的Save test results步骤,它使用find工具,将所有测试结果文件复制到一个公共位置——比如test-results/junit,然后从那里存储它。还要确保将when: always参数添加到步骤中,这样无论上面的测试是成功还是失败,该步骤都会运行。

存储单元测试

对于这个项目,我们希望基于 XML 的结果。调用store_artifacts使得这些结果可以在 CircleCI 平台上解析。如果您愿意,也可以存储 HTML 输出。

...
- android/run-tests:
  test-command: ./gradlew testDebug
- run:
    name: Save test results
    command: |
        mkdir -p ~/test-results/junit/
        find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \;
    when: always
- store_test_results:
    path: ~/test-results
- store_artifacts:
    path: ~/test-results/junit 

存储仪器测试

不同测试类型有不同的 regex 命令。存储测试结果的其余步骤与单元测试示例相同。

- android/start-emulator-and-run-tests:
        test-command: ./gradlew connectedDebugAndroidTest
        system-image: << parameters.system-image >>
    - run:
        name: Save test results
        command: |
          mkdir -p ~/test-results/junit/
          find . -type f -regex ".*/build/outputs/androidTest-results/.*xml" -exec cp {} ~/test-results/junit/ \;
        when: always
    - store_test_results:
        path: ~/test-results
    - store_artifacts:
        path: ~/test-results/junit 

制作发布版本并存储工件

对于这个项目,我们需要存储我们正在构建的应用程序。为了完成流程的这一部分,我们使用了release-build作业,它有两个需要注意的部分。一个run步骤调用./gradlew assembleRelease。然后,store_artifacts指向建成的 APK 所在的位置。对于一个完整 CI/CD 项目,您可以签署并上传 APK 到一个测试版发行服务。你甚至可以和浪子一起在 Play Store 上发布。但是,这两种活动都超出了本文的范围。

jobs:
  ...
  release-build:
    executor:
      name: android/android-machine
      resource-class: xlarge
    steps:
      - checkout
      - android/restore-gradle-cache
      - android/restore-build-cache
      - run:
          name: Assemble release build
          command: |
            ./gradlew assembleRelease
      - store_artifacts:
          path: app/build/outputs/apk/release 

同时在多个 Android 版本上运行测试

Android 平台一直在发展,每个版本都有自己的缺陷和独特之处。我相信你已经遇到了其中的一些。避免不同版本 Android 之间不确定行为的一个好方法是尽可能多的测试它们。CircleCI 作业矩阵功能使在多个版本上运行作业变得更加容易。

要使用职务矩阵,请向要在多个版本上测试的职务添加一个参数。我们的android-test作业使用system-image参数来完成这项工作。我们还为参数指定了一个默认值,因此您可以在没有它的情况下运行作业。

要使用该参数,通过将它放在成对的尖括号- << >>中来指定需要它的值的位置:

 android-test:
    parameters: # this is a parameter
      system-image:
        type: string
        default: system-images;android-30;google_apis;x86
    executor:
      name: android/android-machine
      resource-class: xlarge
    steps:
      - checkout
      - android/start-emulator-and-run-tests:
          test-command: ./gradlew connectedDebugAndroidTest
          system-image: << parameters.system-image >> # parameter being used 

要将值传递给工作流中的参数,请使用我们正在调用的作业的matrix参数。

workflows:
  jobs:
  ...
  - android-test:
      matrix:
        alias: android-test-all
        parameters:
          system-image:
            - system-images;android-30;google_apis;x86
            - system-images;android-29;google_apis;x86
            - system-images;android-28;google_apis;x86
            - system-images;android-27;google_apis;x86
            - system-images;android-26;google_apis;x86
            - system-images;android-26;google_apis;x86
            - system-images;android-26;google_apis;x86
            - system-images;android-26;google_apis;x86
      name: android-test-<<matrix.system-image>> 

通过缓存使每个运行更快

除了构建我们的应用程序、安装依赖项和运行测试,我们通常还希望利用缓存。这让我们更少重复做任务。Android 尤其是 Gradle 为此提供了一个极好的机制-Gradle 构建缓存

这让您可以跳过应用程序的预构建部分。

选择何时运行什么

在开发期间,我们创建许多提交,应该鼓励开发人员尽可能频繁地将它们推送到远程存储库。但是,这些提交中的每一个都创建了一个新的 CI/CD 构建,我们可能不需要为一个提交在所有可能的设备上运行每个测试。另一方面,如果main分支是我们主要的事实来源,我们希望尽可能多的对它进行测试,以确保它总是如预期的那样工作。我们可能希望只在main上启动发布部署,而不是在任何其他分支上。

我们可以使用 CircleCI 的requiresfilters节来定义适合我们流程的工作流。我们的release-build岗位requires那个unit-test岗位。这意味着release-build被放入队列中,直到所有的unit-test任务都已通过。只有这时release-build作业才会运行。

我们可以使用filters来设置逻辑规则,以便只在特定的分支或 git 标签上运行特定的作业。例如,我们的main分支有一个过滤器,它用一个完整的仿真器矩阵运行android-test。在另一个例子中,release构建只在main上触发,并且只有当它的两个必需的作业成功运行时才触发。

workflows:
  test-and-build:
    jobs:
      - unit-test
      - android-test: # Commits to any branch - skip matrix of devices
          filters:
            branches:
              ignore: main
      - android-test: # Commits to main branch only - run full matrix
          matrix:
            alias: android-test-all
            parameters:
              system-image:
                - system-images;android-30;google_apis;x86
                - system-images;android-29;google_apis;x86
                - system-images;android-28;google_apis;x86
                - system-images;android-27;google_apis;x86
                - system-images;android-26;google_apis;x86
                - system-images;android-25;google_apis;x86
                - system-images;android-24;google_apis;x86
                - system-images;android-23;google_apis;x86
          name: android-test-<<matrix.system-image>>
          filters:
            branches:
              only: main 
      - release-build:
          requires:
            - unit-test
            - android-test-all
          filters:
            branches:
              only: main # Commits to main branch 

改进您的 Android 应用 CI 流程

我在本文中描述的步骤只是一个开始。有很多方法可以提高流量。以下是一些想法:

构建一次,运行多次

如果你是一个有经验的 Android 开发者,你会知道connectedAndroidTest总是从一开始就运行整个构建过程。使用 CircleCI,我们可以一次构建整个应用程序和测试,将工件传递给后续作业,并简单地在模拟器上运行测试。这个过程可能会在每次作业运行时节省几分钟的构建时间。

为此,在每个模拟器运行中添加三个命令行步骤:安装应用程序、安装测量应用程序和运行测量测试。对于我们的应用程序,我们将输入:

adb install app/build/outputs/apk/debug/app-debug.apk  
adb install app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk  
adb shell am instrument -w com.example.android.architecture.blueprints.reactive.test/androidx.test.runner.AndroidJUnitRunner 

确实,这段代码将输出发送到命令行。这不是 Gradle 提供的整洁的测试输出。如果你想让输出看起来更好,这个 StackOverflow 主题可能对你有帮助。

在真实设备上运行

仿真器是一种工具,但是真实世界的设备通常(可悲地)非常不同。没有什么可以取代真实世界的设备测试。为此,你可以使用在云中提供真实设备的解决方案,比如 Sauce Labs 或 Firebase Test Lab。

部署到 beta 测试人员,并发布应用程序

这个构建和测试示例项目只是更广泛的 CI/CD 故事的一部分。您可以进一步发展您的 CI/CD 流程。例如,您可以将应用程序的新版本自动发布到 Play Store。CircleCI 有一些指南可以帮助你。本教程是一个很好的起点。浪子官方文件是另一个有用的资源。

结论

在本文中,我描述了用 CircleCI 构建一个 Android 应用程序,并对其进行测试。对于我们的示例项目,我们使用单元测试和 Android 平台模拟器矩阵,使用新的 CircleCI Android orb 和机器映像。我们还谈到了如何存储和显示测试结果,以及如何编排工作流来运行测试,这不会给我们提供 Gradle 提供的整洁的测试输出,所有输出都在一部分设备上。

如果你想让我探讨另一个 Android 主题,请告诉我,无论是在文章中还是在直播中。在 Twitter - @zmarkan 上联系我。*

为多个操作系统架构构建 Docker 映像| CircleCI

原文:https://circleci.com/blog/building-docker-images-for-multiple-os-architectures/

经常有这样的情况,软件被编译并打包成工件,这些工件必须在多个操作系统处理器架构上运行。几乎不可能在不同的操作系统/架构平台上执行应用程序。这就是为什么为许多不同的平台构建版本是一种常见的做法。当您用来构建工件的平台与您想要部署的平台不同时,这可能很难实现。例如,在 Windows 上开发一个应用程序并将其部署到 Linux 和 macOS 机器上,需要为每个操作系统和目标架构平台提供和配置构建机器。管道内的多操作系统构建可以使用各种技术来实现,但是由于处理器架构的严格特性,工件必须在它们所针对的同一硬件上编译和生产。

Docker 是一种将应用程序打包成不可变且可部署的工件的现代方式,其形式为 Docker 映像容器。与传统的工件封装一样,Docker 映像也面临着相同的处理器架构构建限制。Docker 映像还必须构建在它们打算运行的硬件架构上。在这篇文章中,我将讨论如何在针对多处理器架构的 CI 管道中构建 Docker 镜像,例如 linux/amd64、linux/arm64、linux/riscv64 等。

入门指南

让我们来看一个由 Chad Metcalf 构建的示例代码库,它演示了如何将一个应用打包成多架构 Docker 映像。我们将只关注构建这些多架构 Docker 映像的持续集成方面。CircleCI config.yml文件定义了 CI 管道构建指令。它位于.circleci/config.yml目录中。在这篇文章中,我将重点放在这次回购的.circleci/config.yml文件和Makefile文件上。

Makefiles 可以被视为自动化构建过程的 make 实用程序所需的构建/编译指令。这个项目中的Makefile包含从 CI 管道执行的指令和命令。

使用 Docker Buildx

在我深入讨论Makefileconfig.yml文件分解之前,我想花点时间讨论一下 Buildx ,这是一个当前的 CLI 插件,它用莫比 BuildKit builder 工具包提供的全套功能扩展了 Docker CLI。它提供了与docker build相同的用户体验,并增加了许多新的特性,比如创建限定范围的构建器实例和同时构建多个节点。

在撰写本文时,Buildx 特性仍处于试验状态,需要在将要构建 Docker 映像的机器上进行一些环境配置。以下是 Docker 版本19.03及更高版本的 Buildx 安装说明。完整的 Buildx 安装说明可以在这里找到,下面是 TL;安装了 Docker 19.03 的 Linux 机器的灾难恢复说明。以下命令从源代码编译并构建Buildx二进制文件,并将其安装到 Docker 插件目录中:

export DOCKER_BUILDKIT=1
docker build --platform=local -o . git://github.com/docker/buildx
mkdir -p ~/.docker/cli-plugins
mv buildx ~/.docker/cli-plugins/docker-buildx 

你也可以在这里为你的操作系统下载最新的 Buildx 二进制文件,并使用这些 Buildx 发布二进制文件的说明安装它

在 Docker builder 机器上安装 Buildx 之后,您可以利用 Buildx 的所有功能:

我建议您花时间更好地熟悉 Buildx 特性。在构建多架构 Docker 映像时,这是一项必不可少的技术,并且在下面的示例中大量使用。

配置您的 CI 渠道

示例项目中的config.yml文件利用Makefile文件及其功能来执行适当的命令,以完成多架构构建。这个config.yml演示了如何使用机器执行器来利用单个构建作业。这似乎有点不正常,因为 CircleCI 提供了使用 Docker 执行器构建 Docker 映像的能力。

Docker 平台利用共享和管理其主机操作系统内核虚拟机(VMs) 中的内核仿真。由于运行 Docker 容器共享主机操作系统内核,它们在架构上与虚拟机非常不同。虚拟机不基于容器技术。它们由操作系统的用户空间和内核空间组成。虚拟机服务器硬件是虚拟化的,每个虚拟机都有自己独立的操作系统和应用。它共享来自主机的硬件资源,并可以在虚拟机中模拟各种处理器架构/内核。虚拟机的内核和硬件仿真能力是machine executor成为构建多架构 Docker 映像的最佳选择的主要原因。

让我们看看示例项目中的config.yml:

version: 2.1
jobs:
  build:
    machine:
      image: ubuntu-1604:202007-01
    environment:
      DOCKER_BUILDKIT: 1
      BUILDX_PLATFORMS: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6
    steps:
      - checkout
      - run:
          name: Unit Tests
          command: make test
      - run:
          name: Log in to docker hub
          command: |
            docker login -u $DOCKER_USER -p $DOCKER_PASS
      - run:
          name: Build from dockerfile
          command: |
            TAG=edge make build
      - run:
          name: Push to docker hub
          command: |
            TAG=edge make push
      - run:
          name: Compose Up
          command: |
            TAG=edge make run
      - run:
          name: Check running containers
          command: |
            docker ps -a
      - run:
          name: Check logs
          command: |
            TAG=edge make logs
      - run:
          name: Compose down
          command: |
            TAG=edge make down
      - run:
          name: Install buildx
          command: |
            BUILDX_BINARY_URL="https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.linux-amd64"

            curl --output docker-buildx \
              --silent --show-error --location --fail --retry 3 \
              "$BUILDX_BINARY_URL"

            mkdir -p ~/.docker/cli-plugins

            mv docker-buildx ~/.docker/cli-plugins/
            chmod a+x ~/.docker/cli-plugins/docker-buildx

            docker buildx install
            # Run binfmt
            docker run --rm --privileged tonistiigi/binfmt:latest --install "$BUILDX_PLATFORMS"
      - run:
          name: Tag golden
          command: |
            BUILDX_PLATFORMS="$BUILDX_PLATFORMS" make cross-build 

您可能已经注意到,这个配置文件中的大多数command:键执行Makefile中定义的功能。这种模式在配置文件中产生的 YAML 语法要少得多,但是确实使在Makefile中实际执行的内容变得复杂了。

接下来,我将重点解释这个配置文件中的关键command:键。

version: 2.1
jobs:
  build:
    machine:
      image: ubuntu-1604:202007-01
    environment:
      DOCKER_BUILDKIT: 1
      BUILDX_PLATFORMS: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6
    steps:
      - checkout
      - run:
          name: Unit Tests
          command: make test
      - run:
          name: Log in to docker hub
          command: |
            docker login -u $DOCKER_USER -p $DOCKER_PASS
      - run:
          name: Build from dockerfile
          command: |
            TAG=edge make build
      - run:
          name: Push to docker hub
          command: |
            TAG=edge make push 

在上面的代码中,构建使用了一个machine executor并将值赋给了DOCKER_BUILDKIT变量,使 Docker 能够访问实验特性和 Buildx。BUILDX_PLATFORMS变量是将产生 Docker 映像的操作系统和处理器架构的列表。这个列表是针对 Linux 操作系统和各种处理器架构的。

剩下的run:command:键演示了如何执行应用的单元测试,向 Docker Hub 认证以拉和推图像,使用在/app目录中找到的Dockerfile构建 Docker 图像,并将该图像推送到 Docker Hub。

注意: 上面的docker login步骤确保您对 Docker Hub 的请求得到验证。无论何时你用 CircleCI 从 Docker Hub 提取图像或向其推送图像,我们都建议你在 CircleCI 配置中的docker pulldocker push步骤中使用登录你的 Docker Hub 账户。登录将确保您的作业可以访问更高的 Docker Hub 速率限制

 - run:
          name: Install buildx
          command: |
            BUILDX_BINARY_URL="https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.linux-amd64"

            curl --output docker-buildx \
              --silent --show-error --location --fail --retry 3 \
              "$BUILDX_BINARY_URL"

            mkdir -p ~/.docker/cli-plugins

            mv docker-buildx ~/.docker/cli-plugins/
            chmod a+x ~/.docker/cli-plugins/docker-buildx

            docker buildx install
            # Run binfmt
            docker run --rm --privileged tonistiigi/binfmt:latest --install "$BUILDX_PLATFORMS" 

在上面的代码片段中,Buildx 特性用于安装 Buildx 二进制文件,并对其进行配置,以便在执行器中使用。Buildx 工具可以使用多种策略构建多架构映像,但是最简单的方法是使用 Qemu 仿真。它是一个通用的、开源的机器模拟器和虚拟器。当 BuildKit 需要为不同的架构运行二进制文件时,它会通过在binfmt_misc处理程序中注册的二进制文件自动加载它。为了让在主机操作系统上用binfmt_misc注册的 QEMU 二进制文件在容器中透明地工作,它们必须用fix_binary标志注册。

docker run --rm --privileged tonistiigi/binfmt:latest --install "$BUILDX_PLATFORMS"命令为文件前面定义的$BUILD_PLATFORMS变量中列出的每个平台拉出并生成一个 binfmt 容器。

 - run:
          name: Tag golden
          command: |
            BUILDX_PLATFORMS="$BUILDX_PLATFORMS" make cross-build 

上面的代码片段指定了管道中要执行的最后一个命令。它构建了我们想要的多架构 Docker 映像。command:键调用在Makefile中定义的cross-build函数,所以让我们看看与这个函数相关的底层命令。

# Makefile cross-build function

.PHONY: cross-build
cross-build:
	@docker buildx create --name mybuilder --use
	@docker buildx build --platform ${BUILDX_PLATFORMS} -t ${PROD_IMAGE} --push ./app 

上面的代码片段是实际的cross-build make命令,它创建了一个新的 Buildx builder 实例。接下来,使用docker buildx build命令触发进程,为${BUILDX_PLATFORMS}环境变量中列出的每个平台构建一个单独的 Docker 映像。这被输入到指挥的--platform旗中。-t标志标记/命名 Docker 映像,而--push标志会自动将构建结果推送到 Docker 注册表。在这种情况下,它是 Docker Hub。

摘要

这篇文章展示了如何在 CI 管道中为多个操作系统和处理器架构构建各种 Docker 映像。这篇文章还简要介绍了 Docker Buildx 特性,它目前是一个实验性的工具,有望在 Docker 的未来版本中成为事实上的构建工具。我认为 Buildx 是下一代 Docker 映像构建工具,它将提供扩展、高级和优化的功能来增强当前的映像构建体验。

我还简要讨论了构建针对多个操作系统和平台架构的 Docker 映像的一些复杂性,这突出了 Docker 容器和 VM 之间的技术差异。虽然从抽象的角度看,它们看起来很相似,但本质上是不同的。最后,我要重申的是, Docker 已经实施了新的 Docker Hub 速率限制 ,该限制要求对 Docker Hub 的所有呼叫进行认证。每当您从 CircleCI 上的 Docker Hub 提取图像或向其推送图像时,我们建议您使用登录您的 Docker Hub 帐户,以完成 CircleCI 配置中的docker pulldocker push步骤。

感谢你关注这篇文章,希望你觉得有用。请随时在 Twitter @punkdata 上寻求反馈。

在 CI/CD 管道中构建 Kotlin 多平台项目

原文:https://circleci.com/blog/building-kmm-on-cicd/

Kotlin 是最通用的编程语言之一,这在很大程度上是因为 Kotlin 团队致力于将其引入尽可能多的平台。它是开发 Android 应用程序的主要语言,在 JVM 后端很流行。Kotlin 还提供了使用 Kotlin/Native 进行本地二进制编译的目标,以及通过 Kotlin/JS 进行 web 编译的目标。它最有前途的特性之一是能够针对多个平台进行编译。

这就是 Kotlin 多平台移动(KMM) 的用武之地:它让你可以在 Android 和 iOS 应用中编写和重用相同的 Kotlin 代码。由于移动客户端通常以功能对等为目标,这是一种避免重复工作的聪明方法,我们都知道开发人员多么喜欢避免重复工作。相信我,我是一名开发人员,我知道这有多真实。

无论如何,回到科特林多平台。这是一种不同于跨平台工具的方法,如 Flutter 和 React Native,它们在一切之上提供统一的 UI。KMM 允许您编写通用业务逻辑,同时将用户界面和体验牢牢地保留在他们的本地平台领域中。在许多情况下,这种方法更适合应用程序用户,因此也更适合您的客户。

本文将向您展示如何开始在 CI/CD 管道中构建 KMM 项目,并将其包含在您团队的开发工作流中。

跟随注意事项

本文假设读者理解 Kotlin,并且至少熟悉 Android 或 iOS 中的一个,最好是两个平台都熟悉。这篇文章不会教你 KMM。为此,请查看 Kotlin 站点上的现有教程之一,或者示例应用程序的自述文件。

另外请注意:KMM 目前是在阿尔法版本。技术和 API 在不断变化和发展,我们正在尽最大努力使示例保持工作和最新,但除了示例中的 API 版本之外,我们不提供任何保证。

在 CI/CD 管道中构建多平台项目

要在 CI/CD 环境中构建一个多平台项目,可以把它看作是为每个单独的目标平台构建不同的项目。我们的项目基于科特林自己的多平台样本——一个 RSS 阅读器应用。它包含两个应用程序,以及一个以 Kotlin 库形式存在的共享 Kotlin 代码库。共享库保留在 Kotlin 中,用于在 Android 上编译,并向下编译为本机代码,以便在 ARM64 上运行,用于 iOS 目标。

Android 应用程序代码位于androidApp,用熟悉的 Kotlin 代码库编写。iOS 的 app 是用 Swift 写的,位于iosApp。要了解 KMM 是如何让一切工作的,请查看 KMM 官方文档文档和 Kotlin 的 GitHub repo 上的样本项目

建立一个基本的管道

用于 KMM 项目的最简单的 CI/CD 管道在单个工作流中包含两个作业,分别执行每个平台的构建。CircleCI 中的作业是一系列命令或步骤,它们在预定的环境中执行。这种环境是为不同平台构建应用程序的关键。对于 iOS,我们需要在 Mac 硬件上构建它,为 executor 传入macos。Android 更加宽松,但我们仍然可以选择一个包含所有 SDK 和其他内置内容的预构建环境。对于本教程,我们将使用由android orb 提供的android Docker 图像。

这些工作本身很简单,与本教程并不相关:检查代码,编译,可能运行一些测试,构建,甚至部署。作为本指南的一部分,我们将不关注工作。相反,我们将花一些时间为构建设置环境和工作流。如果你想了解更多关于为 Android 设置构建和测试工作的信息,你可以在这篇文章中读到。如果你想了解更多关于在 iOS 上设置构建和测试的信息,你可以在 CircleCI 文档中的本教程中了解更多。

version: 2.1

orbs:
  android: circleci/android@1.0.3

jobs:
  build-android:
    executor: android/android

    steps:
      - checkout
      - android/restore-build-cache
      - android/restore-gradle-cache
      - run:
          name: Run Android tests
          command: ./gradlew androidApp:testDebugUnitTest
      - android/save-gradle-cache
      - android/save-build-cache

  build-ios:
    macos:
      xcode: 12.4.0
    steps:
      - checkout
      - run:
          name: Allow proper XCode dependency resolution
          command: |
            sudo defaults write com.apple.dt.Xcode IDEPackageSupportUseBuiltinSCM YES
            rm ~/.ssh/id_rsa || true
            for ip in $(dig @8.8.8.8 bitbucket.org +short); do ssh-keyscan bitbucket.org,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
            for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
      - run:
          name: Install Gem dependencies
          command: |
            cd iosApp
            bundle install
      - run:
          name: Fastlane Tests
          command: |
            cd iosApp
            fastlane scan

workflows:
  build-all:
    jobs:
      - build-android
      - build-ios 

构建 Android 应用程序

开始构建 Android 应用程序的最佳方式是使用 Android orb。这为您提供了一些内置的作业和命令,使构建变得更加容易。对于这个练习,我们将安装 Gradle 依赖项,并运行 Gradle 命令来运行单元测试。

Kotlin Multiplatform 带来的主要区别是该应用程序不是顶级项目。相反,它位于androidApp模块中。这个模块依赖于shared,所以我们需要调用带有androidApp:前缀的 Gradle 命令。

orbs:
  android: circleci/android@1.0.3

jobs:
  build-android:
    executor: android/android

    steps:
      - checkout
      - android/restore-build-cache
      - android/restore-gradle-cache
      - run:
          name: Run Android tests
          command: ./gradlew androidApp:testDebugUnitTest
      - android/save-gradle-cache
      - android/save-build-cache

      - store_artifacts:
          path: androidApp/build/outputs/apk/debug 

构建 iOS 应用程序

正如我们上面提到的,构建 iOS 应用程序需要安装了 XCode 的 Mac 硬件。对于build-ios作业,我们将使用macos执行器,将 xcode 版本作为参数传递。在我们的情况下是:xcode: 12.4.0

不管您使用的是 CocoaPods 还是 Swift Package Manager,其余步骤都是通用的。您通常会安装一些依赖项,然后您可以使用浪子构建应用程序并运行测试。对于本教程,我们将只运行一些测试。

当我们使用浪子启动构建时,共享的 Kotlin 代码会自动构建为一个框架,所以这里没有什么要做的了。

jobs:
  ...
 build-ios:
    macos:
      xcode: 12.4.0
    steps:
      - checkout
      - run:
          name: Allow proper XCode dependency resolution
          command: |
            sudo defaults write com.apple.dt.Xcode IDEPackageSupportUseBuiltinSCM YES
            rm ~/.ssh/id_rsa || true
            for ip in $(dig @8.8.8.8 bitbucket.org +short); do ssh-keyscan bitbucket.org,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
            for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
      - run:
          name: Install Gem dependencies
          command: |
            cd iosApp
            bundle install
      - run:
          name: Fastlane Tests
          command: |
            cd iosApp
            fastlane scan 

使用动态配置创建高级管道

每当有人提交到存储库时,这个示例管道可以很好地构建和测试您的 KMM 应用程序的两个目标平台。但是我们可以做得更好。移动开发的现实是,我们有专家专注于他们各自的平台。有些人喜欢并使用 Android,喜欢为 Android 制作伟大的 UX 和体验,对这个平台了如指掌。另一方面,也有同样专注于 iOS 的开发者。在公共代码库上工作总会有一些交叉。在大多数团队中,仍然会有专门从事某个平台的人。

开发进度通常以以下一种形式出现:

  • 仅限 Android 前端代码库
  • 仅限 iOS 前端代码库
  • 由两个前端代码库使用的共享 KMM 代码库

对于前两个场景,只为正在工作的平台构建更有意义,并且节省时间、信用和效率。

为了优化这个流程,我们可以使用 CircleCI 的动态配置特性,这有助于我们更有效地编排这样的项目。动态配置允许您只构建应用程序中您已经更改的部分。

动态配置通过设置工作流工作,第一步是评估代码库,检测发生了什么变化(稍后将详细介绍),然后才运行真正的工作流,即构建应用程序相关部分的工作流。

设置工作流程仍然使用 CircleCI 用户可能熟悉的config.yml。执行的工作流使用路径过滤 orb,它检测与指定分支相比的变化。这是完整的配置。

version: 2.1

setup: true

orbs:
  # the path-filtering orb is required to continue a pipeline based on
  # the path of an updated fileset
  path-filtering: circleci/path-filtering@0.0.2

workflows:
  select-for-build:
    jobs:
      - path-filtering/filter:
          base-revision: master
          config-path: .circleci/continue-config.yml
          mapping: |
            shared/.*|^(?!shared/.*|iosApp/.*|androidApp/.*).*  build-all     true
            androidApp/.*                                       build-android true
            iosApp/.*                                           build-ios     true 

以下是该配置的明细:

setup:true向 CircleCI 表示我们正在使用动态配置,这是管道运行的第一部分。设置工作流是此配置的唯一工作流。它只有一个作业path-filtering/filter,来自path-filtering orb。该作业需要几个参数:

  • base-revision表示要比较的分支
  • mapping决定比较哪些路径(稍后将详细介绍)
  • config-path指向continue-config.yml,这是该设置工作流程后要评估的下一个配置。在我们的例子中,我们指向一个静态文件,但是我们也可以预先以编程方式构造这个新的配置文件。

要使用mapping,您需要定义项目的路径,以便与基础版本中的代码库进行比较。每行有一条路径。然后,设置要传递给后续管道的管道参数及其值。所有这些都用空格隔开。

第 2 行和第 3 行相当简单:

androidApp/.* build-android  true 

在这种情况下,我们感兴趣的路径是androidApp/目录中的所有文件和子目录。这就是 Android 专用前端的代码库。流水线参数build-android被设置为true

然而,第一个匹配行更复杂,因为正则表达式匹配器更长:

shared/.*|^(?!shared/.*|iosApp/.*|androidApp/.*).*  build-all  true 

它匹配两个目录中的路径,即我们的 Kotlin 多平台代码所在的目录,以及不在shared/iosApp/androidApp/目录中的任何内容,包括任何其他顶级文件。我们已经将管道参数build-all设置为true,这将触发两个平台的应用构建。

continue-config.yml中所述,在映射评估了所有更改并设置了所有相关的管道参数后,CircleCI 停止该作业,并开始该动态工作流的第二部分。这是完整的文件。

version: 2.1

orbs:
  android: circleci/android@1.0.3

parameters:
  build-all:
    type: boolean
    default: false
  build-android:
    type: boolean
    default: false
  build-ios:
    type: boolean
    default: false

jobs:
  build-android:
    executor: android/android

    steps:
      - checkout
      - android/restore-build-cache
      - android/restore-gradle-cache
      - run:
          name: Build Android app
          command: ./gradlew androidApp:assembleDebug
      - android/save-gradle-cache
      - android/save-build-cache

  build-ios:
    macos:
      xcode: 12.4.0
    steps:
      - checkout
      - run:
          name: Allow proper XCode dependency resolution
          command: |
            sudo defaults write com.apple.dt.Xcode IDEPackageSupportUseBuiltinSCM YES
            rm ~/.ssh/id_rsa || true
            for ip in $(dig @8.8.8.8 bitbucket.org +short); do ssh-keyscan bitbucket.org,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
            for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
      - run:
          name: Install Gem dependencies
          command: |
            cd iosApp
            bundle install
      - run:
          name: Fastlane Tests
          command: |
            cd iosApp
            fastlane scan

workflows:
  run-android:
    when:
      or:
        - << pipeline.parameters.build-android >>
        - << pipeline.parameters.build-all >>
    jobs:
      - build-android

  run-ios:
    when:
      or:
        - << pipeline.parameters.build-ios >>
        - << pipeline.parameters.build-all >>
    jobs:
      - build-ios 

这里没有包含setup:true行,这表明这是一个“标准”CircleCI 配置文件。它确实包含了带有我们在前一阶段定义的管道参数的parameters部分。

parameters:
  build-all:
    type: boolean
    default: false
  build-android:
    type: boolean
    default: false
  build-ios:
    type: boolean
    default: false 

要使用管道参数,我们首先需要在管道中定义它们。我们之前用过。现在您指定它们的类型和默认值:all boolean并设置为false

workflows:
  run-android:
    when:
      or:
        - << pipeline.parameters.build-android >>
        - << pipeline.parameters.build-all >>
    jobs:
      - build-android

  run-ios:
    when:
      or:
        - << pipeline.parameters.build-ios >>
        - << pipeline.parameters.build-all >>
    jobs:
      - build-ios 

一旦定义了参数,我们就可以在过滤工作流时使用它们。与上一节所示的简单得多的配置不同,我们需要将作业划分到两个工作流中:

  • Android 的工作被命名为run-android
  • iOS 的作业名为run-ios

我们可以使用与逻辑操作符结合的when节来指定何时运行特定工作流的过滤器。对于 Android,它是一个build-allbuild-android管道参数。对于 iOS,它是一个build-iosbuild-all

此工作流根据运行与工作流相关的作业的已更改文件有选择地运行。这种设置让您的团队更快、更有效地运作,同时仍然利用 KMM 的所有多平台功能。

结论

在本教程中,您已经学习了如何在 CircleCI 上建立构建 Kotlin 多平台移动项目的管道。它类似于构建任何其他项目,除了知道首先构建哪个平台。移动平台通常是单独开发的,因此我们使用 CircleCI 动态配置功能来构建应用程序开发人员当前正在开发的部分。

如果你对这篇文章有任何问题或建议,或者对未来的文章和指南有什么想法,请通过 Twitter - @zmarkanemail 给我联系我。

使用 MSIX orb | CircleCI 为 Windows 构建

原文:https://circleci.com/blog/building-msix-installers-on-circleci/

MSIX orb 是 CircleCI 的第一个“Windows 专用”orb。当微软向我们提出建立一个 orb 来帮助 Windows 开发人员在我们的平台上进行开发的机会时,我们非常热情。我们的大多数 orb 和一般工作负载都围绕着 Linux,并利用 Bash。然而,我们认识到为在 Windows 上构建应用程序提供良好的 CI/CD 解决方案的迫切需要,并且随着 PowerShell 在 Linux 中的使用稳步增长,是时候冒险了。

MSIX orb 有两种不同的方法来构建新的 MSIX/Appx 安装程序格式:使用 Visual Studio 和手动创建文件。Visual Studio 解决方案文件是 Visual Studio 中的一个项目设置,它链接到其他 Visual Studio 项目,并包含使用正确的元数据构建安装程序包所需的配置。这是使用这个球体和技术最直接的方式。手动选项更麻烦,因为它涉及创建必要的元数据和映射文件,以正确打包预编译的二进制文件。

在本教程中,我们将介绍使用 Visual Studio 解决方案文件构建包,并在最后为手动方法提供一些资源。

关于代码签名,您需要知道什么

代码签名允许用户和操作系统验证特定二进制代码或代码位的作者。有些东西是否被签名并不明显,所以操作系统警告未签名的二进制文件是很常见的。根据您的系统,您可能还会收到关于未签名的软件包和应用程序的警告。该警告提示您确认是否要继续安装或使用该特定软件包。

代码签名是 MSIX 和 Appx 包的必需步骤。它通常涉及一个昂贵但被广泛信任的证书或一个较小的本地创建的证书。除非明确安装,否则本地创建的证书可能不可信。对于本教程,我们将使用一个较小的本地创建的证书,原因有两个:

  • 上传广泛信任的私有证书的私有部分不是一种好的做法。
  • 本地创建的证书更容易获得并用于测试。

虽然有安全的方法可以做到这一点,但对私有证书最常见的建议是要么选择可信的专用服务,要么完全远离互联网。本地创建的证书是自签名的,如果它被泄露或被第三方获得,也不会成为重大事件。

您可以按照微软文档中的步骤生成自签名证书。创建证书后,使用同一链接中的说明导出证书。我们需要将导出的证书上传到 CircleCI。

我们的下一步是将证书转换成基于文本的格式,因为环境变量是显式文本。我们可以使用 base64 编码来做到这一点。把 base64 编解码想象成脱水的食物,后期再补水。这种编码保留了文件内容,同时也将其视为文本。当我们需要原始文件时,可以对其进行解码。

使用以下 PowerShell 代码片段:

[Convert]::ToBase64String([IO.File]::ReadAllBytes($FileName)) 

用导出的证书文件的名称替换$FileName。这个代码片段返回一个字符串,您可以将它添加到 CircleCI 上下文中。您将在稍后的管道中使用它。把这个命名为MSIX_SIGNING_CERTIFICATE

您还需要添加用于将证书导出到相同安全上下文的密码。因为这个证书仅用于测试目的,只要它没有被安装在任何其他机器上,你就不需要担心它被泄露。把这个命名为MSIX_CERTIFICATE_PASSWORD

使用 MSIX 球体

MSIX orb 极大地简化了构建基于解决方案的项目。这种极简的配置应该足以让你开始:

# .circleci/config.yml
version: 2.1

orbs:
  # We pin the minor version but leave out the patch version
  # to automatically accept non-breaking improvements.
  msix: circleci/microsoft-msix@1.1
  # Gives us easy access to Windows execution environments.
  win: circleci/windows@2.4.0
jobs:
  using-sln:
    executor:
      name: win/default
    steps:
      - checkout
      - msix/pack

workflows:
  demo:
    jobs:
      - using-sln 

msix/pack命令获取您的.sln项目并构建它,自动引入您的签名证书。它将最终结果发布为 CircleCI 工件,您可以下载并手动验证。如果您需要调整行为,有几个参数可用,但我们已经确定了大多数情况下的最佳默认值。

因为这是一个命令,所以您还可以通过将生成的文件保存到工作区或者上传到您自己的工件服务器来重用它。

不使用 Visual Studio 构建 MSIX

可以使用通过其他方式构建的、具有 MSIX 可执行文件格式的现有可执行文件。然而,它需要手动创建元数据,你可以在微软的文档中了解更多关于的信息。

当您使用这种方法时,在配置上有几个关键的区别:

  • 您需要手动定义清单和映射文件,因为这两者都没有标准的文件名。
  • 您的安装程序需要使用msix/sign命令手动签名。
version: 2.1

orbs:
  msix: circleci/microsoft-msix@1.0
  win: circleci/windows@2.4.0
jobs:
  using-sln:
    executor:
      name: win/default
    steps:
      - checkout
      - msix/pack:
          using-sln: false
          mapping-file: mapping.txt
          manifest-file: appxmanifest.xml
      - msix/sign:
          import-cert: true

workflows:
  build:
    jobs:
      - using-sln 

在这个代码片段中,打包一个非 Visual Studio 解决方案项目需要更多的手动操作,但是它受 orb 支持。

对于这两种方法,在 orb 源代码库中有一些可用的资源。这些资源主要用于集成测试,但是它们也展示了 orb 的功能。

继续使用 CircleCI 构建 Windows

我和我的工程师同事对使用 PowerShell 编写脚本的体验非常满意。对我们来说,默认情况下,它被证明比 Bash 更健壮。我们的团队对 MSIX 球体的最终结果感到满意,我们希望您和您的团队会发现它很有用。它不是一个通用的执行环境,所以我们不依赖它来运行其他 orb。然而,我们将继续投入时间,并希望建立更多的 orb,为 Windows 执行器提供更好的原生支持。我们致力于改善团队在我们的平台上构建 Windows 操作系统的体验。

在 CircleCI 上构建开源项目

原文:https://circleci.com/blog/building-open-source-projects-on-circleci/

现代的界面、与 GitHub 和 Bitbucket 的一步集成以及免费的构建容器使 CircleCI 成为各种规模的许多组织的首选 CI 工具。很多人不知道的是,我们专门为开源项目提供了额外的资源和设置。

额外的构建容器

CircleCI 上的所有组织(用户和公司)都有一个 linux 容器和 1000 分钟的构建时间来运行他们的项目。包括专有代码。百分百免费。尽管这可能很棒,但我们提供的开源项目甚至更多!一个正在 CircleCI 上构建的开源项目获得了三个额外的构建容器,总共 4 个。更好的是,这些构建容器没有分钟限制,所以您可以想构建多少就构建多少。这是免费提供的价值 150 美元的 CircleCI 服务。

多个构建容器允许您做两件事情中的一件。利用 CircleCI 的并行性,一次构建多个拉请求(PR ),或者更快地构建单个 PR。不管怎样你都赢了。

注意:如果构建在 Linux(我们的默认平台)上的项目是 GitHub 或 Bitbucket 上的“公共”项目,它们将自动接收额外的容器。不过,我们不会冷落 macOS 用户。如果你正在 macOS 上构建一个开源项目,并想利用我们的服务,请联系 billing@circleci.com。

有用的功能和设置

公共构建页面 -您的构建页面可以是公共的,以允许核心贡献者、合作者和一般社区对您的项目透明。

私有环境变量(Private environment variables)——许多项目最终需要 API 令牌、SSH 密钥、密码或其他东西来构建所有的依赖项,或者部署到其他地方。Private envars 允许您安全地存储这些变量,即使您的项目可以被组织外部的成千上万的用户看到和贡献。

高级设置- >构建分叉拉取请求 -这将决定是否构建从分叉库打开的 PRs。这样做的主要好处是快速有效地测试用户的贡献,在你有机会看之前就告诉你他们的公关是否好。一个可能的缺点是大量的或者分叉的 PRs,或者制作不良的 PRs 会使您的构建容器忙碌起来,而不是处理您自己的代码。

高级设置- >从分叉拉取请求中传递秘密给建筑——这个设置对于安全来说极其重要。当这个打开时,之前提到的那些私有 envars,AWS & Heroku 凭证,甚至存储在 CircleCI 上的 SSH 密钥都将对任何创建一个 fork 并为您的项目打开一个 PR 的人可用。这个设置的好处是需要这些变量的构建将能够通过,这总是一个好消息。然而,严重的缺点是允许某人,任何人,简单地编辑您的 CircleCI 配置,将您的 SSH 密钥打印到终端,现在就可以访问您的服务器。

高级设置- >仅构建拉取请求 -该设置意味着仅构建拉取请求和项目的默认分支(通常是主分支)。这对于有大量提交活动的项目非常有用,有助于减少将要运行的构建数量。

CircleCI 已经在建设社区项目

CircleCI 上已经有大量大大小小的开源项目正在建设中。这里有几个例子:

  • React——脸书基于 JavaScript 的 React 是用 CircleCI(以及其他)构建的。
  • Vue——一个构建用户界面的渐进式框架。
  • Calypso——为 WordPress.com 供电的下一代 webapp。
  • Angular——另一个建立在包括 CircleCI 在内的多个提供者之上的 JavaScript 框架。
  • fastlane -一款适用于 Android 和 iOS 的自动构建工具。
  • ——一个快速、可靠、安全的另类 npm 客户端。

加入 CircleCI 讨论分享您的项目,告诉我们如何更好地支持开源社区。

生成邮递员测试报告| CircleCI

原文:https://circleci.com/blog/building-postman-test-reports/

在用 Postman 测试 API 的中,我们使用 Postman 集合和 Postman 命令行实用程序 Newman 为 API 端点设置了自动测试。在本教程中,我们将通过使用Newman CLI 工具来为我们的 API 测试生成详细的、可存储的报告,从而建立这种经验。

先决条件

要跟进这篇文章,需要做一些事情:

  1. JavaScript 的基础知识
  2. Postman for Desktop 安装在您的系统上(您可以在这里下载它
  3. 一个的账户
  4. GitHub 的一个账户
  5. 安装在您系统上的 Node.js (版本> = 10)
  6. 要报告的集合和环境

注意 : 如果您完成了用 Postman 测试 API 教程中的任务,您就有了一个可以用于报告的集合和环境。如果您不熟悉邮递员集合,在开始本教程之前完成该教程可能会有所帮助。

获取邮递员集合公共 URL

现在,每次我们需要添加更多的测试和请求时,我们用来测试 API 的集合都必须更新。这个手动步骤很麻烦。您需要导出集合文件并更新自动化测试项目。幸运的是,Postman 提供了一项服务,可以让你上传和同步你的收藏。

打开 Postman 桌面应用程序并登录。您的本地收藏会自动上传和同步。

一旦你做到了这一点,你就可以获得你的收藏的公共链接。此链接将指向您在邮递员服务上托管的收藏版本。对您的收藏所做的任何更改都可以通过该链接立即获得。只要你登录,邮递员同步所有更新。这就是使用链接而不是将收藏导出为json的最大好处。

要获取您的链接,请打开您的收藏的弹出菜单,然后单击共享。从弹出对话框中复制您收藏的公共链接,如下图所示。

Get Link - Postman

通过此链接,您不再需要导出和移动收藏文件。

使用 Newman 的 HTMLExtra Reporter 创建报表项目

接下来,我们需要为运行 API 测试和生成测试报告设置一个项目。Newman 支持多种报告生成包。我用得最成功的是Newman-reporter-htm extra

虽然纽曼和记者通常在全球范围内安装和运行,但我们将为这个项目做一些稍微不同的事情。我们将在本地安装这两个包,以便在运行项目时避免全局依赖。

使用以下命令创建一个项目文件夹并在本地安装这两个包:

mkdir postman-api-reports
cd postman-api-reports
npm init -y
npm install newman
npm install -S newman-reporter-htmlextra 

将导出的环境文件放在项目的根目录下。现在,项目已经为 API 测试生成报告做好了一切准备。

在本地生成报告

接下来,我们将运行命令来测试 API 并生成测试报告。使用以下格式,使用特定于您的项目的信息:

npx newman run [YOUR_COLLECTION_PUBLIC_LINK] -e ./[POSTMAN_ENVIRONMENT_FILENAME].json -r htmlextra 

这个命令使用npx调用newman的本地安装,然后运行集合中的测试。结果从newman-reporter-htmlextra包传递给htmlextra命令,后者生成报告。

您可以从 CLI 运行此命令来生成报告,但是每次都运行长命令可能不太实际。相反,您可以将它作为名为reportnpm脚本添加到package.json文件中,如下例所示:

"script" : {
    "report": "npx newman run https://www.getpostman.com/collections/778ce5ceb7c3ffbfd6bd -e ./My-Remote-API-Testing.postman_environment.json -r htmlextra"
} 

运行以下命令来运行测试并生成报告:

npm run report 

第一次运行该命令时,它会在项目的根目录下创建一个新文件夹newman,并向该文件夹添加一个带有时间戳的html文件。在浏览器中打开该文件以查看测试报告。

Test Results 1 - Reports

Test Results 2 - Reports

Test Results 3 - Reports

默认情况下,报告显示摘要。摘要显示了测试迭代的数量,以及失败的、通过的和跳过的测试。单击任一选项卡可获得有关该类别结果的更多详细信息。您甚至可以将显示主题从亮模式切换到暗模式。

为报表自动化设置 CircleCI 项目

太棒了——我们已经生成了测试报告。干得好!现在是自动化这个过程的时候了。从将你的项目推送到 GitHub 开始。

添加一个.gitignore文件,并指示 Git 忽略node_modulesnewman文件夹。在您的.gitignore文件中,输入:

node_modules
newman 

对项目做出承诺。

现在,转到 CircleCI 仪表板上的添加项目页面来添加项目。

Add Project - CircleCI

点击设置项目

Add Config - CircleCI

点击使用现有配置。此设置指示您正在手动添加配置文件,而不是使用示例。

接下来,系统会提示您下载配置文件或开始构建。

Build Prompt - CircleCI

点击开始建造。这个构建将会失败,因为我们还没有设置配置文件。我们将在下一步中设置一个。

自动生成报告

现在是时候编写自动化脚本了。在项目的根目录下创建一个名为.circleci的文件夹,并向其中添加一个名为config.yml的配置文件。在该文件中,输入以下代码:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Generate Report
          command: npm run report
      - store_artifacts:
          path: ~/repo/newman 

在上面的配置中,node.js Docker 映像作为运行测试的环境被引入。然后,npm被更新,依赖项被安装并缓存。所有依赖项就绪后,使用npm运行定制的report脚本。该命令运行测试并生成报告。最后,reports 文件夹(newman)被存储为一个工件。它可以在 CircleCI 控制台的工件选项卡上找到。

推动新的变革。

Build Successful - CircleCI

单击构建以显示摘要。

Test Results - CircleCI

点击工件找到您生成的报告。

结论

在本教程中,您已经通过使 Postman 生成可访问的详细测试报告,扩展了它的端到端测试能力。您可以使用这些过程来确保测试运行,并确保每当您向 API 推送更新时都会生成报告。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

在任何组织| CircleCI 上建造私人 CircleCI 球体

原文:https://circleci.com/blog/building-private-orbs/

使用 CircleCI 的 orbs 是跨项目共享 CI/CD 配置的一个好方法。公共 orb 能够很好地被广泛采用,但是私有 orb 对于需要以安全、非公共的方式共享公共内部配置的组织来说是很有帮助的。私有 orb 只在发布它们的组织内工作。

我们最近向所有 CircleCI 客户开放了私人 orbs,包括那些参加了免费计划的客户。这种新的访问方式为您的管道开辟了许多新的可能性。

在这篇文章中,你将了解到私有球体以及如何在你自己的组织中使用它们。我们将使用我构建的示例 orb,它包含一个命令和一个作业,允许您使用 cURL 在其他项目中触发 CircleCI 管道。你可以在这个资源库中找到该项目的源代码。

先决条件

本文假设:

  • CircleCI 的工作知识
  • 了解管道的工作原理
  • 在您的机器上本地安装和配置的 CircleCI CLI
  • 您正在 GitHub 上使用项目组织或您的个人组织

私有球体的用例

与一般的 orb 一样,私有 orb 对于共享多个项目共有的步骤和任务非常有用。

许多组织都有自己的框架和跨许多团队使用的内部工具。有了私人宝珠,他们可以轻松地分享这些工具,而无需向更广阔的世界开放。私有 orb 的一些用例包括部署到您专有的 Kubernetes 集群或其他基础设施,处理定制硬件,一直到日志记录或常见的管理任务。

初始设置

我们将使用 CircleCI CLI 来完成此任务。使用 CLI,您将创建一个新的 orb,并将其注册到 CircleCI,以便它可以被其他 CircleCI 管道引用。CLI 允许您发布 orb 的新版本。

整个 orb 创建过程记录在 CircleCI 文档中。本文将对其进行快速回顾,并解释相关步骤。

创建一个空的 git 存储库并记下它的路径。我在我的zmarkan-demos组织下创建了我的,命名为api-requester-orb-sample,所以路径是:git@github.com:zmarkan-demos/api-requester-orb-sample.git

接下来,使用 CLI 的circleci orb init命令创建实际的 orb,传递您希望 orb 源所在的本地路径。该目录必须是新的;不要使用已经存在的。对我来说,这是:

circleci orb init ~/github/api-requester-orb-sample --private 

公共球体和私有球体的主要区别在于末尾的--private标志。

CLI 要求您要么按照指导设置,这是我做的,要么自己做。我推荐使用自动化过程,因为它将使用一个空的 orb 模板设置目录中的所有内容,并将其链接到您的存储库,为您的 orb 创建一个名称空间,并为新的 orb 创建一个 CircleCI 项目,只需确保从您之前创建的存储库中复制 git URL。

最后,您需要将本地代码推送到远程存储库-

cd ~/github/api-requester-orb-sample
git push origin master 

注意:所有 orb 都存在于您组织内的一个名称空间中。这对于您已经注册的任何跑步代理都是一样的。

你也可以在你的组织设置中查看你的新的私有球体,它们在type部分被清楚地标记为私有。

List of orbs in the UI

开发一个球体

orb 开发过程取决于您是选择自动化指导过程还是遵循您自己的过程。与设置部分一样,CircleCI 文档涵盖了这两种情况。

如果您遵循了快速设置路线,那么 CLI 将从模板中为您生成整个项目。它包含命令、作业、执行器的示例,以及对它的测试。

Orb project view

你可以移除任何你不需要的部分。在我的例子中,我删除了脚本和执行器,保留了命令和作业。orb 只是 CircleCI 配置脚本的集合,当您运行管道时,这些脚本会被解析和绑定。

我们创建了 2 个脚本:

  • 一个commands/trigger-pipeline.yml命令
  • 一份工作

作业包装命令并在基本 Docker 执行器中执行。

使用触发器管道命令

trigger-pipeline命令有 4 个参数和一个单步,如以下代码示例所示:

description: >
  This command triggers CircleCI pipeline specified.
  The job execution environment must have cURL utility available

parameters:
  repo-name:
    type: string
    description: "Repo name to trigger pipeline for"

  branch-name:
    type: string
    default: main
    description: "Branch name to trigger"

  trigger-params-json:
    type: string
    default: "{}"
    description: Pipeline trigger parameters in JSON format

  circle-token-env-var:
    type: env_var_name
    default: CIRCLE_TOKEN
    description: Environment variable containing your CircleCI API Key. Default is $CIRCLECI_API_KEY

steps:
  - run:
      name: Run cURL request
      command: |
        curl --request POST \
            --url "https://circleci.com/api/v2/project/gh/${CIRCLE_PROJECT_USERNAME}/<< parameters.repo-name >>/pipeline" \
            --header "Circle-Token: ${<< parameters.circle-token-env-var >>}" \
            --header "content-type: application/json" \
            --data '{"branch":"<< parameters.branch-name >>","parameters":<< parameters.trigger-params-json >>}"' 

与完整的 CircleCI 管道配置不同,这个配置不需要一个version节。由于只有一个命令源,它也没有jobsworkflows。与完整的 CircleCI 配置中可用的管道参数不同,所有 4 个参数也专用于该命令。

run 步骤中的curl命令点击 CircleCI API 来触发另一个管道。在这篇博文中有详细的描述。

使用触发器循环管道作业

编写一个使用我们刚刚创建的命令的作业非常简单。在同一个 orb 中,您可以使用该命令,就像它是您在配置中定义的本地命令一样。在我们的jobs/trigger-circleci-pipeline.yml示例中,看起来是这样的:

description: >
  Job that trigger CircleCI pipeline in another project with the payload specified

docker:
  - image: cimg/base:2021.12

parameters:
  repo-name:
    type: string
    description: "Repo name to trigger pipeline for"
  branch-name:
    type: string
    default: main
    description: "Branch name to trigger"

  trigger-params-json:
    type: string
    default: "{}"
    description: Pipeline trigger parameters in JSON format

  circle-token-env-var:
    type: env_var_name
    default: CIRCLE_TOKEN
    description: Environment variable containing your CircleCI API Key. Default is $CIRCLECI_API_KEY

steps:
  - trigger-pipeline:
      repo-name: << parameters.repo-name >>
      branch-name: << parameters.branch-name >>
      trigger-params-json: << parameters.trigger-params-json >>
      circle-token-env-var: << parameters.circle-token-env-var >> 

这个命令有参数,和 CircleCI 中的所有其他作业一样,我们需要执行程序:dockersteps节包含一个单独的步骤,使用我们设置的任何参数调用之前的trigger-pipeline命令。

测试和部署 orb

为了构建、测试和部署我们的 orb,orb 模板项目使用 CircleCI 本身。在.circleci/config.yml的配置中,几乎所有东西都是为您预先准备好的。你需要做的就是为它编写测试。默认情况下,它包含一个名为integration-test-1的作业,您可以修改它以使用您自己的 orb。下面是调用trigger-pipeline命令的一个片段:

jobs:
  integration-test-1:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - api-requester-orb-sample/trigger-pipeline:
          repo-name: pinging-me-softly
          branch-name: main
          trigger-params-json: '{"image-name": "cimg/base:2021.12"}' 

要发布发布版本,您需要:

  • 从默认的alpha分支变为mastermain分支
  • 在提交中包含一条[semver:patch|minor|major]消息

结论

在这篇文章中,你已经学习了如何为 CircleCI 建造私人球体,现在 CircleCI 的所有客户都可以使用。私有 orb 允许您在多个项目之间共享 CI/CD 管道配置。组织中的任何团队成员都可以使用这些私有 orb,而不会有将您的配置公开的风险。

整个过程对于发布公共 orb 几乎是相同的。只需在circleci orb init命令中添加--private标志。

要阅读更多关于 orb 开发的内容,请查看创作 orb 的文档页面。

要了解如何使用 BATS 和 ShellCheck 在 orb 中测试和验证 bash 脚本,请查看 orb 测试方法的文档页面

如果你对这篇文章有任何问题或建议,或者对未来的文章和指南有什么想法,请通过 Twitter - @zmarkanemail 给我联系我。

构建强大的分布式团队,一次一个像素

原文:https://circleci.com/blog/building-strong-distributed-teams-one-pixel-at-a-time/

我的工作生活每天都是由数百万像素组成的。我的团队分布在全球各地,大多数时候,我的队友在缩放会议、闲聊和电子邮件中被简化为像素。我们带来了不同的背景、经历、沟通方式和协作偏好,以及更多的差异。

在我过去六年的职业生涯中,我建立并支持了遍布全球的分布式团队。虽然这是一个我将永远感激的机会,我可以和出色的人一起工作,但它也挑战了我作为一名领导者和经理的能力。

通过渗透,或在字面上的水冷却器,很少发生有机的事情,但与此同时,所有团队面临的许多挑战,如沟通和协作,在我们跨地点和时区分布时会加剧。

建立和领导团队的常用方法就是不能简单应用。这意味着作为领导者,我们经常需要更有创造力。

与此同时,作为一名经理,我必须更有目的性,这让我学到了很多东西,我也感谢我的团队如何帮助我进步。我学到的主要经验是,尽管不在同一个地方,我们仍然可以创建更强大、更谨慎的分布式团队,并产生与共享同一张餐桌的团队相同甚至更好的结果。

在这篇文章中,我将分享我在基于三个关键“C”支柱改进团队方面的经验:联系、沟通和协作。无论你是管理世界各地的团队,还是在同一栋大楼里,这些方法都可以帮助你变得更具战略性,更好地沟通,并建立高度一致、联系良好的团队,交付优秀的产品。

创建高绩效分布式团队的五个因素

你如何将复杂、遥远的系统,如我们广泛分布的人类系统,转变成团队?研究表明,团队高绩效有五个关键因素:

  • 心理安全。这意味着我们可以随意向他人表达我们与工作相关的想法和感受,并且我们相信,如果我们犯了一个善意的错误或寻求帮助,他们不会看轻我们。
  • 可靠性。这意味着我们的团队正在按时完成高质量的工作。
  • 结构&清晰。每个人都需要了解自己的角色、期望和计划。
  • 意。能够从事对我们有意义的工作。
  • 影响。我们在工作或产出中找到了目标感。

现在,关于高绩效团队的研究并没有说每个人都必须在同一个办公室,共用同一个冰箱(根据我对办公室冰箱的了解,这实际上可能是一件好事)。但是,当所有人都在同一个房间里时,高绩效团队使用的许多实践要容易得多。

团队成员需要自由地向他人表达与工作相关的想法和感受,并相信如果他们犯了善意的错误或寻求帮助,人们不会轻视他们。

如果构建团队整体上很难,那么构建分布式团队就更难了。通过渗透作用,或在字面上的水冷却器,有机发生的要少得多。当我们分布在不同的地点和时区时,所有团队面临的许多挑战都会加剧。建立和领导团队的常用方法就是不能简单应用。这意味着作为领导者,我们经常需要更有创造力。

领导高绩效团队的三个 c

创建一个高绩效的分布式团队从根本上讲是关于包容的:确保每个人都有平等的贡献机会,方法是创造公平的竞争环境,消除障碍。与此同时,我认为不可能为每个人复制完全相同的工作体验,我也不认为这应该是我们的目标。相反,我们应该确保每个人都能够以有意义的方式做出贡献,并尽自己最大的努力。

那么作为一个分布式团队的领导者意味着什么呢?我们创建结构来帮助人们一起工作,这样每个人都可以做到最好。我们为他人提供依靠和依赖的基石。

作为工程领导者,我们就像砖块之间的砂浆:我们连接结构、团队和人。

我们把他们团结在一起。作为领导者,我们的工作不应该是关于我们自己的。这是关于我们支持的人和团队。我们建立结构来帮助他人发光。

在我到目前为止的工作中,我一直在使用不同的方法来实现这一点,专注于构建结构和支持团队中的联系、沟通和协作。让我们来看看你如何利用这些来帮助你的团队提高。

联系:有目的地一起工作

作为人类,我们努力与更大的目标联系在一起。但我们也寻求与周围的人建立联系。当我们和队友在同一个位置时,我们会接收到关于他们的小信号。当他们早上走进办公室时,我们看到他们的肢体语言。我们知道人们是紧张还是放松,或者可能会意识到他们比平时喝了更多的咖啡。

用小小的行动打开联系之门。微小的信号在远处消失了,这意味着其他事情变得更加重要,比如理解人们不同的能量水平。当我在旧金山的同事开始他们一天的工作时,他们精力充沛,非常兴奋。但对我来说,柏林是下午 5 点或 6 点,如果是冬天,外面可能已经黑了。有些日子我很累,无法集中注意力,但我仍然希望能为我的团队服务。在这种情况下,如果我们在谈话中对对方的精神状态和我们在工作日的位置都很敏感,会有所帮助。

为人类腾出空间。当我在 2018 年加入 CircleCI 时,我与我们工程组织中的所有人见了面,因为我想了解与我们一起工作的人。在一次谈话中,我们的一位工程师对我说,“我需要时不时地亲自见见我的团队,提醒自己他们是真实的人。”这种情绪引起了我的共鸣。

当我们在视频通话中以像素的形式或在聊天应用程序中以图标的形式与人互动时,很容易忘记屏幕的另一端也有人。

当我们以彼此的人性为中心时,我们超越了队友的专业领域。我们需要对他们是什么样的人感到好奇,并知道他们的动力和动机是什么。

沟通:创造明确的期望

定义期望,并揭示什么是重要的。作为领导,我们有责任确保团队中的每个人都清楚我们的期望。这就是为什么每周一对一的会议是我们沟通的支柱,以及我们与队友关系的支柱的众多原因之一。

当您的组织处于高速增长模式时尤其如此。当我在 2018 年加入 CircleCI 时,工程团队的年增长率为 50%,地理分布也在增加。相反,管理团队小得令人难以置信。在所有这些增长之后,我们在发展我们的工程文化方面遇到了挑战。我们团队中有很多知识仓库。但与此同时,我们也有热衷于发展的人。我们需要的是为所有这些热情建立一条途径,让我们能够扩大业务规模。

我们在 CircleCI 做的事情之一是创建一个工程能力矩阵,并将其传达给我们的团队。这是我们工程师的职业发展框架。这个矩阵现在已经融入到我们做的每一件事情中,从招聘到结构化反馈,再到绩效评估。这有助于我们让每个人都遵循相同的标准,并在我们扩展时阐明期望。

一旦我们设定了对增长和绩效的期望,接下来我们需要做的就是传达这些标准并要求彼此遵守。表扬是一种非常好的方式,可以让团队联系和交流。在我们公司 Slack,我们有一个名为#感恩的频道,在这个频道里,人们分享他们对帮助过他们的人的感激。它让我们看到小小的善举如何帮助我们作为一个公司联系在一起。

一起成长。作为一个团队,更好的联系和沟通方式是通过建设性的批评帮助彼此成长。在分布式团队中,这可能很难做好。当我们主要在屏幕上看到对方时,很难进行更艰难的对话。

Feeedback-template.png

我有一个从 M. Tamra Chandler 的《反馈和其他脏话》一书中摘抄的模板,我经常用它作为起点。我将要求我的团队以这种形式讨论我们的反馈偏好,然后分组讨论反馈。我们利用学到的知识给彼此提供更有意义、更具体的反馈。

当我们为彼此的人性腾出空间时,我们澄清了期望,我们帮助彼此成长。我们与队友建立信任,这为我们的团队合作提供了基础。

协作:为他人创造空间

我们在很大程度上用自己的思维模式来塑造协作。例如,我们需要经常思考团队和组织中的权力动态。我们中的许多人通过正式或非正式的领导角色、领域专长、资历或任期,在团队中拥有显性或隐性的权力。但是我们所有人都有偏见和盲点。当我们带着谦逊和对权力动态的认识来对待这些偏见时,我们可以为更好的合作做好准备。

保持谦逊,为他人腾出空间。正如我们试图用许多小方法建立联系一样,也有一些小方法可以让我们在整个工作日成为更好的合作者。例如,我们可以避免主导对话,即使是书面交流。想一想当你给队友发送大量文本时,或者当你将文档隐藏在评论中时。当我们在开会时,或者当我们在聊天中讨论时,我们需要问自己,“我现在占用了多少空间?”我们有责任为他人打开空间,邀请他们加入对话,问他们:“你对此有什么看法?我想听听你们的想法。”

建立基础关系。协作是分布式团队建立关系的最佳工具之一。作为领导者,关系是我们能够做好工作的基础。但是关系对我们团队中的每个人都同样重要,因为协作不仅仅是工作的产出。我们的工程团队经常使用结对编程技术,这有助于他们加强与队友的关系,并使团队更有弹性。结对编程还有助于我们避免知识孤岛和分发信息——对于新队友的入职来说,这是一个非常好的工具。

打击英雄文化,解决结构性问题。“英雄文化”描述了一种文化,在这种文化中,组织奖励那些非常有才华的人,他们在巨大的压力下,坚持不懈地付出额外的努力,独自完成看似不可能的任务。许多公司通过提升、增加影响力、薪酬或知名度来奖励这种英雄行为。英雄文化在我们的行业中仍然非常普遍,我已经看到它对团队和组织产生了灾难性的影响。

英雄文化在分布式团队中更为普遍,因为与其他工作相比,这种类型的工作具有不成比例的高可见性:“英雄任务”,例如独自解决一个通常不会工作的问题,通常具有高可见性,而围绕维持团队和在后台支持队友的艰苦工作通常更不受注意。

英雄文化不仅会削弱团队,还会给英雄施加压力,让他们成为唯一能解决问题的人,从而导致失败。

英雄文化是组织失败的结果,很难对抗,但作为领导者,我们有责任识别并修复它,让受其影响的人过得更好。作为领导者,我们有责任通过确保我们建立的支持结构有助于避免或至少减少这种英雄行为的必要性,来打击英雄文化。我们还需要留意我们鼓励、奖励和要求人们做出的行为,即使是暗示或无意的。

不断建设我们的团队

这里还有一个“C”支柱值得注意:连续性。建立优秀的团队需要做大量的工作,而且这个过程永远不会结束。我们都在不断成长和变化,我们工作的组织也在不断发展,充满了惊喜。这是我如此喜欢与人共事的主要原因之一。

组建团队是每天持续的过程。无论你处于团队发展的哪个阶段——也无论你最亲密的队友是坐在桌子对面还是隔着一个大洋——都要关注强有力的联系、有意的合作以及与队友的有意识的交流。每天继续建立你的团队,一次一个像素。

金丝雀与蓝绿色部署来减少企业停机时间| CircleCI

原文:https://circleci.com/blog/canary-vs-blue-green-downtime/

即使在云出现之前,也没有人喜欢部署停机。由于应用程序托管在限制本地用户访问的传统数据中心,许多组织将部署安排在用户不太可能使用应用程序的时间,如午夜。随着基于云的全天候环境在所有时区的广泛采用,一天中的每个小时都可以使用,易于查找的部署窗口不复存在。每个人都希望他们的应用程序对所有潜在用户始终可用。

https://www.youtube.com/embed/3Wj5yJ5bHKU

视频

过去,开发人员在部署变更和更新时会使应用程序离线,从而导致停机。现在,持续集成和持续部署(CI/CD)管道实现了应用构建、测试和部署的自动化。CI/CD 管道尽可能保持环境正常运行,并加快部署过程。部署应用程序或更新环境仍然会导致停机和其他问题。

在本文中,我们将探讨两种可以几乎消除停机时间的常见部署技术:蓝绿色部署配置和金丝雀部署配置。我们还将了解选择其中一个的一些权衡、要求和优势。

无停机部署

对于大多数企业,尤其是那些具有现代应用程序架构和已建立的 CI/CD 管道的企业,目标是随时部署应用程序和功能,而不会对最终用户产生明显影响。这需要快速、安全地将更改部署到生产环境中,而不需要:

  • 中断可能正在执行关键任务的用户
  • 依靠您的系统完成关键任务流程

您的应用和部署架构在减少部署停机时间方面发挥着关键作用。一般来说,您的环境应该满足 canary 和 blue-green 部署方法的以下要求:

  • 可以构建、测试和部署到特定环境的部署管道
  • 分布在负载平衡器后面的多个应用程序节点或容器
  • 无状态的应用程序,允许集群中的任何节点随时为请求提供服务

当您对应用程序进行更改时,它们应该不会破坏您的数据层。这意味着您应该使数据集中的列可选或可为空。不要为了不同的目的而重命名或重用列。在您的数据层中采用这种非破坏性的方法使您能够在出错时回滚更改或恢复特性。

考虑到这些要求,让我们深入了解第一个零停机部署选项:蓝绿色部署。

什么是蓝绿部署?

蓝绿色部署是我们正在考虑的两个选项中更常见的一个,它将您的应用程序环境分成两个资源相等的部分,蓝色和绿色。您在环境的一半(蓝色)上为当前应用程序提供服务,使用负载平衡器来引导流量。然后,您可以将新应用程序部署到环境的另一半(绿色),而不会影响蓝色环境。

在您测试和部署到绿色环境的同时,使用您的负载平衡器来引导流量,保持您的蓝色环境为生产用户无缝运行。当您的部署和测试成功时,您可以切换您的负载平衡器以实现绿色环境,而不会对您的用户产生任何影响。

以这种方式设置应用程序环境还有许多其他好处。例如,如果您的应用程序负载很重,绿色环境还可以充当即时热备用,甚至可以作为灾难恢复活动的一部分。

什么是金丝雀部署?

金丝雀部署的工作方式与蓝绿色部署类似,但使用的方法略有不同。canary 部署不是在部署完成后等待切换另一个完整的环境,而是先切换一小部分服务器或节点,然后再完成其他部分。

有许多方法可以为 canary 部署配置您的环境。最简单的方法是像往常一样在负载平衡器后面设置您的环境,但是保留一个或两个额外的节点或服务器(取决于应用程序的大小)作为未使用的备用节点。这个备用节点或服务器组是您的 CI/CD 管道的部署目标。一旦您构建、部署和测试了这个节点,您就可以在有限的时间内将它添加回您的负载均衡器,供有限的人员使用。这允许您在对集群中的其他节点重复该过程之前,确保更改成功。

配置 canary 部署的另一个选项是使用一种称为特性切换的开发模式。功能切换(有时称为功能标志)的工作方式是,将您的更改构建并部署到由打开这些更改的配置所控制的应用程序中。您可以从集群中取出一个节点,进行部署,然后再将它添加回去,但不需要通过负载平衡器进行任何测试或控制。然后,当所有节点都更新后,在向所有人推广该特性之前,您可以为许多用户打开该特性。

然而,这种方法的缺点是开发时间和修改应用程序以支持特性切换的成本。根据应用程序的年龄和大小,开发这个特性可能相当复杂或者几乎不可能。

蓝绿色部署与淡黄色部署

那么,蓝绿色和金丝雀这两种方法中,哪一种是零宕机的最佳部署方式呢?两者都是有效的策略,并且都需要一个相当相似的架构,但是有不同的特征。

如果您有能力运行两个完整的应用程序托管环境,并且您的应用程序很少以不向后兼容的方式进行更改,蓝绿色以最少的应用程序更改提供了最次要的好处。它实现了零停机环境,在发生性能问题或灾难恢复时,您也可以利用这一环境。

但是,如果您可以提供的额外资源有限,或者您的应用程序是模块化和配置驱动的,那么您可以使用 canary 部署选项。当您缺少一个额外的环境来处理其他事务时,您可以最大限度地减少运行和维护环境的开销。Canary deployment 还提供了一种更简单的方法,可以随时或根据任何标准启用和禁用功能。

然而,这两种方法都需要对应用程序和环境的架构进行一些预先规划和思考。

包裹

blue-green 和 canary 部署方法都有助于部署您的应用程序,不会给用户带来任何停机或中断。实现这个目标需要在您的环境和应用程序中启用特定的功能,尤其是负载平衡和无状态架构。

如果您的目标是零宕机,那么在更新和更改环境时,部署自动化和一致性也很重要。使用您的 CI/CD 管道来处理应用程序环境的自动化部署、测试和转换非常有帮助。

现在,您已经有了关于金丝雀和蓝绿色部署策略的更多信息,您可以选择最适合您的企业的策略,并且有望以零停机时间部署您的应用程序。您可以马上开始,今天就注册您的 CircleCI 免费试用

捕获失败的端到端测试的截图,这些测试在您的机器上通过,但在 CircleCI 上中断

原文:https://circleci.com/blog/capture-screenshots-of-flaky-end-to-end-tests/

如果你熟悉 Ruby on Rails,你就会知道它是一个内置了测试实践的 web 框架。在我工作的公司, BridgeU ,我们努力通过从单元特性测试的大量测试来尽可能地保护我们编写的特性。当正确设置时,测试套件会给你对系统的可靠信任感,并在你必须重构部分代码时提供指导——这是开发生命周期中不可避免的一部分。然而,建立一个全面而稳定的测试套件需要实践、经验和一点点尝试和错误。所有这些测试的目的是增加你对你的系统的信心,但是有一个问题会困扰这种安全感:古怪的测试。

背景

如果在您的持续集成 (CI)系统上一次又一次地通过测试,那么一切都很好。如果它总是失败,那也不错——您可以发现它,了解它来自哪里,并解决它。然而,如果你遇到一个有时失败的测试,那可能是一个真正的挫折来源。您发现自己在测试真正通过和代码投入使用之前,在 CI 系统上点击了几次 rebuild。这可能看起来无害,但是如果几个开发人员一天多次发布新东西,那么时间浪费就很大了。

前一段时间,我们最大的挑战是 thoughtbot 所谓的特性测试,这是一种验收测试。这些是围绕整个应用程序的高级场景,以确保每个部分都能协同工作。也许是这些测试中移动部分的绝对数量,或者也许是它们更高层次的关注使我们更难准确预测较低层次变化的后果。事实是,我们发现自己创建了一个电子表格来记录这些古怪的测试,所以我们可以知道它们有多频繁。

这些测试的不稳定性使他们很难找到可重复的案例。此外,我们得到的错误信息根本不够丰富。我们开始寻找保留更多故障数据的方法,这时候我们突然想到了这一点。如果发生时我们看不到测试失败,那就拍张照片吧!唯一要弄清楚的是在 CI 系统上的哪里以及如何存储这些照片——但是有一个 API 可以做到这一点: CircleCI Artifacts

归结起来就是:如果一个测试失败了,截取一个网页的截图并保存在某个地方——这样,我们可以在测试套件运行后查看失败的原因。有了所发生事情的图像,我们就可以知道从哪里开始寻找问题。从不再存在的简单按钮,到来自其他测试的后台 JavaScript 请求,以及对当前正在运行的测试的干扰,我们能够一个接一个地修复这些脆弱的测试。在本文中,我将向您展示如何用 CircleCI 设置这个截图收集过程。

示例应用程序

让我们设置一个 Rails 应用程序来看看它的运行情况。我们将使用有意义的默认值来加快设置过程。如果您已经有了一个想要设置屏幕截图收集的应用程序,您可以直接跳到“保存屏幕截图”部分。否则,请继续阅读。

我们假设你的机器上安装了 Ruby on Rails,但是如果你没有,有几个教程(比如这个,和这个)可以带你去那里。现在已经安装了 Rails,您可以运行下面的代码来获得一个简单的应用程序来进行测试:

rails new circle_see_eye --skip-action-mailer --skip-active-storage --skip-javascript --skip-spring 

如果你在没有旗帜的情况下运行这个,我建议你去喝杯咖啡——这将需要一段时间。这些可选参数将跳过生产应用程序中可能需要的组件,但这些组件不是本演示所必需的。如果你对它们感兴趣,你可以通过运行 rails new-help 来检查所有可用的标志。

让我们确保设置好数据库,因为 Rails 自带 SQLite 支持:

cd circle_see_eye
bin/rails db:migrate 

注意:对我来说,设置 Rails 5 应用程序揭示了 sqlite3 上的一个小问题,在这里阅读更多。

应用程序设置的最后一步是确保一切按预期运行。进入刚刚创建的项目文件夹,运行您的服务器:bin/rails 服务器

你可以在http://localhost:3000看到你的新 Rails 应用!🙌

写一个测试

目前,我们没有测试。因为本文的目的是获得失败测试的可视化,所以让我们创建一个。如果你已经有了想看一眼的测试,直接跳到“保存截图”部分。

Rails 是围绕资源的概念构建的,因此对于这个应用程序,我们将使用 CircleCI: builds 上熟悉的东西。让我们创建一个 Builds 控制器,我们可以在其中设置一个#index 操作——只是一个供我们加载的默认页面。

bin/rails generate controller Builds index --no-test-framework 

检查 Rails routes 会显示我们现在访问的页面及其路径:

bin/rails routes 

要快速加载我们创建的页面,只需使用 bin/rails server 再次运行您的服务器,并转到我们上面看到的路径,http://localhost:3000/builds/index

我们现在准备为我们的特性编写一个测试!然而,这就是事情分裂的地方。

系统测试与集成测试

在其 5.1 版本中,Rails 引入了系统测试的概念,为用户交互测试带来了几个有用的默认值。我们不会详细讨论这个版本之前和之后的验收测试,但是让我们只说系统测试和它们的默认值,框架为你做了大部分的工作。从版本 5.1 开始,失败的系统测试的屏幕截图是开箱即用的,所以如果你有系统测试,你真幸运!然而,如果像我们一样,你不…有更多的设置要做。

启用系统测试?

如果你的应用有系统测试,做得好!这使得设置测试变得更加容易。让我们再次运行我们神奇的 Rails 助手之一:

bin/rails generate system_test builds 

测试还没有做任何事情,它的所有代码都被注释掉了。让我们把它改成简单但真实的东西:

require "application_system_test_case"

class BuildsTest < ApplicationSystemTestCase
  test "visiting the index" do
    visit builds_index_url

    assert_selector "h1", text: "Builds"
  end
end 

您可以使用 bin/rails test:system 运行测试套件(目前只有这个测试)。既然我们已经看到它运行并通过,让我们让它失败。

改变…

 assert_selector "h1", text: "Builds" 

…到…

 assert_selector "h1", text: "Huzzah!" 

再次运行(bin/rails test:system)并等待它失败。我们现在建立了正确的条件,让我们的截图进入 CircleCI。接下来,让我们把你的项目放到 CircleCI 上。

没有系统测试?

如果您有一个在 Rails 5.1 之前启动的应用程序,很可能您没有进行系统测试,但是进行了某种端到端测试,可能由 Selenium 提供支持。如果你这样做了,很好,你可以跳到下一节“把你的项目放到 CircleCI 上”。如果你没有,让我们确保我们有一个案例运行。

让我们从为这个过程安装重要的宝石开始。打开您的 Gemfile 并添加:

group :test do
  gem 'activesupport-testing-metadata'
  gem 'capybara'
  gem 'webdrivers', '~> 4.0', require: false
end 

activesupport-testing-metadata将允许我们在 JavaScript 支持下标记要运行的测试(这样我们就可以截图),水豚将使用硒来控制这些测试,而网络驱动程序是为测试提供真实浏览器的一种更新方式。

有了这些新的宝石,我们可以为这些类型的测试设置一个新的助手,让我们称之为test/integration_test_helper.rb:

require 'test_helper'

require 'active_support/testing/metadata'
require 'capybara/minitest'
require 'capybara/rails'
require 'webdrivers/geckodriver'

class AcceptanceTest < ActionDispatch::IntegrationTest
  include Capybara::DSL
  include Capybara::Minitest::Assertions

  setup do
    if metadata[:js]
      Capybara.current_driver = :selenium_headless
    end
  end

  teardown do
    if metadata[:js]
      Capybara.use_default_driver
    end
  end
end 

简单地说,这个文件是:

  • 基于 Rails 的ActionDispatch::IntegrationTest创建一个新的测试类(AcceptanceTest)
  • 包括有用的水豚助手
  • 为此类连接两个挂钩,setupteardown。他们将负责检查测试属性,并在需要时调用 JavaScript 驱动程序

有了我们的验收测试类,我们将创建一个使用它的测试,test/integration/builds_test.rb:

require 'integration_test_helper'

class BuildsTest < AcceptanceTest
  test "can see the builds page", js: true do
    visit builds_index_path

    assert has_content? "Builds#cats"
  end
end 

注意我们故意引入的错误,看看测试失败:#index → #cats。现在通过执行bin/rails test来运行这个测试,并观察它的失败。

我保证这是测试设置的最后一步。为了在同一级别的系统测试中直观地记录失败的测试,我们还需要一个东西:保存一个截图。

我们在应用程序中设置元数据 gem 的主要原因有两个:

  • 为了保存截图,我们需要一个可以运行 JavaScript 的驱动程序,所以我们需要标记哪些测试需要不同的驱动程序;
  • 我们可能会在一个浏览器上运行每个测试,但由于启动这些浏览器比使用 Capybara 的默认驱动程序 ( rack_test)需要更长的时间,所以根据具体情况战略性地使用另一个驱动程序是一个好的举措。

现在还有最后一步,允许我们新的AcceptanceTest类记录失败测试的截图。让我们编辑我们的集成测试助手,如下所示:

require 'test_helper'

require 'active_support/testing/metadata'
require 'capybara/minitest'
require 'capybara/rails'
require 'webdrivers/geckodriver'

class AcceptanceTest < ActionDispatch::IntegrationTest
  include Capybara::DSL
  include Capybara::Minitest::Assertions

  setup do
    if metadata[:js]
      Capybara.current_driver = :selenium_headless
    end
  end

  teardown do
    if metadata[:js]
      save_timestamped_screenshot(Capybara.page) unless passed?

      Capybara.use_default_driver
    end
  end

  private

  def save_timestamped_screenshot(page)
    timestamp = Time.zone.now.strftime("%Y_%m_%d-%H_%M_%S")
    filename = "#{method_name}-#{timestamp}.png"
    screenshot_path = Rails.root.join("tmp", "screenshots", filename)

    page.save_screenshot(screenshot_path)
  end
end 

我们添加的方法将获得当前时间戳,为我们将要保存的图像创建一个路径,并将当前页面的截图保存到该路径中(在tmp/screenshots/内的某个位置)。这将只针对标有js: true且未通过的测试运行。我们现在正处于一个很好的时机,可以在 CircleCI 上保存这些截图,以供以后分析。

把你的项目放到 CircleCI 上

在 CircleCI 上看到任何东西之前,我们还需要一样东西:CircleCI 配置。您可以前往文档阅读更多相关内容,但 CircleCI 上有一个有用的总结,告诉您如何在添加项目后立即完成:

为了简单起见,我们从 GitHub 仓库添加我们的项目。只需点击 Set Up Project,您将看到一个简单的操作说明和一个要添加的示例 CircleCI 配置文件:

好了,让我们按照说明(添加配置文件,推到 GitHub 并按下开始构建)看看我们得到了什么。不要忘记将 gem 文件中的 Ruby 版本与.circleci/config.yml上的版本进行匹配。Rails 文件的最小功能配置如下所示,例如:

version: 2.1
orbs:
  ruby: circleci/ruby@0.1.2

jobs:
  build:
    docker:
      - image: circleci/ruby:2.5.3-stretch-node-browsers
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: bundle
      - run:
          name: Database setup
          command: bin/rails db:migrate
      - run:
          name: Run tests
          command: bin/rails test 

第一次构建可能需要一段时间,因为还没有为它缓存任何东西,并且需要获取和安装所有的 gem。但是现在我们已经有了一个工作管道,让我们建立一个测试并捕捉它的失败。

保存截图

现在我们已经准备好了一切,我们的应用程序已经有了可视化测试,让我们告诉 CircleCI 保存它们以供以后分析。记住困难的工作已经完成:您的应用程序截取失败测试的截图并保存这些图片。如果你有这个,现在在 CircleCI 上保存它们就很简单了。

你可以在文档中读到更多关于它的内容,但是关键是工件的概念——它们在工作完成后仍然存在。考虑到这一点,我们需要在我们的.circleci/config.yml文件中描述我们希望保存什么工件。

打开 CircleCI 配置文件,将以下内容作为最后一项添加到 steps 键中:

 - store_artifacts:
          path: tmp/screenshots 

从现在开始,每当你的测试套件在 CircleCI 上运行并且一个可视化测试失败时,Rails 将会截取一个失败的截图,CircleCI 将会保存它以备将来调查。以下是您将在界面中看到的内容,以及我们的屏幕截图:

结论

古怪的测试令人讨厌,但是有一些工具可以帮助你摆脱它们。有了正确的心态,他们会教你避免有问题的情况,并帮助你建立更健壮的测试用例场景。在 BridgeU,对这些问题进行截图有助于我们识别某些测试相互干扰的情况,并且我们的测试实践也因此得到了改进。

此外,为什么只在测试失败时截图?也许您可以使用这种技术来自动抓取产品的更新快照,并帮助您保持文档或营销材料的更新。你的想象力是这项技术带来的所有自动化可能性的极限!


贡萨洛·莫莱斯是一名计算机工程师,对网络情有独钟。他目前正在帮助 BridgeU 的学生选择更好的职业,由 Rails 和 JavaScript 提供支持。Gonç alo 是 Recurse 中心的校友,偶尔也是 ultrarunner 和 boulderer。

阅读更多贡萨洛·莫莱斯的文章

自动将 Angular 应用部署到 Firebase | CircleCI

原文:https://circleci.com/blog/cd-angular-firebase/

本教程涵盖:

  1. 克隆示例应用程序
  2. 在 Firebase 上创建和设置项目
  3. 初始化和部署项目

开发人员使用 Angular、React 和 Vue.js 等 JavaScript 框架构建各种各样的单页面应用程序,从简单到复杂。通过分离 JavaScript 和 CSS,框架让开发团队以模块化的代码块来构建应用程序,实现单一的功能。

这很好,但是一旦您的应用程序准备好部署到生产环境中,您将需要一个命令来编译并将独立的文件捆绑成一个文件。然后,您将需要部署到一个平台,如 Firebase 托管。

Firebase 是 Google 开发的一个开发平台,提供文件存储、托管、数据库、认证和分析。Firebase 是免费的,默认提供 SSL 证书,并在多个地区提供令人印象深刻的速度。

在本教程中,我将向您展示如何为一个 Angular 应用程序设置持续部署到 Firebase 主机。

先决条件

对于本教程,您需要:

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

克隆演示项目

要开始,请运行以下命令:

git clone https://github.com/CIRCLECI-GWP/circleci-angular-demo.git angular-ci-firebase 

Git 会将演示应用程序克隆到一个名为angular-ci-firebase的新文件夹中。进入新克隆的应用程序并安装其所有依赖项:

// Change directory
cd angular-ci-firebase

// Install dependencies
npm install

// Run the application
ng serve 

转到http://localhost:4200打开应用程序。

View demo app

这个应用程序从测试用的免费 Rest API 中检索虚拟用户列表。

在本地运行测试

使用以下命令运行应用程序测试:

npm run test 

下面的输出将是这样的:

> circleci-angular-demo@0.0.0 test
> ng test --no-watch --no-progress --browsers=ChromeHeadless

06 02 2022 10:10:27.402:INFO [karma-server]: Karma v6.3.9 server started at http://localhost:9876/
06 02 2022 10:10:27.406:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
06 02 2022 10:10:30.530:INFO [launcher]: Starting browser ChromeHeadless
06 02 2022 10:10:37.570:INFO [Chrome Headless 97.0.4692.99 (Mac OS 10.15.7)]: Connected on socket njOp1_5ETVTaRVo-AAAB with id 68498281
Chrome Headless 97.0.4692.99 (Mac OS 10.15.7): Executed 4 of 4 SUCCESS (0.001 secs / 0.055 secs)
TOTAL: 4 SUCCESS 

演示应用程序现在已经在本地设置好了,并且运行良好。

在 Firebase 上创建项目

如果你还没有这样做,打开一个 Firebase 账户,导航到 Firebase 主页,点击创建一个项目

Firebase Console page

接下来,使用以下步骤:

  1. 点击添加项目按钮。
  2. 输入项目的名称。我给我的取名为angular-ci-project。请记住,项目 id 在 Firebase 中是唯一的。
  3. 点击继续
  4. 禁用谷歌分析;这个项目不需要它。
  5. 再次点击继续

Firebase app setup

这就是了。您已经成功地在 Firebase 上创建了一个项目。

设置 Firebase 托管

为了在 Firebase 上成功地托管您的应用程序,您需要安装它的工具,并在您的项目中初始化它。

安装 Firebase CLI

打开新的终端。要全局安装 Firebase 工具,请运行以下命令:

npm install -g firebase-tools 

您现在可以全局访问 Firebase 命令行界面工具。您可以使用它们将代码和资产部署到新创建的 Firebase 项目中。

初始化 Firebase 项目

从终端登录到您的 Firebase 帐户:

firebase login 

Firebase, logged in

接下来,初始化项目:

firebase init 

系统会提示您回答一些问题。

  • 选择托管:为 Firebase 托管配置文件,并(可选)设置 GitHub 操作部署。
  • 使用现有项目:选择您之前创建的 Firebase 项目:angular-ci-project
  • 输入dist/angular-ci-firebase作为公共目录。注意angular-ci-firebase是项目文件夹的名称。如果您的不同,请更改此内容。
  • 配置为单页应用程序:是。
  • 使用 GitHub: No 设置自动构建和部署。对于本教程,我们使用 CircleCI 来运行测试和处理部署。

下面是步骤的输出和截图。

 ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /Users/yemiwebby/tutorial/circleci/angular/angular-ci-firebase

? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. Hosting: Configure files for Firebase Hosting and (optionally)
 set up GitHub Action deploys

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

? Please select an option: Use an existing project
? Select a default Firebase project for this directory: angular-ci-project (angular-ci-project)
i  Using project angular-ci-project (angular-ci-project)

=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? dist/angular-ci-firebase
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
? Set up automatic builds and deploys with GitHub? No
✔  Wrote dist/angular-ci-firebase/index.html

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete! 

Firebase initialized

项目的初始化过程还在项目的根目录下生成了两个唯一的文件。这些文件是成功部署所必需的,并且必须签入源代码管理。它们是:

  • firebase.json包含您项目的托管配置。它的内容指示 Firebase CLI 上传和部署项目目录中的文件。
  • .firebaserc指定成功部署到 Firebase 后,连接到上传代码的项目。

更改输出路径

一旦您准备好构建您的生产应用程序,Angular CLI 将把它编译成在angular.json文件中指定的输出路径。打开这个文件,确保outputPath指向dist/angular-ci-firebase:

 "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/angular-ci-firebase",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "assets": ["src/favicon.ico", "src/assets"],
            "styles": [
              "./node_modules/bootstrap/dist/css/bootstrap.css",
              "src/styles.css"
            ],
            "scripts": []
          }, 

配置 CircleCI

接下来,将.circleci/config.yml的内容替换为:

version: 2.1
orbs:
  browser-tools: circleci/browser-tools@1.2.3
jobs:
  build:
    working_directory: ~/ng-project
    docker:
      - image: cimg/node:16.13.1-browsers
    steps:
      - browser-tools/install-chrome
      - browser-tools/install-chromedriver
      - run:
          command: |
            google-chrome --version
            chromedriver --version
          name: Check install
      - checkout
      - restore_cache:
          key: ng-project-{{ .Branch }}-{{ checksum "package-lock.json" }}
      - run: npm install
      - run: npm install --save-dev firebase-tools
      - save_cache:
          key: ng-project-{{ .Branch }}-{{ checksum "package-lock.json" }}
          paths:
            - "node_modules"
      - run:
          name: "Run test"
          command: npm run test
      - run:
          name: "Build application for production"
          command: npm run build
      - run:
          name: "Deploy app to Firebase Hosting"
          command: "./node_modules/.bin/firebase deploy --token=$FIREBASE_TOKEN" 

这个配置指定了在 CircleCI 上安装和运行应用程序测试所需的所有工具。config 用npm install --save-dev firebase-tools安装了 Firebase 工具,并设置了一个命令,一旦测试成功,就自动将应用程序部署到 Firebase。

部署将需要FIREBASE_TOKEN。由于您是从终端登录的,因此可以使用 Firebase CLI 轻松创建令牌。输入以下命令:

firebase login:ci 

这将打开一个浏览器,您可以在其中验证您的帐户。然后,令牌会打印在您的终端上。

Generate the Firebase token

把这个复制下来,保存在方便的地方。你以后会需要它的。

下一步是在 GitHub 上建立一个资源库,并将项目链接到 CircleCI。查看将项目推送到 GitHub 以获取指示。

登录您的 CircleCI 帐户。如果你注册了你的 GitHub 账户,你所有的库都可以在你项目的仪表盘上看到。

在您的angular-ci-firebase项目旁边,点击设置项目

Set up project

系统将提示您编写新的配置文件或使用现有的配置文件。选择现有的分支,并输入您的代码在 GitHub 上所在的分支的名称。点击走吧

Configuration File

您的第一个工作流将开始运行,但是不出所料,构建将会失败,因为您还没有创建FIREBASE_TOKEN环境变量。

要解决这个问题,您需要添加FIREBASE_TOKEN作为环境变量。点击项目设置

Project settings

点击左侧工具条上的环境变量并创建该变量:

  • FIREBASE_TOKEN是您之前从终端生成的令牌的值。

Add Firebase token

回到仪表板。点击从失败的重新运行工作流程。

Rerun workflow

您的构建应该会成功完成!

Hosted successfully

导航到上一步中显示的托管 URL。对我来说网址是:https://angular-ci-project-4c4cf.web.app

Live Firebase app

结论

您已经到达了本 Angular to Firebase 部署教程的末尾。在系统上本地安装 Firebase 工具后,您可以直接部署到 Firebase,但是这违背了持续集成和部署的目的。理想的方法是使用 CircleCI 这样的平台运行测试,一旦测试通过,立即部署到您的主机提供商,在这种情况下是 Firebase。

利用您在本教程中学到的知识,您可以在每次更改代码库时轻松地运行测试和部署应用程序。

我希望这对你有所帮助。本教程的完整源代码可以在 GitHub 上找到


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他解决问题的技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。由于精通技术,他的爱好包括尝试新的编程语言和框架。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

将 Node.js 持续部署到 Azure VM | CircleCI

原文:https://circleci.com/blog/cd-azure-vm/

虚拟机为托管 web 应用程序提供了极大的灵活性。作为开发人员或工程师,您可以配置和控制应用程序运行所需的每个软件和每个设置。 Azure ,最大的云托管平台之一,为基于 Linux 和 Windows 的操作系统提供虚拟机产品。在本教程中,您将学习如何设置一个连续部署管道来将 Node.js 应用程序部署到 Azure 虚拟机。

先决条件

要跟进这篇文章,需要做一些事情:

  1. 您系统上安装的 Node.js (版本> = 10.3)
  2. 一个蓝色的账户
  3. 一个的账户
  4. GitHub 的一个账户
  5. 安装在您系统上的 Azure CLI

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

安装并设置好所有这些之后,您就可以开始本教程了。

克隆 Node.js 项目

首先,您需要克隆将要部署到 Azure VM 的项目。这个项目是一个基本的 Node.js API,它有一个端点用于返回一组todo对象。转到您想要存储项目的位置并克隆它:

git clone --single-branch --branch base-project https://github.com/CIRCLECI-GWP/cd-node-azure-vm.git 

项目克隆完成后,转到项目的根目录并安装依赖项:

cd cd-node-azure-vm
npm install 

使用npm run dev命令运行应用程序。应用程序将在地址http://localhost:3000启动。当应用程序启动并运行时,在浏览器中输入http://localhost:3000/todos以查看todos列表。

Todos endpoint - Node app

现在,转到项目的package.json文件,将这些脚本添加到scripts部分:

"scripts": {
    .....,
    "stop": "pm2 kill",
    "start": "pm2 start server.js"
} 

startstop脚本将使用 pm2 进程管理器来启动和停止虚拟机上的 Node.js 应用程序。当pm2脚本设置好后,它将被全局安装在虚拟机上。

在项目的根目录下,运行rm -rf .git命令来删除任何.git历史。然后把项目推给 GitHub 。确保这是连接到您的 CircleCI 帐户的 GitHub 帐户。

在 Azure 上设置虚拟机以运行 Node.js

接下来,在 Azure 上创建一个新的 VM,并设置它的环境来托管 Node.js 应用程序。这些是步骤:

  1. 创建新的虚拟机实例
  2. 安装〔t0〕engine〔t1〕
  3. nginx配置为代理服务器。将虚拟机上端口80的所有流量路由到端口3000上正在运行的 Node.js 应用程序实例
  4. 在虚拟机上安装 Node.js,并将应用程序从 GitHub repo 克隆到虚拟机的一个文件夹中
  5. 全局安装pm2

不要被这些步骤的复杂性吓倒!您可以用一个命令完成这五项任务。在项目的根目录下,创建一个名为cloud-init-github.txt的新文件;这是一个云初始化文件。Cloud-init 是云实例初始化的行业标准方法。

在云初始化文件中,输入:

#cloud-config
package_upgrade: true
packages:
  - nginx
write_files:
  - owner: www-data:www-data
    path: /etc/nginx/sites-available/default
    content: |
      server {
        listen 80;
        location / {
          proxy_pass http://localhost:3000;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection keep-alive;
          proxy_set_header X-Forwarded-For $remote_addr;
          proxy_set_header Host $host;
          proxy_cache_bypass $http_upgrade;
        }
      }
runcmd:
  # install Node.js
  - 'curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -'
  - 'sudo apt-get install -y nodejs'
  # clone GitHub Repo into myapp directory
  - 'cd /home/azureuser'
  - git clone "https://github.com/CIRCLECI-GWP/cd-node-azure-vm" myapp
  # Install pm2
  - 'sudo npm install pm2 -g'
  # restart NGINX
  - service nginx restart 

我前面描述的所有五个步骤都包含在这个文件中。确保在git clone命令中用您的存储库的名称替换示例 GitHub repo。注意,您正在主目录/home/azureuser下的myapp文件夹中克隆项目。

接下来,使用上面文件中的配置在 Azure 上创建一个新的 VM 实例。确保您在 CLI 上登录到 Azure(运行az login登录):

首先,您需要一个资源组。运行以下命令创建一个:

az group create --name Demos-Group --location eastus 

注意: 您不必为此创建新的资源组。如果您愿意,可以使用现有的。

接下来,运行以下命令创建虚拟机实例:

az vm create --resource-group Demos-Group --name node-vm --image UbuntuLTS --admin-username azureuser --custom-data cloud-init-github.txt --generate-ssh-keys 

记下命令中每个参数集的值:

  • Demos-Group是您正在其中创建虚拟机的 Azure 资源组
  • node-vm是您的虚拟机的名称
  • eastus是您正在创建虚拟机的区域
  • UbuntuLTS是虚拟机操作系统
  • azureuser是虚拟机的管理员用户;它将用于通过 SSH 连接到虚拟机
  • cloud-init-github.txt是你刚刚写的云 VM 配置文件

当命令运行完毕时,一个响应对象被打印到屏幕上。确保保存了对象的privateIpAddress属性。privateIpAddress是您将用于在浏览器中通过ssh访问您的应用程序的 IP 地址。

转到 Azure 门户中的资源页面。点击虚拟机查看您的虚拟机实例。

VM - Azure

默认情况下,虚拟机上的 Web 端口80未打开。您需要显式打开此端口,以允许 web 请求到达服务器。要打开端口80,运行:

az vm open-port --port 80 --resource-group Demos-Group --name node-vm 

请注意,虚拟机名称和资源组是在该命令中传递的。当这个命令完成后,一大块json就会打印在您的 CLI 上。我们在教程中不会用到它,所以可以忽略它。

在服务器上生成 SSH 密钥

您的下一步是在服务器上生成ssh密钥,以允许连续部署管道脚本访问 VM。

SSH 到您的服务器,确保用设置 VM 时返回的内容替换YOUR-PUBLIC-IP-ADDRESS:

ssh azureuser@YOUR-PUBLIC-IP-ADDRESS 

这个命令让您进入主目录。通过运行cd .ssh转到.ssh文件夹。生成 ssh 密钥:

ssh-keygen 

键输入接受默认位置,文件名为id_rsa。CircleCI 需要一个空密码进行访问,因此按两次 Enter 以响应passphraseconfirm passphrase提示。

接下来,通过运行以下命令将公钥的内容附加到authorized_keys文件中:

cat id_rsa.pub >> authorized_keys 

然后,打印出私钥的内容:

cat id_rsa 

复制这些信息并保存在安全的地方。稍后您将把它添加到 CircleCI。

向虚拟机用户分配权限

在设置过程中克隆项目文件夹时,root用户创建了myapp文件夹。这意味着您的虚拟机管理员用户azureuser不能更新myapp文件夹的内容。你需要给你的虚拟机的管理员用户更新myapp文件夹的权限。

首先,确保您已经从.ssh文件夹移回到包含myapp的主目录。使用命令将azureuser更改为默认组root:

sudo usermod -g root azureuser 

接下来,您需要将myapp中所有文件的所有者和组更改为azureuserroot。使用命令:

sudo chown -R azureuser:root myapp 

现在,如果您运行ls -l myapp,您将看到输出表明azureuser拥有myapp及其内容的所有权。

User permissions - VM

您现在可以为ownergroup设置所有文件夹和文件的读/写/执行权限,而为public设置任何权限。输入:

sudo chmod -R 770 myapp 

配置部署管道

在项目的根目录下,创建一个文件夹,并将其命名为.circleci。在该文件夹中,创建一个名为config.yml的文件。在config.yml里面,输入这个代码:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:18.10.0
    steps:
      - checkout
      - add_ssh_keys:
          fingerprints:
            - $AZURE_VM_SSH_FINGERPRINT
      - run:
          name: Copy updated files to VM
          command: scp -o StrictHostKeyChecking=no -r ./* $AZURE_VM_USER@$AZURE_VM_IP:~/myapp

  deploy:
    machine:
      enabled: true
    steps:
      - run:
          name: Deploy Over SSH
          command: |
            ssh $AZURE_VM_USER@$AZURE_VM_IP "cd myapp && sudo npm run-script stop && sudo npm install && sudo npm start"

workflows:
  version: 2
  build:
    jobs:
      - build:
        filters:
          branches:
            only: main
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: main 

您的新config.yml文件包含两个任务。

build作业检查代码,并使用 SSH 密钥将更新的文件从应用程序安全地复制到 Azure VM myapp文件夹,使用scp命令。该命令的StrictHostKeyChecking=no部分抑制了要求确认检查主机密钥的提示。这可以防止提示阻塞自动化过程。

然后,deploy作业通过 SSH 部署应用程序。它进入myapp文件夹,停止应用程序,安装依赖项,并重启应用程序。

config.yml文件包含一个workflow定义,确保在deploy运行之前build任务成功完成。工作流还确保只有当代码被推送到main分支时,部署才会发生。当团队成员正在推进特性分支时,这阻止了应用程序的部署。

下一步是用新的更新来更新存储库。查看将项目推送到 GitHub 以获取指示。

将项目添加到 CircleCI

首先,确保您已经将项目的最新更新发布到了 GitHub。接下来,登录您的 CircleCI 帐户。如果你注册了你的 GitHub 账户,你所有的库都可以在你项目的仪表盘上看到。

找到你的cd-node-azure-vm项目,点击设置项目

Add Project - CircleCI

出现提示时,输入main作为包含您的 CircleCI 配置的 GitHub 分支的名称,然后点击设置项目

Add Config - CircleCI

CircleCI 然后将启动我们的管道,它将运行测试,但无法部署。此构建失败,因为您尚未设置配置文件来存放 Azure 上虚拟机的变量。

Build failed

创建管道配置环境变量

要解决这个问题,首先添加您之前保存的 SSH 密钥(您复制到安全位置的私有密钥)。转到项目的项目设置页面。从侧面菜单中,点击 SSH 按键。向下滚动到附加 SSH 密钥部分并点击添加 SSH 密钥。出现提示时,在主机名字段中输入您的公共 IP,在私钥字段中输入您的私钥。点击添加 SSH 密钥保存信息。

Add SSH Key - CircleCI

添加密钥后,它将显示在附加 SSH 密钥部分的表格中的主机名/指纹对中。复制这个指纹。管道配置文件中将需要它。

因为 VM 用户和公共 IP 将在管道配置中使用,所以最好将它们作为环境变量。从侧面菜单中,点击环境变量并输入以下信息:

  • 对于AZURE_VM_SSH_FINGERPRINT,输入添加 SSH 密钥后生成的指纹
  • 对于AZURE_VM_USER,输入虚拟机管理员,azureuser
  • AZURE_VM_IP中输入您的虚拟机公共 IP 地址

回到仪表板。点击从失败的重新运行工作流程。这将触发工作流,这次应该会成功构建。

Build successful - CircleCI

点击build任务查看详细信息。

Build details - CircleCI

接下来,单击deploy任务查看其详细信息。

Deploy details - CircleCI

所以工作流程运行顺畅。这很好,但是更有说服力的流程测试是在浏览器中打开部署的应用程序。在您的浏览器中,加载端点http://[YOUR_PUBLIC_IP]/todos。确保用正确的信息替换占位符YOUR_PUBLIC_IP

Todos Live - Browser

现在,那个是有说服力的。

接下来,向您的todos.js文件添加更多的todo对象:

module.exports = [
  ...,
  {
    id: 4,
    task: "Make Dinner"
  },
  {
    id: 5,
    task: "Take out the trash"
  }
]; 

提交您的更新并将其推送到您的存储库,以再次运行管道。工作流完成后,重新加载浏览器以检查您的更改。

Todos updated - browser

结论

虚拟机具有强大的功能和灵活性,Azure 提供了市场上最可靠的虚拟机产品之一。如本教程所示,将应用程序自动部署到 Azure 虚拟机,结合了 Azure 的可靠性和 CircleCI 的易用性。您的团队将受益于更容易部署的可靠的 web 应用程序。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

连续部署一个 Dockerized。AWS ECR - CircleCI 的 NET Core 应用程序

原文:https://circleci.com/blog/cd-for-dockerized-dotnet-core-to-aws-ecr/

本教程涵盖:

  1. 为克隆的创建容器映像。Net 核心演示应用程序,在本地构建和运行容器
  2. 将 Docker 映像部署到 AWS ECR
  3. 使用 CircleCI 编写一个配置文件来自动构建和部署容器映像

容器是部署应用程序的有用工具,因为它们允许将应用程序的代码、库和依赖项打包到一个独立的单元中。这使得在安装了必要的容器软件的任何计算机或服务器上部署和运行应用程序变得容易。

AWS 弹性容器注册(ECR) 是一个托管容器注册服务,用于存储、管理和部署容器映像。它与其他 AWS 服务完全集成,并提供了一种可伸缩且安全的方法来管理您的容器映像。ECR 允许您使用 Docker CLI 存储和检索图像,还可以与其他 Docker 工具集成,如 Docker Compose 和 Docker Swarm。使用 ECR,您可以在一个中心位置轻松存储和管理 Docker 图像,并从 AWS 帐户的任何位置访问它们。

在本教程中,您将使用 Docker 构建一个 ASP.NET 核心容器,并将容器映像推送到 Amazon Elastic Container Registry(Amazon ECR)。当您完成项目时,由于对您的代码库所做的更改而构建的每个新容器映像都将被推送并存储在 AWS 容器注册表中。

先决条件

本教程需要以下内容:

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

克隆演示项目

首先,从终端发出以下命令,使用 Git 从 GitHub 克隆演示项目:

git clone https://github.com/CIRCLECI-GWP/docker-dotnet-api-ecr 

这将把项目克隆到您的开发文件夹中的一个docker-dotnet-api-ecr文件夹中(或者当您从运行命令时)。

在本地构建 Docker 映像

为了为这个项目构建一个定制的 Docker 映像,您需要创建一个名为Dockerfile的特殊类型的文件。

Dockerfile是一个文本文件,包含一组用于构建 Docker 映像的指令。它是一个简单而强大的工具,允许开发人员定义以一致和可靠的方式运行他们的应用程序所需的环境和依赖关系。

在本教程中,您将使用下载的演示项目中包含的 Dockerfile 来构建自定义容器映像。Dockerfile包含安装所有项目依赖项、构建项目并运行它所需的命令。

转到应用程序的根目录,打开Dockerfile,确保它有以下内容:

# Build Stage
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /app
COPY . ./
RUN dotnet restore
RUN dotnet publish -c Release -o output

# Serve Stage
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime
WORKDIR /app
COPY --from=build /app/output .
ENTRYPOINT [ "dotnet", "docker-dotnet-api.dll" ] 

该文件中指定了两个不同的阶段:

  • Build采用。NET SDK 在构建项目并将其发布到名为out的文件夹之前安装任何所需的依赖项。
  • Serve使用ASP.NET 核心运行时映像从指定的工作目录运行应用程序,在本例中是app

这有时被称为多级 Dockerfile 。它将开发和生产指令合并到一个 Dockerfile 文件中,降低了流程的复杂性。

接下来,发出以下命令来构建 Docker 映像:

docker build -t dotnet-api:latest . 

基于应用程序和 Dockerfile 文件的内容,这个命令将构建容器映像。您可以使用下面的命令打印出容器图像列表,确认图像是创建的:

docker images 

您将看到一个图像列表,包括dotnet-api

REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
dotnet-api          latest    b7b390e1661b   43 seconds ago   215MB 

在本地运行 Docker 映像

成功构建本地版本的容器映像后,使用以下命令运行它:

docker run -dit -p 5001:80 dotnet-api:latest 

该命令在后台运行容器,将容器 ID 打印到终端,并在端口5001上运行应用程序。请访问http://localhost:5001/api/weather查看。

App running on Docker locally

将 Docker 映像部署到 Amazon ECR

在本节中,您将创建一个用户,该用户可以通过编程将容器图像推送到 Amazon registry。为此,导航到 AWS 管理控制台并选择 IAM。此服务允许您管理对 AWS 资源的访问。

AWS console

创建具有唯一名称的用户。我把我的叫做dotnet-user。确保您勾选了选项访问键-编程访问

Add user

接下来,为用户设置权限。点击直接附加已有保单。搜索并选择这些策略:

  • AmazonEC2ContainerRegistryFullAccess:该策略允许用户完全访问容器注册表及其 API。
  • AmazonEC2ContainerRegistryPowerUser:授予用户读写存储库的管理权限。

Set permissions

从这里,点击下一个两次,然后点击查看页面中的创建用户按钮。

您将看到一条成功消息以及您的用户凭据。该页面只显示一次,因此请记下访问密钥 ID秘密访问密钥。如果您愿意,可以下载包含相同凭据的. csv 文件。

Credentials created

自动化容器映像部署

接下来,您将使用 CircleCI 自动将 Docker 映像部署到 Amazon ECR。要轻松做到这一点,您将利用专门用命令创建的圆形球体名称 circleci/aws-ecr 来自动:

  1. 建立码头工人形象
  2. 登录亚马逊容器注册中心
  3. 创建一个 Amazon ECR repo(如果不存在)
  4. 将图片推送到亚马逊 ECR

下载的项目已经包含一个带有config.yml文件的.circleci文件夹。打开config.yml文件,确认里面有这个内容:

version: 2.1
orbs:
  aws-ecr: circleci/aws-ecr@8.1.3
workflows:
  build_and_push_image:
    jobs:
      - aws-ecr/build-and-push-image:
          context: dev
          create-repo: true
          dockerfile: Dockerfile
          path: .
          repo: dotnet-ecr
          tag: "$CIRCLE_SHA1" 

这个配置文件使用 Amazon 弹性容器注册中心 orb 来构建和部署 Docker 映像。dockerfile:命令指定 docker 文件的路径。

repo命令将 ECR 储存库指定为dotnet-ecr。如果create-repo标志声明它不存在,那么它将被创建。此外,要成功地将映像部署到 Amazon ECR,您需要提供您的 Amazon 凭据。为此,你应该在 CircleCI 中使用上下文

为 Amazon ECR 凭据添加上下文

从您的 CircleCI 仪表板,点击侧边栏中的链接,进入组织设置页面。

然后,选择上下文,点击创建上下文按钮,为您的上下文添加一个唯一的名称。上下文出现在安全设置为All members的列表中。这意味着组织中的任何人都可以在运行时访问该上下文。正如本教程的.circleci/config.yml配置中所指定的,上下文名称应该是dev

Create context

接下来,选择dev上下文,点击添加环境变量,输入这些变量:

  • AWS_ACCESS_KEY_ID -您之前创建的dotnet-user IAM 角色的 AWS 访问键 id。
  • AWS_SECRET_ACCESS_KEY -您之前创建的dotnet-user IAM 角色的 AWS 密钥。
  • AWS_ECR_REGISTRY_ID -与 ECR 账户相关的 12 位 AWS id。这也称为帐户 ID。你可以从 AWS 支持页面找到这个。
  • AWS_REGION -您的 ECR 资源将位于的 AWS 区域。

将应用程序连接到 CircleCI

现在你可以改变你的远程存储库 URL ,如果你克隆了本教程的 repo 并且将你的项目推送到 GitHub

登录您的 CircleCI 帐户。如果你注册了你的 GitHub 账户,你所有的库都可以在你项目的仪表盘上看到。找到dotnet-ecr项目,点击设置项目

Select project

在 GitHub 上输入您的代码所在的分支的名称,然后点击设置项目

Set up project

您的第一个工作流将开始运行并成功完成。

Pipeline build successful

要查看存储库信息,请转到您的 AWS 管理控制台

View image url

结论

通过正确配置 CircleCI 部署,您可以轻松地对您的代码库进行更改,部署到 GitHub,并确保您在 Amazon container registry 上的 Docker 映像将始终更新为新版本,而无需您进行任何手动操作。

我希望本教程对你有所帮助。编码快乐!


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

离子应用的持续部署| CircleCI

原文:https://circleci.com/blog/cd-for-ionic-apps/

当谈到移动应用程序开发时, Ionic 框架仍然是首选,多年来,它已经发展成为一个用于开发移动、桌面和渐进式 web 应用程序的全功能应用程序框架。它受欢迎的部分原因是它是框架不可知的。它允许开发者使用 AngularReactVue 进行开发。在本教程中,我们将演示如何将 Ionic 移动应用程序部署到 Firebase 托管平台。

先决条件

要跟进这篇文章,需要做一些事情:

  1. React.js 的基础知识
  2. 系统上安装的 Node.js
  3. 安装在系统上的 Ionic CLI
  4. 一个的账户
  5. 一个 Firebase 账户(带有一个 Gmail 账户)

构建一个简单的离子应用程序

首先,让我们通过运行以下命令来构建一个新的 Ionic 应用程序:

ionic start ionic-cd tabs --type=react 

这将立即触发 Ionic CLI 使用名为ionic-cd的文件夹中的tabs模板为我们搭建一个新项目。

注意:如果提示Create a free Ionic account?则提示。点击n拒绝。欲了解更多关于创建 Ionic 账户的信息,请点击本页

完成后,进入应用程序的根目录(cd ionic-cd),运行以下命令在您的 web 浏览器中为应用程序提供服务:

ionic serve 

一旦该命令完成初始化,您将看到一个类似于下图的应用程序视图。CLI 会自动在默认浏览器中打开一个选项卡。

注意 : 我使用的是 Chrome 开发工具中激活的移动预览。

接下来,让我们开始构建主应用程序。进入应用程序的src/pages文件夹,打开文件Tab1.tsx,这是新搭建的应用程序的默认主页。删除该文件中的所有内容,并替换为以下代码:

import React, { useState } from "react";
import {
  IonContent,
  IonHeader,
  IonPage,
  IonTitle,
  IonToolbar,
  IonList,
  IonItemSliding,
  IonItem,
  IonLabel,
  IonItemOptions,
  IonItemOption,
  IonFab,
  IonFabButton,
  IonIcon,
  IonModal,
  IonButton,
  IonCard,
  IonCardContent,
  IonInput
} from "@ionic/react";

import { add } from "ionicons/icons";

import "./Tab1.css";

interface Task {
  id: number;
  name: string;
}

const Tab1: React.FC = () => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [showModal, setShowModal] = useState(false);
  const [taskName = "", setTaskName] = useState<string>();

  function addNewTask() {
    const new_id = tasks.length + 1;

    const newTask = {
      id: new_id,
      name: taskName
    };

    tasks.push(newTask);

    setTasks(tasks);

    setTaskName("");

    setShowModal(false);
  }

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle color="primary">Task Manager</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          {tasks.length > 0 ? (
            tasks.map((item: Task) => {
              return (
                <IonItemSliding key={item.id}>
                  <IonItem className="todo-item">
                    <IonLabel>{item.name}</IonLabel>
                  </IonItem>
                  <IonItemOptions side="end">
                    <IonItemOption onClick={() => {}}>Done</IonItemOption>
                  </IonItemOptions>
                </IonItemSliding>
              );
            })
          ) : (
            <IonItem>
              <IonLabel color="danger">
                You have not yet added tasks for today
              </IonLabel>
            </IonItem>
          )}
        </IonList>

        {/* Modal*/}
        <IonModal isOpen={showModal}>
          <IonCard>
            <IonItem>
              <IonLabel color="primary">Add New Task</IonLabel>
            </IonItem>

            <IonCardContent>
              <IonItem>
                <IonInput
                  value={taskName}
                  placeholder="Enter Task Name..."
                  onIonChange={(e) => setTaskName(e.detail.value!)}
                ></IonInput>
              </IonItem>

              <IonButton
                expand="full"
                color="success"
                onClick={() => addNewTask()}
              >
                Add Task
              </IonButton>
            </IonCardContent>
          </IonCard>
          <IonButton color="success" onClick={() => setShowModal(false)}>
            Close Modal
          </IonButton>
        </IonModal>

        {/* FAB */}
        <IonFab vertical="bottom" horizontal="end" slot="fixed">
          <IonFabButton color="success" onClick={() => setShowModal(true)}>
            <IonIcon icon={add} />
          </IonFabButton>
        </IonFab>
      </IonContent>
    </IonPage>
  );
};

export default Tab1; 

我们在这里构建的是一个简单的任务列表应用程序,我们可以在其中添加当天的任务。让我们看一下上面的代码片段。

我们首先导入必要的依赖项,包括页面的 CSS 文件。然后我们定义一个接口来定义我们的任务对象。

...
interface Task {
  id: number;
  name: string;
}
... 

接下来,我们将组件创建为类型为React.FC的 React.js 函数,并通过使用钩子定义我们想要保存在状态中的数据来开始该函数:

  • 一组Tasktasks
  • 一个showModal布尔值来控制我们的任务创建表单的打开和关闭
  • 一个保存新任务值的taskName
...
const [tasks, setTasks] = useState<Task[]>([]);
const [showModal, setShowModal] = useState(false);
const [taskName = "", setTaskName] = useState<string>();
... 

接下来是我们调用的添加新任务的函数。该函数通过根据数组的长度设置其id来创建一个新任务,并在将新任务添加到我们现有的任务列表后清除表单。

...
function addNewTask() {
  const new_id = tasks.length + 1;

  const newTask = {
    id: new_id,
    name: taskName
  };

  tasks.push(newTask);

  setTasks(tasks);

  setTaskName("");

  setShowModal(false);
}
... 

接下来,我们呈现我们的模板以显示我们的任务列表和一条有用的消息,当任务列表为空时显示为You have not yet added tasks for today。列表后面是一个模态组件,它包含用于添加新任务的任务表单。组件下面是一个浮动的操作按钮,用户单击它可以打开模式。

在我们预览之前,打开与Tab1.tsx位于同一文件夹的Tab1.css,并用以下代码替换其内容:

.todo-item {
  --min-height: 70px;
  font-size: 1.2em;
} 

这增加了列表项的高度和字体大小。

现在进入你的浏览器,加载你的应用程序的主页(Tab1)。

如页面所示,因为我们还没有添加任何任务,所以我们会显示消息您还没有添加今天的任务。右下角还有我们的添加任务按钮,带有plus符号。

要添加新任务,请单击右下角的绿色按钮(带加号)并键入任务。

点击添加任务,然后再添加两三个。现在,我们在页面上有足够的任务让我们的应用程序看起来正常。

为部署到 Firebase 进行设置

现在我们已经有了一个工作的 Ionic 应用程序,让我们为部署到 Firebase 做准备。

首先,您需要安装 Firebase 工具。要检查是否已安装,请运行以下命令:

firebase 

这将在您的终端中输出 Firebase CLI 命令/选项列表。如果没有,您需要运行以下命令来安装 CLI:

npm install -g firebase-tools 

如果您的 Firebase CLI 版本低于8,您也需要运行这个命令。要检查您的版本,请运行以下命令:

firebase --version 

要为我们的项目设置 Firebase 托管,我们需要创建一个 Firebase 项目。去你的 Firebase 控制台创建一个新项目。

点击添加项目,在弹出的第一页输入你的项目名称。

Create Firebase Project

点击继续。在下一页(关于添加谷歌分析,关闭Enable Google Analytics for this project切换按钮。这是一个演示项目,我们不需要分析。

现在点击创建项目。等待 Firebase 完成项目设置,然后单击继续导航到您的项目仪表板。

下一步是使用我们刚刚创建的项目在 Firebase 上设置我们的 Ionic 应用程序。在默认浏览器上保持登录 Firebase。然后转到您的 CLI 运行以下命令:

firebase login:ci 

该命令将通过重定向到您当前登录的浏览器,让您登录 Firebase。身份验证过程完成后,您将在 CLI 上登录 Firebase,并且您的 Firebase 令牌将打印在屏幕上的✔ Success! Use this token to login on a CI server行下方。请保管好这个令牌,因为您将在本教程的后面需要它。

接下来,在项目的根目录下运行以下命令来初始化 Firebase 设置:

firebase init 

您将从该命令得到的第一个提示是? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices.。选择Hosting并点击Enter进入下一个提示。

Select Project Features

下一个提示是关于将您的本地项目与您的 Firebase 帐户上的 Firebase 项目相关联。在这里,您可以选择使用现有项目或创建一个新项目。选择Use an existing project并点击Enter进入下一个提示。

这一选择将导致 CLI 加载 Firebase 项目,供您在下一个提示中选择。在 Firebase 控制台上选择我们刚刚创建的项目,然后点击Enter确认您的选择。

Select Project

注意 : 如果您的项目在列表中不可见,停止这个过程(Ctrl + C),然后重新运行带有--project选项的命令:firebase init --project <projectId>用您新创建的 Firebase 项目 ID 替换projectId。像以前一样选择托管

下一个提示询问项目文件夹,并建议使用public文件夹。Ionic 将它的生产版本保存在一个名为build的文件夹中,因此在这个提示符下输入build并点击Enter

以下提示显示? Configure as a single-page app (rewrite all urls to /index.html)?。键入y,然后点击Enter

这将完成设置,您将拥有两个新文件:.firebaserc,它为该应用程序设置项目 id,以及firebase.json,它包含关于我们在设置过程中选择的选项和一些其他默认设置的详细信息。我们现在可以着手构建我们的部署管道了。

构建 CD 管道

要设置我们的持续部署管道,我们需要采取以下步骤:

  1. 将我们的项目推到一个连接到 CircleCI 帐户的远程存储库(本例中是 GitHub)
  2. 将我们的应用程序作为新项目添加到 CircleCI 上
  3. 将 Firebase 令牌作为环境变量添加到 CircleCI 项目中
  4. 在项目中本地安装firebase-tools
  5. 创建我们的管道配置文件
  6. 将项目变更推送到我们的存储库以启动部署

我们开始吧。

首先,通过运行以下命令来搭建一个快速的package.json文件:

npm init -y 

然后,将你的项目推送到 GitHub

下一步是将我们的项目的存储库设置为 CircleCI 项目。

CircleCI 控制台上,选择账户查看您的Projects页面。如果 GitHub repo 在这里不可见,那么点击侧菜单上的添加项目

Add Project

点击设置项目

Start Building - Config sample

在设置页面上,点击开始构建。在构建开始之前,您会得到一个提示,要么下载并使用所提供的 CircleCI 配置文件,并将它放在一个单独的分支上,要么手动设置一个。

Start Building - Add manually

选择手动添加继续。这将提示另一个对话框,检查确认您已经设置了配置文件,可以开始构建了。

Start Building - Confirm configuration

点击开始构建完成设置。这将立即触发管道。构建将失败,因为我们还没有添加管道配置文件。

我们的下一步是将 Firebase 令牌作为环境变量添加到我们刚刚创建的 CircleCI 项目中。在管道页面,选中我们的项目,点击项目设置(在网页右上角)。

Project Settings

在设置页面侧菜单上,点击Environment Variables。在变量设置页面,点击添加环境变量。将出现一个对话框。在Name字段中,输入FIREBASE_TOKEN,并在Value字段中,粘贴您之前从 CLI 获得的 Firebase 令牌。点击Submit完成该过程。现在,您已经注册了令牌变量。

Project Settings

返回到系统上的项目。运行以下命令,在应用程序的根目录下安装firebase-tools,这样您就可以在package.json中将它注册为开发依赖项:

npm install -D firebase-tools 

一旦该过程完成,就该创建我们的部署配置文件了。在项目的根目录下,创建一个名为.circleci的文件夹和一个名为config.yml的文件。在config.yml文件中,输入以下代码:

version: 2
jobs:
  build:
    docker:
      - image: cimg/node:12.16
    working_directory: ~/repo
    steps:
      - checkout
      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}
            - v1-dependencies-
      - run:
          name: Install Dependencies
          command: npm install
      - run:
          name: Build Application
          command: npm run build
      - save_cache:
          key: v1-npm-deps-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Deploy to Firebase
          command: ./node_modules/.bin/firebase deploy --token "$FIREBASE_TOKEN" --only hosting 

在上面的部署文件中,我们从从我们的远程存储库中检出项目开始。然后我们安装我们的依赖项。接下来,我们运行package.json中的build脚本,在build文件夹中创建 Ionic 应用的生产版本,然后缓存我们的依赖关系。最后,我们从本地安装运行我们的firebase-tools,使用我们的 Firebase 令牌来部署我们的应用程序。

是时候把钱花在我们该花的地方了。让我们提交我们的更改,并将它们推送到我们的存储库,以触发我们的部署脚本,并将我们的应用程序部署到 Firebase 主机。

Build success

单击构建查看部署的幕后情况,如下所示。

Build Process

Deploy to Firebase部分。您可以看到部署的应用程序的 URL。对于本教程,它是https://my-ionic-app-d2a1d.web.app/。将您的应用程序加载到浏览器中以测试您的应用程序。

Live Application

结论

如果您仍然在每次更新应用程序时手动将文件复制到您的服务器,那么您需要设置一个自动化部署管道,以便在您推送更新时完成部署,从而减轻您的压力。有了它,您只需要担心构建您的应用程序, CircleCI 会为您完成部署。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

渐进式 web 应用的持续部署| CircleCI

原文:https://circleci.com/blog/cd-for-pwa/

渐进式网络应用(PWAs) 由于其类似本地的属性,继续获得广泛的关注、接受和与网络浏览器的兼容性。部署这些应用程序的强制性安全考虑之一是它们必须安全托管。因此,PWA 功能将无法在不安全的 URL 上工作,即不使用安全https://协议的 URL。在本帖中,我们将创建一个自动部署管道,将我们的 PWA 部署到 Firebase 上的一个安全 URL。

先决条件

要跟进这篇文章,需要做一些事情:

  1. Javascript 的基础知识
  2. 系统上安装的 Node.js
  3. 一个 HTTP 服务器模块全局安装在您的系统上(npm install -g http-server)
  4. 一个火基账户
  5. 一个的账户
  6. GitHub 的一个账户

所有这些安装和设置,让我们开始教程。

设置演示应用程序

第一项任务是创建我们将部署到 Firebase 的演示应用程序。运行以下命令为项目创建一个目录,并进入该目录的根目录:

mkdir my-pwa-firebase
cd my-pwa-firebase 

接下来,让我们创建我们的应用程序主页。在项目的根目录下创建一个名为index.html的文件,并将以下代码粘贴到其中:

 <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="manifest" href="manifest.json" />
    <meta name="theme-color" content="#db4938" />
    <link rel="stylesheet" type="text/css" href="styles.css" media="all" />

    <title>DogVille</title>
  </head>
  <body>
    <h2>
      Welcome to the home of Dogs
    </h2>

    <div class="dog-list">
      <div class="dog-pic"><img width="300px" src="images/dog1.jpg" /></div>
      <div class="dog-pic"><img width="300px" src="images/dog2.jpg" /></div>
      <div class="dog-pic"><img width="300px" src="images/dog3.jpg" /></div>
      <div class="dog-pic"><img width="300px" src="images/dog4.jpg" /></div>
    </div>

    <script src="app.js"></script>
  </body>
</html> 

在上面的主页文件中,我们链接到三个文件:manifest.json,我们将使用它们来设置添加到主屏幕 PWA 特性,styles.css将一些基本样式应用到我们的页面,以及app.js,它们将加载我们还没有创建但很快就会创建的服务工作者。在我们页面的主体,我们有一个标题,写着欢迎来到狗狗之家,在它下面,我们显示了一个狗狗图片列表。如你所见,这是一个狗的网站(向爱猫人士道歉)。

狗的图像包含在项目根目录下的一个images文件夹中。我已经根据images中使用的文件名引用了index.html中的狗图片。继续创建这个文件夹,你可以在这里免费下载狗狗图片,并给它们重新命名。

让我们通过在项目的根目录下创建文件styles.css并在其中粘贴以下代码来添加样式:

body {
  background-color: orange;
}

h2 {
  color: white;
} 

这个文件给了我们的页面背景颜色,并使标题文本为白色。让我们在项目的根目录下运行以下命令,调用全局http-server模块来启动本地服务器为我们的应用程序提供服务,以此来测试我们的应用程序:

http-server 

当你在浏览器中加载网址时,你会看到一个类似下面的屏幕(你的狗可能与我的不同):

App first view

添加服务人员

现在,让我们加入 PWAs 的果汁,这位服务人员。在项目的根目录下创建一个serviceworker.js文件,并将以下代码粘贴到其中:

var cacheName = "sw-v1";
var filesToCache = [
  "./",
  "./index.html",
  "./styles.css",
  "./app.js",
  "./images/dog1.jpg",
  "./images/dog2.jpg",
  "./images/dog3.jpg",
  "./images/dog4.jpg"
];

self.addEventListener("install", function (e) {
  console.log("<ServiceWorker> ---- Install v1");
  e.waitUntil(
    caches.open(cacheName).then(function (cache) {
      console.log("<ServiceWorker> --- Caching app shell");
      return cache.addAll(filesToCache);
    })
  );
});

self.addEventListener("activate", (event) => {
  event.waitUntil(self.clients.claim());
});

self.addEventListener("fetch", function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      if (response) {
        return response;
      }
      return fetch(event.request);
    })
  );
}); 

在上面的服务工作者文件中,我们缓存了所有的静态文件,包括我们的项目根和图像。然后我们监听install事件来安装我们的服务工作者,并使用指定的cacheName作为标识符为这些文件创建一个缓存。

接下来,我们监听activate事件,以确保已经安装的任何新服务工作器都是为我们的缓存服务的,而不是旧版本。

最后,我们监听fetch事件来拦截任何请求,并检查我们的缓存中是否已经有了所请求的资源。如果是,我们提供缓存版本,如果不是,我们发出新的请求来获取资源。

现在,让我们将服务人员文件加载到应用程序中。在项目的根目录下创建一个app.js文件,并将以下代码粘贴到其中:

 if ("serviceWorker" in navigator) {
  window.addEventListener("load", function () {
    navigator.serviceWorker.register("./serviceworker.js").then(
      function (registration) {
        console.log("Hurray! Service workers with scope: ", registration.scope);
      },
      function (err) {
        console.log("Oops! ServiceWorker registration failed: ", err);
      }
    );
  });
} 

让我们带我们的服务人员去兜兜风。确保您的应用程序仍在运行,然后在当前加载应用程序的浏览器选项卡上进行硬重新加载(Ctrl + Shift + R)。现在检查浏览器控制台,查看我们为确认服务人员的安装而编写的控制台日志消息。您将在控制台中看到以下消息。

Service Worker Installation

为了确认我们现在已经安装了服务人员的离线功能,使用Ctrl + C关闭http-server服务,然后在浏览器上刷新应用程序。此时,您通常会看到脱机页面,因为应用程序不再运行,但是借助服务人员的魔力,您仍然可以看到您的应用程序主页。还要注意,所有的狗图片都是离线加载的。

太好了!

添加清单文件

让我们通过在项目的根目录下创建我们的manifest.json文件来结束我们的演示应用程序。你可以生成一个简单的带有图标manifest.json文件。

下面是我的manifest.json文件中的代码。注意 : 我已经在我的例子中移除了一些图标。

{
  "name": "my-dogsite-pwa",
  "short_name": "my-dogsite-pwa",
  "theme_color": "#000000",
  "background_color": "#ffffff",
  "display": "standalone",
  "orientation": "portrait",
  "scope": "/index.html",
  "start_url": "/index.html",
  "icons": [
    {
      "src": "icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
} 

在上面的文件中,定义了应用的name、首选的short name、应用栏的theme_color和闪屏的background_color

注意: 添加short_name是一个最佳实践,这是一个可选字段,指定将在应用程序启动器或新标签页中显示的名称。否则会使用name,超过 12 个字符就会被截断。

icons数组还添加了一组图标对象定义,指向我们将用于 PWA 的图标。记得创建icons文件夹,并用清单文件中命名的图标填充它。

设置 Firebase 的部署

现在我们的 PWA 已经完成,让我们开始为部署到 Firebase 做准备。您需要安装 Firebase 工具。要检查是否已安装,请运行以下命令:

firebase 

这将向您的 CLI 返回 Firebase 命令列表。如果没有,您需要运行以下命令来安装它:

npm install -g firebase-tools 

如果您的firebase-tools版本低于 8,您也需要运行上面的命令。要检查您的firebase-tools版本,运行以下命令:

firebase --version 

要为我们的项目设置 Firebase 托管,我们需要创建一个 Firebase 项目。去你的 Firebase 控制台创建一个新的 Firebase 项目。

点击Add Project,在弹出的第一页输入你的项目名称。

Create Firebase Project

点击Continue,在下一页关于添加谷歌分析的页面,关闭Enable Google Analytics for this project切换按钮。由于这是一个演示项目,我们将不需要分析。

现在点击Create Project。等待 Firebase 完成项目设置,然后单击Continue导航到您的项目仪表板。

现在我们已经设置好了项目,下一步是使用我们刚刚创建的项目来设置 PWA 以托管在 Firebase 上。在默认浏览器上保持登录 Firebase,然后转到 CLI 运行以下命令:

firebase login:ci 

该命令将通过重定向到您当前登录的浏览器,让您登录 Firebase。一旦认证过程完成,你的 Firebase 令牌将被打印在屏幕上的✔ Success! Use this token to login on a CI server行下面。请安全地保存这个令牌,因为在本教程的后面会用到它。

接下来,在 PWA 项目的根目录下运行以下命令来初始化 Firebase 设置:

firebase init 

该命令的第一个提示是

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. 

。使用箭头键导航到Hosting选项,点击Spacebar进行选择,点击Enter进入下一个提示。

Select Project Features

接下来的提示是将您的本地项目与您的 Firebase 帐户上的 Firebase 项目相关联。在这里,您可以选择使用现有项目或创建一个新项目。选择Use an existing project并点击Enter进入下一个提示。这个选项将提示 CLI 工具加载 Firebase 项目,供您在下一个提示中进行选择。我正在选择我们刚刚在 Firebase 控制台上创建的项目。

点击Enter确认您的选择。

Select Project

下一个提示询问项目文件夹并建议文件夹public。对于我们的项目,一切都发生在根上,所以只需输入/并点击Enter继续。

下一个提示是? Configure as a single-page app (rewrite all urls to /index.html)?。因为我们的整个应用程序驻留在index.html中,所以键入y并点击Enter。这个提示对于区分单页应用程序和传统的多页应用程序非常重要,这样 Firebase 主机就知道如何处理它们。

下一个提示检测到我们已经有了一个index.html文件,并询问是否应该覆盖它。为此键入N并点击Enter。这就完成了设置,现在您将拥有一个为该应用程序设置项目 id 的.firebaserc文件,一个包含我们在设置过程中选择的选项和一些其他默认设置的详细信息的firebase.json文件,以及一个用于 Firebase 的标准.gitignore文件。

这样,我们现在可以继续创建我们的部署管道。

构建 CD 管道

要设置我们的自动化部署管道,我们需要采取以下步骤:

  1. 将我们的项目推到一个连接到 CircleCI 帐户的远程存储库(本例中为 GitHub)
  2. 将我们的应用程序作为新项目添加到 CircleCI 上
  3. 将 Firebase 令牌作为环境变量添加到 CircleCI 项目中
  4. 在项目中本地安装firebase-tools
  5. 创建我们的管道配置文件
  6. 将项目变更推送到我们的存储库以启动部署

我们开始吧。通过运行以下命令搭建一个快速的package.json文件:

npm init -y 

然后,将项目推送到 GitHub

下一步是将我们的项目的存储库设置为 CircleCI 项目。

在 CircleCI 控制台上,转到添加项目页面。

Add Project

点击设置项目。这将加载下一个屏幕。

Start Building - Config sample

在设置页面上,点击开始构建。在构建开始之前,您会得到一个提示,要么下载并使用所提供的 CircleCI 配置文件并将它放在一个单独的分支上,要么手动设置一个。

Start Building - Add manually

选择手动添加继续。这将提示另一个对话框,检查确认您已经设置了配置文件,可以开始构建了。

Start Building - Confirm configuration

点击开始构建以完成设置。这将立即触发管道。构建将失败,因为我们还没有添加管道配置文件。

我们的下一步是将 Firebase 令牌作为环境变量添加到我们刚刚创建的 CircleCI 项目中。在管道页面,选中我们的项目,点击项目设置

Project Settings

在设置页面侧菜单上,点击Environment Variables。在变量设置页面上,点击Add Environment Variable。将出现一个对话框。在Name*字段中,输入FIREBASE_TOKEN,并在Value*字段中,粘贴上一步中从 CLI 获得的 Firebase 令牌。点击提交完成流程。现在,您已经注册了令牌变量。

Project Settings

返回到系统上的 PWA 项目。运行下面的命令将firebase-tools安装在项目的根目录下,这样您就可以在package.json中将它注册为一个开发依赖项:

npm install -D firebase-tools 

一旦该过程完成,就该创建我们的部署配置文件了。在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在config.yml文件中,输入以下代码:

version: 2
jobs:
  build:
    docker:
      - image: circleci/node:10.16.0
    working_directory: ~/repo
    steps:
      - checkout
      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}
            - v1-dependencies-
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: v1-npm-deps-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Deploy to Firebase
          command: ./node_modules/.bin/firebase deploy --token "$FIREBASE_TOKEN" --only hosting 

在上面的配置文件中,我们从从我们的远程存储库中检出项目开始。然后,我们安装依赖项,缓存它们,并从本地安装运行我们的firebase-tools,以使用我们的 Firebase 令牌来部署我们的应用程序。

现在是关键时刻了。让我们提交我们的更改,并将它们推送到我们的存储库,以触发我们的部署脚本,并将我们的应用程序部署到 Firebase hosting。

Build success

点击进入构建查看项目的幕后。

Build Process

Deploy to Firebase部分。您可以看到部署的应用程序的 URL。对于这个练习,是https://my-dog-site-pwa.web.app。将您的应用程序加载到浏览器中以测试您的应用程序。

Live Application

正如您在上面看到的,地址栏正在加载我们的 Firebase URL,我们可以看到控制台消息,表明我们的服务人员已经安装。如果你关闭你的网络并刷新这个页面,你会看到应用程序,所有的狗图片和样式,而不是通常的离线屏幕。

结论

大多数开发人员都不喜欢为https://URL 设置 SSL 证书。有时这导致不愿意采用公共福利援助方案。然而,在这篇文章中,我们展示了如何通过 CircleCI 和 Firebase 建立一个自动化的持续集成 /持续部署管道来安全托管 PWAs。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

首席执行官吉姆·罗斯给 CircleCI 员工的电子邮件| CircleCI

原文:https://circleci.com/blog/ceo-jim-rose-email-to-circleci-employees/

今天早些时候,CircleCI 首席执行官 Jim Rose 向 CircleCI 员工发送了以下电子邮件。

大家好,

今天,我宣布一些对我们团队来说困难的消息。我们正在将 CircleCI 的员工减少 17%,并与我们非常重视和尊重的队友分道扬镳。

做出这个决定是非常困难的,我知道对于那些离开我们组织的人来说会更加困难。如果您是受影响的人之一,在接下来的 20 分钟内,您将收到与 CircleCI 高层领导进行一对一会谈的邀请。这些会发到你的个人邮箱。

对于那些今天将要离开切尔莱西的人,我深感抱歉。我现在的首要任务是为我们即将离开的队友尽我们所能;他们确实应该得到我们的感谢和支持。下面有更多关于我们计划如何支持他们的细节,但首先,让我分享一下我们是如何走到这一步的。

什么变了?

我们的长期前景依然乐观;我们的业务服务于对工程团队的关键需求,我们预计在自动化和开发人员效率方面的投资将随着时间的推移而增加。然而,现在我们这个行业的不确定性产生了连锁反应。自第四季度开始以来,我们已经看到每个公司的绩效评估方式发生了巨大变化。企业曾因不惜一切代价实现增长而受到称赞。市场预期很快发生了变化。现在的重点是最大化效率。

在过去的一年中,我们已经采取措施来适应这种新的经济现实:严格管理我们的支出,减缓我们的招聘工作,以合理的估值运营,并重新安排我们的时间和投资的优先次序。我们希望这些行动能让我们避免这个困难的决定。但是,像我们周围的其他公司一样,我们也不能幸免于市场,需要更大的变化。

对于那些离开 CircleCI 的人,这里是你可以期待的:

  • 最后一天:对于离职员工,正式来说,您的最后一天将是 12 月 15 日,您将在这一天拿到工资。受影响的员工在今天之后不会继续工作。
  • 离职福利:所有受影响的员工将获得一份离职补偿,包括所有离职员工至少 12 周的基本工资。如果你已经在这里工作了 3 年以上,从第 3 年开始,每多工作一年,你将获得额外两周的遣散费。例如,如果你在这里工作了 3 年,你将获得 14 周的遣散费。
  • 健康福利:我们希望限制员工的医疗费用负担,我们知道这是许多人最关心的问题。对于加入 CircleCI 健康计划的美国员工,您的健康福利将持续到 12 月底,我们将通过 COBRA 为您支付额外 6 个月的持续保险费用。非美国员工将获得相当于 6 个月的健康保险,与我们的地区福利一致。此外,任何尚未使用年度健康津贴的人都将把这笔金额加到他们的最终工资中。
  • 股权:我们将取消过去一年里我们雇佣的所有人的股票期权的一年悬崖要求。这是为了让每个离职的人,不管他们在 CircleCI 工作了多久,都有机会成为股东。我们还将把我们的标准离职后行使期从 90 天延长至 5 年,这意味着受影响的员工在 2027 年 12 月之前不必就其股权做出决定。所有受影响的队友将在 2022 年 12 月 15 日前获得授权。
  • 奖金:如果您有资格获得 2023 财年(2022 年)的奖金,在计算您的最终奖金时,我们将假设您的个人绩效达到 100%。您仍将在与 CircleCI 其他员工相同的时间框架内(2023 年 3 月)获得按比例分配的 23 财年奖金。
  • 职位安排支持:我们的目标是让受影响的员工轻松过渡。我们已经开展了一项名为 Careerminds 的服务,为即将离职的队友提供职业指导、简历帮助和工作安排。
  • 设备:您可以保留您的笔记本电脑(以及任何其他提供的办公设备,包括显示器和椅子)。

我们知道即将离开的队友会有疑问,我们在这里回答他们。我们的人力资源合作伙伴和人力资源团队可以帮助受影响的员工。感谢你们为建设 CircleCI 所做的一切:你们将永远是我们校友社区的宝贵成员。

下一步是什么

当这种规模的事情发生时,它不是照常营业。我们的工作方式将会不同,我知道每个人都需要方向、清晰和对未来的信心。

我将在太平洋时间 12 月 8 日星期四上午 9 点召开的全组织会议上重申上述观点。我们会把大部分时间留给提问。请留意日历邀请,稍后会有更多相关信息。

尽管今天的新闻,我们对我们的业务充满信心。我相信我们有正确的战略来取得长期的成功。我们拥有市场上最好的 CI/CD 平台。我们看到这一成功反映在我们业务的持续增长上,在过去 12 个月中,我们的平台增加了数千个高性能的工程团队。我们的客户是地球上最具创新性、以工程为中心的企业,帮助他们做好工作将继续是我们的工作重点。

谢谢你,

吉姆(人名)

DevOps 公司- CircleCI 的增长工程挑战

原文:https://circleci.com/blog/challenges-of-growth-engineering/

什么是增长工程?

增长工程是一种实践,其中产品、工程和设计从产品本身内部支持公司的增长努力。

增长工程在面向消费者的公司中获得了牵引力。在过去十年中,这种做法在 SaaS 世界获得了很大的吸引力,有助于支持自助服务用户的增长,这些用户经常在没有传统销售团队参与的情况下购买服务。

CircleCI 的增长工程

CircleCI 最近成立了一组增长工程团队,让工程师更密切地参与到提高产品采用率的工作中。我们成长团队的目标是优化用户体验,让他们尽可能有效地使用我们的工具。

但是,作为一家 B2B DevOps 工具公司,我们的客户是工程组织,而不是个人用户。因此,我们面临一些独特的挑战,这些挑战在 B2C 组织的成长工程团队中并不常见。需要衡量我们的实验对付费组织的影响,而不是用户,这改变了我们处理增长工程的方式。

衡量成功的不同标准

当公司谈论增长时,尤其是那些受众是消费者的公司,他们的目标之一通常是让用户参与他们的产品。例如,对于 LinkedIn 这样的产品,目标是通过经常访问平台来保持访问者与平台的互动,并且当在网站上时,尽可能长时间地停留在网站上。最后,他们希望访问者尽可能经常地回到网站。

我们的情况完全不同。作为一个 DevOps 工具,CircleCI 是任务关键型的,允许我们客户的团队向数百万用户交付他们的代码。我们与平台用户的日常接触非常少,这是设计使然。我们的工具通常在后台运行,用户不会每天对其 CI 系统进行重大更改。因此,花在我们平台上的时间并不能像社交平台那样很好地代表用户的成功。

本质上,从 CircleCI 用户的角度来看,我们的日常 UI 是一个简单的集成点,它将通知用户他们的 CI/CD 管道是否成功,它看起来就像这样:

2021-03-03-PassingBuild.jpg 2021-03-03-FailingBuild.jpg

我们的用户需要进入我们的系统的用例是相当有限的:例如,更新他们的配置,对构建失败进行故障排除,或者建立一个新的项目。虽然这些表演不太频繁,但他们周围经常有一种紧迫感。这意味着当用户进入我们的系统时,我们的成功是以他们在我们的系统中花费的时间更少而不是更多来衡量的。

带着这个目标,我们大部分的实验都是为了找到让我们的 UI 对用户更有效的方法

  • 改进错误预防;在错误发生之前捕捉它们。

  • 减少解决错误所需的时间。

  • 使工程师能够就配置或错误进行协作。

  • 提供上下文信息,以便工程师可以更快地做出更改。

  • 更改配置时缩短反馈循环。

  • 提高 CI/CD 管道的成功机会。

  • 减少构建和部署所需的时间。

这给我们带来了在 CircleCI 做实验时面临的重大挑战。

测量增长实验的挑战

我们的用户不是我们的付费客户,我们的付费客户也不是我们的用户

在 CircleCI,任何人今天都可以免费开始构建,我们平台上的数千名工程师在训练营、辅助项目、 OSS 项目中使用 CircleCI,或者免费构建创业原型。我们的付费客户几乎都是专业组织,因为他们需要更多的容量,并需要访问仅在付费计划中提供的高级功能。

组织被定义为多个工程团队的集合,每个团队由一个或多个工程师组成。通常组织映射到一个公司,但是理论上,一个公司可以在其源代码控制管理系统中定义多个组织。对我们来说,关键的概念是,我们不把用户从免费转换成付费:我们转换的是组织。为 CircleCI 的产品付费的是组织,而不是最终用户。

考虑到这一点,我们需要让我们的实验面向那些有转化潜力的组织中的用户,并衡量我们的实验对组织而不是用户的影响。我们使用一些重要的指标来帮助我们定义组织转化的可能性,例如:

  • 组织中的成员数量。

  • 如果他们为他们的源代码控制系统付费。

  • 他们为他们的源代码控制计划支付的费用。

受众限制

首先,增长工程团队的受众局限于我们的用户中有潜力成为付费用户的一部分。这种限制意味着我们经常面临更大的困难来达到我们度量的统计上的显著结果。在某些情况下,统计意义永远达不到,因为观众对于实验来说太受限制了。例如,如果我们只针对新组织的管理员,那么在任何给定的一天,看到这些实验的人数可能只有几百人。对此没有简单的解决办法——我们必须根据实验变得具有统计显著性的可能性来管理和优先考虑我们的实验,或者有时,只是接受我们在几周内不会得到结果的事实。

组织级实验

我们面临的另一个挑战是,大多数行业现成的实验工具都是面向用户级实验和度量的。虽然这些数据对我们有一定的价值,但是可用的工具不允许我们在更高的层次上对数据进行分组。为了正确理解组织如何使用我们的产品,我们需要能够在组织层面上测量我们的实验的影响。我们还希望在一个组织内为所有用户提供一致的体验。这对我们来说至关重要:为了真正理解我们的实验对一个组织的影响,我们需要该组织内的所有用户进行相同的实验,并在组织级别上测量它们的影响。在 CircleCI,我们使用 Optimizely,这使我们能够按组织交付实验,并在组织级别收集数据进行分析。

结论

增长工程作为一种支持业务增长的产品开发方法,正在我们的行业中获得发展势头。然而,这仍然是一种新的做法,大多数采用这种做法的公司都在用户层面迎合他们的增长,并希望让用户尽可能积极地参与他们的产品。

正如我们所展示的,由于我们拥有的工具和用户的类型,我们正面临着这种方法的独特挑战。我们专注于让用户参与进来,但对我们来说,这意味着让用户花更少的时间在我们的前端。我们还在建立自己的指标和数据管道,以确保我们能够在组织层面而不是用户层面衡量我们的实验的影响。

想为 CircleCI 的增长工程做贡献吗?参见我们的开放角色

混沌测试:云原生应用的可靠性

原文:https://circleci.com/blog/chaos-testing-for-app-reliability/

可靠性是软件交付团队的一个关键问题。性能低下或服务中断的每一秒都会带来高昂的成本。其后果不仅仅是金钱上的支出,还会对公司的声誉产生巨大影响。在 2022 年进行的一项调查中,参与者报告称,超过 60%的数字基础设施故障会导致 10 万美元或更多的损失。

尽管对每一种可能的情况都进行测试可能是不现实的,但是创建可靠的应用程序的重要性给开发人员带来了巨大的激励,使他们在整个软件开发生命周期中实施广泛的测试,以消除错误和模拟故障。

随着应用程序(尤其是云原生应用程序)变得越来越相互关联,越来越依赖于各种各样的系统,预测系统如何响应故障变得更加复杂。云原生应用依赖于跨多个环境运行的许多依赖项和微服务。为了理解这些系统如何相互影响,开发人员必须开发新的解决方案和策略来测试软件。

混沌测试是一种在问题产生之前找到弱点的方法。创造受控的混乱和模拟真实的故障给开发人员和站点可靠性工程师(SREs)一个机会来弄清楚为什么以及如何可能发生中断。

什么是混沌测试?

把混沌测试看作一个更大的框架——混沌工程的一个组成部分。混沌工程对应用程序在压力条件下的表现做出假设,然后对应用程序进行模拟实际故障条件的混沌测试。

混乱测试可能包括关闭虚拟机或阻止对微服务的访问。其他 chaos 测试采用了一种更精细的方法,引入了一些问题(如延迟或连接错误),以研究这些因素如何影响应用程序性能或导致中断。

混沌工程的起源

虽然开发人员已经寻找模拟故障的方法很长时间了,但现代混沌工程的概念始于 2010 年代初的网飞。一位名叫 Greg Orzell 的工程师有了一个聪明的想法,他创建了一个工具,可以终止网飞生产环境中的随机实例——允许团队对他们的应用程序进行压力测试。

奥泽尔的工具——现在被称为混沌猴——最终在网飞之外找到了出路,并于 2012 年向公众推出。虽然很新颖,但 Chaos Monkey 是一个简单的工具,在生产或测试环境中通过简单地关闭随机的服务器组来工作。

Chaos Monkey 项目后来扩展成一整套工具,能够创建许多不同类型的混沌测试。被称为猿猴军的这个项目带来了诸如潜伏猴、一致性猴、安全猴等工具,以及各种各样的混沌测试能力来补充混沌猴的基本功能。

混沌测试的好处和挑战

通过适当的规划和实施,混沌测试可以识别错误,并为应用程序及其环境提供宝贵的见解。以下是一些主要的好处:

  • 理解系统操作:一个设计良好的混沌测试可以揭示应用程序如何响应紧急情况的许多有价值的见解。在进行混沌测试之前,工程师首先测量稳定条件,然后制定一个关于系统如何处理特定类型压力的理论。通过将实验结果与理论模型进行比较,团队可以深入了解他们的系统如何工作以及哪些方面可以改进。
  • 增强可靠性:混沌测试通常可以揭示潜在危害可靠性的缺陷。系统经常以难以预测的方式对故障做出反应。例如,混乱测试可能揭示看似不相关的微服务在停机期间如何相互影响。这对于具有许多离散服务的云原生应用程序来说尤其有用。随着软件变得越来越复杂和相互关联,模拟问题通常是理解系统不同部分如何相互影响的最快方法。
  • 压力测试事件响应:确保可靠性是主动和被动的同等部分。虽然大部分混沌工程侧重于收集信息,以主动提高可靠性,但它也可以协助反应方面:事件响应。通过混沌测试模拟实际事件,组织有一个难得的机会看到他们的事件响应策略在发挥作用,使他们能够评估其性能并做出调整,以更好地为实际事件做准备。

然而,混沌测试并非没有挑战,包括:

  • 假设和建模失败:为了使混沌测试有效,首先了解系统的常规操作以及它将如何响应混沌测试是至关重要的。没有一个明确的假设和模型,结果可能是不确定的,混沌测试的洞察力可能是有限的。因此,有必要强调正确规划混沌测试的重要性。
  • 意外的损害:有时,测试的“受控”混乱会产生意想不到的后果。在生产环境中进行测试时,如果“爆炸半径”(给定测试的最坏情况)没有得到适当控制,模拟事件有可能变成实际事件。应该采取措施确保混沌测试只影响预期的系统。测试人员应该能够停止混乱测试,并在不造成损坏的情况下将系统恢复到正常运行状态。
  • 不充分的可观测性:为了使混沌工程有效,测试人员依赖健壮的可观测性工具来监控和记录测试的影响。如果没有合适的工具来监控性能和收集系统指标,混沌测试可能会白费力气,无法生成提高系统可靠性所需的数据。

作为 DevOps 的一部分实现混沌测试

混沌工程实验已经迅速成为测试可靠性的主要手段。随着它越来越受欢迎,新的工具和技术已经发展到允许更有效的混沌测试。

Gremlin混沌网这样的平台使得团队能够设计、执行和测量混沌测试。这些工具甚至可以帮助自动化混沌测试的过程。你可以定期设置测试或者配置成随机运行来模拟不可预知的故障。按计划运行自动化混乱测试允许团队定期了解他们的系统如何处理紧急情况。通过定期对系统进行模拟故障,一致的、预定的测试有助于团队了解其可靠性和事件响应的发展。

因为混乱测试经常在生产环境中运行,团队需要设计混乱测试方法来隔离测试的影响。混沌工程师应该试图理解和控制测试的“爆炸半径”。在准备混乱测试时,工程师应该关注对系统的直接受影响部分和任何其他互连服务进行测试的潜在后果。

例如,影响交付前端内容的微服务的测试可能能够运行,而不会产生许多意想不到的影响。相比之下,影响基本网络服务的测试可能会导致更深远和不可预测的问题。

增强云原生可靠性

无论您是想确保关键任务系统的可靠性,还是想更好地了解应用程序如何处理故障,混沌测试都可以提供宝贵的数据。在微服务彼此高度依赖的云原生系统中,即使是一个小问题也会产生间接后果,影响远程连接的服务。幸运的是,现代混沌工程工具可以帮助开发人员识别这些系统在发生故障时如何相互影响。

当谈到提高可靠性或压力测试事件响应时,对于导致停机的计划外事件,没有确切的模型。但是,一个设计良好的混沌测试可以提供一个接近的第二,特别是如果设计模拟一个真实的事件。混沌测试允许工程师在受控环境中探索典型或极不寻常的情况,提供可能只有在代价高昂的事件发生后才能发现的洞察力。

使用 CRIU - CircleCI 检查点和恢复 Docker 容器

原文:https://circleci.com/blog/checkpoint-and-restore-docker-container-with-criu/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


TL;灾难恢复:Docker 检查点和恢复的简单演示

启动容器

$ export cid=$(docker run -d busybox tail -f /dev/null) 

检查集装箱

$ docker checkpoint $cid
7cc692f22c11 

它不再跑了

$ docker ps --quiet
No containers shown here 

恢复容器

$ docker restore $cid
7cc692f22c11 

它又跑了!!

$ docker ps --quiet
7cc692f22c11 

什么是 CR 和 CRIU?

CR (checkpoint and restart)是一种将进程的内存状态保存到文件中,并从保存的状态恢复进程的技术。 CRIU 是最初开发用于 LXC 集装箱的工具。

既然 Docker 可以运行 LXC 容器,我们应该可以通过使用 CRIU CR Docker 容器,对吗?我以前做过这个实验,写过这篇文章。不幸的是,实验没有成功,因为 CRIU 当时没有很好地支持 Docker。

一年多过去了,CRIU 团队为支持 Docker 付出了很多努力。

在这篇文章中,我将向您展示 Docker 检查点/恢复如何与 CRIU 一起工作,以及为什么我对它的用例感到兴奋。

CRIU Vagrant box

使用 CRIU 的 CR Docker 仍在实验中,因此您需要构建启用了实验特性的 Docker。此外,它没有完全合并到 Docker 中,所以你需要使用 CRIU 团队的一个开发人员创建的 Docker 的一个分支。除此之外,您还需要编译启用了特殊内核模块的内核。

做这些事情都不是很好玩,但是不用担心!我已经为你创建了一个包含所有这些事情的流浪盒子,并且已经上传了!你只需要下载盒子并在你的本地机器上运行。

旋转流浪盒

假设 VM 的名字是 vg-1 ,运行下面的命令。

vagrant box add https://atlas.hashicorp.com/kimh/boxes/criu
mkdir 'path to vg-1'
cd 'path to vg-1'
vagrant init kimh/criu
vagrant up
vagrant ssh 

这就是你尝试 CRIU 所要做的一切!

docker 恢复/检查点命令

运行在 vg-1 上的 Docker 被启用了实验性功能,并拥有两个你从未见过的命令。

$ docker checkpoint --help

Usage:  docker checkpoint [OPTIONS] CONTAINER [CONTAINER...]

Checkpoint one or more running containers
....

$ docker restore --help

Usage:  docker restore [OPTIONS] CONTAINER [CONTAINER...]

Restore one or more checkpointed containers
....

这些checkpointrestore命令使用了我编译并安装在流浪者盒子上的 CRIU 可执行文件。

$ criu --help

Usage:
  criu dump|pre-dump -t PID []
  criu restore []
  criu check [--ms]
  criu exec -p PID
  criu page-server
  criu service []
  criu dedup

.... 

这里有一个非常简单的例子来说明如何 CR Docker 容器。

启动容器

docker run \
  --name np \
  --rm \
  busybox:latest \
  /bin/sh -c \
  'i=0; while true; do echo $i; i=$(expr $i + 1); sleep 1; done' 

这个命令下载 busybox 映像并运行一个数字打印机容器,该容器一直打印递增的数字。

检查容器

现在,让我们检查一下这个集装箱。你可以用docker checkpoint命令来完成。因为 number-printer 容器在前台运行,所以从另一个终端执行此操作。

docker checkpoint np 

一旦你检查容器,你会看到它停止打印数字。docker ps命令不再显示容器。

恢复容器

让我们用docker restore命令恢复数字打印容器。

docker restore np 

如果恢复成功,数字打印容器再次开始打印数字。也是回到了docker ps的结果里。我们成功地检查和恢复了容器!

暂停/取消暂停与检查点/恢复

你可能会说,“等等,你已经可以用 docker pause/unpause 命令做到这一点了”。

没错,在前面的示例中,您看不到暂停/取消暂停和检查点/恢复之间的行为差异。这两个命令都在中途停止容器的作业,稍后再继续。

区别在于检查点/重启将容器的内存状态保存到磁盘中,而暂停/取消暂停则没有。

您可以将 pause/unpause 视为向 UNIX 进程发送 SIGSTOP 和 SIGCONT。您可以在前台运行一个进程,按 Ctrl+z 将停止该进程。也可以用fg job-id还原过程。暂停/取消暂停命令做的事情与运行容器类似。

检查点/恢复做更复杂的事情。它将正在运行的容器的内存状态转储到磁盘中,并通过读取内存转储来恢复容器。

因为检查点/恢复将容器的状态保存到磁盘中,所以我们可以做更多有趣的事情,而这些事情是暂停/取消暂停所做不到的。我们将在下一节看到这些有趣的用例。

用例

以下是 CR Docker 容器的一些使用案例。

(1)恢复长期运行的容器

你有时想运行需要很长时间的任务。例如,如果您在 Docker 容器上运行一个计算圆周率位数的程序,该容器需要长时间运行才能计算万亿位数。

但是如果你不小心关闭了你的主机呢?这将关闭 Pi 容器,您将丢失容器计算的万亿位数。

CRIU 是解决这个问题的一个很好的工具。你可以定期检查集装箱,为事故做好准备。如果事故发生,你只需要恢复容器,中间恢复计算。

(2)加快慢速启动集装箱

有些应用程序需要很长时间才能启动。您可以通过在缓慢的应用程序启动后对容器进行检查点操作来加速这些容器。

这里有一个例子来演示这个用例。我们假设 redis 启动超级慢(这在现实中完全不成立!!).

我们只是像往常一样启动一个 redis 容器。

cid=$(docker run -d redis) 

因为 redis 在这个博客的世界中启动很慢,所以我们需要等待 20 秒,redis 才能准备好接受连接。等了 20 秒后,我们检查了集装箱。

docker checkpoint --image-dir=/tmp/redis $cid 

我们需要将 redis 容器恢复到一个新的容器中,以便您可以从保存的容器中启动多个容器。为此,您需要使用–force = true并传递一个新的容器 id。

docker create --name=redis-0 redis
docker restore --force=true --image-dir=/tmp/redis redis-0 

启动的容器立即准备好接受连接,无需等待 20 秒。

酷的事情是,你可以重复同样的过程来启动多个容器非常快。

for i in 1 2 3 4 5; do
  docker create --name=redis-$i redis
  docker restore --force=true --image-dir=/tmp/redis redis-$i
done 

上述示例理论上需要 100 秒(20 秒 x 5)才能完成,没有 CR。有了 CR,这五个容器瞬间就启动了。

(3)集装箱迁移

你可以和 CRIU 一起做码头集装箱迁移。

要了解这是如何工作的,您需要运行两个流浪虚拟机。你应该已经运行了 vg-1 ,所以你需要再运行一个虚拟机 vg-2

创建 vg-2

mkdir 'path to vg-2'
cd 'path to vg-2'
vagrant init kimh/criu
vagrant up 

一旦 vg-2 启动,运行以下命令来运行容器。这是必要的,因为在 CRIU 有一个错误。否则,下面的例子不起作用。所以,请不要忘记这样做。

ssh 漫游–' dock run–name = foo-d busybox tail-f/dev/null & & docker RM-f foo '

你现在运行的是两个流浪虚拟机 vg-1vg-2 。在 vg-1 上运行数字打印机容器。

docker run \
  -d \
  --name np busybox:latest \
  /bin/sh -c \
  'i=0; while true; do echo $i; i=$(expr $i + 1); sleep 1; done' 

你可以看到数字打印机容器一直在打印数字。

$ docker logs -f np
1
2
3
4
5
.... 

现在让我们将容器迁移到 vg-2 。我做了一个助手 shell 脚本来做这件事,所以你需要把它下载到你的本地机器上。

curl -L -o docker-migrate.sh https://gist.githubusercontent.com/kimh/79f7bcb195466acea39a/raw/ca0965d90c850dcbe54654a6002678fff333d408/docker-migrate.sh
chmod +x docker-migrate.sh 

您需要传递三个参数来使用该脚本。

第一个参数是要迁移的容器的名称。在我们的例子中,是 np

第二个和第三个参数是迁移源目录和迁移目标目录的路径。

docker-migrate.sh np 'path to vg-1' 'path to vg-2'

Ex. /tmp/docker-migrate.sh np /Users/kimh/vagrant/vg1 /Users/kimh/vagrant/vg2 

一旦脚本成功完成,你可以去 vg-2docker logs -f np命令检查。您应该看到数字打印容器从它在 vg-1 上暂停的地方恢复打印数字。

Migration_Succeeded迁移成功!

包扎

我们已经看到了如何用 CRIU 和一些用例来 CR Docker 容器。我希望人们能想出更多有趣的用例,并开发利用 CR 优势的工具。

这里是有用的资源,以了解更多关于 CR Docker 与 CRIU。

CRIU 主网站 Docker 叉着 CRIU 支持 Kubernetes 关于 CRIU 的博客

为工作选择正确的(构建)工具| CircleCI

原文:https://circleci.com/blog/choosing-the-right-build-tool-for-the-job/

除了选择编程语言、编辑器、CI/CD 和 VCS,选择构建工具是我们作为开发人员可以做出的最重要和最有影响力的决定之一。每次测试或编译应用程序时,或者捆绑应用程序以部署到生产环境时,都会用到它。

考虑到构建工具的重要性,选择它可能是一项艰巨的任务,在这篇文章中,我将试着让它变得简单一些。

我将介绍几件事情:

  • 构建工具的作用和一些工具的例子
  • 为什么您可能想要考虑构建工具——无论是从零开始一个项目,还是当您正在迁移一个现有的项目时
  • 您应该如何测试任何构建工具,以确保它是最适合您、您的团队和您的项目的

注意: 这篇文章假设你需要一个商业项目的构建工具(为你赚钱的工具)。一个爱好或教育项目会有不同的需求,这些需求会影响您对工具的选择。既然如此,你做你的,做你想做的。

剧透提示:我有偏见,我相信默认的、最广泛应用于你的平台的工具很可能也是最适合你的。

什么是构建工具?

构建工具将许多实用程序和脚本结合在一起,用于编译、打包、测试、运行和发布应用程序。通常他们会让你进行依赖管理。有些允许你编写更多的步骤和插件,比如代码覆盖率和安全扫描。您的构建工具充当与 CI/CD 系统的单点联系,取代了必须手动运行的各个工具。

构建工具的示例

一些流行的构建工具包括 NPM 和 Yarn - for Node、Gradle 和 Maven。Ant 是 Java 的构建工具,Rake 是 Ruby 的构建工具,Make 是 Unix 和 Linux 系统的构建工具。一个特定的工具可能与他们使用的平台和编程语言相关联,比如用于 JVM 的 Gradle 和用于基于 Ruby 的项目的 Rake。这意味着它们为处理各自的平台带来了有用的抽象。有些还被设计成语言不可知的,如 Make 或 Bazel,这使得它们可以被在多种平台和语言上开发的组织使用。

工具的制造者和维护者也各不相同。Make 和 Rake 是“适当的”开源项目,仅由社区提供支持。NPM 和格雷迪得到了他们各自盈利组织的支持,这些组织对企业功能、支持或服务收费。像 Bazel 和 Buck 这样的工具是由大型科技公司(分别是谷歌和脸书)作为内部工具开发的,他们决定作为开源项目发布,而不从中赚钱。

为什么选择构建工具?

既然我们已经介绍了构建工具领域的一些例子,我们可以看看您选择工具的可能原因。

如果你是从零开始一个项目

选择构建工具的第一个原因是从头开始一个项目。如果你不想手动调用所有低级命令,你需要一个构建工具。手工努力不可避免地以一组未经测试的、半生不熟的 bash 脚本告终,这是一个糟糕的想法。

我建议您使用默认工具,无论您的编辑器、项目生成器或模板使用什么工具。例如,对于 Android 项目,缺省值是 Gradle。或者,您可以使用通用工具,如 Buck 或 Bazel,或者不同的 JVM 专用工具,如 Maven。

如果您正在迁移现有项目

第二个原因是,有时默认的构建工具并不适合您。这可能是因为您正面临着性能问题:构建缓慢或内存不足。有时构建是易变的和不确定的,导致您花费大量时间调整构建脚本和配置项配置,试图修复构建。

在这种情况下,换一种不同的工具,也许是一种在博客帖子和会议讨论中被誉为速度极快并被大型科技巨头接受的工具,可能是令人信服的。我过去也是这么做的。

挑选构建工具的步骤

我建议您通过一系列测试来使用这个构建工具,看看它如何为您的项目工作。这些测试从简单到复杂,后面的阶段可能会花费你几天甚至几周的时间。同样,选择构建工具是一个重大的决定,所以要小心行事。花些时间记录每个测试的整个过程和结果。

选择一个构建工具不应该是一个人的努力,所以要确保你的团队成员尽早并且经常参与进来。任何新工具或流程要取得成功,都需要他们的认可。构建工具也不例外。

从问题陈述开始

考虑你在构建工具中寻找什么。选择工具有许多合理的理由,但是“我们应该采用它,因为 BigCo Inc .使用它”不是其中之一。该测试可能需要几个小时才能完成,通常更短。

以下是一些好的问题陈述的想法:

  • 开发人员生产力既适用于现有团队成员和新入职员工,也适用于您内部团队和部门的生产力。

  • 构建的性能在将现有项目迁移到新的构建工具时尤为重要,因为构建的性能会降低。有时这是一个合理的担忧,但其他时候这可以通过调整和优化现有工具的使用来弥补。例如,优化您的 CI/CD 构建配置也可以提高性能。

  • 整个组织的统一意味着建立一个公共的构建基础设施团队,并配备专家来维护和调整整个组织的构建工具和基础设施。为了使这种策略有效,您的组织应该足够大,以维持一个基础结构团队。在这种情况下,将一些工作交给这个团队是有意义的。参见:如何成为一名 CI/CD 工程师

如果您努力提高开发人员的生产力,并且其他两个问题都不是问题,那么您的团队使用默认选项可能会得到最好的服务。虽然每个 Android 开发人员都知道如何使用 Gradle,但很少有人会精通 Buck 或 Make。

评估文档

一旦您知道了选择构建工具的主要动机,您就可以开始考虑第二个因素了——评估文档和其他学习材料。这应该比定义您的问题陈述花费更长的时间,但是您不应该在每个构建工具上花费超过一天的时间。

您在文档中寻找的内容可能会有所不同,但您最终可能会问以下一些问题:

  • 创建该工具时的动机和设计考虑是什么?这可能是 docs 的一部分,也可能是一篇独立的博客文章,但是能够进入作者的大脑,了解他们最初为什么想要创建这个工具是非常好的。也许它特别适合在 monorepo 中构建微服务,而这正是您所需要的。

  • 文件是否完整?检查文档的各个页面,看看你是否找到任何仍然保留的重要文档,比如如何测试你的应用部分。这取决于你对风险的容忍度,但是不完整的文档可能意味着工具还没有准备好。

  • 跨各种渠道的支持是怎样的?文献不仅仅是docs/。这个社区大吗,活跃吗,乐于助人吗,支持吗?是否有行为准则和版主来维护?检查 GitHub 上公开和关闭的问题,StackOverflow 上该工具的受欢迎程度,以及您可能使用的任何其他渠道,如 Slack teams 或 Discord 服务器。如果缺少文档,一个有用的社区肯定是必要的。

  • 代码看起来怎么样?检查重要文件中是否有遗漏的注释。工具是否集成了 CI/CD 服务?主枝是否通过?最新版本是什么时候?不同的用例有很多例子吗?

如果该工具满足或超出了您对所有这些因素的预期,请继续。否则,使用默认设置。

评估一个示例项目

现在是时候看看他们的一些样本或教程了,看看他们感觉如何。克隆一个示例项目(或者更好的是,按照一步一步的教程),并探索它。如果你想彻底做到这一点,一个工具很容易就能花上一天的时间。

在本地构建项目,然后运行测试、更改步骤、添加依赖项、编写附加脚本,并与您的团队使用的第三方服务集成。添加您认为您的真实项目将需要的任何内容。

该工具的行为如何?一切都如你所愿吗?示例项目是否适合您的平台和语言选择,或者您是否必须从头开始创建它?

此时,您应该已经用您的 CI/CD 工具建立了示例项目,并确保它能够很好地构建和运行测试。您的默认构建工具可能是开箱即用的,但对于不太流行的工具,您可能需要将它安装在您的构建机器上。安装时间和难度会有所不同。检查安装和设置需要多长时间。这个过程顺利吗,或者你遇到了什么小故障吗?

一旦您在 CI 平台上构建了最小的示例项目,您还可以返回并跨您正在评估的多个构建工具重新实现相同的示例应用程序。如果您正在寻找速度和性能的改进,您的 CI 平台应该为您提供一种简单的方法来测试和比较结果。

前面的步骤简单易行吗?在构建或测试它时,您是否经历了巨大的努力?你遇到文档问题了吗?与下一步考虑相比,这些问题中的任何一个都可能只是冰山一角。

迁移到真实项目(或者尽可能接近真实项目)

如果你仍然快乐,仍然试图做出决定,是时候迈出一步,真正使用这个工具了。如果您正在迁移一个现有的项目,您知道您需要构建什么。如果您不是,并且正在研究一个从零开始构建的项目,请扩展示例以包含您可能需要的所有内容。

您将在此执行的检查与示例项目的上一步类似,但是具有使用真实场景评估工具的优势。最重要的是要注意这个练习的时间。花几天,一周——无论你需要多长时间。咨询团队,并像在 sprint 计划中一样添加一些内容。

在本练习结束时,您应该准备好做出决定,并让团队相信您选择的构建工具是正确的选择。

获得团队成员的同意

团队对开发人员工具的认可就是一切;有了像构建工具这样有影响力的东西,就更是如此了。与您的团队交流,并向他们展示您的测试结果。确保他们知道该工具的与众不同之处,以及使其成为团队正确选择的复杂性和细节。

学会如何使用这个工具花了多长时间?用这个时间乘以你团队的人数。这足以说服你的经理和利益相关者吗?如果你对自己的选择有信心,并有数据证明,你应该处于有利位置。向前去建造吧!

结论

开发人员的构建工具是与编程语言和框架同样重要的选择。知道并理解你为什么选择某样东西会对你和你的团队的生产力有很大的影响。我有意谨慎地提供了这些考虑因素。他们喜欢现状和你平台的默认选项。根据我的经验,作为一名成功的开发人员,良好的社区支持、文档和示例项目是你真正需要的。一个好的构建工具也会帮助你、你的团队和你的产品取得成功。

选择正确的技术组合| CircleCI

原文:https://circleci.com/blog/choosing-the-right-technology-stack-for-your-startup/

选择技术组合时要考虑的因素

创业是一个不可思议的旅程,但往往会给他们的领导者带来很大的压力,让他们做出正确的选择并快速前进。每个决定都让人觉得至关重要。有几个关键策略可以帮助你专注于重要的事情,而不是在不重要的事情上浪费时间。这篇博客要考虑的因素与技术变革成本和关键的领导决策有关。

以下是领导层在选择技术组合时需要考虑的三大因素:

  • 改变技术的成本
  • 产品与市场的匹配
  • 在正确的时间做出正确的技术决策:廉价技术与昂贵技术

寻找一组更详尽的因素来考虑你的技术堆栈下载我的最新电子书,选择一个你不会后悔的技术堆栈:快速增长的初创公司专注于技术领导的秘密

不同技术的成本各不相同

技术堆栈的变化往往伴随着高昂的价格。意识到改变技术的潜在成本对于避免时间浪费和确保团队灵活性是至关重要的。在选择技术时,一定要考虑价格标签,以及改变的决定可能会给你带来的代价。

例如,选择最新的技术,因为它们有趣或满足特定的功能,可能会在短期内解决你的问题,但从长期来看,可能会让你付出更大的代价。事实是,没有哪种技术能帮你一把,因为它是新的,与其他人使用的不同。唯一能帮你一把的东西是每个人都知道如何使用的东西。

与堆栈相关的决策是技术性的,它们对您的用户来说并不重要。产品有。构建伟大的产品放在用户手中,以后再担心最新的和最伟大的补充。

选择支持您创新的技术,以发现产品与市场的契合度

现代软件工具使得你的想法在第一天就呈现在用户面前成为可能——但情况并非总是如此。通过使用云部署平台,您能够以前所未有的速度将您的想法传递到用户手中。这样,当用户与你的产品交互时,你就可以完全专注于产品的开发,这样你就可以尽快地从这些用户那里学到东西。

这种方法与专注于产品市场的有关。一定要让你的产品出现在用户面前,这样你就可以快速失败,并在需要的地方重复计划。

一套合适的工具将让你做出正确的决定

作为一家旨在扩大规模和发展的初创公司的技术领导者,你会发现你的工作将很快从编写代码转向为团队设定方向。一旦你雇佣了第一批两三名工程师,这种转变可能会比你预期的要快得多。你如何为你的团队做出好的决策,建立一个你不会后悔的技术架构?

他们说时机对一个创业公司来说就是一切,但是仅仅因为你已经听过一百万次了,这并不意味着它是不正确的。你的时机,你选择今天关注什么,忽略什么,都是至关重要的决定。这些决定如此重要,他们应该感到不舒服。

要知道,如果你把产品与市场的契合度放在首位,而把其他所有目标都排除在外,你将会制造混乱和问题,而这些问题你将不得不在以后清理。你将选择快捷方式或技巧,帮助你快速向用户推出产品,快速推出新功能或改进,并获得你是否正确的信号。

寻找最适合您的技术组合

每个创业公司在目标、愿景和团队结构上都是独一无二的。通过坚持不懈地推动产品与市场的匹配,并随着规模的扩大与技术复杂性作斗争,您将能够根据取得成功的需要,尽可能快、尽可能频繁、尽可能经济高效地进行调整。

作为一名技术领导者,如果你保持专注于你的最高杠杆决策,你将处于一个有利的位置,有时间和金钱来解决你快速但有战略意义的早期决策。凭借策略、不懈的专注和一点点运气,你会成功的。

希望深入了解如何使用您的技术堆栈(以及我的 14 条技术领导力规则)?下载我最新的电子书,选择你不会后悔的技术组合:快速成长的初创公司专注技术领导力的秘密

代码质量度量:如何评估和改进您的代码

原文:https://circleci.com/blog/ci-cd-code-quality-metrics/

优质代码高效可靠,运行良好无 bug,满足用户需求。它可以处理错误或异常情况。它也易于理解、维护和扩展新特性。此外,它的可移植性意味着它可以在尽可能多的机器上运行。

开发团队使用不断变化的代码库。他们添加、删除和修改现有代码,以提高速度或实现新功能。这意味着他们阅读大量的代码,所以对他们来说,质量等同于可读性。

不幸的是,不断的代码变更通常会导致其质量逐渐下降。这个挑战提供了一个机会来检查是什么使得代码可读、可理解,并且具有可持续的高质量。

什么是代码质量,为什么要关注代码质量?

代码越容易阅读,就越容易理解和编辑,并且运行速度更快,错误更少,从而增强了可维护性和可扩展性。所有这些导致了更快的开发和更少的错误,提供了长期的成本降低。

这些特征定义了高质量的代码,但是开发团队如何使代码更容易阅读呢?

您可以从使用描述性变量名开始,但是适当地处置非托管资源也会有所帮助。虽然前者在所有技能水平上都很容易实现,但后者很容易被忽视——不管资历如何。提供描述性的变量名是一种基本的做法,但它可以极大地增强可读性。有效的资源处理需要使用的工具的知识和注意力来有效地管理它们。

为了最好地度量代码质量,所使用的度量标准分为两类:定量的或定性的。

量化代码质量

定量质量测量可以用数值来定义。例如,代码覆盖率——单元测试期间运行的代码的百分比——是一个介于 0%和 100%之间的离散值。该值越大,未被发现的错误就越少。因此,代码质量可能会更高。

考虑每个函数或类的平均代码行数。通常,行数越少,可读性越好。毫无疑问,大多数经验丰富的程序员都遇到过 1000 多个 LOC 函数,这些函数不太可能容易阅读或理解。

关于可维护性还有一个更复杂的度量,叫做圈复杂度。虽然我们很少遇到它,但 Visual Studio(例如)为它的度量提供了默认支持。

圈复杂度计算源代码包含的路径数。If-then 语句、循环、开关和其他控制语句都会增加代码的圈复杂度。不出所料,更高的复杂性意味着维护难度的增加和出现错误的可能性增大。

虽然量化指标至关重要,但它只能提供部分情况。即使 100%的测试覆盖率也不意味着代码没有问题。相反,高圈复杂度不一定会导致严重的可维护性问题。

量化代码度量的解释是一种主观的实践。一个团队成员可能认为 50%的代码覆盖率就足够了,但是另一个成员可能认为 80%是最低可接受的。幸运的是,定量指标找到了与定性指标的平衡。

定性代码质量

定性度量不能用数字来表达,但在评估代码时同样重要。考虑遵守编码标准的重要性,为对象使用有意义的名称,或者建立代码库的最大线宽(线宽是一个数字,但它对代码质量的影响不是)。总之,这些实践可以形成优秀代码的基础。

定性测量可能非常主观。一些程序员喜欢使用更长的变量名来表达他们的目的,而另一些人则更喜欢使用像ordcust这样的缩写名。命名约定因语言而异。这使得定性指标更难定义,特别是对于那些主要语言可能与他们的同龄人不同的人。

提高代码质量并保持关于主观质量度量的公开讨论的一个重要方法是执行定期的代码评审。在这个过程中,同事们可以对彼此代码的整体质量进行评级。

例如,一个由五名开发人员组成的团队可以各自提交代码,这些代码在合并回主分支之前必须经过至少两个同行的审查和接受。从一个方面来说,这个过程可以检测到可能没有被注意到的问题。更重要的是,程序员可以互相学习,并始终保持最新的软件变化。

持续质量:测量 CI/CD 管道中的代码质量

有许多工具和指南可以帮助开发团队跟踪和评估代码质量。无论您使用什么工具,在将代码推送到源代码控制之前,都很容易忘记检查代码。这就是为什么将这些工具集成到您的持续集成和持续部署(CI/CD)管道中非常重要。这样做可以确保每次提交源代码控制时都运行代码质量检查。

例如,linter 可以检查您的代码是否符合您公司的编码标准,并且可以在构建时或提交前运行。许多 CI/CD 工具提供了集成代码质量工具的简单方法。例如,CircleCI 有个球体用于 ESLintPylintsonar cube。还有很多可用的。

ESLint 可以检查您的 JavaScript 语法,以及您是否正确使用了varletconst关键字。它还评估您的变量是否有非预期的范围,这可能会导致意外的行为。Pylint 为 Python 执行相同的任务。您可以配置 linters 来满足您的特定需求和偏好。

SonarQube 是一个强大的工具,可以检查各种编程语言中的数千个问题。它可以检测重复代码、潜在的无限重复、未被丢弃的可支配资源、硬编码的凭据、常见的键入错误,甚至是没有WHERE子句的SQL UPDATEDELETE语句。

SonarQube 已经成为扫描问题代码的事实上的标准,但是还有许多可行的替代方案,比如 Code Climate 。一个有效的代码分析器可以告诉你潜在的问题、最佳实践、错误,甚至是安全隐患。

无论是使用 linter 还是代码分析工具,您都可以让构建在某些问题上失败,或者发送一封电子邮件或列出所有错误和警告的 Slack alert

建立单元测试并在 CI/CD 管道中运行它们也是至关重要的。如果单元测试失败,您的构建将会失败。如果您的代码覆盖率低于某个阈值,您的构建也可能会失败。

结论

代码质量可以提高代码的可读性、可维护性和可扩展性。更好的质量意味着更少的技术债务和更少的错误,导致更容易的开发和更低的成本。

将代码质量构建到您的开发过程中并不困难。理想情况下,您已经从一开始就这样做了,但是开始度量和改进您的代码质量永远不会太晚。

要了解更多可用于评估和提高团队速度和绩效的指标,请阅读如何找到和监控团队成功的工程指标

JavaScript 立即调用函数表达式| CircleCI

原文:https://circleci.com/blog/ci-cd-for-js-iifes/

本教程涵盖:

  1. 何时使用 IIFEs 代替传统函数
  2. 为生活写测试
  3. 集成 CI/CD 进行寿命测试

JavaScript 立即调用函数表达式(IIFEs)是在初始化时执行的函数。可以初始化或定义一个生命(发音为“iffy”)来实现某个目的。在本教程中,您将了解 IIFEs 的用例,以及使用它们优于传统函数的好处。您还将为您的函数编写测试,并为这些测试集成 CI/CD。

先决条件

要轻松跟上,您需要具备:

  • 一个 GitHub 账户。
  • 一个 CircleCI 账户。
  • 本地安装的节点。
  • 对 JavaScript、Git 和单元测试有基本的了解。

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

什么是生活?

IIFE(立即调用的函数表达式)是在 JavaScript 事件循环中被调用或调用时运行的函数。

在某些情况下,拥有一个这样的函数会很有用。

防止全球 JS 范围的污染。在传统的函数中,如果你在函数中创建一个变量,它可以在全局对象中被访问。如果你在生活中定义了一个变量,它只能在函数中直接访问。

例如,库 jQuery 拥有对象$。如果您有另一个模块也导入这个对象,您可能会遇到一些混乱和错误。生活会消除那些错误。您可以使用 IIFE 为 jQuery 及其方法创建一个范围。

使用 IIFEs 的另一个好处是执行异步操作,比如setTimeout()方法。在本教程后面关于闭包的部分,您将有机会使用它。

IIFEs 也可以用来创建私有变量。这些变量在需要防止意外修改或更改重要值的情况下非常有用。

How IIFEs work

设置应用程序

克隆教程的。它包含您将在首选位置使用的代码。打开终端并运行以下命令:

git clone https://github.com/CIRCLECI-GWP/iife-Javascript.git` 

克隆存储库后,通过运行以下命令将目录更改为项目目录:

cd  iife-JavaScript 

您还需要通过运行以下命令来安装所需的依赖项:

npm install 

在函数中使用函数

生活可以用于不同的目的。这些函数的主要用途之一是声明全局变量。

这段代码片段展示了如何使用 IIFE 进行全局变量声明:

// Global variable declaration
<script>
  (function ($){
     let counter = 1;
      $("#increment").click(function () {
        $(this).text("Number of clicks: " + counter++);
      })
  })(jQuery)
</script> 

通过这样使用 jQuery,您可以访问$对象。IIFE 中使用该对象的任何代码都将只引用 jQuery。然后,这个对象让我们可以访问 jQuery 函数,而不会弄乱全局名称空间。

理解生命本身就是函数也很重要。唯一的区别是它们不需要等待调用。通过进一步研究,您可以在这个代码片段中看到:

(function myFunc() { // Beginning of function
    console.log(greeting)
    }) // End of function
    () //calls the function execution e.g. myFunc() 

生命前的分号

需要注意的一件事是没有被封闭的变量或者 JavaScript 认为没有被封闭的变量。如果省略分号,可能会出错,尤其是在使用分号的应用程序中。这里有一个例子:

const greeting = "Hello world"

(function () {
    console.log(greeting);
})() 

如果你运行这个,你会得到一个错误。

Error code

对于像变量这样的未终止语句,自动分号插入 (ASI)和像 webpack 这样的捆绑器将防止这类错误。然而,它们并不适用于所有情况,包括生活。您可以通过在语句的末尾或生命的开头添加分号来绕过这一点。此代码片段执行时没有错误,因为您关闭了变量:

const greeting = "Hello world"; 
(function () {
    console.log(greeting);
})() 

命名与匿名生活

你可以给你的生活命名,也可以匿名。请注意,命名它们并不意味着它们在执行后会被调用。命名非常有用,尤其是当您有几个执行不同操作的生命彼此靠近的时候。以下代码片段显示了一个命名生命的示例:

const greeting = "Hello world";

(function myGreeting() {
    console.log(greeting);
})() 

这个函数被命名为 life,但它并不比未命名的 life 更有优势。未命名的生命没有函数名,只有关键字function。这是匿名生活的结构:

(function() {
//Your code goes here
})() 

语法类似于 JavaScript 的匿名函数。您也可以使用箭头函数变体:

(() => {
//Your code goes here
})() 

使用带有闭包的文件

生命的另一个重要用途是闭包。闭包是函数利用其局部范围之外的变量或其他信息的能力。例如,上一个代码片段中的函数显示了问候语,尽管它的值是在函数之外定义的。这个例子展示了 IIFEs 如何使用闭包:

const friendlyFunction = (function () {
    let greetCount = 0;
    return function () {
        console.log(`Hello ${greetCount}x`);
        return greetCount++;
    }
})();

friendlyFunction();
friendlyFunction();
friendlyFunction(); 

在这段代码中,您定义了生命,并将其存储在变量friendlyFunction中。如前所述,生命在被定义时就被执行。不过在这种情况下,内部函数直到调用friendlyFunction()才会执行。如果您注释掉底部的函数调用,控制台上将不会有输出。

greetCount变量是私有的,只能在生命中访问。当分别调用函数时,greetCount变量的值会随着每次调用而持久化和递增。如果没有闭包,值将始终是0。因此,IIFE 中的闭包确保了全局名称空间不会被污染,即使您决定使用全局范围内存在的变量名来命名您的 IIFE 变量。

积分电路

上一节描述了生命有用的一些方式。如果不编写测试并将它们与 CI/CD 集成,这个列表就不完整。在这种情况下,CircleCI。

要做到这一点,你首先需要为你的生命函数编写测试。您将使用 Jest,这是一个 JavaScript 测试框架。

在克隆的库中的users.js下,你有这种从免费的 API 获取用户的异步生活。以下是您将在本部分使用的内容:

const axios = require('axios')

//Asynchronous IIFEs
const getUsers = (async () => {
    async function fetchUsers() {
        const users = await axios.get('https://jsonplaceholder.typicode.com/users')
            .then((response) => response.data)
            .catch((err) => console.log(err))
        return users
    }

    return {
        array: await fetchUsers()
    } 

})() // should get users when we execute `node users.js`

module.exports = getUsers 

这个函数返回一个包含 10 个用户的数组。您可以使用该数组中的数据来编写测试,以确保获得正确的用户数组:

const getUsers  = require('./users')

describe('JavaScript IIFE test', () => {
    test('should get the users array', async () => {
        const response = await getUsers
        expect(response).not.toBeNull();
        expect(response.array).not.toBeNull();
        expect(response.array.length).toBe(10);
        expect(response.array).toBeInstanceOf(Array);
        expect(response.array[0]).toBeInstanceOf(Object);
    });

    test('should get a single user', async () => {
        const response = await getUsers
        expect(response).toBeDefined();
        expect(response.array[0].name).toEqual("Leanne Graham")
    })
}); 

这个代码片段有一个测试套件,它使用 Jest 来确保您收到的数据是预期的。这个代码可以在app.test.js文件中找到。在本地运行此测试文件:

 npm run test 

如果您的断言是正确的,测试应该通过,如下所示:

PASS  ./app.test.js
  JavaScript IIFE test
    √ should get the users array (1563 ms)
    √ should get a single user (10 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        4.956 s
Ran all test suites. 

积分电路

CircleCI 是一个 CI/CD 工具,它可以帮助你自动化工作流程和运行测试。

在您的项目的根文件夹中,创建一个.circleci文件夹。在里面添加一个config.yml文件。在config.yml文件中,添加以下内容:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:14.17.1
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: JavaScript IIFE test
          command: npm test
      - store_artifacts:
          path: ~/iife-JavaScript 

CircleCI 为管道执行创建了一个虚拟环境。在这个代码片段中,我们正在安装必要的依赖项,为更快的构建保存缓存,然后执行您的测试。

保存您的本地更改,提交并将其推送到 GitHub。登录您的 CircleCI 帐户。在 Projects 选项卡中,单击远程存储库名称旁边的 Set Up Project

Project setup

在弹出的界面上,选择最快,然后点击设置项目

Config.yml

CircleCI 将运行构建作业,它应该会成功。

Successful build

结论

在本教程中,您了解了 JavaScript 立即调用函数表达式(IIFEs)如何因为立即调用而导致更快的工作流。您还了解了生命的不同用例以及它们解决的一些问题。最后,您编写了测试并集成了 CircleCI。我希望你喜欢阅读这篇教程,直到下一篇,继续学习!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

使用 Kubernetes Docker | CircleCI 的 Node.js 项目

原文:https://circleci.com/blog/ci-cd-for-node-js-projects-using-circleci-kubernetes-and-docker-with-deployment-to-the-google-cloud-platform/

2020-01-23-node1.png

图片来源:Meshstudio

自动化软件交付可以解决许多与手动部署相关的问题。这篇文章的目标是提供对 Node.js 项目的持续集成/持续部署(CI/CD)的见解。本教程将重点介绍几种工具的使用:[Docker](https://www . Docker . com/为了帮助我们实现容器化, Kubernetes 将被用作我们的编排工具,谷歌云平台 (GCP)将成为云服务提供商,最后, CircleCI

通过阅读这篇文章,您将对部署自动化有更好的理解。这将减少软件交付所需的时间并提高生产率。这是一个伟大的技能,将有利于许多开发人员。

以下是我们将要遵循的关键步骤:

  1. 构建应用程序并添加测试
  2. 通过 Docker 文件为此应用程序定义新的 Docker 映像
  3. 设置谷歌云平台
  4. 配置 Kubernetes
  5. 添加一些脚本
  6. CircleCI 设置

构建应用程序

首先,创建一个index.html文件,并用以下内容填充它:

<html>
 <head>
  <title></title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
 </head>
 <body>

  <div class="col-md-10 col-md-offset-1" style="margin-top:20px">
   <div class="panel panel-primary">
     <div class="panel-heading">
       <h3 class="panel-title">Welcome To My Demo App</h3>
     </div>
      <div class="panel-body">
       <div class="alert alert-success">
          <p>This is a basic app used to demonstrate CI/CD on Google Cloud using K8s, CircleCI and Docker </p>
       </div>
         <div class="col-md-9">
           <p>Author:</p>
           <div class="col-md-3">
             <a href="https://twitter.com/delan_hype">@delan_hype</a>
           </div>
         </div>
      </div>
  </div>
  </div>

<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
 </body>
</html> 

接下来,用以下内容创建一个名为server.js的服务器文件:

const express = require('express');
const path = require('path');
const morgan = require('morgan');
const bodyParser = require('body-parser');

/* eslint-disable no-console */

const port = process.env.PORT || 3000;
const app = express();

app.use(morgan('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: 'true' }));
app.use(bodyParser.json({ type: 'application/vnd.api+json' }));

app.use(express.static(path.join(__dirname, './')));

app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, './index.html'));
});

app.listen(port, (err) => {
  if (err) {
    console.log(err);
  } else {
    console.log(`App at: http://localhost:${port}`);
  }
});
module.exports = app; 

要创建的另一个重要文件是一个package.json文件,它将保存这个项目所需的 Node.js 包。创建文件并复制以下内容:

{
  "name": "gcloud-deploy-node-app-ci-ci-circleci",
  "version": "1.0.0",
  "description": "Tutorial leading to a deployment of a simple Node.js app to gcloud",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "test": "mocha"
  },
  "keywords": [
    "gcloud",
    "node"
  ],
  "author": "Collins",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.17.2",
    "express": "^4.15.3",
    "morgan": "^1.8.2"
  },
  "devDependencies": {
    "chai": "^4.1.2",
    "mocha": "^5.2.0",
    "request": "^2.88.0"
  }
} 

在本地运行应用程序

导航到上面项目所在的目录。通过运行以下命令安装所需的 Node.js 包:

npm install 

现在,您可以使用以下命令启动应用程序:

npm start 

在浏览器中,导航到 http://localhost:3000。应用程序应该如下所示运行:

本地运行的项目

Docker 设置

按照这个链接的说明安装 Docker。当你打开上面的链接,你会发现为你的操作系统安装 Docker 的基本步骤。

如果你想跟进但不想自己构建项目,你可以在 GitHub 这里找到回购。这是一个简单的 Node.js 应用程序,为 HTML 页面提供服务。我构建这个项目是为了让这篇文章能够实际操作,我保持它的简单,这样我就可以清楚地演示 CI/CD 流程。在存储库中,您会找到如下所示的各种文件:

项目文件夹结构

为了将项目归档,我们需要创建一个 dockerize 文件,包含以下几行:

FROM node
ENV NPM_CONFIG_LOGLEVEL warn
RUN mkdir -p /usr/src/app
EXPOSE 3000
WORKDIR /usr/src/app
ADD package.json /usr/src/app/
RUN npm install --production
ADD . /usr/src/app/
ENTRYPOINT ["npm", "start"] 

打开一个文本编辑器,将这些行复制到其中。将文件保存为项目根目录中的Dockerfile

在本文中,我不会深入介绍 Docker,但是对于那些不熟悉 Docker 的人,让我解释一下上面文件中的一些命令:

FROM - 在我们的例子中,构建新映像的基础映像是节点。该命令必须位于 Dockerfile 文件的顶部。
ENV-它定义了一个环境变量。
RUN - 用于在 docker 映像的构建过程中执行命令。
添加- 将文件从主机复制到新的 docker 镜像。
ENTRYPOINT - 定义容器运行时将执行的默认命令。
工作目录- 这是要执行的入口点命令的指令。
EXPOSE - 公开容器运行的指定端口。

现在,让我们建立我们的形象。运行:

docker build -t ci-cd . 

结果将类似于:

Docker build steps

Docker 构建步骤

为了测试图像是否构建良好,让我们运行它,看看是否可以访问我们的应用程序。

Dockerized application running

Dockerized 应用程序运行

导航到 http://localhost:3000 应该会产生以下结果:

Dockerize application running - access on browser

在浏览器上实现应用程序运行访问

这表明项目构建成功。我们需要做的下一件事是将它推送到 Docker 注册表。这是存储图像的地方。谷歌计算注册表(GCR)将在这里使用,因为我们正在使用 GCP。

标记图像:

docker tag <HOSTNAME>/<YOUR-PROJECT-ID>/<IMAGE-NAME> 

示例:

docker tag ci-cd gcr.io/ci-cd-12347/ci-cd:0.1.0 

推送图像:

gcloud docker -- push <HOSTNAME>/<YOUR-PROJECT-ID/<IMAGE-NAME> 

示例:

gcloud docker -- push gcr.io/ci-cd-12347/ci-cd:0.1.0 

Google 云平台设置

我将使用 GCP 作为应用程序将部署的云服务提供商。您需要做的第一件事是安装和配置gcloud。如果你没有谷歌云账户,你可以在这里创建一个

Mac OS/Linux

在您的终端上,运行:

curl https://sdk.cloud.google.com | bash 

完成上述步骤后,重新启动 shell:

exec -l $SHELL 

初始化gcloud环境:

gcloud init 

Windows 操作系统

在这里下载安装程序并启动它。安装完成后,启动 cloud SDK shell,使用命令初始化gcloud:

gcloud init 

接下来要做的是通过运行以下命令来验证gcloud:

gcloud auth login 

将自动打开一个浏览器,提供将在终端上显示的链接。类似于:

Google Cloud authentication

谷歌云认证

在打开的浏览器选项卡上,点击允许认证gcloud:

Granting Google Cloud SDK permission

授予 Google Cloud SDK 权限

Kubernetes 设置

在这个例子中,我们将使用 Kubernetes 来编排和管理容器。安装 Kubernetes 有多种方法。最简单的方法是使用下面的命令下载作为 Google Cloud SDK 一部分的kubectl二进制文件:

gcloud components install kubectl 

通过运行以下命令确认 Kubernetes 安装正确:

kubectl version 

输出将类似于:

Client Version: version.Info{Major:"1", Minor:"8", GitVersion:"v1.8.6", GitCommit:"6260bb08c46c31eea6cb538b34a9ceb3e406689c", GitTreeState:"clean", BuildDate:"2017-12-21T06:34:11Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"8", GitVersion:"v1.8.0", GitCommit:"0b9efaeb34a2fc51ff8e4d34ad9bc6375459c4a4", GitTreeState:"clean", BuildDate:"2017-11-29T22:43:34Z", GoVersion:"go1.9.1", Compiler:"gc", Platform:"linux/amd64"} 

对于成功的安装,您应该看到客户端和服务器版本。

接下来,按照此处列出的步骤创建一个 GCP 项目。该项目将帮助我们创建、启用和使用所有的 GCP 服务,如 Kubernetes 引擎和容器注册。此后,按照这些步骤创建并建立一个带状 Kubernetes 集群。集群是 Kubernetes 引擎的基础。所有容器化的应用程序都运行在集群之上。因此,我们需要集群来运行和管理我们的应用程序。

例如,要使用您的终端创建集群,请运行以下命令,用您的值替换默认值:

gcloud container clusters create CLUSTER_NAME 

其中CLUSTER_NAME是您为集群选择的名称。

最后,通过在终端上运行以下命令来本地设置集群:

gcloud container clusters get-credentials <CLUSTER_NAME> --zone <TIME_ZONE> --project <PROJECT_ID> 

例如:

gcloud container clusters get-credentials ci-cd --zone us-west1-a --project ci-cd-12347 

您可以通过运行以下命令来确认集群集是否正确:

kubectl config current-context 

输出应该是上一步中创建的集群的名称。

库比涅斯配置

这涉及到使用 YAML 创建清单。让我们将服务和部署放在一个文件中,如下所示,用---分隔每个文档。如果你熟悉 YAML,那么这应该是一个熟悉的概念。

apiVersion: v1
kind: Service
metadata:
  name: ci-cd
  labels:
    app: ci-cd
spec:
  ports:
    - port: 3000
  selector:
    app: ci-cd
    tier: frontend
  type: LoadBalancer
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: ci-cd
  labels:
    app: ci-cd
spec:
  selector:
    matchLabels:
      app: ci-cd
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: ci-cd
        tier: frontend
    spec:
      containers:
      - image: gcr.io/ci-cd-217118/ci-cd:0.1.0
        name: ci-cd
        ports:
        - containerPort: 3000
          name: ci-cd 

如果你对 Kubernetes 有点熟悉的话,理解上面文件中的内容并不困难,但是我将在下面简要描述一下其中的亮点:

apiVersion - 指定 Kubernetes API 版本。
【种类】- 指定需要供应什么资源,例如服务、部署等。
元数据- 给出了资源的更多描述,例如名称,它指定了资源名称。
spec - 给出资源规格,如港口、集装箱等。
图像- 指定容器使用的图像。它需要图像的 URL。

将上面几行复制并粘贴到文本编辑器中,并在项目的根目录下保存为deployment.yaml

现在,让我们使用以下命令创建/触发初始部署:

kubectl create -f deployment.yaml 

确认 pod 的状态为Running,外部 IP 地址显示在端口 3000 上,并且AVAILABLE标签至少为 1:

List Kubernetes pods, services, and deployments

列出 Kubernetes pods、服务和部署

如您所见,部署、服务和 pod 都已启动并运行。在我的例子中,应用程序运行在 http://35.227.172.161:3000/,上,如上图所示。如果您一直跟随,您的应用程序将运行在不同的 IP 上。在端口 3000 上导航到它。

Deployed application

部署的应用程序

注: 当你读到这篇文章的时候,我已经为了节约成本而破坏了集群,所以这个 IP 不会再起作用了。

要删除集群,请在终端中运行以下命令:

gcloud container clusters delete [CLUSTER_NAME] 

然后删除具有集群的 GCP 项目:

gcloud projects delete [PROJECT_NAME] 

如您所见,在部署期间,我们不得不手动运行kubectl命令。如果每次应用程序有变化时都必须运行它,那么这将是很费力的。接下来,我们将使用 CircleCI 来实现自动化。

脚本

这需要一个简单的 shell 脚本来帮助部署。这个脚本将由 CircleCI 运行,作为部署应用程序的构建步骤。该脚本如下所示:

# !/bin/bash
set -e
echo "Deploying to ${DEPLOYMENT_ENVIRONMENT}"
echo $ACCOUNT_KEY_STAGING > service_key.txt
base64 -i service_key.txt -d > ${HOME}/gcloud-service-key.json
gcloud auth activate-service-account ${ACCOUNT_ID} --key-file ${HOME}/gcloud-service-key.json
gcloud config set project $PROJECT_ID
gcloud --quiet config set container/cluster $CLUSTER_NAME
gcloud config set compute/zone $CLOUDSDK_COMPUTE_ZONE
gcloud --quiet container clusters get-credentials $CLUSTER_NAME
docker build -t gcr.io/${PROJECT_ID}/${REG_ID}:$CIRCLE_SHA1 .
gcloud docker -- push gcr.io/${PROJECT_ID}/${REG_ID}:$CIRCLE_SHA1
kubectl set image deployment/${DEPLOYMENT_NAME} ${CONTAINER_NAME}=gcr.io/${PROJECT_ID}/${REG_ID}:$CIRCLE_SHA1
echo " Successfully deployed to ${DEPLOYMENT_ENVIRONMENT}" 

创建一个名为deployment.sh的文件,用上面的内容填充。

圆形构型

配置文件将位于根目录下的.circleci文件夹中。如果你很好奇,查看这里阅读更多关于如何配置 CircleCI 的信息。

最终配置如下所示,您可以将其复制粘贴到您的.circleci/config.yml文件中:

version: 2
jobs:
  build:
    docker:
      - image: wecs/circle-ci-gcloud-node:0.1.0
    working_directory: ~/workspace
    environment:
      DEPLOYMENT_NAME: ci-cd
      PROJECT_ID: ci-cd-217118
      CLUSTER_NAME: ci-cd
      CLUSTER_NAME_STAG: ci-cd-stag
      CLOUDSDK_COMPUTE_ZONE: us-west1-a 
      CONTAINER_NAME: ci-cd
      IMAGE: ci-cd
      REG_ID: ci-cd
      REG_ID_PROD: ci-cd-prod
      IMG_TAG: 0.1.0
      PROJECT_NAME: ci-cd
      DEPLOYMENT_ENVIRONMENT: staging
    steps:
      - checkout
      - setup_remote_docker
      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "package.json" }}
          - v1-dependencies-
      - run:
          name: Install node packages
          command: |
            npm install

      - run:
          name: Start app
          command: |
            npm start &
      - run:
          name: Run tests
          command: |
            npm test
      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "package.json" }}
          - v1-dependencies-
      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}

      - run:
          name: Build and Deploy
          command: |
            if [ "${CIRCLE_BRANCH}" == "master" ]; then
              ./deployment.sh
            fi 

接下来,提交更改并推送到 GitHub :

要理解 CircleCI 配置文件的核心步骤,请阅读本演练。还有一点需要注意的是,我用了一个自定义的基础镜像,wecs/circle-ci-gcloud-node:0.1.0,里面安装了gcloudkubectldockernode等。使用自定义图像的原因是,我想要一个基础图像,其中包含该项目的所有必需工具。

要将 CircleCI 整合到您的项目中,首先注册 CircleCI 账户,如果您还没有的话。然后,在控制台上点击添加项目。它位于仪表板页面的最左侧。将出现如下所示的项目列表:

Adding a project in CircleCI

在 CircleCI 中添加项目

接下来,导航到页面的最右侧,点击设置项目:

Project configuration button

项目配置按钮

将出现如下所示的页面。点击开始构建并观察构建。构建将失败,因为我们还没有认证 CircleCI 从 GCP 写/读。

Initial building

初始建筑

CircleCI 需要一种方法在 GCP 上验证自己。我们将使用服务帐户。前往 GCP 控制台,按照那里列出的步骤创建一个服务帐户。

Creation of service account

服务帐户的创建

然后,将创建服务帐户后下载的文件内容复制为 CircleCI 中的环境变量,首先对其进行编码:

base64 ci-cd-2328-6575986.json 

然后将结果复制到 CircleCI 环境变量中。

Adding a service account key as an environment variableAdding a service account key as an environment variable

添加服务帐户密钥作为环境变量

测试

现在让我们测试我们的 CI/CD 管道。

这是初始的 HTML 文件。该文件应该与 docker 文件位于同一目录中。请参考我上面分享的项目的文件夹结构。

<html>
 <head>
  <title></title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
 </head>
 <body>
  <div class="col-md-10 col-md-offset-1" style="margin-top:20px">
   <div class="panel panel-primary">
     <div class="panel-heading">
       <h3 class="panel-title">Welcome To My Demo App</h3>
     </div>
      <div class="panel-body">
       <div class="alert alert-success">
          <p>This is a basic app used to demonstrate CI/CD on Google Cloud using K8s, CircleCI and Docker </p>
       </div>
         <div class="col-md-9">
           <p>Author:</p>
           <div class="col-md-3">
             <a href="https://twitter.com/delan_hype">@delan_hype</a>
           </div>
         </div>
      </div>
  </div>
  </div>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
 </body>
</html> 

让我们修改它,看看 CI/CD 管道是否会部署新的更改。尝试在 HTML 文件的主体部分添加一些内容。例如,替换为:

<p>This is a basic app used to demonstrate CI/CD on Google Cloud using K8s, CircleCI and Docker </p> 

有了这个:

<p>If this change is reflected, then the CI/CD pipeline is functional... 👏🏿 😈 </p> 

如果您在一个不同的 Git 分支上,将变更推送到 master 或者将 PR 合并到 master。

Successful build steps

成功的构建步骤

A successful deployment to staging

成功部署到试运行

Image showing the added changes

耶!成功了!您现在可以使用管道了,但是请确保完成上述步骤来删除您的集群,并避免在完成后产生任何费用。

结论

我们已经在 GCP 上使用 CircleCI 2.0 成功地为 Node.js 项目建立了一个 CI/CD 管道。现在,您可以为自己的 Node.js 项目设置 CI/CD 管道,并按照上面的步骤利用 Kubernetes 进行编排,将您的应用程序 dockerize 并部署到 GCP!

完整的项目可以在这里找到


Collins Wekesa 是安德拉的一名肯尼亚 DevOps 工程师,也是一名区块链爱好者。在空闲时间,他喜欢写技术博客。

阅读柯林斯·韦克萨的更多帖子

posted @ 2024-10-31 16:48  绝不原创的飞龙  阅读(12)  评论(0编辑  收藏  举报