2015年是各种容器技术与名词扎堆的一年,Docker的出现使得“应用容器”的实施变得易如反掌的同时,也带动了它的许多竞争者。其中一个比较有趣的看点就在于“容器规范”的较量,最近红帽和英特尔也按捺不住,拿出自家的产品趁势搅局。
5月14日,红帽宣布了新的 多容器应用规范Nulecule (DockOne 翻译了这篇新闻 ),同时推出符合这个规范的一个实现:AtomicApp。(这个路子怎么看都有点像AppC和Rkt采用的模式)
因特尔则是发挥自家的特长,在5月18日,发布了 介于虚拟机与容器之间的跨界产品ClearLinux (DockOne同样 翻译了这篇新闻 )。之所以特别要提这个项目,是因为它首先会实现基于Rkt/AppC规范的容器模型,而将Docker放在了其次的位置。
“容器规范”的概念,看起来让人有些摸不着头脑,但在容器业界中,它确实是颇具诱惑力的一块蛋糕。在这一篇里,我们就来聊一聊支撑Rkt背后的那个“容器规范”:AppC Spec。
使用了开源软件的人,未必都会有心情仔细阅读各种开源协议的内容。大多数的使用容器产品用户,也不见得要对容器规范的内容有很高的兴致。
不过,为了更好的理解后面将要介绍到的相关工具,还是不妨稍微深入的了解一些 AppC规范 约定的内容。其内容归纳起来主要有四个方面,下面依次罗列出来,并与当下的主流容器Docker做一个简要的对比。
PS:严格来说,AppC与AppC Spec两个词是有区别的。前者指的是CoreOS的 App Container
这个项目,包括规范和相关的工具,而后者特指AppC中约定的容器规范。但在许多地方,特别是翻译的文章中,经常看到这两个词被混用,因此一般也不必太讲究了。
本质上说,容器镜像就是符合特定目录结构的文件压缩包。镜像中的内容在容器启动后被展开,然后复制到一个独立的namespace空间内,并通过cgroup限制容器能够使用的系统资源。稍后在制作镜像时,会详细介绍AppC Spec规定的镜像目录结构。这里只先指出一点,AppC的镜像没有支持像Docker那样的分层结构,这种设计简化了容器运行时的一些操作,但带来的弊端也是很明显的:无法复用镜像相同的部分。因此在磁盘空间的利用上造成了浪费,也增加了容器镜像在网络传输成本。
除了目录的结构,镜像还需要一个描述镜像内容的文件,称为“镜像属性清单文件(Image Manifest)”,其中定义的内容包括:镜像的作者信息、容器暴露的端口、暴露的挂载点、所需的系统资源(CPU/内存)等。此外,AppC Spec的约定的属性清单中,还会包含许多编排调度所需的信息,例如容器运行所依赖的其他容器、容器的标签。
在这方面来说,AppC镜像的信息量远远多于Docker镜像。相当于囊括了Docker镜像本身、Compose编排配置以及一部分Docker运行参数的内容。
此外,AppC规范也约定的镜像ID和签名的生成方法,关于镜像ID和签名的作用和在Rkt文章上篇中已经介绍过,稍后还会详细介绍镜像签名的生成方法。
分发协议主要是约定镜像下载使用的协议类型和URL的样式。AppC的镜像URL采用类似Docker的 domain.com/image-name
这样的格式,但其实际处理方式有些不同。此外,在没有指定域名时,Docker会默认在官方的DockerHub寻找镜像,AppC的镜像没有所谓“官方源”,因此也没有这样的规则。
Rkt/AppC目前支持以下几种URL格式:
第一种方式是AppC推荐的镜像分发URL,这种方式有点像Docker Repository,但实际上只是HTTPS协议的简写方式。AppC会根据指导的域名和路径依照约定的方式转换为完整URL地址,然后下载指定的镜像。
第二种方式相当于导入本地镜像。值得一提的是,即便使用本地镜像,AppC同样要求镜像有签名认证,关于签名文件的细节在后面的内容里会详细讨论。
第三种和第四种方式都是直接通过完整URL获取镜像,规范中并不推荐直接这样使用裸的HTTPS的URL,因为这种命名过于随意的镜像地址不利于镜像的管理和统一,特别是HTTP协议的URL更只应该在内网的环境中出现。
第五种方式不是AppC规范支持的协议类型,目前只是Rkt支持这种协议(本质上还是HTTP或HTTPS)。兼容Docker镜像的URL,只需要在前面加上 docker://
即可,下载后会自动转换为AppC镜像格式。由于Docker的镜像仓库不支持签名认证,使用这种URL时,用户需要显示的加上参数 --insecure-skip-verify
允许使用未认证的镜像来源。
AppC规范中的容器编排和集群描述方式与Kubernetes十分相似,采用“容器组属性清单文件(Pod Manifest)”描述。其中沿用了Kubernetes中诸如 Pods
、 labels
等用于在集群中进行调度策略的规划和管理的概念。
Pods
直译便是“豆荚”,它指的是由一系列相互关联的容器组成的,能够对外提供独立服务功能的容器集合。例如将用于数据收集功能的容器、用于缓存服务的容器以及用于搜索服务的容器组合在一起,作为一个 Pod
提供完整的数据查询服务暴露给外部用户。 Pod
可以作为容器参与集群调度的单独集合提供给集群管理器,在例如Kubernetes这样的集群管理模型中, Pod
实际上就是进行服务跨节点调度的最小单位。
labels
用于标示具有同一类特性的容器,为容器的过滤和选择提供了十分灵活的策略。许多的集群管理器都能够在调度时利用选择指定标签对 Pods
进行筛选。
考虑到CoreOS公司与谷歌共同合作的背景(已经推出了Tectonic CaaS平台),这样的设计为Kubernetes未来与符合AppC规范的容器进行深度集成提供了良好的技术基础。
执行器,也就像是Rkt这样的容器工具。这个部分规范了设计符合AppC Spec的容器执行器所需要遵循的原则和应该具备的功能。
例如,必须为每个容器提供唯一的UUID;在容器的运行上下文中必须至少提供一个本地Loopback网卡,以及0个至多个其他TCP/IP网卡;应该将容器中程序打印到Stdout和Stderr的日志进行收集和展示等细节。
其中还详细约定了,对于镜像属性清单中的诸多属性,执行器应当如何进行处理。这些内容对大部分的使用者而言都只能作为参考,还是需要以具体实现的容器产品文档为准。
在AppC的项目中,除了一纸洋洋洒洒的规范文书以外,还提供了不少AppC镜像相关的示范工具。不像Docker这种一个命令集成所有功能的玩法,这些工具中的每个只是关注于容器的某个方面功能。例如通用类型镜像的制作、打包、格式转换以及特定类型镜像的制作等。
目前已有的工具主要包括:
其中, Actool
和 Acbuild
都是用于镜像构建的工具,它们的区别类似于通过 docker commit
和Dockerfile两种方式构建镜像。需要指出的是,前不久才刚刚建立的 Acbuild
项目,现在还 只是一个计划 ,没有发布任何实际可用的版本,其目的是替代之前的另一个项目 baci
。后者已经 无法使用 并且不再继续更新。
Goaci
的作用是获取指定路径的项目,进行自动编译,然后把编译后的可执行程序制作成一个镜像,所有的这些操作只需要一条命令就可以完成: goaci <项目路径>
,项目路径支持所有 go get
命令所支持的代码托管网站,包括BitBucket、GitHub、Google Code和Launchpad等。不过,它只能用于使用Golang语言并托管在上述网站中的开源项目。不具有普遍的适用性。
下面将重点介绍 Actool
和 Docker2Aci
这两个工具。为了方便非CoreOS系统用户尝鲜,也会介绍在这些工具在其他64位Linux发行版的安装方法。
与镜像制作相关的工具是 Actool
,这个软件已经预装在CoreOS系统较新的版本上了,可以通过 actool --help
命令验证并获得Actool的版本。其他64位Linux的用户可以通过下面的命令安装它:
wget https://github.com/AppC/spec/releases/download/v0.5.2/AppC-v0.5.2.tar.gz tar zxf AppC-v0.5.2.tar.gz sudo mv AppC-v0.5.2/actool /usr/local/bin/
说到构建镜像,前面已经提到,新的命令式构建镜像的工具 Acbuild
目前还没有发布任何可用版本。因此当下的情况是,构建AppC镜像还只能手工创建镜像属性清单文件,拷贝容器中所需的文件,然后直接打包生成镜像。索性,这样创建镜像除了失去诸如“基础设施即代码”的好处以外,并没有多少值得非议的地方,构建流程本身并不复杂。
下面来制作一个十分朴素的AppC容器镜像,这个镜像中只包含一个可执行文件。
首先新建一个用于制作镜像的工作目录,例如 AppC-image
:
>mkdir AppC-image
接下来,为了让这个例子足够简单,我们需要一个能够不依赖任何外部动态库或运行时环境,能够单独运行的程序。我们写一个C语言的“Hello World”吧。新建一个叫 hello.c
的文件,内容如下:
#include <stdio.h> int main(int argc, char* argv[]) { printf("Hello AppC/n"); //随便输出点什么东西 return 0; }
然后,需要一个C语言编译器,有些Linux系统已经自带了这个东西,用 gcc --version
命令可以验证。如果没有安装,那么...大家看着办吧,比如在Ubuntu系统下面可以通过apt-get来获取:
sudo apt-get install gcc
CoreOS系统会稍微麻烦一点,需要借助一个额外的容器来完成。提示一下,Docker有一个官方的C/C++语言运行环境镜像,就叫 gcc
。可以 docker pull gcc
或者 rkt --insecure-skip-verify fetch docker://gcc
来获取它,然后启动一个容器,注意需要映射一个Volumn到主机上,方便编译完成后将生成的可执行程序拷贝出来。
编译的命令如下,其中的 --static
参数是必须的,否则编译出来的程序在执行时会需要依赖外部的动态库:
gcc --static -o hello hello.c
在刚刚工作目录里面新建一个叫 rootfs
的目录,将编译生成的hello可执行文件拷贝进去。这个 rootfs
目录中的内容就是以后容器里所包含的文件内容了,因此建议在其中再建立一些标准的目录结构,例如 /bin
目录,将可执行程序放到这个目录里面。
mkdir -p AppC-image/rootfs/bin cp hello AppC-image/rootfs/bin/
现在镜像的目录结构已经成型了。下面就可以开始创建镜像的属性清单文件了,在工作目录中新建一个名为 manifest
的文件,内容如下:
{ "acKind": "ImageManifest", "acVersion": "0.5.2", "name": "my-app", "labels": [ {"name": "os", "value": "linux"}, {"name": "arch", "value": "amd64"} ], "app": { "exec": [ "/bin/hello" ], "user": "0", "group": "0" } }
此时,工作目录里的文件结构应该是这样的:
AppC-image ├── manifest └── rootfs └── bin └── hello
最后就可以用 actool
命令构建镜像了:
actool build AppC-image hello.aci
容器镜像的来源无非有两种:本地的或者远程的。
因此对镜像的验证也就包含两部分内容。
对于前一种情况,前面说过,容器镜像其实就是符合一定标准结构的打包文件。
$ file hello.aci hello.aci: gzip compressed data
在AppC规范中,镜像文件的后缀名应该是 .aci
,但具有这个后缀名的打包文件未必就是正确的镜像。因此需要一个方法来验证镜像文件的正确性,相应的命令是 actool validate
。
直接执行这个命令时,actool只会通过命令的返回值表示验证的结果,返回0表示验证通过:
$ actool validate hello.aci $ echo $? 0
可以加上 -debug
参数让actool直接将结果打印在控制台上:
$ actool -debug validate hello.aci hello.aci: valid app container image
对于后一种情况,URL的验证,同样可以通过actool工具完成,相应的命令是 actool discover
。
这个命令会返回镜像的实际下载地址:
$ actool discover coreos.com/etcd ACI: https://github.com/coreos/etcd/releases/download/latest/etcd -latest-linux-amd64.aci, ASC: https://github.com/coreos/etcd /releases/download/latest/etcd-latest-linux-amd64.aci.asc Keys: https://coreos.com/dist/pubkeys/aci-pubkeys.gpg
试一下在Rkt里面用过的Docker镜像地址,会发现这个地址是无效的。
$ actool discover docker://ubuntu error fetching docker://ubuntu: Get https://docker?ac-discovery=1: dial tcp: lookup docker: no such host
这也证实了在前面说的, docker://
这种协议只是Rkt额外支持的镜像获取方式,并不是AppC的规范中的标准协议。
AppC的规范制定者们显然很清楚,哪些轮子该重造,哪些轮子是可以直接复用的。在Docker的各种镜像已然是铺天盖地的当下,一个新的容器工具想要最快积累镜像数量,最好的办法就是兼容Docker镜像或者将Docker的镜像进行转换。
其实对于镜像兼容这个问题,新标准们有各自不同的做法,红帽的Nulecule选择了支持Dockerfile格式,只需要把已有的镜像代码加上一些额外的配置文件,重新构建一次就可以。而AppC做过同样的尝试,(之前有个 baci
项目就是干这个的,不过已经没有更新了),效果上有些不伦不类,因此索性更干脆,提供个工具允许将任何的Docker镜像导出后直接转换成自己的镜像格式。
下面就来说说从Docker到AppC镜像的转换,相应的工具是 Docker2Aci
。
这个工具不论是在Ubuntu或者CoreOS上都没有预装,因此需要单独安装。这个工具还没有正式发布,因此官方也没有提供编译好的二进制包,要获得它只能从源代码编译。值得庆幸的是,Golang语言的编译方式还是比较友好的,如果本地已经安装了Golang语言的开发环境,可以直接通过 go get github.com/AppC/docker2aci
命令完成整个下载和编译的过程。
考虑到大多数用户都是没有Golang开发环境的,另一个比较简单的办法是:通过容器。因为,Docker官方已经提供了一个用于Golang开发的容器镜像,名字就叫 golang
。用下面一条命令就可以搞定编译。
sudo docker run -v $(pwd):/pkg -i -t golang:1.4 /bin/bash -c "go get github.com/AppC/docker2aci; cp /$GOPATH/bin/docker2aci /pkg/"
编译好的 docker2aci
二进制文件会被拷贝到当前目录,将它放到系统变量PATH所指的的任意目录中即可,比如:
sudo mv docker2aci /usr/local/bin/
执行 docker2aci --version
命令可以打印出软件的使用帮助,证明已经成功安装。
$ docker2aci Usage of docker2aci: docker2aci [--debug] [--nosquash] IMAGE Where IMAGE is [--image=IMAGE_NAME[:TAG]] FILEPATH or docker://[REGISTRYURL/]IMAGE_NAME[:TAG] Flags: -debug=false: Enables debug messages -image="": When converting a local file, it selects a particular image to convert. Format: IMAGE_NAME[:TAG] -nosquash=false: Don't squash layers and output every layer as ACI
将一个Docker镜像导出,然后可以通过 docker2aci
命令转换成AppC镜像。
$ docker pull ubuntu $ docker save -o ubuntu.docker ubuntu $ ./docker2aci ubuntu.docker ... ... Generated ACI(s): ubuntu-latest.aci
转换后的镜像会保存在当前目录,并自动用“<镜像>-<标签>”的格式命名。
此外,比较实用的是, docker2aci
同样支持 docker://
协议的URL直接获取网上的镜像。
$ docker2aci docker://busybox ... ... Generated ACI(s): busybox-latest.aci
这个时候,如果直接用Rkt运行刚刚创建的 hello.aci
镜像,会发现Rkt提示由于找不到有效的签名文件,因此拒绝运行这个镜像。
$ sudo rkt run hello.aci error opening signature file: open /home/core/hello.aci.asc: no such file or directory
镜像的签名,是AppC引入的一种镜像来源验证机制,本质上是利用非对称加密的标准数字签名。通过将镜像提供者的私钥和镜像文件本身加密生产一组签名字符串,通过发布者提供的公钥就能够解开这串字符并得到与镜像匹配的信息,这样就能验证镜像是否是真的来自特定的作者或来源。
AppC的签名算法是标准是RSA,采用的是开源的GPG实现,关于GPG的详细介绍参考 这篇 文章。
首先准备一个密钥配置文件,命名为 gpg-batch
,内容如下:
%echo Generating a default key Key-Type: RSA Key-Length: 2048 Subkey-Type: RSA Subkey-Length: 2048 Name-Real: 你的英文名字 Name-Email: 你的邮箱地址 Name-Comment: ACI signing key Expire-Date: 0 Passphrase: 签名时的密码 %pubring rkt.pub %secring rkt.sec %commit %echo done
然后用下面的命令生成一个密钥对:
gpg --batch --gen-key gpg-batch
执行完成后,在目录中会多出 rkt.sec
和 rkt.pub
两个文件,这就是私钥和公钥了。
然后就可以使用这对密钥给镜像签名了:
gpg --no-default-keyring --armor --secret-keyring ./rkt.sec --keyring ./rkt.pub --output hello.aci.asc --detach-sig hello.aci
在提示输入密码时,输入在 gpg-batch
中设置的密码。然后就获得了 hello.aci
镜像的签名文件 hello.aci.asc
。
再次尝试运行容器:
$ sudo rkt run hello.aci openpgp: signature made by unknown entity
这次提示的错误是,签名文件虽然找到了,但是这个签名的来源并没有在信任列表中。为了将签名添加到信任里面里面,首先要用 rkt.sec
和 rkt.pub
这两个二进制的密钥文件导出为一个文本的公钥文件。
gpg --no-default-keyring --armor --secret-keyring ./rkt.sec --keyring ./rkt.pub --export 在gpg-batch中的邮箱 > pubkeys.gpg
然后将这个文本文件中的公钥添加到Rkt的信任列表中。
$ sudo rkt trust --root pubkeys.gpg Prefix: "" Key: "pubkeys.gpg" GPG key fingerprint is: 37E2 6071 5382 5868 5A0D 1356 98A9 5E24 6E19 7AED Subkey fingerprint: 46AF 81E4 77D4 BFCA DFCE 73C6 3D94 79C2 2611 F243 Kelsey Hightower (ACI signing key)Are you sure you want to trust this key (yes/no)? yes Trusting "pubkeys.gpg" for prefix "". Added root key at "/etc/rkt/trustedkeys/root.d/37e26071538258685a0d135698a95e246e197aed"
这次运行容器,就可以看到容器中的Hello程序已经正确的执行了。
$ sudo rkt run hello.aci rkt: signature verified: Kelsey Hightower (ACI signing key) <kelsey.hightower@coreos.com> Hello AppC
最后简单的介绍一下AppC的镜像仓库(Image Repository)。
AppC规范定义了获取镜像的URL,其形式大致是 域名/镜像路径:版本
,例如CoreOS提供的包含Etcd的镜像可以通过命令 rkt fetch coreos.com/etcd:v2.0.9
来获取。
这里只说两个比较有意思的地方。
首先,AppC是没有所谓“官方镜像仓库”的,所以URL中的 域名
部分始终会存在。由CoreOS公司提供的镜像被放在 coreos.com
域名下的普通仓库中。这一点也符合AppC标准开放化的初衷。
其次,AppC会对用户的URL尝试通过两种方式解析。换句话说,镜像仓库的实现方式可以有两种。第一种几乎不需要额外的配置工作,将镜像依照一定的命名规则放在域名的相应路径下即可,例如 coreos.com/etcd:v2.0.9
,如果使用第一种方式建仓库,相应的镜像就应该保存在 https://coreos.com/etcd-v2.0.9-linux-amd64.aci
(当然,CoreOS的镜像实际上是用的第二种方式,所以这个路径不存在)。第二种方式更加灵活,但需要额外的程序来处理镜像地址的映射,具体过程,不再详述。
此外,前面介绍过,对于内网环境,还可以直接使用HTTP路径获取镜像。举个栗子,把之前制作好的镜像文件 hello.aci
和签名文件 hello.aci.asc
放到一个目录里面,然后在这个目录中启动一个简易的HTTP服务:
$ python -m SimpleHTTPServer Serving HTTP on 0.0.0.0 port 8000 ...
在任意另一个主机上就可以直接使用HTTP全路径下载镜像了。
$ sudo rkt fetch http://<服务器IP>:8000/hello.aci ... ... Downloading ACI: [ ] 1.26 KB/358 KB sha512-f7a2feff02a07ed7c604c14133b7aede
这种简单粗暴到无以复加的方式,对于懒人们也许算得上是一种福利,然而不论是从安全性还是镜像版本的可管理性上看来,其能力都相当弱。这从一个侧面说明,一些为开发者们提供的便利通道,如果没有规范的约束,可能带来很大的隐患。在实际运用时,建议遵循AppC的URL解析规范设计仓库为上策。
与处于Alpha开发期的Rkt一样,AppC项目距离最终的成熟还有很长的路要走。然而随着谷歌、VMWare和因特尔等互联网公司的加入,他们旗下的产品,如Kubernetes、Photon、ClearLinux等都已经将集成AppC容器作为其运营策略的一部分,其运用场景也日渐明朗。
“轻量”、“开放”、“安全”,很难说这样一种由“一个公司牵头,多个公司入伙,希望依赖社区力量发展”的容器标准会走向何方。至少我们已经看到,不论是CoreOS的AppC还是红帽的Nulecule,这些后来的容器标准的出现,或多或少是为了改善Docker的某些短板,也为容器业界提供了的一些可供选择的新思路。从技术的大环境来说,它们的未来是值得期待的。
感谢郭蕾对本文的策划和审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入InfoQ读者交流群 )。