.. _roast_cloud_native: ================= "云原生"吐槽 ================= 吐槽是因为我觉得我们技术解决方案中还有很多不足,我想要思考和辨析这些方案中有哪些优势和不足,并且我想用我的实践来解决和改善它。 在梳理 "云原生" 的 "缺陷" 时,其实我意识到,很多场景并不是 Cloud Native 的不足,而是我们背负了太多的历史包袱和陈旧的技术观念,导致无法获得技术收益,甚至付出了沉重的代价。 .. note:: 虽然PPT把很多有一些亮点但存在不少缺陷的技术包装得光鲜靓丽,但是作为实际部署和实现技术的技术工作者,正视不足和改进技术实现才是正途。 基础镜像之罪 ============= :ref:`docker` 容器技术采用了镜像了分发程序运行环境,带来的好处是应用程序运行环境一致性,可以很容易快速部署成千上万完全一致的App运行环境。 镜像技术有一个非常重要的概念,就是"层":上层文件系统在下层文件系统之上,继承了下层文件系统所有内容继承之上,修改必要的部分实现新的部署。这使得在同一个物理服务器上部署的不同容器可以共用相同的基础,降低了存储和分发的成本。可以说文件系统层是容器技术的"杀手锏"之一。 文件层的概念也彻底改变了传统操作系统运维的方式,你不再需要在海量服务器上安装和升级软件包,你只需要重新分发镜像, ``不断`` 重新分发镜像。如果只是修改很小的一部分,那么你的分发镜像只是修改部分内容,传输非常快速。 然而,现实并不是如此简单的文件层修改,难度和付出的代价要放大很多倍: ``任何微小的镜像修改都需要容器销毁重建`` 仅仅修改一行配置,如果是被所有应用容器引用的基础镜像,可以说整个数据中心的所有容器都需要被重建一遍。 理想中,云原生的应用服务器是完全无状态的,相互间没有依赖关系的。然而,实际上一个巨型应用网站的应用之间存在及其复杂的依赖关系,应用的依赖关系使得业务重启是非常困难和沉重的,甚至是一个 `Mission: Impossible `_ 。 .. note:: 吐槽:如果不能随意销毁和重建容器, ``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主机,甚至在数据中心通过全局管理和分发 当容器尽可能简化功能之后,很多无谓的基础镜像更新消耗就引刃而解了:只需要更新业务相关的运行库和配置,如果能够做到无状态更新,则可以实现海量容器的更新运维。 .. note:: 在生产环境中维护基础镜像成本极高,所以正确的方向应该是推进整个技术栈的转换,而不是为了容器化而复刻原有的技术栈,否则带来的结果甚至不如原先虚拟化的云计算。 臃肿的镜像绝不是云原生 ====================== 号称轻量级的Docker在生产环境中使用往往会让人跌破眼镜:不是说秒级启动么?现实是等待了数分钟,甚至十数分钟,都没有见到应用容器启动,时不时还出现因为启动超时而导致容器被销毁。 导致这个奇葩的现象是因为对镜像的滥用,导致不断在镜像中堆积文件层,加上初始镜像无节制的安装各种无用的软件包。一个简单运行服务的镜像需要几个G的容量,在生产环境中大规模并发部署,导致网络阻塞,更拖慢了容器创建的进程。 镜像瘦身 ------------ 实际上,在Docker容器化一定要坚持功能单一和镜像简: - 无用的内容绝不要打包到镜像中 - 多层次镜像(不断迭代)一定要做镜像瘦身 当需要大规模部署的同质环境,可以通过基础镜像预分发,使得首次启动速度加快。不过,这个工作是非常繁琐和需要投入持续改进,很多生产环境缺乏动力(kpi驱动)进行优化,所以Docker的启动性能往往不尽如人意。 .. note:: 我将在 :ref:`moby` 深入探讨镜像制作,并在镜像精简上做更多实践。 安全? ====== 容器的隔离和数据安全是极大的挑战,从一个容器逃逸到host主机,进而侵入到其他非授权容器,是云原生技术需要解决的最大问题。 对于host主机,容器不过是操作系统中运行的一个进程,通过namespace切换,可以进入不同容器内部。这带来了运维的全新上帝视角,也带来了安全隔离的挑战。 热迁移的遗憾 ============= 当前容器技术有一个非常"致命"的短板,就是缺乏类似虚拟化的 :ref:`kvm_live_migration` 技术,所以在修补底层缺陷(安全漏洞)上具有很大的局限性。