PouchContainer容器技术的演进,助力阿里云原生升级为在线应用的运行时基础和运维载体。每年双11,超过100万个PouchContainer同时在线,为电商及所有相关线上应用的顺畅运行提供载体,保障大促期间顺畅的购物体验。我们通过PouchContainer容器运行时层标准为应用程序开发和基础架构团队构建了一个标准接口。每年,应用程序都有新的要求和新的变化。同时,基础设施也有云/混合/神龙/存算分离/这些网改升级,双方并行演进,互不干扰。技术设施和PouchContainer都经历了伟大的架构演进,而这些架构和技术演进中有许多是应用程序开发人员所不知道的。在容器技术支撑的云原生技术形成趋势的今天,PouchContainer容器技术支撑的业务方不再只是集团的电商业务和线上业务。通过标准化的演进,我们把所有的定制化功能都做成了插件化,适配了不同场景的需求。除了集团线上应用,还有跑在离线调度器上的离线作业任务、跑在搜索调度器上的搜索广告应用、跑在SAE/CSE上的serverless应用、专有云产品和公有云(ACK+CDN)等场景使用PouchContainer提供的功能。运行时演进2015年之前,我们使用LXC作为运行时。为了在镜像后顺利接管原有的T4容器,PouchContainer在LXC中支持了一种新的镜像组装方式,支持交互式exec和内置网络模型。随着云原生的进步,我们在运行时支持containerd+runc在用户不知情的情况下,以标准化的方式添加内部功能插件,实现对内部功能特性的支持并被各种标准化运维系统使用的目标无缝整合。LXC和runc都允许所有容器共享Linux内核,使用cgroup和namespace进行隔离,不适用于强安全场景和强隔离场景。为了让更多场景受益,比如容器这种开发友好、运维友好的交付形式,我们很早就开始探索这项技术,并与groupOS创新团队和AntOS虚拟化团队合作构建套路securitycontainer和gvisorsecurecontainer技术,在容器生态嫁接、磁盘、网络和系统调用性能优化方面做了很多优化。在兼容性要求高的场景,我们优先推广kata安全容器,目前已经支持SAE和ACK安全容器场景。在语言和运维习惯确定的场景下,我们在618大促的时候也上线了一些合适的电商,使用了gvisor的运行时隔离技术,稳定性和性能都得到了验证。针对部分专有云场景的落地,我们今年也首次支持了Windows容器运行时,在容器依赖的部署运维方面做了一些探索,帮助敏捷版专有云赢得了部分客户。除了安全性和隔离性之外,我们的运行时演进还确保了标准化。今年最新版本的PouchContainer将diskquota、lxcfs、dragonfly、DADI等特性做成了可插拔的插件。不需要这些功能的场景可以完全不受这些功能代码的影响。我们甚至针对某些场景做了一个containerd发行版,支持纯标准的CRI接口和丰富的runtime。镜像技术的演进镜像不可避免地会给镜像分发的效率带来困难。一是速度,二是稳定性。既要保证中心节点不被压垮,又不能给发布扩容过程增加太多时间。PouchContainer从一开始就支持使用Dragonfly进行P2P图片分发,就是为了应对这种问题。这是我们的第一代图像分发解决方案。在研发领域,我们也推广了镜像分层的最佳实践,保证在基础环境不变的情况下,每次下载的镜像层都是最小的。图片加速要解决的问题有:构建效率、推送效率、拉取效率、解压效率、组装效率。第一代镜像加速方案,结合Dockerfile最佳实践,解决构建效率、拉取效率、中心压力。第一代镜像分发的缺点是无论用户启动过程中使用了多少镜像数据,在启动容器前都需要将所有镜像文件拉取到本地,这在很多场景下是一种浪费,尤其是扩容设想。因此,针对二代镜像加速方案,我们调研了阿里云的盘古,盘古的抓拍、挂载、抓拍的使用方式与镜像分发的过程完美契合;可以实现二级镜像拉取,因为拉取镜像的时候只需要鉴权,下载镜像manifest,然后挂载盘古,还可以按需读取镜像内容。2018年双十一,我们小规模上线了盘古远程镜像,也验证了我们的设计思路。这一代镜像加速方案结合新的overlay2技术,在第一代的基础上解决了PouchContainer效率/拉取效率/解压的问题。效率和装配效率。但也存在一些问题。首先,镜像数据不存储在中心镜像仓库中,只有manifest信息,所以镜像的分发范围是有限的,在盘古集群中制作的镜像必须在盘古所在的阿里云集群中使用集群位于;其次,没有P2P能力,在大规模使用时会给盘古后台带来很大的压力,尤其是在离线场景下,很多进程的可执行文件的pagecache因为内存压力被清除,然后需要重新加载。后端带来更大的压力。基于这两个原因,我们和ContainerFS团队一起构建了第三代镜像分发方案:DADI(基于块设备的按需P2P加载技术,后期有计划开源该镜像技术)。DADI在构建阶段保留了镜像的多层结构,保证了镜像在多个构建过程中的复用性,并在每一层对每个文件的偏移量和长度进行了索引。在推送阶段,镜像还是推送到中央镜像仓库,保证每个机房都能拉取到这个镜像。每个机房设置超级节点进行缓存,每段内容在特定时间内只从镜像仓库下载一次。如果有时间做镜像预热,比如双11场景,预热阶段就是把镜像从中央仓库预热到本地机房的超级节点,后续在同一机房的数据传输会很快速地。在镜像拉取阶段,只需要下载镜像的manifest文件(通常只有几K大小),速度非常快。在启动阶段,DADI会为每个容器生成一个块设备。这个块设备的chunk是从超级节点或者附近的节点P2P读取的内容,保证在容器启动阶段只在节点上读取需要的部分内容。为了防止在容器运行过程中发生iohang,我们会在容器启动后将整个镜像的所有内容拉取到后台的node节点,在享受超快启动的同时避免后续可能发生的iohang最大程度。使用DADI镜像技术后,今年双11高峰期,每次群里有人说有扩容任务,当我们值班同学去看工单的时候,扩容基本完成,并且拓展经验达到了二级。网络技术的演进PouchContainer最初的网络功能是集成到PouchContainer自己的代码中,通过集成代码的方式支持各个时期集团的网络架构。在Sigma-2.0时代,我们利用libnetwork将集团现有的各种网络机器架构统一为CNM标准网络插件,沉淀了集团和专有云都在使用的阿里巴巴自己的网络插件。在线调度系统推广期间,CNM网络插件不再适用。为了不重新实现所有的网络插件,我们将原有的网络插件进行了封装,沉淀了CNI的网络插件,并集成了CNM的接口,转换为CNI的接口标准。内网插件支持的主流单机网络拓扑的演变过程如下图所示:从单机拓扑可以看出,使用神龙eni网络模式可以避免容器做网桥转接,但是在使用神龙弹性网卡和CNI网络插件的时候也存在一些问题需要避免,尤其是在容器扩容的时候热插拔eni弹性网卡。在创建eni网卡的时候,udevd服务会分配一个唯一的idN,比如ethN,然后在容器N启动的时候,会把ethN移动到容器N的netns中,从里面重命名为eth0。当容器N停止时,eth0被重命名为ethN,并从容器N的netns移动到宿主机的netns。在此过程中,如果容器N没有停止,并且将另一个容器和eni分配给该主机,udevd可能会将这个新的eni的名称分配为ethN,因为它看不到ethN。当容器N停止时,将eth0重命名为ethN的步骤可以成功,但是移动到宿主机根netns的步骤会因为名称冲突而失败,导致eni网卡泄漏,下次容器N启动时,它的eni将找不到。这个坑可以通过修改udevd的网卡名称生成规则来避免。运维能力演进PouchContainer容器技术支持在线数百万容器同时运行。经常会有一些问题需要我们排查,其中很多都是已知问题。为了解决这个问题,我也写了千篇一律的PouchContainer,遇到用户查询,或者重复提问的时候,直接交给用户。但是提升PouchContainer及相关环节的稳定性和运维能力是最好的方式。今年我们搭建了一个container-debugger和NodeOpscenter系统来自动检测和修复用户提出的一些容器问题。任何修复都有灰度筛选和灰度部署能力,一些经常需要回答的问题,用户已经提出来了。友好的提示和修复也减轻了我们自身的运维压力。内部集中日志采集和实时分析,自带各模块健康保活逻辑。所有模块均提供Prometheus接口,接口暴露成功率高,耗时长。提供自动检测和修复常见问题的工具。积累运维经验,提供用户问题修复建议提供灰度工具,任何变化都可以通过金丝雀工具逐步灰度分析,过程中插入代码的能力Pouch具有一键释放的能力,快速修复容器使用的演变提供一个容器平台给应用程序使用,而且在容器启动之前肯定有很多平台相关的逻辑需要处理,这也是我们之前使用富容器的原因。安全相关:安全路由生成,安全脚本配置cpushare相关配置:tsar和nginx配置运维代理更新相关:运维代理更新比较频繁,基础镜像更新很慢,不能依赖基础镜像更新到更新运维代理配置相关逻辑:同步页头页尾,支持隔离环境,插件部署强弱依赖SN相关:模拟写入SN到/dev/mem,保证dmidecode可以读取正确的SN操作和维护相关的agent拉起来,很多运维系统都依赖节点上的agent,不管节点是容器/ecs还是物理机。与隔离相关的配置:比如nproc的限制是对用户的。同一个image的容器不能使用同一个uid,否则nproc无法隔离,现在随着基于k8s的编排调度系统的推广,我们有了Pod的能力,我们可以把一些预置的逻辑放在pre-hook中去执行。当然,富容器可以瘦身,这也取决于运维代理。从中间拆掉之后,那些只能靠volume共享运行的agent可以先移到sidecar上,这样就可以把运维容器和主业务容器分到不同的容器中。分离上面的隔离,主容器是Guaranteed的QOS,运维容器是Burstable的QOS。同时在kubelet上支持Pod级别的资源控制,保证Pod整体是Guaranteed的,同时限制整个Pod的资源使用量不超过单个应用实例的应用资源。还有一些agent可以放在sidecar的运维容器中,不只是做volume的共享。比如arthas需要能够attach到主容器的进程,必须能够在主容器的非volume路径上加载jar文件才能正常工作。.针对这种场景,PouchContainer容器也提供了与多个Pod容器共享ns的能力。同时配合ns遍历,让这些agent在部署方式和资源隔离上可以脱离主容器,但是在运行过程中,它仍然可以做它能做的事情。容器技术不断发展。可插拔的插件架构和更精简的调用环节仍然是容器生态的主流方向。kubelet可以直接调用pouch-containerd的CRI接口,可以减少中间组件的远程调用。但是CRI的接口还不够完善,很多运维相关的命令都缺失了。日志接口也依赖容器API实现,运行环境与构建环境分离,用户无需在宿主机上执行构建。所有运维系统不再依赖于容器API。在这些约束下,我们可以减少对中间组件的系统调用,直接使用kubelet调用pouch-containerd的CRI接口。现在每个应用都有很多Dockerifle,如何让Dockerfile更具有表现力,减少Dockerfile的数量。并发构建也是构建过程中的一个优化方向。Buildkit是这方面的可选解决方案。Dockerfile表达能力不足的问题也需要新的解决方案。buildkit中间状态的LLB就是gocode。可以使用gocode代替Dockerfile,定义更具表现力的Dockerfile替代方案。容器化是云原生的关键路径。当容器技术的运行时和镜像技术逐渐趋于稳定,热点和开发者的注意力开始向上层转移。K8s和基于它的生态,将成为未来能产出更多的容器技术。在诸多创新领域,PouchContainer技术也在朝着更云原生、更适配的K8s生态方向发展。网络、磁盘配额、隔离等PouchContainer插件的尝试,也是我们未来在K8s生态中进行适配和优化的方向之一。一。原文链接本文为阿里云内容,未经许可不得转载。
