在席卷全行业的云原生趋势中,企业虽然通过云原生转型收获了很多成果和价值,但也遇到了不少障碍和挑战。其中,由于云原生项目工具数量庞大且缺乏整体规划和标准,企业在 DevOps 场景中就因此遇到了种种效率难题。
为了帮助企业基于容器化核心平台——Kubernetes 来更好地应对云原生 DevOps 场景,亚马逊云科技特别举行了主题为《云原生 DevOps 的 Kubernetes 技巧》的 Tech Talk。本场 Tech Talk 特别邀请汇量科技 Mobvista 技术副总裁 & 首席架构师蔡超,以 DevOps 工程师的视角分享汇量科技 Mobvista 在工程实践中的相关经验。
云原生,我们为什么要这样做?
很多企业在上云之后会逐渐发现,云原生其实是企业的一个必然选择。随着企业系统规模增大、结构愈加复杂,IT 部门就需要采用新的方法来更好、更快地交付应用系统。在这样的背景下,云原生概念应运而生。经过多年发展和多次迭代,云原生已经包含了容器、服务网格、微服务、不可变基础设施和声明式 API 等一系列技术,使工程师能够轻松对系统频繁作出可预测的重大变更。总体而言,云原生就是一种充分利用云计算模式的优点来构建和运行应用的方法。
回顾历史会发现,软件架构其实一直在演进,从单体架构到微服务之间经历了多次迭代,这种进化是与基础设施的进化相伴而生的。到了云原生时代,之所以 CNCF 推荐使用微服务架构,是因为它是一种具有更高可用性、更可恢复的服务架构。在单体服务中,服务器崩溃时系统会损失一个全功能单元;但微服务将功能拆分到细粒度级别,所以服务器崩溃时只会损失一部分功能,并较容易通过微服务的功能分布式特性来实现服务降级等面向失效的设计,降低失效对客户的影响。另一方面,单体服务中某个功能出现瓶颈,必须复制整个实例,新加的服务器还要承载运行整个单体服务,伸缩粒度非常粗放;相比之下,微服务提供了更精准、更细粒度的伸缩,更合适的计算资源,微服务小型化还能让服务启动速度更快,更好地配合云端动态资源的快速弹性,适应云的按使用付费(通过更小更便宜的机型实现细粒度伸缩),所以是更适合云的服务架构模式。
宠物与牛群
上云之前,很多企业会用到小型机、工作站这样的高性能机器,这些机器对企业而言就像是宠物一样的资产。因为每一台机器都会做专门的工作,有固定的 IP,就像宠物有自己的名字一样。但在企业上云之后,系统管理的机器会随着弹性伸缩而随时增减,负载在大量机器之间来回迁移,就像牛群一样不可能给每台机器都定义名称和专用的目的。所以在上云后,管理大规模弹性集群就像是从饲养几只宠物变成了放养一片牛群。
这一变化带来的影响不止于硬件层面。硬件上,过去升级机器时会给每一台机器提升配置,叫做 Scale Up;今天则是扩展集群机器数量,亦即 Scale Out。软件侧,过去针对每一台机器都有特定的操作和维护方式,今天的运行环境尤其是在云上,资源的弹性伸缩,使得集群中的虚机不断更替,因此必须做到可重复,可以简单、稳定、在线部署到任何一台机器上。
云原生很重要的一个概念是不可变,它就是与牛群模式相对应的。首先,整个 CI/CD 过程就是一个不断将可变变为不可变的过程。上图中每一步都是要锁定一部分内容,做到不可变。有了不可变,才能随时得到不同资源组合的稳定版本,然后才能重复部署,或者安全及准确的回退到之前的版本。
康威定律:软件系统是组织架构的映射
知名的康威定律认为,组织团队之间的通讯和交流的模式,最终会映射成软件系统或者产品的形态。反过来说,就是组织结构要同软件系统的架构相匹配。
对于微服务来说,背景就是组织的团队拆分得更小,每个团队都有自己的边界,有自己的微服务,并且还要独立部署。独立部署是微服务的精髓,服务部署时还要依赖其他团队,本质上就不是微服务了。
容器化是另一个云原生实践。Kubernetes 是今天最常用的容器编排平台,被视为云原生操作系统。根据康威定律,在 Mobvista 内部我们通过自研的系统 MaxCloud(MaxCloud 是 Mobvista 自研的 DevOps 平台,它封装了云原生技术栈的复杂性,内置了云原生的最佳实践)将企业的组织结构映射到 K8S 中。
这里的一种尝试是利用命名空间。今天很多公司用命名空间来划分不同的服务。如果我们把命名空间和项目、组织和集中在一起映射上,就可以做到很多事情。例如我们可以为每一个团队,根据团队所做的项目涉及的服务来划分权限。甚至可以设定每个命名空间可以使用的 CPU 和内存资源,来实现很多资源管控策略。
面向 DevOps 团队:一些有用的技巧
下面这些技巧是汇量科技在实践中总结出来的,对于 DevOps 团队来说很有参考价值。
Pod 生命周期
Pod 是 Kubernetes 的基本调度单位。要用好 Kubernetes 就要掌握好 Kubernetes 的生命周期。下图就是 Pod 的生命周期。
首先看 Init container。Init container 一定要在 Main container 之前执行,而且成功之后才会开始 Main container,如果它失败,Pod 也会失败。根据这样的特性,我们可以用它来做初始化的工作。当我们要在 Main container 加载一些配置数据时,在它启动之前可以用 Init container 来替它加载,如下图。
两个 container 可以共享一个存储,叫做 volume;通过 Init container 把数据准备到这个共享存储空间的路径上,Main container 就可以在这个存储上加载数据。这样一来,Main container 就会与数据加载以及初始化的方式彻底解耦。我们可以使用不同的方式来加载,比如说从 GitHub、数据库、S3 上加载,但都不需要修改程序,只需要放不同的 Init container 即可。当然也可以利用 Init container 做依赖项的等待,比如说一个依赖要不断检查,检查好之后 Init container 退出,Main container 起来,这样是可以的。
下图是另一个例子,加载负载配置。这里利用另一个 container 的组合让 Pod 软件解耦数据装载的方式,提高扩展性。
与 postStart 对比:
postStart 是异步执行的。
postStart 过程不能保证在 container 的入口点前执行完成。
Container 的状态在 postStart 过程完成后才会被置为 RUNNING。
利用上述特性,如果在 pod 中包含多个 container,当我们需要某个 container 先完成启动就绪,就绪完成后才继续下一个 container 的创建,那么就可以在前一个 container 中加一个 postStart。这个 postStart 过程只是检查自己是否就绪,如果一直没有就绪,退出后返回不正常时整个 Pod 会失败,无法创建成功。如果创建成功,意味着已经就绪,就可以继续启动下一个 container。而且这个方式可以用在不同场景中,尤其是在 sidecar 的场景中,需要确保 sidecar 首先就绪,。以下是两者的简单对比。
此外,终止也非常重要。当一个 Pod 被终止,它会在服务发现里摘掉,不接受流量,状态变成 terminating。这时它会收到一个信号,叫 SIGTERM,告诉它应该准备停止了。这个时候并不会把 container 马上杀掉,而是会给一段时间,默认值是 30 秒。在这个 30 秒之后会收到另一个信号叫 SIGKILL,是强制的,立刻杀掉。当然我们并不需要一定要处理 SIGTERM,而是可以用一个叫 PreStop 的 hook 来优雅地处理。它可以实现完美中断,避免数据损失或服务发现未摘掉等情况。
最后,是健康检查,也就是 readiness。它的重点是在发布和伸缩时确保新增 Pod 都是就绪的,避免未就绪时就接收外部请求带来的很多错误。
QoS vs Priority
Qos 和 Priority 是非常容易搞混的概念,很多人一直以为他们心目中的 Priority 是 Priority,其实那是 Qos。下图展示的是 Priority 的内容。
通常情况下我们不提倡设定对 pod 设定优先级,因为优先级越来越高,很容易失控。我们更期望的是与调度无关、与启动顺序无关的更好的架构。
下图是 QoS,这才是很多人心目中的 Priority。当一个 Node 上有很多 Pod 在运行,可能会出现 Node 硬件不够用的情况,那么哪些 Pod 会被杀死?哪些 Pod 会被保留下来?这个叫 QoS。
Affinity vs Taint
Affinity 亲和性和 Taint 这两个概念有很多人都搞不清楚。所谓亲和性是从 Pod 的视角去看,什么样的 Node 适合我来运行。Taint 是从 Node 的角度来看自己可以运行什么样的 Pod,兼容了 node 上的 taint 的 pod 才可以被调度到 node 上。
集成化和版本化
Kubernetes 是从资源的视角来设计的,所以在部署时要写很多资源定义相关的 yaml 文件,不仅学习曲线陡峭,维护起来也不容易。所谓 DevOps 就是让一个团队负责开发和运维,但开发人员往往并不习惯资源视角,就会犯很多错误。
汇量科技对此的思考是,如果做 DevOps,是不是应该把资源视角转化成一种对于应用的管理视角?是不是应该把这种不可变性进一步扩展,把所有资源聚集在一起,变成应用程序的一个包(Bundle)。能不能实现这个包的版本化?让它实现不可变性,这样就会让开发人员应用起来更自然。
于是汇量科技就开发了一个平台,叫 MaxCloud。MaxCloud 结合汇量科技对 Amazon EKS 容器管理的实践,可以帮助其它客户更轻松地部署和管理 K8s 容器集群,提升研发人员对容器的使用效率。
Amazon EKS 是一项托管容器服务,可以在云中和本地,来运行和扩展 Kubernetes 应用程序。基于 Kubernetes 的现有应用程序与 Amazon EKS 兼容。企业可以利用 MaxCloud 把组织结构和项目、微服务匹配在一起,实现康威定律。不仅如此,MaxCloud 封装了很多云原生的复杂性,同时给大家提供一些最佳实践。
下图是 MaxCloud 的界面。这里把各种碎片的依赖组合到一起成为一个 bundle。这个 bundle 不是完全手动打包,而是会自动进行。选中了一个 service,它会自己把一些有依赖关系的资源如 deployment,configmap、secret 等等聚合在一起,完成后可以手动调整或添加我们认为相关的资源。一旦打成了这个应用的 bundle,就可以通过这个应用 bundle 聚合一些不同资源的告警事件,便于我们找到这些独立事件的相关性,快速定位根本问题。
打成 bundle 之后要对 bundle 做版本化,和 GitOps 打通。当利用 GitOps 提交到任何一个资源,只要是 bundle 当中的,都会触发 MaxCloud 起一个新的 bundle 的版本,然后自动同步到多个集群中。当出现问题时,可以按整个按 bundle 回退,不会出现其中某个资源被忘记这种尴尬。
有效利用云的特性:按需获取,按使用付费
Kubernetes HPA 可以自动伸缩 Pod,但只是伸缩 Pod 没用,Node 才是真正要付费的。这个时候可以利用 CA,它会把一些闲置的 Node 从集群里面卸掉,提升 Node 利用率,降低成本。用量波峰差距较大时,一般会选择 Spot instance 模式。Amazon EC2 Spot 实例是使用闲置 EC2 容量的实例,其价格远低于按需实例的 EC2 实例价格。但 Spot 实例在发生中断时将暂停或停止 / 关闭 实例,当容量可用时实例可以从之前的状态中恢复。Kubernetes 集群对 Spot instance 比较友好,如下图所示。
在使用 Spot 时也有几点需要注意。首先,因为 Node 会回收 Spot instance,所以不要把所有的 Pod 部署在一个 Node 上,否则一回收全没了,这个可以利用 Antiaffinity 来做到。其次,当你开始在集群中引入 Spot instance,需要注意原有的应用程序和部署是否支持。最好的方法是给 Spot instance node 打一个污点,让可以兼容它的部署才部署在上面。
如何能够降低 Spot instance 回收带来的中断影响呢?可以借助一些外部工具,比如说汇量科技的 SpotMax。SpotMax 是汇量科技基于 Amazon EC2 Spot 实例构建的云原生弹性集群管理平台,可以实现自动化的 Spot 实例资源管理与调度而无需担心资源容易中断回收而影响稳定性。
SpotMax 能够无缝整合 Kubernetes 和 Amazon EKS。它首先通过利用大数据及线上实时学习技术,实时优化线上集群组成,降低整个集群的中断概率。在中断发生前,SpotMax 会在资源层面替你先把 Node 加进去,初始化好。这个过程中它会主动告诉 EKS 或 Kubernetes 的 API server,你现在可以把 Pod 安全地迁移到我新加入的这个 Node 上,这个 Node 也是根据 SpotMax 里的 expert system 选择出来的一个中断率更低的 Node,平滑迁移 Pod。整个过程几秒钟就可以完成(在不使用 SpotMax 的情况下这个过程需要大约 2 分钟)。
汇量科技(Mobvista)规模化使用亚马逊云科技的低成本算力资源,构建了云原生的弹性集群管理平台 SpotMax,SpotMax 加上 MaxCloud,帮助汇量科技移动广告业务的单位广告请求成本降低 65%。利用亚马逊云科技多项数据分析和机器学习服务打造的广告交易平台,轻松应对日均 10 亿台的独立移动设备高达 2000 亿次的广告请求。
引入了容器之后,带来的改变绝对不仅仅是构建、部署和维护的方式这么简单。其实每一次软件设计的变更都涉及到新增的构建块,带来新的设计思考。但这些设计的出发点还是一样的,例如 Sidecar 模式就是利用了一个 Pod 里面的 container 可以共享 volume 或者 container 间网络通讯的特性,来将一些功能从主 container 中解耦,使得主 container 更具扩展性,能够和不同 sidecar 配合适应不同场景。比如一个 Main container 打日志出来,就可以用 Sidecar 来整理这些日志,与业务逻辑实现解耦。这里有很多模式,蔡超推荐大家可以通过《Kubernetes Pattern》一书来具体了解。
评论