没有码头的码头

2021-04-09 12:11:11

我们是fly.io.我们采取集装箱图像并在世界各地的硬件上运行它们。它很整洁,你应该看看;使用已有工作的Docker容器,您可以在10分钟内运行。

尽管我们的大多数用户将软件传递给我们作为Docker容器,但我们不使用Docker运行它们。 Docker是很棒的,但我们是高密度的多助词,尽管步幅,Docker的隔离不够强大。因此,我们将容器图像转移到鞭炮微VM中。

他们尽最大努力使它看起来更加复杂,但OCI图像 - OCI是Docker使用的标准化容器格式 - 非常简单。 OCI图像只是一堆塔尔巴尔。

备份:大多数人从Dockerfiles构建图像。查看Dockerfile的有用方法是作为一系列shell命令,每个都会产生一个tarball;我们称之为这些“层”。为了将容器从其图像中水进行再水化,我们刚刚启动第一层并在下一个顶部打开一个。

您可以编写一个shell脚本来从其注册表中提取Docker容器,这可能会澄清。从某种配置开始;默认情况下,我们将抓住Golang的基础图像:

我们需要进行身份验证以从Docker注册表中取出公共图像 - 这是无聊但与下一节相关 - 这很简单:

该令牌将允许我们抓住容器的“清单”,这是容器部分的JSON指数。

函数幻帆{token =" 1美元"图像=" $ 2" DIGEST =" $ {3: - 最新}" curl -fssl \ -h"授权:持票人$令牌" \ -h'接受:application / vnd.docker.distribution.manifest.list.v2 + Json' \ -h'接受:application / vnd.docker.distribution.manifest.v1 + JSON' \ -h'接受:application / vnd.docker.distribution.manifest.v2 + JSON' \" $ {registry_url} / v2 / library / $ {image} / manifests / $ {digest}" }

我们制作的第一个查询为我们提供了“清单列表”,这使我们指向每个受支持的架构的图像:

将摘要从匹配的架构条目中拉出,并将其再次执行相同的获取作为参数,我们得到了清单:json指针到每个图层tarballs:

抓取与这些条目相关的实际数据如您希望的实际数据很容易:

