在“选择Docker还是Rocket做容器?为何不选择两个?”一文中,曾提到CoreOS的创始人Polvi和Docker的创始人Sonomon都认为,Rocket和Docker没有竞争性。Docker平台是一个产品,Rocket是一个组件。企业可以选择Docker替代Cloud Foundry,也可以使用Rocket构建Cloud Foundry。CoreOS在发布Rocket时就指出,Rocket的出现是因为有些人需要一个更“纯净”的容器。换句话说,Rocket算是“App Container Specification”的标准实现。本文作者从“App Container Specification”入手,分析了Rocket和Docker在技术实现上的不同。以下为原文:Docker和Rocket,殊途同归首先,对于那些把Docker与Rocket放在一起比较的人,我想劝你们首先适当地调查一下。如果有这样两个软件:一个已经有1年多的历史,作为一个开源项目由全世界上千的开发者共同参与,并且已经或即将在实际的生产环境中被部署、测试;而另一个软件则是“新鲜出炉”的。如果这个较成熟的软件不是特别烂的话,那么很有可能它就是赢家。 所以,将Docker0.1.1与最近预发行的Rocket拿来比较会更公平一些。此外, CoreOS的CTO Brandon Philips也多次强调,Docker的一些功能将不计划加入到Rocket中——不只是现在,也许永远不会。为什么?因为Rocket项目的重点,至少目前的情况——不是重新实现Docker。Rocket是 “App Container Specification”的实现,因此,想比较两者,最好比较“App Container Specification”和最初的Docker清单或者Docker实现的其它规范。 以下的比较都将基于以上共识: Systemd通常情况关于Rocket的讨论最多的话题是systemd-spawn或者是 systemd 。 CoreOS已将systemd作为他们Linux发行版的init系统。他们可以接着使用Init 脚本或者upstar,但是他们选择使用了将来会成为所有主流Linux版本标准的init系统。实话说我并没有关于systemd独到的见解,只是读过Lennart等人的一些文章。对于CoreOS来说,将systemd作为新的Linux发行版的init系统的是不明智的。另外,CoreOS已经获得了我的信任,我对此深信不疑。你可能会抛出 btrfs与我争论,但是, 那个会很快消失,我希望它会被修复,或者被其他可靠的方案替换。毕竟稳定性比功能更重要。 好吧,让我们回到问题上来。其中systemd做的或者说能够做的事情多是进程管理。你可能会认为,systemd解决这个问题的方式,多少有些重叠了服务、进程管理的概念,并且将一些原本能在PID1之外很简单的任务复杂化了,但因为CoreOS是systemd的忠实用户,如果使用其它工具来实现反而显得奇怪。 此外,据Brandon介绍,Rocket的最初实现使用systemd-nspawn的原因是他们想用systemd,Systemd-naspawn 已经实现,而且正在做他们想做的东西,所以它有助于项目的开始。坦率地说,在0.1.1版本,我并不关心他们使用的技术,没准以后都会变的,因为Rocket的设计是可插拔的。如果你不喜欢systemd-nspawn,可以用你自己的stage1实现,Rocket已经提供了详细的命令行参数。 另外需要指出的是,那些认为想使用Rocket,就必须运行systemd的人就完全错了。Rocket根本不需要systemd。它也能与其它init系统,如SysV或upstart配合工作。我在upstart上测试了Rocket,没有遇到任何问题。Rocket仅仅是“重用”了systemd和systemd-nspawn,处理stage1和stage2。 App Container有些人可能还没有准确地理解“App Container Specification”。他们一直在说systemd,并说Docker是如何好,不需要在容器中用systemd运行进程。Docker通过后台进程管理单进程的容器。Docker后台进程未被写成一个进程管理工具,当你的容器停止时,这种设计的弊端就会显现出来。你最终要通过主机或者容器中的进程管理工具来应付它。如果你还没有经历过这样的事,要么你可能是幸运的,又或者没有在环境中运行大量的容器。 我们需要进程管理,或进程管理提供的某些功能。如果读过“App Container Specification”,你会理解这点的。我将其中一部分摘录如下: 容器执行一个或多个应用程序,共享PID namespace、network namespace、mount namespace、IPC namespace和UTS namespace。执行之前,每个应用程序将开始转为(比如chroot)自己特有的读写根文件系统。容器的定义是,包含一系列应该在一起启动的应用,以及应用与整个容器的隔离器。 上面明确提到了几个进程共享Linux的命名空间。换言之,Kubernetes的Pod与以上对容器的定义很像,我不知道这是否与CoreOS正在积极参与Kubernetes的开发有什么关系。但Kubernetes Pod是一组容器,而不是容器中的一组进程。这让你能从多个Docker镜像组成一个Pod中获益。这是App Container Specification定义了 依赖关系的地方,所以你的容器可以依赖于其他容器,因此通过 Stage0创造的最终容器runtime可能是 这样的。 现在,你已经在容器中运行多个进程,其中一些可能是后台进程,并且可能需要被监督,你需要一个进程管理器。至今我用过最好的管理器是runit,但话说回来,当你已经积累了大量关于systemd的经验时,你为什么会想要写runit脚本或使用其他管理器?如果我是CoreOS,也会做出一样的决定——systemd。 Docker和命名空间现在,让我们回到Docker。Docker一直倡导一个容器运行一个进程。总的来说,我同意这一点,因为Linux容器主要是为了实现主机的进程隔离,而每个容器运行一个进程,会给你带来更多的灵活性和复合型,独立的更新、回滚等,操作更加简单。然而,当你创建一个新的Docker容器或事实上LXC容器,容器被重新分配一套由libcontainer提供的全新的Linux命名空间。实话说,我觉得这有点浪费。于Docker还挺有意义,因为你要在一个容器中运行一个单独的进程,并且如果你想提供更通用的进程环境,来覆盖大多数的用例,你可能需要libcontainer高效提供的大量可用命名空间。 只为一个进程创建一组命名空间,增加了内核大量的额外管理工作。如果你的主机上运行了很多的容器,可能会出现内核某些方面的瓶颈。你可能会说开销很小,但是为什么非要过度占用内核呢? 因此,为了节省开销,可以共享进程之间的命名空间。但是有个问题,如果你开始共享命名空间,并且创建它的容器已经终止,它会删除共享命名空间的所有的进程。Kubernetesde在设计Pod时,就考虑到这个问题了。 Kubernetes Pod中第一个被创建的容器,会被从internal/24 VLAN中分配一个IP地址,该IP地址也被Pod内共享一个network namespaces的容器共享。 你可以看到这里微妙之处。每当“网络”创建的容器停止时,他会撤销Pod中所有的其它容器,因为没有可以共享的命名空间了,所以需要从头创建新的Pod,重新分配IP。更糟糕的是,你的连接也没了。所以,当你的容器停止后,不能自动重启它们。我相信通过观察Docker销毁容器的过程,可以找到解决办法,但是,那有些绕。所以,如果你想要在Docker容器中运行多个进程,你会需要一个进程管理工具。 |