客座文章最初由Andrew Seigner在Bouyant博客上发表
这篇文章是Andrew在2020KubeCon欧洲上的演讲。
介绍
在2019年中,Linkerd项目的持续集成(CI)花了45分钟,所有的测试都在一个Kubernetes集群上串行化,多小时的备份也很常见。迁移到Kubernetes in Docker(kind)集群和GitHub Actions使CI不到10分钟,并且可以并行。
这篇文章将详细介绍Linkerd从一个单一的、持久的Kubernetes集群,到理论上无限的一次性类型集群的CI旅程。这段旅程包含了一些关于哪些模式和工具在Linkerd的用例中工作得好(或者不太好)的弯路。
Linkerd是什么?
虽然本文的目标是详细说明最终用户,如何在CI中高效地测试Kubernetes应用程序,但一些有关Linkerd的背景知识会有所帮助。Linkerd是一个开源的服务网络,也是一个CNCF成员项目。想要更多地了解什么是服务网格,请查看“The Service Mesh: What Every Software Engineer Needs to Know about the World’s Most Over-Hyped Technology”。出于这篇文章的目的,有必要了解一些关于Linkerd的简单事实:
- 它是用Rust、Go和Javascript编写的。
- 它在Kubernetes中以多部署(deployment)的形式运行。
- 它通过插入到你的pod中的代理来管理你的服务之间的所有流量。
Linkerd架构
测试Linkerd
既然Linkerd负责管理Kubernetes集群中的所有流量,那么Linkerd的正确性和性能就非常关键了。为了帮助确保这一点,我们的CI包括一系列静态、单元和集成测试,包括Rust、Go和JavaScript测试。这篇文章主要关注集成测试。我们将介绍这些测试的三个迭代。
测试Linkerd。集成测试可以在左下角的绿色框中看到。
迭代一:在GKE + Travis上运行CI
2019年中,Linkerd的集成测试以作业(job)的方式在Travis上运行。每个作业将构建Linkerd Docker镜像,将其推到gcr.io,并在单个GKE集群上执行集成测试。因为它是一个单一的Kubernetes集群,所以我们必须确保每个集成测试在卸载了Linkerd之后自己清理干净。随着时间的推移,我们需要测试在不同配置下安装Linkerd。例如,使用Helm,或者通过升级路径。这意味着我们现在要安装Linkerd,运行集成测试,每次CI运行要卸载Linkerd五次。整个过程大约花了45分钟。将此与同时出现的多个拉请求(PR)结合起来,多个小时的备份就变得很常见了。在这一点上,我们采取了禁用对PR的集成测试的选项,我们将只在合并时运行它们。当然,从我们这么做的那一刻起,我们的主要分支就开始不断地失败集成测试,因为直到合并时才会发现失败。
迭代一:GKE + Travis
对CI需求排优先级
在这一点上,我们意识到我们需要后退一步,重新评估我们关于测试Linkerd的选择。我们列出了这张需求优先级列表:
需求1:可重现的构建和测试
Linkerd的集成测试套件包括在Kubernetes集群上安装大量资源,并验证流量是否正确流动。如果我们在CI中观察到测试失败,最重要的是确保我们可以在CI和本地开发中轻松地重现该失败。
需求2:浏览构建和测试历史的UI
对于CI系统来说,浏览测试历史的UI似乎是显而易见的,但是当我们收集需求时,我们并没有认为任何事情都是理所当然的。我们考虑了查看构建和测试历史的其他方法,包括后台作业和脚本,可以通过电子邮件状态或向PR发布GitHub评论。最终,我们知道我们需要一种简单的方法来共享测试失败的链接,我们相互ping的时候可以使用指向特定集成测试失败中的特定线路的URL。
需求3:GitHub/PR集成
事后看来,我们还需要整合我们目前的PR系统,GitHub。我们之前已经尝试过自己构建这些集成,但我们希望能够找到一些开箱即用的东西,而不是给自己更多的维护工作。
需求4:密封建造和测试
许多Linkerd的PR来自社区,通常来自我们以前从未共事过的人。我们希望确保我们的测试在一个尽可能隔离的环境中运行,因为我们在我们花钱维护的硬件上运行不受信任的代码。我们还希望在运行测试之前不需要维护人员对每个PR进行抽查。
需求5:快
测试的周转时间对于开发人员的生产力总是至关重要的。有时需要五次或更多的尝试来修复一个测试。如果每个测试运行需要一个小时,那么你就损失了将近一天。这一要求被转化为一个计划,以避免在internet上推Docker镜像,支持增量重建,并尽可能在远程机器上构建Linkerd。
需求6:廉价或免费
作为一个开源项目,我们希望在预算很少或没有预算的情况下满足上述所有需求。
需求7:OSS
作为开源维护者,我们总是更喜欢使用开源工具。但是请注意,这是我们最后的要求。我们会在可能的情况下使用开源工具,但是如果闭源工具满足了所有其他需求,我们不会自动放弃它。
CI技术评估
考虑到需求的优先级,我们开始评估我们在这个领域可以找到的任何工具:
k8s发行版:kind、k3d、k3s、GKE、AKS、EKS、DigitalOcean K8s
计算:Packet
构建:skaffold、Bazel
作业管理:GitHub Actions、Prow、Travis、CircleCI、Azure Pipelines、Jenkins X、Gitlab CI、garden.io
发布/CD:Kubernetes Release、werf.io
我们用所有这些工具在不同程度上构建了概念证明。当时,我们不知道自己会选择其中的一种还是五种,并保持开放。(对我们未选择到的工具的任何作者:请注意,这并非打击你的任何作品,我们对技术的选择在很大程度上取决于我们的用例,其中包括上面列出的优先需求,有限的时间和预算,以及我们对现有工具的熟悉程度。)
差点错过:Prow
考虑到这一点,我想谈谈一个我们非常喜欢但最终没有选择的工具:Prow。
Prow是一个强大的基于Kubernetes的CI/CD系统。它由Kubernetes社区维护,并用于测试Kubernetes本身,每天通过数千个作业进行测试。这对我们很有吸引力。如果工具对Kubernetes是足够好,它肯定能处理Linkerd。
我们用Prow构建了一个端到端的概念验证,所有的Linkerd Docker构建和集成测试都运行在Prow集群上。最终,由于对正在进行的维护和支持的关注,我们转向了不同的方向。Prow非常强大,但是像Kubernetes和大多数生产系统一样,需要持续维护以确保健康状态。我们的CI系统对我们来说很重要,但是我们想要一些能够在我们的小型开发团队很少或没有注意的情况下继续运行的系统。而Prow确实有一个漂亮的仪表盘:
prow.k8s.io
最后,我们从技术评估中选择了三种工具:kind(Kubernetes in Docker)、Packet和GitHub Actions。
kind
kind(Kubernetes in Docker)是我们选择的第一个工具。它允许你在大约30秒内在Docker容器中启动Kubernetes集群。这满足了我们的许多要求。最重要的是,kind是一种可以轻松编写脚本,并在本地和CI中运行的工具。这意味着我们可以像CI系统那样在开发机器上运行集成测试。它提供了一个自包含的Kubernetes集群,我们可以在每次测试后丢弃它。它也非常快的启动和删除,它允许我们运行Kubernetes,无论我们在哪里构建Docker镜像。不再在互联网上推送镜像。还有一个巨大的好处:它是测试Kubernetes项目本身的核心技术,而且它是开源的!甚至在我们选择其他工具之前,我们就知道我们想要围绕kind来构建CI系统。
Packet
Packet提供高性能裸金属服务器,看起来可能是一个令人惊讶的选择。通过与CNCF的伙伴关系,Packet为CNCF项目提供免费的按需硬件。这意味着我们可以在一个高性能的Packet主机上运行快速、缓存的Docker构建和kind集群。这些主机的性能足以让我们并行地运行所有的集成测试,并在此之上并行地运行多个PR。
GitHub Actions
当我们评估技术时,GitHub Actions才刚刚完成beta。这里有几个属性促成了我们的选择。最直接的是,它已经集成到GitHub的PR中,这意味着少了一个集成点。它支持矩阵构建,在这里我们可以轻松地参数化我们的8个集成测试,每个kind集群一个。它还支持任务之间灵活的依赖关系。例如,我们可以让两个任务并行运行,一个用来启动一个kind集群,另一个用来构建Docker。当两者都完成时,我们就可以开始集成测试了。另外,GitHub Actions对开源项目是免费的。虽然它本身不是开源的,但这是次好的事情。
迭代二:使用kind + Packet + GitHub Actions的CI
选定技术后,我们实施并推出了第二代CI系统:
迭代二:kind + Packet + GitHub Actions
GitHub Actions提供了PR集成和作业管理,我们使用他们的矩阵构建来启动我们的8个kind集群:
通过GitHub Actions矩阵构建启动8个kind集群
这整个设置允许所有集成测试(和PR)并行运行,使用快速、缓存的Docker构建包。我们的CI时间从小时减少到大约10到15分钟!
请注意,虽然任务是由GitHub管理的,但繁重的工作是在Packet主机上进行的。为了实现这一点,我们使用了一种聪明的(hacky)技术来创建远程类集群并与之交互。要通过SSH连接到远程Docker,可以将DOCKER_HOST环境变量设置为SSH://[PACKET_HOST]。这允许你在远程主机上创建类集群。然而,本地kubectl配置仍然期望类集群在本地主机上。为了解决这个问题,我们从kubectl配置中读取远程类型集群的端口,并将端口转发给它。这里有一个演示视频来演示这个:
https://www.youtube.com/embed...
我们不确定这是否是一个已知的模式,或者是否有更好的方法,所以我跳到Kubernetes Slack上的#kind频道去问。幸运的是,kind的创建者立即回复了我们,告诉我们虽然我们所做的并不是完全意料之中的事情,但它看起来相当正常:
kind FTW
对这个善良的kind社区(以及它的创建者)大声鸣谢,因为它创造了一个欢迎和支持的环境。正是这些交互作用让开源变得很棒,也是我们试图在Linkerd社区中模仿的东西。
迭代三:kind + buildx + GitHub Actions
敏锐的读者可能已经注意到,在迭代儿中,我们只在Packet上运行非分叉的PR(non-forked PRs)。这是由于我们之前的要求,即不希望不受信任的代码运行在我们负责的硬件上。这并不理想,因为这意味着forked PR仍然需要很长时间才能通过CI,这对项目新手来说不是很好的体验。几个月过去了,我们的团队开始试验Docker Buildx。这个工具使我们能够将Docker构建缓存保存到一个文件中,以便在随后的GitHub Actions作业中重用。这允许我们删除对Packet的依赖,并在GitHub行动主机上全速运行所有的构建:
迭代三:kind + buildx + GitHub Actions
这里有一个视频演示了Linkerd的端到端经验,即推一个提交,并观察8个Kubernetes集群并行启动:
https://www.youtube.com/embed...
总结
在所有这些工作之后,一些关键的经验教训:
使用kind
Kind是一个很好的工具,不仅对于CI,对于本地的开发也是如此。Kubernetes也有类似风格的发行版,比如Minikube和k3d。我们选择kind是因为它被Kubernetes社区用于测试Kubernetes本身。同时,也大声鸣谢Kubernetes Slack中的#kind频道。
缓存你的[docker]构建
在CI运行之间缓存Docker构建是加快CI周转时间的关键因素--这适用于所有形式的构建缓存。
DOCKER_HOST=ssh://
通过SSH使用Docker非常方便。我个人已经有好几个月没有在自己的开发系统上运行Docker了。
Docker Buildx
Docker Buildx不仅提供缓存,而且支持跨平台构建。这使得Linkerd最近开始构建、测试和发布arm构建。
鸣谢Packet和GitHub Actions对OSS的支持
虽然他们自己不是开源的,像Packet和GitHub这样的公司,为开源项目提供的支持,对于像Linkerd这样的项目来说是无价的。非常感谢他们!
还有一件事:Linkerd CI指标
我们非常喜欢Prow的一点是它可以显示构建历史。我们想要类似的东西,所以我们的一个维护者Alejandro设计了一个Linkerd CI指标仪表盘。来看看。
Linkerd CI指标仪表盘
CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux Foundation,是非营利性组织。
CNCF(云原生计算基金会)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。扫描二维码关注CNCF微信公众号。