在过去几年中,容器技术不仅仅在开发者中成为热门话题,许多企业也参与其中。这种对容器兴趣的日益增加,使得对其安全提升和加固的需求不断提升,同时也对可扩展性和互操作型有了更高的要求。这些工作都是大工程,本文介绍了红帽在企业级容器支持上所做的工作。
当我在2013年秋季首次遇到Docker公司(Docker.io)的代表时,我们还在研究如何在红帽企业版(Red Hat Enterprise Linux,RHEL)中使用Docker容器。(现在Docker项目的一部分已经被改名为Moby。)在将这项技术融入到RHEL时,我们遇到了许多问题。首先遇到的最大障碍是找到一个支持写时复制(Copy On Write,COW)的文件系统来处理容器镜像分层。红帽最终为包括Device Mapper、btrfs和最初版本的OverlayFS贡献了部分COW实现。对于RHEL,我们默认使用Device Mapper,虽然当时对OverlayFS的支持已经快完成了。
另一个大障碍是启动容器的工具。当时,上游docker使用LXC工具来启动容器,但是我们不希望在RHEL中支持LXC工具集。在与上游docker公司合作之前,我和libvirt团队合作,制作了名为virt-sandbox的工具,它能够通过libvirt-lxc来启动容器。
当时,红帽公司一些人认为,将LXC工具集移除,在Docker守护进程和libvirt之间通过libvirt-lxc来桥接启动容器是个不错的想法。但是这种实现方式令人担忧。使用该方式通过Docker客户端(docker-cli)来启动容器,客户端和容器进程(pid1OfContainer)之间的调用层次有:
docker-cli → docker-daemon → libvirt-lxc → pid1OfContainer我不太赞同启动容器的工具和实际运行容器之间有两个守护进程。
我的团队开始和上游docker开发者一起,通过原生Go语言实现了一个名为容器运行时库:libcontainer。该库最终作为OCI运行时规范runc的初始实现发布。
docker- cli → docker-daemon @ pid1OfContainer有许多人误以为当他们执行一个容器的时候,容器进程是docker-cli的子进程。事实上,docker是一个典型的C/S架构,容器进程运行在一个完全独立的环境中。这样的C/S架构,可能导致不稳定和潜在的安全隐患,另外还会导致一些系统特性无法使用。例如,systemd有一个称为套接字激活(socket activation)的特性,用户可以设置守护进程仅在进程连接到套接字时才运行。这样可以降低系统内存使用,让服务按需运行。套接字激活的实现原理是systemd监听一个TCP套接字,当该套接字接收到数据包的时候,systemd激活需要监听该套接字的服务。当这个服务激活之后,systemd将套接字转交给新启动的守护进程。将这个守护进程放到基于Docker的容器中之后,就会产生问题。通过在systemd的unit文件中使用Docker客户端命令可以启动容器,但是systemd无法简单的通过Docker客户端命令将套接字转移到守护进程。
类似这样的问题,让我们意识到,有必要换一种运行容器的方式。
容器编排问题上游docker项目致力于让容器使用变得更加方便,它一直是学习Linux容器的好工具。我们可以通过简单的使用类似docker run -ti Fedora sh命令来启动容器并进入容器中。
容器的真正优势在于同时启动大量组件,并将它们组装成一个强大的应用程序。设置一个多容器应用的难点在于其复杂度的指数级增长,以及通过简单的docker命令将分散的各个部分串联起来。如何在资源有限的集群节点中管理容器布局?如何管理这些容器的生命周期?类似问题还有很多。
在第一届DockerCon上,至少有7个公司/开源项目展示了容器编排的方式。我们演示的是红帽OpenShift的项目geard,它松散的基于OpenShift v2容器。红帽决定我们需要在容器编排上重新审视,也可能和其他开源社区的人员合作。