SREWorks 持续交付云原生化: 镜像构建
1 背景
在应用运维领域中,CI/CD 已逐步演化成持续集成(Continous Integration)、持续交付(Continous Delivery)和持续部署(Continous Deployment)三个核心阶段,以支持更加复杂的大型系统建设。
CI 依然代表应用开发人员的开发、测试、合并等自动化阶段,而 CD 已分化成持续交付和持续部署两个关联阶段,为云原生背景下的应用运维提供了更多的可操作空间。其中,持续交付在不同的阶段,都有相对应的事实标准,大致可分为“On-Machine”阶段,“On-Container”阶段以及目前的“On-CloudNative”阶段。
本文主要讲解一下 SREWorks 在持续交付阶段,镜像构建云原生化的解决方案,一句话概括就是:SREWorks 在执行镜像构建时,按需启动一个 Pod 去执行镜像构建任务。
1.1 发展阶段
“On-Machine”阶段:交付应用往往以一个编译后能正确运行的语言包组成,比如 Jar 包等;
“On-Container”阶段:容器技术的出现,此时,容器镜像 Image 作为应用交付的最终形态,解决了语言包对机器运行环境的强依赖,进一步提升了应用运维的效率;
“On-CloudNative”阶段:是以“On-Container”为基础的,是前者在云原生时代的高级形态。“On-CloudNative”阶段最终交付物不仅包含最基本的容器镜像 Image,还包含了一系列的运维特性,以支持容器编排等云原生场景需求。
1.2 持续交付云原生化
云原生是以分布式和云架构为基础的,但其对各种技术架构提出了更标准化的要求。在云原生思想逐步扩展开后,云原生能为传统的 CI/CD 带来什么影响,成为 SREWorks 考虑的主要问题之一。
其中最基本的,我们希望持续交付可以基于底座资源池去做弹性构建(按需启动一个构建 pod,构建完释放资源),而不用常置一台特定的构建机去做构建。
SREWorks 团队在经过不断的探索实践之后,认为云原生化的持续交付应包含以下几个显著的特点:
资源弹性:根据构建需要从资源池申请 cpu 等资源,提高资源利用率;
安全性:实时动态地执行构建任务,有明确的归属和权限隔离管控;
免运维:不再需要管理特定的构建机器或进程,机器管理统一由底座集群完成;
快速交付:底座集群保证构建任务容器能正确调度执行,自动规避故障节点,快速响应构建请求。
SREWorks 正是基于上述的几个点,逐步完成自己的镜像构建云原生化建设。
2 架构演进
2.1 Docker build 机制
按照 Docker 官方文档给出的架构图,Docker 主要分为 Client,Host,Registry 三个部分。
Docker 按照 C/S 架构,通过 Client 与 Host 进行通信。Host 作为后端,负责处理所有 Client 请求以及后端模块的调度及管理工作。Registry 作为中心化的镜像仓库,存储所有需要保存的镜像。
此处,我们主要关注 docker build 命令。可以看到 Host 中 Docker daemon 模块负责接受和处理 Client 的命令请求。Docker daemon 内部架构此处不做详细描述,只需要知道 Docker daemon 在接受到 docker build 命令之后,会启动一个 create job 去执行构建。在构建过程中,可能会需要到远程 Regiestry 中心镜像仓库拉取基础镜像(如果本地不存在的话)。在构建完成之后,Host 会将构建好的镜像保存在本地的 Repository 中。
Docker daemon 作为 docker 的服务端,由于需要系统的 ROOT 权限等要求,导致其不能天然地运行在另一个 docker 容器之中。这也为镜像构建云原生化提出了新的技术问题。
2.2 初始态:Docker out of K8S
在 Docker build 机制的支持下,在最初阶段是通过在 K8S 外搭建构建机器做镜像的构建任务,其架构如下:
AppManager:是 SREWorks 的中心管控服务,负责与 K8S 底座交互,应用构建发布及生命周期管理等核心功能。
在初始态,AppManager 在需要执行镜像构建时,直接连接部署在 K8S 之外的某个 ECS 上 Docker daemon 来执行镜像构建。依然处于“On-Machine”的发展阶段。
这种方式的缺点包括但不限于:
安全性差:当这台 ECS 被劫持之后,恶意程序通过在镜像构建过程中注入非法代码,能够影响所有部署在云上的应用;
效率较低:当构建任务过多时,一起抢占 ECS 机器资源,会引起构建拥塞。
2.3 过渡态:Docker out of Docker
Docker out of Docker,下面简称“DooD”,依然使用 docker build 机制的远程构建能力,每个执行构建任务的 pod 都连接到本地 node 的 Docker daemon 实现镜像构建。
通过 DooD 的方式,AppManager 可以动态的在集群层面拉起 pod 去执行镜像构建任务,而不用再去跟集群外部服务打交道。镜像构建任务可以均匀的分布到每台 node,避免构建拥塞。且 DooD 通过操作基础设施 node 节点,实现了一种“伪弹性构建”的能力。
DooD 的缺点也是显而易见的:
实施成本高:需要人工为每台 Node 节点安装 Docker daemon 程序,并保证其正确运行;
安全性差:构建 Pod 有能力通过 node 节点 Docker daemon 进程影响其他 Pod;
资源浪费:由于每台 node 节点都常置 Docker daemon 服务进程,不仅没有减少资源消耗,反而加重了资源浪费,这也是为什么称其为“伪弹性构建”。
2.4 终态:Docker in Docker
Docker in Docker,下面简称“DinD”,与 DooD 是对应的,表示容器内 client 直接连接容器自身的 Docker daemon 进程来完成镜像构建任务。其架构如下:
DinD 的架构,实现了按需拉起执行镜像构建任务的 Pod,并在任务完成后及时归还 Pod 资源。构建任务的 Pod 也不再感知 Node 机器,所有管控及调度工作由 K8S 等云底座统一实现。其优点包括:
弹性构建:所有构建 Pod 所需资源由 K8S 等云底座统一调度和回收;
安全可靠:构建 Pod 满足所有资源及权限管控要求,更加安全可控;
无服务器:Pod 是容器化的,不再运行在 ECS 等机器之上;
快速交付:在集群层面完成构建任务调度,更加及时且可扩展。
3 基于 Kaniko 的设计实现
目前支持在容器内部进行镜像构建的开源技术有很多:Kaniko,BuildKit,Img 等,其中 Kaniko 作为 Google 官方开源的容器化构建解决方案,目前收获了最高的 star 数量,社区活跃度也最高。排名其后的 BuildKit 和 Img 也通过其自身的技术优点吸引了一大批粉丝。
从架构上看,BuildKit 和 Img 都是通过内置一个非 Root 权限的嵌套容器去执行构建。而 Kaniko 没有拉起嵌套容器去做构建,而是直接实现了 Docker daemon 中 create job 的功能,更加的轻量、高效、安全。因此,SREWorks 基于 Kaniko 去做云原生下的镜像构建。
在引入 Kaniko 之后,SREWorks 的中心管控 appmanager 服务按照如下架构来对接 ApiServer 拉起和管控执行构建任务的 pod。
AppManager 关键模块讲解:
Provider 模块:AppManager 内置了一系列的 Provider 负责处理 Client 客户端的请求;
构建任务状态管理器(ComponentPackageTaskStateActionManager):负责监听各类构建任务状态更新事件,进而调用不同构建阶段的子处理器;
RunningState,FailureState,SuccessState 等模块:具体负责处理不同构建阶段的事务;
ComponentPackageBuilderService 模块:负责具体的镜像构建、构建 Pod 管控等工作。
这里主要讲一下镜像构建的主要工作流程:
将应用 Component 包 (每个应用可包含多个 Component,每个 Component 又可包含多个 Container) 拆解为多个 Container 构建任务;
启动构建 Pod Informer 监听;
下载每个 Container 构建源代码,并渲染 Dockerfile 文件;
渲染并下发构建 Pod yaml 并监听构建 pod 状态;
当构建 pod 到达终态时,进行 Component 交付包生成、资源清理等工作。
4 设计反思
通过以上基于 Kaniko 架构设计和实现,SREWorks 提出了持续交付云原生化的设计要求,并为此设计并实现了自己的解决方案。在此罗列一些目前的架构 Feature 供大家深入发掘一下:
内置了 Dockerfile 参数动态渲染功能,这为运维人员提供了可操作空间。
由于目前云底座 K8S 等,都已支持所有实现了 Container Runtime Interface(CRI)的容器运行时(例如 Docker、containerd、CRI-O),所以本文所提 DinD,在目前云原生形态下称其为 DinC(Container)更为准确。
SREWorks 的持续交付物的核心是镜像,在某些特殊场景下,交付物即使转变为机器时代的 Jar 包或者比镜像更高级的形态,但是本文的弹性架构设计依然适用。
共享 PV 下发构建 tar 包:可以去除 MinIO 的依赖,执行构建任务的 pod 直接从共享 PVC 内读取构建 tar 包。但是由于在某些云底座上,无内置“ReadWriteMany”访问模式的 PV,所以暂时关闭了这项功能。
构建 Pod 替换成 Job:使用 Pod 进行构建的初衷是足够轻量化,且 AppManager 具有绝对的管控权限。目前还没有足够的替换理由。
5 云原生建设感悟浅谈
广义地来看,可以说现在的几乎所有云产品都是云原生的,因为他们都或多或少满足了云原生所标榜的微服务、容器、持续交付、DevOps。但是,狭义地来看,在某些场景下,其往往又不能完全满足云原生的某些特性:例如可复用、免运维、资源池化等。
正如 CNCF 对云原生的定义:“云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式 API”。在作者看来,其内在含义代表着一种以产品能力为导向的最佳上云实践。
脱离“产品”这个导向,云原生化往往是没有意义的;“最佳实践”代表云原生化是不断迭代的;以本文镜像构建的演进为例,也是在产品能力的指导下,不断迭代和完善的,这就是云原生化的意义。
同时也欢迎大家多提 issue,协力将 SREWorks 打造得更好,欢迎各位加入钉钉群或微信群分享和交流~
SREWorks 开源地址:
https://github.com/alibaba/sreworks
更多开源项目合集:
https://www.aliyun.com/activity/bigdata/opensource_bigdata__ai
评论