K8S学习之当我们部署应用的时候都发生了什么?
前言
最近在学习K8S,基础的知识参考于《Kubernetes in Action》。看完整本书基本上用了2~3个月的时间,进度比较慢;主要都是每天早晨到公司后,在正常工作时间之前的1个小时里完成的。由于时间拉的很长,各章的知识在我脑袋里是散状的,所以在我整理这篇笔记的时候,就想通过一个主题,把相关的内容串起来。第一篇笔记定的主题是“调度-当我们部署应用的时候都发生了什么?”,先从大的框架上记录一下K8S的架构与原理;对于卷、网络、configmap等组件会放在第二篇。初学者级别的学习笔记,有问题的地方大佬们及时勘误。 我在这里先给大家简单描述一下,当我们在aone里手动点击了升级之后,k8s里都发生了什么? 当我们在aone里手动点击了重新部署,aone会通过k8s的api接口通知master节点创建一个Deployment,Deployment会按照配置里的声明要求创建一个新的RepliactionSet,RepliactionSet会按照配置的创建一个或者若干个Pod,pod会调度到相应的工作节点上,通过docker拉取镜像,启动应用。 如果你能完全看懂我的描述,并且知道这些英文的工作原理,那你可以关掉网页了;如果你看完这段描述并不知道我在说什么,并且还想了解”当我们部署应用的时候都发生了什么?“的话,那请继续往下看。经常混在一起出现的名词:Docker&虚拟机&容器
我在看之前经常会把一些名词”搞混“,至少是没太明确的了解其概念和关系-Docker&虚拟机&容器。应用容器引擎:让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化;如Docker、RKT等。
虚拟机(Virtual Machine):指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统;如vmware、virtualBox。其实严格意义上来讲,Docker和虚拟机不是同一个层级的定义。应用容器引擎和虚拟机是俩种虚拟技术方案,而Docker只是应用容器引擎中最出名的,RKT是后起之秀。
- Docker共享宿主操作系统的内核,虚拟机独立操作系统内核
- Docker不同容器之间资源隔离,虚拟机在同一个VM上的app共享资源
- Docker镜像分层,相同的层可以共享,减少网络传输压力;虚拟机镜像不能共享
- Docker可移植性没有虚拟机好,需要考虑内核版本等;虚拟机完全独立可以移植
K8S简介-What Why How
从上一节的介绍应该可以看出来,一个应用的部署使用Docker已经完全可以运行起来,那K8S是什么,在整个应用声明周期中的作用是什么?先看一下用户在使用docker部署应用的过程。
Docker部署的时候,需要开发者来做运维操作的有俩步,一是用Docker构建和推送镜像到镜像库,二是在生产机器上拉取镜像并运行。这俩步操作看起来没什么工作量,毕竟Docker在里面做了大量的工作;但如果一个App需要部署多台服务,并且还要做升级更新以及后续的运维操作;如果一个人要负责多个系统成百上千个服务的部署运维,那工作量对他来说就是个灾难。所以我们需要 一个软件系统,帮我们更简单的部署和管理应用,这就是K8S存在的价值。
开发者需要做的是:1、将镜像构建并推送到镜像库(在我们常使用的Devops平台,比如Aone,甚至已经将打包镜像通过平台实现),2、定义好每个应用需要数据的节点数量,其他的运维工作都由K8S来代理;这里还是需要运维同学的,但由于K8S出色的运维能力支持,使运维工作也很轻松,物理资源利用率会更高。
先简单抛一下K8S的架构简图,可以先有个大概的了解。 架构可以分为俩部分,控制面板(也就是master节点)用于控制和管理整个集群; 工作节点是运行容器化应用的机器,通过一些组件来完成运行、监控、管理。
工作节点- 容器:docker、rtk等
- kubelet:与API服务器通信,管理所在节点容器
- kube-proxy:组件之间的负载均衡
- KubernetesAPI:与其他组件通信
- Scheculer:调度应用
- ControllerManager:执行集群级别的功能,复制组件、跟踪工作节点、处理节点失败
- etcd:持久化存储集群配置
看到这些概念肯定会更头疼,后面的笔记会尽量用平时我们工作接触到的东西来帮大家逐步理解。
k8s和docker的关系我们已然了解; ASI是阿里云基于K8S之上封装的一个容器管理平台,支持几乎所有阿里上云的中间件,集成了阿里的安全组件,使集团内部使用更方便和安全; Aone和EAS都是依赖ASI+各种Devops功能,以此应对各自垂直场景上的运维易用性需求。
应用都跑在哪里?Pod
在K8S架构简图里有这样一个细节,有一些应用是独立容器部署的,有一些则是多个容器组合放在一起部署,这是符合实际需求的。比如俩个应用需要通过ipc(进程之间通信)、或者需要共享本地文件系统,但在linux容器之间是相互隔离的。所以在K8S中,并不是以容器为最基础的部署单位来运行的,而是抽象出一个概念叫Pod。概念:Pod是一个并置容器,作为K8S最基础的构建模块。 一个pod中可以有多个容器,但一个容器是不会跨工作节点的。我们平时部署的应用就是在pod中运行的,一个容器中原则上只有一个应用进程(除非有守护进程),这样做的好处也是为了可以减少应用之间的依赖方便扩缩容等运维动作。
K8S会将同一个pod中的容器,放在同一个linux的namespace下。
对于容器和pod之间如何合理的组合,个人理解一个最基础的判断方法,就是这俩个容器是否是同生命周期的;其实我们遇到的大多数的情况都是一个pod中只有一个容器。标签是一种简单却功能强大的 Kubernetes 特性,不仅可以组织pod,也可以组织所有其他的 Kubernetes 资源。
之所以在这里提出来标签的概念,是因为后续介绍的K8S的运行机制里,大量的使用标签筛选器(Selector),是个非常重要的属性。后面聊到的 各种资源里圈定控制的范围就是通过标签选择器来搞定的。 如下图所示,通过标签的筛选分类,可以区分出各种环境,以此来做灵活的批量运维管理。
如何保证启动的数量和版本?ReplicationController
因为这篇笔记主要想记录K8S中部署应用时是如何调度的,所以我们先略去用Pod内部涉及到的一些其他组件,比如volume、网络、configMap等。我们直接进入到K8S的副本机制,也就是 K8S是通过什么机制来保证Pod符合配置要求(镜像版本,部署个数等)。
用途
Pod selector:用于确认作用域内有哪些pod
Replicas:指定应运行的副本数量
Podtemplate:用于创建新的pod副本模板
场景1: 当一个Pod出现故障,我们将其手动触发删除时;RC会拉取template中配置版本的镜像,创建一个相同标签的Pod。在实际工作中,如果遇到某一个节点无法work,我们在aone里操作销毁的时候,最底层的调度就是这样的。
场景2: 当一个标签为kubia的pod,被重新打上新标签的时候,RC会发现数量不符合要求,拉取template中配置版本的镜像,创建标签为kubia的Pod。在实际工作中,当我们在重新部署的时候,第一个要重新部署的pod可能会被打上”待销毁“的标签,等新的pod启动之后,才正式的销毁。
场景3: 当RC感知到 Replicas声明减少。实际工作中,在缩容的时候,K8S会按照一定的优先级规则,对已有容器进行销毁。优先级规则的底层逻辑总结起来就是:状态越稳定,销毁优先级越低。
优先级如下: 1 如果pod没分配到节点,先被删除 。 2 如果pod的状态是 Pending>PodUnknown>PodRunning,则Pending优先被删除,PodUnknown次之,PodRunning最后被删除。 3 不是Ready状态的Pod先被删除。 4 如果Pod都是Ready状态,则最后一个变成Ready状态的Pod先被删除(Ready时间最短的)。 5 重启次数大的Pod先被删除。 6 创建时间最新的Pod先被删除。
场景4: 当RC感知到Replicas声明增加。实际工作中的扩容场景,过程与场景1类似,不赘述。
场景5: 当镜像版本升级的时候。
注意,修改模板的动作,并不会触发容器的重新部署,但当场景1、2出现的时候,RC会控制用新的镜像版本创建Pod。StatefulSet:从rc和rs的副本机制可以看出来,当新创建一个pod的时候,和原有pod是完全没有关系的,也就是说他们是用来管理无状态Pod的。而 StatefulSet是用于管理有状态Pod的 ,应对新的pod需要与原pod具有相同的网络标识,可以访问同一份持久化数据等需求。由于涉及到的其他资源很多,我会放到第二篇笔记中来记录,顺便引出volume和网络等资源。
如何触发应用升级?
手动操作通常有俩种模式。
第二种是 rolling-update ,也是创建俩个rc,销毁一个旧的,然后创建一个新的,直到全部切换。这样的问题是手工操作很复杂,通过kubctl客户端访问api的方式进行,很有可能中间中断鲁棒性很低。而且很大一个问题是由于是先销毁,所以无法回滚。
手工操作太麻烦了怎么办?Deployment
Deployment的优点在于,只需要做声明式的升级,仅改动一个字段,k8s就会接管后续所有的升级动作,稳定可靠。 并且Deployment会默认保存俩版replicationSet(只保留就的rs,不保留旧pod),所以可以做到回滚。
-
maxUnavailable: 和期望ready的副本数比,不可用副本数最大比例(或最大值),这个值越小,越能保证服务稳定,更新越平滑; -
maxSurge: 和期望ready的副本数比,超过期望副本数最大比例(或最大值),这个值调的越大,副本更新速度越快。
机理:声明式+监听
到这里其实我们已经算是了解了手动触发升级的时候,k8s里会涉及到的大部分的资源组件,以及他们各自的工作原理,接下来我们把他们整个流程串起来,还原一下现场。 简单回顾一下k8s的架构简图,架构分为控制平面(master)与工作节点。API服务器用于所有内部外部的组件通信;etcd是个分布式持久化引擎,用于存储pod、rs等等的配置;控制器执行集群级别的功能,复制组件、跟踪工作节点、处理节点失败;调度器负责判断pod应该调度到哪个工作节点上;kubelet用于工作节点与api服务器通信和控制本工作节点上几乎所有事情。 为了保证高可用状态,集群里有多个主节点,默认配置下会有三个主节点,其他俩个的控制器和调度器就是非活跃状态,但是API服务器是都会接受通信请求,etcd之间也会同步配置信息。
声 明式API也分为俩种,eedge triggering(边缘触发) 和 level triggering(水平触发)
水平触发 (level-triggered,也被称为条件触发)LT: 只要满足条件,就触发一个事件(只要有数据没有被获取,就不断通知你)
边缘触发 (edge-triggered)ET: 每当状态变化时,触发一个事件
这俩种方式各有各的优势与应用场景,K8S使用的 水平触发(LT)方式 ,因为虽然在正常情况下ET比LT更节省资源,但如果在状态变化的时候,使用ET的方式并且正好丢掉了消息(在实际复杂网络环境下很有可能发生),就会使状态与实际预想的不一致。 而K8S架构中最重要的就是状态的声明,如果消息丢失,则会引发各种不受控的问题出现。 做了一次分享,会觉得这部分比较难理解,举个例子:团队有100个bug,Leader在群里发消息: 100个bug明天下班之前改完(谁改Leader不管),这就是声明式。
100个bug,A处理60个,B处理40个;第二天Leader看了下aone,还有超过50个没改,@C一起来处理,这就是命令式。 声明式的好处是:1、Leader只要结果不关注细节,可以做其他事情。2、如果A居家隔离,没有收到消息,为了达到目标,团队里会有人自动补位一起改bug。 不管哪种方式的消息,如果Leader一直在确认是否所有人都收到,不是就不断的发,这是水平触发; 如果Leader发进群里就不再管了,这是边缘触发。
如何自动扩缩容?HPA&VPA
我们可以通过调高 ReplicationController、 ReplicaSet、 Deployment等可伸缩资源的 replicas 字段,来手动实现 pod中应用的横向扩容 。我们也可以通过增加pod容器的资源请求和限制来纵向扩容pod (尽管目前该操作只能在pod创建时,而非运行时进行)。虽然如果你能预先知道负载何时会飙升,或者如果负载的变化是较长时间内逐渐发生的,手动扩容也是可以接受的,但指望靠 人工干预来处理突发而不可预测的流量增长,仍然不够理想 。 K8S提供了HorizontalpodAutoscaler(HPA),就是英文字面意思。横向伸缩的意思是,通过增加或者减少同样配置的pod的数量,来动态满足业务需求量。
上面介绍的是个简单的场景,用户只通过cpu使用率来控制伸缩,如果用户希望通过多硬件指标监控的话, K8S会取多个度量的最大值来进行扩容。 HPA也同时支持多种类型的度量方式,Resource度量就是上面提到的,根据硬件指标来做度量; Pod度量类型可以用来监控QPS或者消息队列中的数量等; Object度量类型可以用来监控外部资源,比如依赖外部服务的个数来动态调整pod的个数。
VPA 全称 Vertical Pod Autoscaler,即垂直 Pod 自动扩缩容 ,它根据容器资源使用率自动设置 CPU 和 内存 的requests,从而允许在节点上进行适当的调度,以便为每个 Pod 提供适当的资源。垂直自动扩容是在k8s1.9版本之后才提供的。
缺点:VPA的扩容需要重启pod,这样可能会造成服务的不稳定。虽然在社区中已经有一些优化方案( https://github.com/kubernetes/enhancements/issues/1287 ),但重建Pod环境无论怎样都会有一些风险。
所以HPA和VPA各有各的使用场景,我们团队是做toB的业务,我大概看了一下,基本上都是VPA的伸缩方式。但如果是2C的应用,对稳定性的要求非常高,建议使用HPA,在应用发布之前应该做好每个pod的各项资源的最佳配比。 上面一直在聊的是Pod的伸缩,但如果集群中工作节点的资源不能满足需求了怎么办?K8S提供了Cluster Autoscaler(CA),它的伸缩方式与HPA大体一致,如果有感兴趣的可以自行了解一下细节。结尾
本篇文章最后,把前言中的”当我们在aone里手动点击了升级之后,k8s里都发生了什么?“再贴在这里看一下↓↓↓ 当我们在aone里手动点击了重新部署,aone会通过k8s的api接口通知master节点创建一个Deployment,Deployment会按照配置里的声明要求创建一个新的RepliactionSet,RepliactionSet会按照配置的创建一个或者若干个Pod,pod会调度到相应的工作节点上,通过docker拉取镜像,启动应用。如果大家看完文章能看懂这段文字,说明已经了解了整个过程,要是还能想起来一些运行机理或者其中资源的实现原理就更好了。
整篇文章到这里已经快9K字了,其实为了更简单易懂的把整个知识点串起来,已经隐藏掉了很多实现细节,文章中的内容也大概输出了学习笔记中不到50%的章节。希望后续还有时间把后面没有分享出来的内容码下来,毕竟整个过程对自己是个很大的巩固和提升。