功能图层{echo" 1美元" | JQ - 输出' .Layers []。摘要' }令牌= $(auth_token" $ image")amd64 = $(linux_version $(manifest" $令牌"" $ image")mf = $ (明文" $令牌"" $ image"" $ amd64")i = 0以$(图层" $ mf" $ mf&# 34;); Doblob" $令牌" " $图片" " $ l" " layer_ $ {i} .tgz"我= $((i + 1))完成

按顺序解压缩Tarballs,您已有文件系统布局容器期望运行。拉动“Config”JSON,并且您已获得用于容器的entrypoint;我猜,你可以拉扯和运行一个码头容器,除了一个壳牌脚本,我可能是1,000人指出的人。无论如何,这是整件事。

你可能是一个关于这个的两种心态之一:(1)这是非常愚蠢的,因此非常好,或者(2)这是非常愚蠢的,因此可怕。

UNIX TAR是有问题的。总结Aleksa Sarai:Tar并不充分标准化,可能是不可预测的,并且在随机访问和增量更新时不好。在图层之间的大文件的变化无意中复制这些文件;糟糕的作业tar管理容器存储是人们燃烧这么多时间优化容器图像大小的一部分。

另一个有趣的细节是OCI容器与Git存储库共享一个安全性脚步:很容易意外构建一个秘密,然后在稍后的图像中无意中隐藏它。

我们是关于OCI图像的第三个心态,这是他们令人恐惧,这是解放。他们在练习中很好地工作!看看他们带来了多远!放松并制作疯狂的设计;他们是你可能需要的。

回到fly.io.我们的用户需要为我们提供OCI容器,以便我们可以解压缩并运行它们。有标准的Docker工具做到这一点,我们使用它:我们托管Docker注册表我们的用户推送。

运行Docker注册表的实例非常容易。你现在可以做到这一点; Docker Lift Registry&& Docker运行注册表。但是我们的需求比标准的Docker注册表更复杂:我们需要多租赁,并授权围绕我们的API。这结果并不难,我们可以走过它。

要了解蝙蝠的事物:我们的用户使用名为Flyctl的命令行实用程序驾驶Fly.io。 Flyctl是一个在Linux,MacoS和Windows上运行的Go程序(具有公共来源)。在集装箱环境中工作的一件好事是整个生态系统以相同的语言建立,你可以通过导入它来快速工作。因此,例如,我们可以通过调用Docker的客户端库从Flyctl驱动我们的Docker存储库客户端。

如果你'重新建立自己的平台,你有手段,我强烈推荐我们采取的Cli-First Tack。它是如此选择。 Flyctl使添加新功能,如数据库,专用网络,卷和我们的Bonkers SSH访问系统非常容易。

在Serversife上,我们开始简单:我们用它的授权代理运行标准Docker注册表的实例。 Flyctl管理持票人令牌,并使用Docker API启动Docker推送该令牌的推动;令牌授权使用调用进入我们的API的存储库服务器。

我们现在所做的并不比这更复杂。我们构建了一个自定义存储库服务器而不是运行vanilla docker注册表。与客户一样,我们只需将Docker的注册码作为Go依赖项导入Docker的注册码即可获得Docker注册表实现。

我们已经提取并简化了我们在此处建立的一些GO代码,以防任何人想要与同样的想法一起游戏。这不是我们的生产代码(特别是,所有实际身份验证都被删除),但它离IT不远,就像你可以看到的那样,它没有太多的。

我们的自定义服务器不与我们之前的vanilla注册表/代理系统不同的架构。我们将Docker Registry API处理程序用授权器中间件检查令牌,引用和重写存储库名称。有一些非常少数的托克斯:

Docker是满足的,对于他们的SHA256哈希,并尝试重用不同存储库之间共享的Blobs的“命名为”。您需要捕获这些交叉存储库安装架并重写它们。

Docker的注册表代码生成带有_state参数的URL,用于嵌入对存储库的引用;那些需要改写。 _State是HMAC标记的;我们的代码只在注册表和授权器之间共享HMAC密钥。

在这两种情况下,谁有谁有储存库以及返回我们API服务器的数据库在哪里。您的推送载有持票人令牌,我们解析为组织ID,以及您推动的存储库的名称,以及我们的设计是您可能会提出的才能提出这项工作。我想我的意思是,它很容易滑入Docker生态系统。

我们现在需要做的是安排这些棋子,以便我们可以将容器运行为鞭炮VM。

据我们'重新关注,容器图像只是一堆tarballs和一堆配置(我们也在层上附加配置)。 tarballs展开到VM运行的VM目录树,并且配置告诉我们该文件系统中的二进制文件以在VM启动时运行。

同时,鞭炮想要的是一组块设备,即Linux将在靴子上安装。

在Linux上有一个简单的方法来拍摄目录树并将其转换为块设备:创建一个文件备份的循环设备,并将目录树复制到其中。那个'我们如何努力做事。当我们的Orchestrator要求在我们的一个服务器上启动VM时,我们会:

打开容器(在这种情况下,使用Docker' s go库)进入安装的循环设备。

创建第二个块设备并注入我们的init,内核,配置和其他goop。

追踪附加到应用程序的任何持久卷,用luks解锁它们,并收集其解锁块设备。

创建一个点击设备,为我们的网络配置它,并将BPF代码附加到它。

这个系统工作,但尚未诞生。 Firecracker的一部分是为了播放您(或AWS)可以在其中托管Lambda函数而不是长时间运行的程序启动。我们的大问题是缓存;一个服务器,例如,达拉斯' s要求为客户运行一个VM,很可能被要求提供更多该服务器的实例(Fly.io应用程序规模琐碎;如果你' ve得到了1个运行的东西,并将与其中10个更快乐,您只需运行Flyctl Scale 10)。我们尽一点缓存试图使这种更快,但它具有可疑的效果。

与集装件文件系统所关注的情况,我们正在运行的系统,而不是这篇文章顶部的shell脚本更复杂。所以杰罗姆取代了它。

我们现在所做的是,在我们的每一个服务器上运行,一个容器的一个实例。 Containerd做了一堆东西,但我们用它作为缓存。

如果你和20世纪90年代的UNIX人物,就像我一样,你最近就开始关注Linux存储如何再次运作,你可能注意到很多改变了很多。在过去20年的某个时间,Linux中的块设备层很有意思。 LVM2可以池池原始块设备,并在其中创建合成块设备。它可以将块设备尺寸视为抽象,将1TB块设备切成1,000个5GB的合成器件(只要您在所有这些设备上实际使用5GB)。它可以创建快照,在另一个合成设备中保留设备上的块,并在具有编写复制的语义中与相关设备共享这些块。

Containerd知道如何推动所有这些LVM2的东西,而虽然我猜它的'在现在使用DevMapper后端使用DevMapper,它适用于我们的目的。所以现在,要获取图像,我们将其从注册表中拉到我们的服务器本地Containerd,配置为在LVM2薄池上运行。 ContainTD管理我们运行的VM /容器的每个实例的快照。它的API提供了一个简单的"租赁"基于垃圾收集计划;当我们引导VM时,我们在集装箱快照上租赁租赁(基于图像合成新的块设备,其中包含我们的Containerd Unpacks); LVM2母牛意味着多个容器彼此互相跳跃'当VM终止时,我们会投降租约,最终会租约。

我们的一个服务器上的VM /容器的第一次部署有一些提升,但随后的部署快速闪电(第二部部署的VM Build-and-Boot进程比我们所做的日志更快)。

Jerome在Rust中写了我们的init,而且,在Josh Triplett的Cajoled之后,我们发布了您可以阅读的代码。

闪光灯在快照结账上安装的文件系统,我们创建非常原始。我们init的第一份职位是填写空白,以完全填充根文件系统,其中Linux需要运行正常程序。

我们将配置文件注入携带用户,网络和运行图像所需的入口信息的每个VM中。 init读取它并配置系统。我们使用自己的DNS服务器进行私人网络,因此Init覆盖Resolv.conf。我们为Uriguard的用户登录运行一个小小的SSH服务器; Init Spawns和监视该过程。我们产卵并监控入口点计划。就是这样;这是初步。

所以,'大约是飞蝇背后的一半想法。我们在世界各地的机架中运行服务器硬件;这些服务器与插入我们API的编排系统一起绑定。我们的CLI,Flyctl,使用Docker'工具将OCI图像推向我们。我们的编排系统向服务器发送消息以将那些OCI图像转换为VM。它'所有漂亮的neato,但我希望也有点容易让你的头包裹着。

另一个"半"飞行是我们的任何Accast网络,它是一个内置生锈的CDN,它使用BGP4 yourcast路由将流量直接到最近的应用程序实例。关于哪个:更稍后。