在建设 PaaS 平台的过程中,我们常常面临选择胖容器还是瘦容器。毫无疑问,K8S/Docker 等社区都推荐以瘦容器的方式运行,但是在落地的过程中,受已有技术和业务要求的影响,往往可能会选择以胖容器的形态承载业务。公司在建设 PaaS 的过程中选择了胖容器,经历了种种困难和妥协,回首曾经曲折的历程,感慨油然而生:胖容器和瘦容器的选择,某种程度上是在传统静态体系和理想中弹性体系的权衡。

选择胖容器的因素有非常多,归根结底都是为了兼容现有的技术体系,尊重业务方原有的使用习惯,降低业务的接入成本。现有的技术平台越是完善,越是更有可能会设计成胖容器。建设前公司已经有完善的运维体系和服务治理体系。运维体系主要包括基础运维、安全、监控、日志、发布系统等,这些服务分别以 agent 的方式部署在物理机(虚拟机)上;服务治理体系主要包括服务发现、熔断、旁路、降级等。如果以瘦容器的方式接入业务,容器中没有安装运维的 agent,意味着容器缺乏完善的监控,无法登陆,无法收集日志等,影响业务的正常运行。另外一件很遗憾的事情是,因将现有的服务治理功能迁移到 K8S 的改造成本过高,所以 K8S 的 Service 没有用起来,导致我们的 PaaS 无法提供服务治理能力。

我们就这样开始胖容器的建设历程,胖容器中的 1 号进程肯定不能是业务进程,由于运维的很多 agent 都是 systemd 或者 supervisord 拉起的,同时参考业内的经验,我们选择了 systemd 作为 1 号进程,把虚拟机镜像的库和各种 agent 一个不少的装进了容器,做了一个大大的基础镜像。Systemd 引发的第一个问题是它必须在 privilege 下运行,虽然经过改造后 systemd 可以在非 privilege 下运行,但是会带来其它副作用。由于 privilege 下用户可以执行 reboot 等各种特权命令,在造成了一些意料之中和重大的意料之外的故障后,我们清除了容器中的高危命令。当然,也遇上了 systemd 内存泄漏的 bug,至今不知如何彻底修复。经过一定的改造和优化,最终将这些 agent 注入容器中。

注入数十个 agent 后的容器胖起来了,易用性也得到提高。然而还不够,因为容器隔离性欠缺的原因,常用的 free, top 等命令看到的都是宿主机的信息,所以依次修改 free,top 等常用命令,甚至使用了lxcfs 解决这个问题。为了解决 sysconf 系统调用隔离性的问题,我们还 fake 了这个系统调用,以便返回真实的 cpu 信息。甚至修改了内核,从而获取容器的真实负载。我们做了很多,在做的过程中了踩了不少坑,每次发布,推动业务方升级镜像更是巨大的工作量,耗费了双方的精力,也降低了用户体验 ———— 虚拟机用着好好的,一用容器就各种事情找上来。

当 DevOps 遇上容器时,PPT 上往往有各种遐想的空间。在实际落地的过程中,因为公司已有成熟的发布系统,该发布系统是基于虚拟机时代的场景设计,发布主要是更新下代码和重启服务。因而在虚拟机下每次发布 IP 都是一样的,发布前后的环境也是一样的。在 K8S 的 Deployment 发布的流程下,发布前后的 IP 都是不确定的。为了保持 IP 不变性,我们每次发布只是将原来的容器杀死,维持 Infra 容器不变,因而 K8S 推荐的发布方式难以用起来。另一件要命的事情是,容器下的发布都会导致环境销毁,对某些业务方而言增加制作镜像的成本(将一些库业务库依赖库打入镜像)。总而言之,我们在兼容现有发布系统下尝试的容器 DevOps,做的并非成功。

为了满足业务方面的一些诉求,我们对 K8S 也做了一些非常值得商榷的改造。比如容器故障异地重建保持 IP 不变;以及指定删除某个 IP 然后让集群的 replica 减 1;甚至保证宿主机重启后能够及时的把停止的容器拉起来。这些把 K8S 往静态的方向改造的需求,和 K8S 弹性理念存在严重的冲突,所谓改造一时爽,埋坑升级火葬场。

当时间荏苒,回首 2 年来 K8S 建设历史,我们接入了一些平台性质的应用,比如 redis,搜索引擎等等,这些业务看重的是资源管理和调度,以及 API 式生命周期的管理能力。但是对业务应用来说,这些应用具有数量多,但是平均每个应用的所用的资源并不多的特点,它们接入 PaaS 的收益比就另当别论了,所以 PaaS 在业务应用的推广并不顺利。这也容易理解,我们把容器做的那么胖,容器的状态并没有真实的代表业务进程的状态,业务方还必须引入回调等以确认服务是否正常运行,同时对 K8S 往静态的方向妥协改造,service 功能被阉割,失去了 K8S 弹性和强大的编排功能。这个胖容器虽然像虚拟机,但是又比虚拟机难用:挂掉后 IP 会变化,存储会丢失(没有用到共享存储),而且隔离的不彻底。对业务应用来说,它除了比 OpenStack 的虚拟机启动的稍微快一点,节省一点资源外,毫无其它优势。

胖容器对本文而言更多是一个引子,问题的核心点在于建设 PaaS 的过程中,在现有的技术体系和 PaaS 理想的玩法中该如何权衡。我想在不同的公司有不同的答案,技术的演进是循序渐进的,而且在演进的过程中要保障业务的稳定和发展。在设计 PaaS 架构时,最好从顶层进行设计。在接入业务的过程中,尽量优先接入能够按照 K8S 原生的方式玩法的业务。在改造 K8S 的时候,不要违背它的核心理念,特别勿往静态的方向去改造。那么具体该如何落实,后续在相关文章会进行探讨。