“云原生”吐槽

吐槽是因为我觉得我们技术解决方案中还有很多不足,我想要思考和辨析这些方案中有哪些优势和不足,并且我想用我的实践来解决和改善它。

在梳理 “云原生” 的 “缺陷” 时,其实我意识到,很多场景并不是 Cloud Native 的不足,而是我们背负了太多的历史包袱和陈旧的技术观念,导致无法获得技术收益,甚至付出了沉重的代价。

备注

虽然PPT把很多有一些亮点但存在不少缺陷的技术包装得光鲜靓丽,但是作为实际部署和实现技术的技术工作者,正视不足和改进技术实现才是正途。

基础镜像之罪

Docker Atlas 容器技术采用了镜像了分发程序运行环境,带来的好处是应用程序运行环境一致性,可以很容易快速部署成千上万完全一致的App运行环境。

镜像技术有一个非常重要的概念,就是”层”:上层文件系统在下层文件系统之上,继承了下层文件系统所有内容继承之上,修改必要的部分实现新的部署。这使得在同一个物理服务器上部署的不同容器可以共用相同的基础,降低了存储和分发的成本。可以说文件系统层是容器技术的”杀手锏”之一。

文件层的概念也彻底改变了传统操作系统运维的方式,你不再需要在海量服务器上安装和升级软件包,你只需要重新分发镜像, 不断 重新分发镜像。如果只是修改很小的一部分,那么你的分发镜像只是修改部分内容,传输非常快速。

然而,现实并不是如此简单的文件层修改,难度和付出的代价要放大很多倍: 任何微小的镜像修改都需要容器销毁重建

仅仅修改一行配置,如果是被所有应用容器引用的基础镜像,可以说整个数据中心的所有容器都需要被重建一遍。

理想中,云原生的应用服务器是完全无状态的,相互间没有依赖关系的。然而,实际上一个巨型应用网站的应用之间存在及其复杂的依赖关系,应用的依赖关系使得业务重启是非常困难和沉重的,甚至是一个 Mission: Impossible

备注

吐槽:如果不能随意销毁和重建容器, cloud native 的优势荡然无存

如果你的修改,是一个和业务无关但又非常关键的底层操作系统修订,例如 syslog 配置修改,原本通过配置管理工具 puppet 或者 ansible 可以轻而易举在数分钟或数十分钟内完成海量应用容器更新。

但是,线上数百万的容器翻一遍可行么?答案在于你有没有合理组合技术栈。一定要将应用无关的服务剥离出容器镜像,才能够实现基础服务的共享和随时更新。

基础镜像解决之道

实际上我们是错误使用了基础镜像: 基础镜像不应该包含任何与运行应用无关的软件

最理想的镜像,应该是只包含应用运行的库文件。受到传统的操作系统运维模式影响,我们常常会不自觉地复刻传统的运维模式:

  • 容器内部运行ssh服务,通过ssh登陆服务器执行脚本维护系统

  • 在容器内部运行配置管理agent,例如puppet client进行软件包安装或者配置修订

  • 依然在容器内部像以前vm一样运行日志采集服务,这样运行多少个容器就需要运行多少个容器内部的日志采集agent

  • 在容器内部运行crond执行定时任务

  • 很多vm内部服务集成到systemd中管理,所以想当然在一个容器内移植完全一致的服务,甚至把systemd也移植到容器内部来管理服务

在容器内部运行传统的各种操作系统辅助服务是代价极大的”错误”。

从运维经验来看,操作系统级别的服务(软件)常常需要不断进行更新和修复,以解决安全漏洞、性能问题以及避免故障。一旦和运行应用无关的服务被打包到容器镜像中,带来维护对象的数十倍甚至数百倍的扩大,更新非常困难。而且,重复的辅助服务没有合并成host主机上功能集中的daemonset,带来系统资源的极大浪费。

由于容器对于host操作系统,实际上只是namespace的隔离,所以在host主机上可以实现对容器对侵入,这样可以把很多基础服务合并到host主机上集中提供能力:

  • 容器内部剥离ssh服务,只在host主机上提供ssh服务,当登陆到host主机上,可以通过 docker attach 进入容器内部维护

  • 配置管理不应该在容器内部独立运行,在host主机上实现统一更新配置

    • 配置通过卷挂载到容器内部

    • 更新是否可以在host上通过卷实时更新?

  • Docker容器内部是支持运行cron服务设置cron定时任务的,但是我感觉应该尽可能避免在容器内部运行cron:很多cron不过是在vm内部做定时日志清理,服务定时检查重启,这些传统的运维项目完全可以剥离到host主机,甚至在数据中心通过全局管理和分发

当容器尽可能简化功能之后,很多无谓的基础镜像更新消耗就引刃而解了:只需要更新业务相关的运行库和配置,如果能够做到无状态更新,则可以实现海量容器的更新运维。

备注

在生产环境中维护基础镜像成本极高,所以正确的方向应该是推进整个技术栈的转换,而不是为了容器化而复刻原有的技术栈,否则带来的结果甚至不如原先虚拟化的云计算。

臃肿的镜像绝不是云原生

号称轻量级的Docker在生产环境中使用往往会让人跌破眼镜:不是说秒级启动么?现实是等待了数分钟,甚至十数分钟,都没有见到应用容器启动,时不时还出现因为启动超时而导致容器被销毁。

导致这个奇葩的现象是因为对镜像的滥用,导致不断在镜像中堆积文件层,加上初始镜像无节制的安装各种无用的软件包。一个简单运行服务的镜像需要几个G的容量,在生产环境中大规模并发部署,导致网络阻塞,更拖慢了容器创建的进程。

镜像瘦身

实际上,在Docker容器化一定要坚持功能单一和镜像简:

  • 无用的内容绝不要打包到镜像中

  • 多层次镜像(不断迭代)一定要做镜像瘦身

当需要大规模部署的同质环境,可以通过基础镜像预分发,使得首次启动速度加快。不过,这个工作是非常繁琐和需要投入持续改进,很多生产环境缺乏动力(kpi驱动)进行优化,所以Docker的启动性能往往不尽如人意。

备注

我将在 Docker Moby 深入探讨镜像制作,并在镜像精简上做更多实践。

安全?

容器的隔离和数据安全是极大的挑战,从一个容器逃逸到host主机,进而侵入到其他非授权容器,是云原生技术需要解决的最大问题。

对于host主机,容器不过是操作系统中运行的一个进程,通过namespace切换,可以进入不同容器内部。这带来了运维的全新上帝视角,也带来了安全隔离的挑战。

热迁移的遗憾

当前容器技术有一个非常”致命”的短板,就是缺乏类似虚拟化的 KVM热迁移 技术,所以在修补底层缺陷(安全漏洞)上具有很大的局限性。