CoreOS那些事之Rkt容器尝鲜(下)

2015年是各类容器技术与名词扎堆的一年,Docker的出现使得“应用容器”的实施变得易如反掌的同时,也带动了它的许多竞争者。其中一个比较有趣的看点就在于“容器规范”的较量,最近红帽和英特尔也按捺不住,拿出自家的产品趁势搅局。html

5月14日,红帽宣布了新的多容器应用规范Nulecule(DockOne翻译了这篇新闻),同时推出符合这个规范的一个实现:AtomicApp。(这个路子怎么看都有点像AppC和Rkt采用的模式)python

因特尔则是发挥自家的特长,在5月18日,发布了介于虚拟机与容器之间的跨界产品ClearLinux(DockOne一样翻译了这篇新闻)。之因此特别要提这个项目,是由于它首先会实现基于Rkt/AppC规范的容器模型,而将Docker放在了其次的位置。linux

“容器规范”的概念,看起来让人有些摸不着头脑,但在容器业界中,它确实是颇具诱惑力的一块蛋糕。在这一篇里,咱们就来聊一聊支撑Rkt背后的那个“容器规范”:AppC Spec。git

AppC规范究竟约定了什么

使用了开源软件的人,未必都会有心情仔细阅读各类开源协议的内容。大多数的使用容器产品用户,也不见得要对容器规范的内容有很高的兴致。github

不过,为了更好的理解后面将要介绍到的相关工具,仍是不妨稍微深刻的了解一些AppC规范约定的内容。其内容概括起来主要有四个方面,下面依次罗列出来,并与当下的主流容器Docker作一个简要的对比。golang

PS:严格来讲,AppC与AppC Spec两个词是有区别的。前者指的是CoreOS的App Container这个项目,包括规范和相关的工具,然后者特指AppC中约定的容器规范。但在许多地方,特别是翻译的文章中,常常看到这两个词被混用,所以通常也没必要太讲究了。算法

1.容器的镜像格式

本质上说,容器镜像就是符合特定目录结构的文件压缩包。镜像中的内容在容器启动后被展开,而后复制到一个独立的namespace空间内,并经过cgroup限制容器可以使用的系统资源。稍后在制做镜像时,会详细介绍AppC Spec规定的镜像目录结构。这里只先指出一点,AppC的镜像没有支持像Docker那样的分层结构,这种设计简化了容器运行时的一些操做,但带来的弊端也是很明显的:没法复用镜像相同的部分。所以在磁盘空间的利用上形成了浪费,也增长了容器镜像在网络传输成本。docker

除了目录的结构,镜像还须要一个描述镜像内容的文件,称为“镜像属性清单文件(Image Manifest)”,其中定义的内容包括:镜像的做者信息、容器暴露的端口、暴露的挂载点、所需的系统资源(CPU/内存)等。此外,AppC Spec的约定的属性清单中,还会包含许多编排调度所需的信息,例如容器运行所依赖的其余容器、容器的标签。ubuntu

在这方面来讲,AppC镜像的信息量远远多于Docker镜像。至关于囊括了Docker镜像自己、Compose编排配置以及一部分Docker运行参数的内容。缓存

此外,AppC规范也约定的镜像ID和签名的生成方法,关于镜像ID和签名的做用和在Rkt文章上篇中已经介绍过,稍后还会详细介绍镜像签名的生成方法。

2.镜像的分发协议

分发协议主要是约定镜像下载使用的协议类型和URL的样式。AppC的镜像URL采用相似Docker的domain.com/image-name这样的格式,但其实际处理方式有些不一样。此外,在没有指定域名时,Docker会默认在官方的DockerHub寻找镜像,AppC的镜像没有所谓“官方源”,所以也没有这样的规则。

Rkt/AppC目前支持如下几种URL格式:

  • <域名>/<镜像名>
  • <本地文件路径>
  • https://<完整网络路径>
  • http://<完整网络路径>
  • docker://<与Docker同样的镜像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容许使用未认证的镜像来源。

