Systemd与Docker的爱恨情仇
DevConf.cz 是一个由红帽赞助的开发者大会,今年在捷克共和国布尔诺举行。会议上有很多不同的演讲,但 最大的主题是容器 。大部分演讲是实践性质的,要么是展示如何使用各种容器技术的教程,比如 Kubernetes 和 Atomic.app ,要么是对新产品的介绍,比如 Cockpit 。
不过,红帽容器工程主管Dan Walsh所做的演讲无疑是容器主题中最有趣的。他展示的是Linux容器世界核心冲突之一:systemd与Docker守护进程。这绝非是一个新问题;在Ubuntu采用systemd,以及CoreOS引入 围绕systemd构建的容器系统 Rocket时,这个问题就出现了。
Systemd vs. Docker
“这是Lennart Poettering,”Walsh一边展示一张照片一边说。“这是Solomon Hykes”,他展示了另一张照片。“两个人都不愿意妥协。而我则介于他们之间。”
由于Walsh曾受命让systemd支持Docker,他详述了两个系统间代码、个人及运营冲突的历史。从很多方面来说,这也是红帽与Docker公司之间补丁冲突的历史。Poettering是systemd的主要作者,并在红帽工作,而Hykes则是Docker公司的创始人和CTO。
依据Walsh的演讲,冲突的根源在于Docker守护进程被设计用来接管很多systemd也为Linux完成的功能。包括初始化、服务激活、安全及日志。“从很多方面讲,Docker想成为systemd,”他声称,“它梦想成为systemd。”
他阐述的第一个冲突在于服务初始化与重启。在systemd模式中,这些都是由systemd控制的;在Docker的世界中,则全由Docker守护进程控制。比如说,可以在systemd单元文件中以“docker run”语句来定义服务,以便以容器方式运行它们,也可以在Docker守护进程中将服务定义成“自动重启”容器。两种方式都行得通,但混合起来就不行。 Docker文档 推荐使用Docker自动重启,除非是在混合容器化服务与非容器化服务时;这种情况下它推荐使用systemd或Upstart。
不过,当以容器方式运行的服务依赖于其他容器化服务时,问题就出现了。对于常规服务,systemd有一个名为 sd_notify 的功能,可以在服务准备就绪时传递消息,然后就可以启动那些依赖于它们的服务。但是,Docker采用的是C/S架构。客户端为每个用户会话调用docker run及其他命令,但容器的启动和管理是在Docker守护进程中进行的(相当于“服务端”)。客户端无法发送sd_notify状态消息,因为它不做容器服务的实际管理,也不清楚服务的就绪时间,守护进程也无法发送这类消息,因为它不是通过systemd的单元文件调用的。因此,Walsh的团队尝试了一种曲折的变通方式来启用sd_notify:
- systemd请求来自Docker客户端的sd_notify
- 该客户端发送一条sd_notify消息给Docker守护进程
- 守护进程设置一个容器来完成sd_notify
- 守护进程从该容器获取一条sd_notify消息
- 守护进程发送一条sd_notify消息给该客户端
- 该客户端发送一条sd_notify消息通知systemd Docker容器已就绪
用于启用这个错综复杂系统的补丁未被Docker项目接纳,对此Walsh毫不意外。sd_notify确实可以用于Docker守护进程自身,因此systemd可以依赖于守护进程的运行。但是还是无法为单个容器化服务执行sd_notify,因此Docker项目依然 没有可靠的办法来管理容器化服务依赖的启动顺序 。
Systemd有一项功能叫“ 套接字激活(socket activation) ”,可以在收到针对某个特定网络套接字的请求时自动启动服务。这使得服务器可以支持那些“偶尔需要”的服务,而无须始终运行。Docker守护进程自身曾经支持过套接字激活,不过由于与Docker的自动重启相冲突而将其禁用了。
Walsh的团队对单个容器的套接字激活更感兴趣。其好处是可以消除“始终运行”容器的额外开销。但是,开发人员发现他们不得不采用与sd_notify变通方式类似的方法,差别在于传递的是一个套接字,而不仅仅是一条消息。他们甚至没有尝试实现。
Linux的 cgroup 可用来定义每个服务的系统资源额度,比如CPU、内存及I/O限制。Systemd允许在初始化文件中定义cgroup限制,这样你就可以定义服务启动时的资源策略。但是,使用Docker时,这又与其C/S模式相冲突。Systemd的cgroup设置只对客户端产生作用;不对容器实际运行所在的守护进程产生作用。相反的,每个容器都继承了Docker守护进程的cgroup设置。不过,用户可以通过docker run命令 参数 来传递cgroup限制,这虽然可行,但未能与系统的整体管理策略整合。
Walsh能联系得上的唯一成功之处是日志。Docker的日志也无法与systemd的 journald 协作。容器的日志输出都是在每个容器本地,一旦删除容器,所有日志将被自动清除。从安全审计的角度来看,这是一大败笔。Docker 1.9开始支持--log-driver=journald开关,将日志记录到journald中。不过,Docker容器默认不使用journald,因此每次都要传递这个开关。
容器内部的systemd
Walsh还想在Fedora、红帽企业Linux(RHEL)及CentOS容器基础镜像中启用systemd,部分是因为很多软件包需要systemctl功能以便正确安装。他一开始尝试的是使用“ fakesystemd ”来代替systemctl,该服务用于满足软件包的systemctl需要,没有其余的功能。结果这会造成问题,他很快就放弃了,不过还是迟了一步,未能阻止它在RHEL 7.0中发布。
在RHEL 7.1中,该团队添加了“systemd-container”,这是systemd的一个大幅度删减版本。这依然会对那些软件中需要完整systemd的用户造成问题,Poettering要求容器团队进行修改。在RHEL 7.2中,容器具有了真正的systemd,减少了需要安装的依赖,因此尺寸更小。Walsh的团队正着手进一步缩减这些依赖。
根据Walsh所说,容器中没有systemd最大的问题是它“退回到了使用初始化脚本之前。”每个镜像作者都在容器内创建自己的疯狂的启动脚本,而不是使用软件包作者精心制作的启动脚本。他演示了在具有systemd的容器内,服务初始化是何尝的简单,创建一个运行Apache httpd服务器的容器,其Dockerfile只有三行:
FROM fedora RUN yum -y install httpd; yum clean all; systemctl enable httpd; CMD [ "/sbin/init" ]
不过,要在Docker中使用systemd,有一个主要障碍:运行具有systemd的容器要求运行时要带有--privileged标记,这让它变得不安全。这是因为Docker守护进程要求容器运行的“服务”应用程序其PID永远是1。在具有systemd的容器中,其PID是1,应用程序则具有其他的PID,这会造成Docker认为容器失败并将其停止。
Poettering说PID 1具有特殊要求。其中之一是杀死那些被它们的调用会话所遗弃的“僵尸”进程。对Docker来说,这是一个真正的问题,因为应用程序以PID 1运行,却不处理僵尸进程。比如,运行Oracle数据库的容器可能在退出时会遗留几千个僵尸进程。另一个要求是写入syslog,除非配置容器写入日志到journald,否则将进入/dev/null。
Walsh尝试了几种办法以便systemd在非特权容器中工作,并提交了四个不同的拉取请求( 7685 、 10994 、 13525 及 13526 )给Docker项目。所有请求都被Docker维护人员拒绝了。当Docker贡献者之一Jessie Frazelle带着印有“我对systemd相关拉取请求说不(I say no to systemd specific PRs)”的名牌来到 DockerCon.EU 2015 大会时,有关这些修改的争论达到了顶峰。
Systemd与容器的未来
红帽容器团队还大量参与了开放容器项目 runC工具 的开发。该项目是开放容器组织(OCI)的实践性产出,OCI是 由Linux基金会于2015成立 的非营利组织,目的是为容器API设置行业标准。OCI还维护着 libcontainer ,这是Docker用于启动容器的类库。按照Walsh所述,Docker最终需要采纳runC作为其技术栈的一部分,以便在其他平台上运行,尤其是Windows。
使用来自runC的成果,红帽人员已经创建了一组名为“ oci-hooks ”的补丁,为Docker添加了大量systemd支持功能。它使用一个“钩子”,可以在容器启动之后应用程序运行之前触发指定目录下的所有可执行文件。使用这种方法执行的东西中间是 RegisterMachine钩子 ,它会通知宿主机上systemd的machinectl容器正在运行。用户可以使用machinectl命令看到所有Docker容器,以及runC容器:
# machinectl MACHINE CLASS SERVICE 9a65036e4a6dc769d0e40fa80871f95a container docker fd493b71a79c2b7913be54a1c9c77f1c container runc 2 machines listed.
这些钩子还允许在非特权容器里运行systemd。这个拉取请求( 17021 )同样被Docker项目拒绝了。尽管如此,它还是包含在了红帽发行的Docker包中。因此,Docker与systemd未来的一部分可能会涉及Docker的派生。
Walsh同时指出,cgroup、sd_notify以及套接字激活对runC都是开箱即用的。这是因为runC不使用Docker的C/S模型;仅仅是个可执行文件。他看不到Docker公司与红帽之间未来在systemd问题上修复的突破口。Walsh预测红帽可能会更多地转向runC,并远离Docker守护进程。按他的话来说,Docker正忙于“ containerd ”,这是一个新的systemd替代品,将取代初始化系统的功能。
但是,鉴于自Docker项目启动以来短时间内Linux容器生态系统的快速变化,几乎不可能预测systemd、Docker及runC今后一年的关系将如何。毫无疑问的是,将会有更多的变化和冲突报道。
原文链接: Systemd vs. Docker (翻译: 梁晓勇 )