3.容器的编排结构

AppC规范中的容器编排和集群描述方式与Kubernetes十分类似,采用“容器组属性清单文件(Pod Manifest)”描述。其中沿用了Kubernetes中诸如Podslabels等用于在集群中进行调度策略的规划和管理的概念。

Pods直译即是“豆荚”,它指的是由一系列相互关联的容器组成的,可以对外提供独立服务功能的容器集合。例如将用于数据收集功能的容器、用于缓存服务的容器以及用于搜索服务的容器组合在一块儿,做为一个Pod提供完整的数据查询服务暴露给外部用户。Pod能够做为容器参与集群调度的单独集合提供给集群管理器,在例如Kubernetes这样的集群管理模型中,Pod实际上就是进行服务跨节点调度的最小单位。

labels用于标示具备同一类特性的容器,为容器的过滤和选择提供了十分灵活的策略。许多的集群管理器都可以在调度时利用选择指定标签对Pods进行筛选。

考虑到CoreOS公司与谷歌共同合做的背景(已经推出了Tectonic CaaS平台),这样的设计为Kubernetes将来与符合AppC规范的容器进行深度集成提供了良好的技术基础。

4.容器的执行器

执行器,也就像是Rkt这样的容器工具。这个部分规范了设计符合AppC Spec的容器执行器所须要遵循的原则和应该具有的功能。

例如,必须为每一个容器提供惟一的UUID;在容器的运行上下文中必须至少提供一个本地Loopback网卡,以及0个至多个其余TCP/IP网卡;应该将容器中程序打印到Stdout和Stderr的日志进行收集和展现等细节。

其中还详细约定了,对于镜像属性清单中的诸多属性,执行器应当如何进行处理。这些内容对大部分的使用者而言都只能做为参考,仍是须要以具体实现的容器产品文档为准。

镜像工具

在AppC的项目中,除了一纸洋洋洒洒的规范文书之外,还提供了很多AppC镜像相关的示范工具。不像Docker这种一个命令集成全部功能的玩法,这些工具中的每一个只是关注于容器的某个方面功能。例如通用类型镜像的制做、打包、格式转换以及特定类型镜像的制做等。

目前已有的工具主要包括:

  • Actool - 用于镜像的构建和验证
  • Docker2Aci - 用于将Docker导出的镜像转换为AppC镜像
  • Goaci - 用于Golang语言的项目一键打包和构建镜像的工具
  • Acbuild - 经过指令的方式构建镜像

其中,ActoolAcbuild都是用于镜像构建的工具,它们的区别相似于经过docker commit和Dockerfile两种方式构建镜像。须要指出的是,前不久才刚刚创建的Acbuild项目,如今还只是一个计划,没有发布任何实际可用的版本,其目的是替代以前的另外一个项目baci。后者已经没法使用而且再也不继续更新。

Goaci的做用是获取指定路径的项目,进行自动编译,而后把编译后的可执行程序制做成一个镜像,全部的这些操做只须要一条命令就能够完成:goaci <项目路径>,项目路径支持全部go get命令所支持的代码托管网站,包括BitBucket、GitHub、Google Code和Launchpad等。不过,它只能用于使用Golang语言并托管在上述网站中的开源项目。不具备广泛的适用性。

下面将重点介绍ActoolDocker2Aci这两个工具。为了方便非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

镜像的验证

容器镜像的来源无非有两种:本地的或者远程的。

所以对镜像的验证也就包含两部份内容。

  • 检验本地的文件是否符合AppC规范的镜像
  • 检验远程的URL是不是有效的AppC镜像地址

对于前一种状况,前面说过,容器镜像其实就是符合必定标准结构的打包文件。

$ 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.secrkt.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.secrkt.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的某些短板,也为容器业界提供了的一些可供选择的新思路。从技术的大环境来讲,它们的将来是值得期待的。

相关文章
相关标签/搜索