一、案例概述
传统部署时代:
早期是在物理服务器上运行应用程序。无法为物理服务器中的应用程序定义资源边界,这会导致资源分配出现问题。例如:如果在物理服务器上运行多个应用程序,则可能会出现一个应用程序占用大部分资源的情况,结果可能会导致其他应用程序的性能下降。一种解决方案是在不同的物理服务器上运行每个应用程序,但是由于资源利用不足而无法扩展,并且组织维护许多物理服务器的成本很高。
虚拟化部署时代:
虚拟化功能允许在单个物理服务器上运行多个虚拟机(VM)。虚拟化功能可以实现应用程序在VM之间隔离,并提供安全级别,因此一个应用程序的信息不能被另一应用程序自由地访问。
因为虚拟化可以轻松地添加或更新应用程序、降低硬件成本等等,所以虚拟化可以更好地利用物理服务器中的资源,并可以实现更好的可伸缩性。大家可以理解为每个VM是一台完整的计算机,在虚拟化硬件之上运行所有应用组件,包括其自己的操作系统。
容器部署时代:
容器类似于VM,但是它们具有轻量级的隔离属性,可以在应用程序之间共享操作系统(OS)。因此。容器具有自己的文件系统、CPU、内存、进程空间等。由于它们与基础架构分离,因此可以跨云和OS分发进行移植。
随着Docker 技术的发展和广泛流行,云原生应用和容器调度管理系统成为IT领域大热的话题。由于虚拟机镜像大、镜像标准、打包流程和工具不统一,导致业界无法广泛接受,限制了云原生应用的发展。而Docker 技术的出现正好解决了云原生应用构建、交付和运行的瓶颈,使得构建云原生应用成为使用Docker的开发者的优先选择。那么Docker从单机走向集群已经成为必然趋势。Kubernetes作为当前唯一一个被广泛认可的Docker分布式解决方案,在未来几年内,会有大量的系统选择它。
二、Kubernetes概述
Kubernetes是由Google在2014年6月开源的容器编排调度引擎,使用Go语言开进行发,最初源于谷歌内部Brog引擎。由于Kubernetes的K和s间有8个字母,因此国内行业人员简称为K8S。 2015年7月Kubernetes V1.0正式发布,截止到目前最新稳定版本是V1.21.x。Kubernetes 拥有一个庞大且快速增长的生态系统。
Kubernetes 这个名字,起源于古希腊,是舵手的意思,所以它的 logo 即像一张渔网又像一个罗盘,谷歌选择这个名字还有一个深意:既然docker把自己比作一只鲸鱼,驮着集装箱,在大海上遨游,google 就要用Kubernetes去掌握大航海时代的话语权,去捕获和指引着这条鲸鱼按照主人设定的路线去巡游。
Kubernetes 是一个可移植、可扩展的开源Docker容器编排调度引擎,和Docker容器结合在一起,可实现容器技术的分布式架构方案。主要用于自动化部署、扩展和管理容器类应用,提供资源调度、部署管理、服务发现、扩容缩容、监控等功能。它提供完善的管理工具,涵盖开发、部署测试、运维监控等各个环节。它的目标不仅仅是一个容器编排系统,而是提供一个应用规范,用户可以描述集群的架构,定义服务的最终状态,Kubernetes可以将系统达到和维持在这个状态。对于负载均衡、服务发现、高可用、滚动升级、自动伸缩等容器云平台的功能要求有原生支持。
随着对K8S系统架构与设计理念的深入了解,可以发现K8S系统正是为运行云原生应用而设计考虑。使得基于K8S系统设计和开发生产级的复杂云原生应用,变得像启动一个单机版容器服务那样简单易用。
Kubernetes可以调度计算集群节点、动态管理节点上应用,并保证它们按用户期望状态运行。通过使用「Labels(标签)」和「Pods(荚)」的概念,Kubernetes将应用按逻辑单元进行分组,方便管理和服务发现。
官网:Kubernetes
文档:https://kubernetes.io/zh/docs/home/
2.1、使用Kubernetes具备的好处
- 具备微服务架构
微服务架构的核心是将一个巨大的单体应用分解为很多小的互相连接的微服务。一个微服务背后可能有多个实例副本支撑,副本的数量可能会根据系统负荷变化而进行调整,而K8S 平台中内嵌的负载均衡器发挥着重要作用。微服务架构使得每个服务都可以由专门的开发团队来开发,开发者可以自由选择开发技术,这对于大规模团队来说很有价值。另外,每个微服务独立开发、升级、扩展,使得系统具备很高的稳定性和快速迭代进化能力。
- 具备超强的横向扩容能力
对于互联网公司来说,用户规模等价于资产,谁拥有更多的用户,谁就能在竞争中胜出,因此超强的横向扩容能力是互联网业务系统的关键指标之一。K8S集群中可从只包含几个Node的小集群,平滑扩展到拥有成百上千Node 的大规模集群,利用K8S提供的工具,甚至可以在线完成集群的扩容。只要微服务设计的合理,结合硬件或者公有云资源的线性增加,系统就能够承受大量用户并发访问所带来的压力。
2.2、Kubernetes 服务功能
2.3.2、以服务为中心
2.3.3、高可用
2.3.4、滚动更新
- 数据卷
- 当Pod 中容器之间想要共享数据时,可以使用数据卷。
- 应用程序监控检查
- 容器内服务可能进程阻塞无法处理请求,可以设置监控检查策略保证应用健壮性。
- 复制应用程序实例
- 控制器维护着Pod副本数量,保证一个Pod或一组同类的Pod数量始终可用。
- 弹性伸缩
- 根据设定的指标(CPU利用率)自动缩放Pod副本数。
- 服务发现
- 使用环境变量或DNS服务插件保证容器中程序发现Pod入库访问地址。
- 负载均衡
- 一组Pod 副本分配一个私有的集群IP地址,负载均衡转发请求到后端容器。在集群内部其他Pod可以通过这个cluster IP访问应用。
- 滚动更新
- 更新服务不中断,一次更新一个Pod,而不是同时删除整个服务。类似于灰度发布。
- 服务编排
- 通过文件描述部署服务,使得应用程序部署变得更高效。
- 资源监控
- node 节点集成cAdvisor资源收集工具,可通过Heapster汇总整个集群节点资源数据,然后存储到InfluxDB时序数据库,再由Grafana展示。
- 提供认证和授权
- 支持角色访问控制(RBAC基于角色的权限访问控制 Role-Based Access Control)认证授权等策略。
-
2.3、Kubernetes 服务特点
2.3.1、自动化
- kubernetes有一套自动化机制。可以降低整个集群的运维成本和运维难度。
- 通过K8s我们可以实现自动扩容、自动更新、自动部署、自动化管理资源等等。
- kubernetes以服务为中心,可以让我们抛开系统环境和运行细节,有更多精力去处理逻辑业务。
- 构建在kubernetes上的系统,可以独立运行在物理机、虚拟机、私有云以及公有云。
- kubernetes会定期进行检查应用实例,这包括对这些实例的数量检查,实例健康状态检查等等。
- kubernetes如果发现有新的应用实例启动,会自动加入负载均衡中。
- kubernetes如果发现有应用实例状态不可用。kubernetes会自动干掉这个问题实例,并重新调度一个新实例。
- kubernetes可以使整个集群平滑升级(rolling-update)。
- 就是说,kubernetes可以在不停止对外服务的前提下完成应用的更新。
- 在规模比较大的集群中,kubernetes这一特性会非常实用。
2.4、Kubernetes架构
2.5、Kubernetes节点
(1)Master 节点
Master 节点提供集群的管理控制中心,对集群进行全局决策,并检测和响应集群事件(例如:当复制控制器的“副本”字段不满足时启动新的Pod)。 基本上K8S所有的控制命令都是发给Master,Master负责具体的执行过程。 Master节点可以在群集中的任何计算机上运行,但建议Master节点占据一个独立的服务器并做好高可用。因为Master节点是整个集群的大脑,如果Master所在节点宕机或者不可用,那么所有的控制中心都将失效。
在Master节点上运行着以下关键进程。
- APIServer:APIServer是整个集群的控制中枢,提供集群中各个模块之间的数据交换,并将集群状态和信息存储到分布式键-值(key-value)存储系统Etcd集群中。同时它也是集群管理、资源配额、提供完备的集群安全机制的入口,为集群各类资源对象提供增删改查以及watch的REST API接口。APIServer作为Kubernetes的关键组件,使用Kubernetes API和 JSON over HTTP提供Kubernetes的内部和外部接口。
- Scheduler:Scheduler是集群Pod的调度中心,主要是通过调度算法将Pod分配到最佳的节点(Node),它通过APIServer监听所有Pod的状态,一旦发现新的未被调度到任何Node节点的Pod(PodSpec.NodeName为空),就会根据一系列策略选择最佳节点进行调度,对每一个Pod创建一个绑定(binding),然后被调度的节点上的Kubelet负责启动该Pod。Scheduler是集群可插拔式组件,它跟踪每个节点上的资源利用率以确保工作负载不会超过可用资源。因此Scheduler必须知道资源需求、资源可用性以及其他约束和策略,例如服务质量、亲和力/反关联性要求、数据位置等。Scheduler将资源供应与工作负载需求相匹配以维持系统的稳定和可靠,因此Scheduler在调度的过程中需要考虑公平、资源高效利用、效率等方面的问题。
- Controller Manager: Controller Manager是集群状态管理器(它的英文直译名为控制器管理器),以保证Pod或其他资源达到期望值。当集群中某个Pod的副本数或其他资源因故障和错误导致无法正常运行,没有达到设定的值时,Controller Manager会尝试自动修复并使其达到期望状态。该控制器管理器可与API服务器进行通信以在需要时创建、更新或删除它所管理的资源,如Pod、服务断点等。
节点控制器(Node Controller):负责在Node节点出现故障时及时发现和响应;
复制控制器(Replication Controller):负责维护正确数量的Pod;
端点控制器(Endpoints Controller):填充端点对象(即连接Services和Pods);
服务帐户和令牌控制器(Service Account & Token Controllers):为新的命名空间创建默认帐户和API访问令牌。
- Etcd: Etcd由CoreOS开发,用于可靠地存储集群的配置数据,是一种持久性、轻量型、分布式的键-值(key-value)数据存储组件。Etcd作为Kubernetes集群的持久化存储系统,集群的灾难恢复、状态信息存储都与其密不可分,所以在Kubernetes高可用集群中,Etcd的高可用是至关重要的一部分,在生产环境中建议部署为大于3的奇数个数的Etcd,以保证数据的安全性和可恢复性。Etcd可与Master组件部署在同一个节点上,大规模集群环境下建议部署在集群外,并且使用高性能服务器来提高Etcd的性能和降低Etcd同步数据的延迟。n
(2)Node 节点
Node节点也被称为Worker或者Minion,Node 节点可以是一台物理主机,也可以是一台虚拟机。Node 节点是K8S 集群中的工作负载节点,每个Node 都会被Master 分配一些工作负载。当某个Node 宕机时,Node上的工作负载会被Master自动转移到其他Node节点上去。
每个Node 节点上都运行着以下关键进程:
- Kubelet:作为守护进程运行在Node节点上,响应Master主机下发的任务,同时负责上报该节点上所有Pod的运行状态,确保节点上的所有容器都能正常运行。当Node节点宕机(NotReady状态)时,该节点上运行的Pod会被自动地转移到其他节点上。
- Kube-proxy:负责Pod之间的通信和负载均衡,将指定的流量分发到后端正确的Pod上。
查看Kube-proxy工作模式:curl 127.0.0.1:10249/proxyMode
- Ipvs:监听Master节点增加和删除service以及endpoint的消息,调用Netlink接口创建相应的IPVS规则。通过IPVS规则,将流量转发至相应的Pod上。
- Iptables:监听Master节点增加和删除service以及endpoint的消息,对于每一个Service,他都会创建一个iptables规则,将service的clusterIP代理到后端对应的Pod。
- Docker Engine(docker):Docker引擎,负责本机的容器创建和管理工作。
Node 节点可以在运行期间动态增加到Kubernetes 集群中,前提是这个节点上已经正确安装、配置和启动了上述关键进程。在默认情况下,Kubelet 会向Master注册自己,这也是Kubernetes 推荐的Node 管理方式。一旦Node 被纳入集群管理范围,Kubelet 进程会定时向Master 汇报自身的情况,例如操作系统、Docker 版本、机器的CPU 和内存情况,以及之前有哪些Pod 在运行等。这样Master 可以获知每个Node 的资源使用情况,并实现高效负载均衡资源调度策略。而某一个Node 超过指定时间不上报信息时,会被Master判定为失联的状态,被标记为不可用,随后Master 会触发节点转移进程。
(3)插件
- Calico:符合CNI标准的网络插件,给每个Pod生成一个唯一的IP地址,并且把每个节点当做一个路由器。
- CoreDNS:用于Kubernetes集群内部Service的解析,可以让Pod把Service名称解析成IP地址,然后通过Service的IP地址进行连接到对应的应用上。
- Dashboard 是Kubernetes集群的通用基于Web的UI。管理员可以管理集群中运行的应用程序以及集群本身并进行故障排除。
2.6、Kubernetes资源对象
Kubernetes 包含多种类型的资源对象:Pod、Replication Controller、Service、Deployment、Job、DaemonSet等。所有的资源对象都可以通过Kubernetes 提供的kubectl工具进行增、删、改、查等操作,并将其保存在Etcd 中持久化存储。从这个角度来看,Kubernets 其实是一个高度自动化的资源控制系统,通过跟踪对比Etcd 存储里保存的资源期望状态与当前环境中的实际资源状态的差异,来实现自动控制和自动纠错等高级功能。下面对常用的资源对象分别进行介绍。
- Pod
- Pod(豆荚)是Kubernetes 创建或部署的最小/最简单的基本单位,一个Pod 代表集群上正在运行的一个进程。一个Pod 由一个或多个容器组成,Pod 中容器可以共享存储和网络,在同一台Docker主机上运行。每个Pod都有一个特殊的被称为“根容器”的Pause容器,Pause容器对应的镜像属于Kubernetes平台的一部分。除了Pause容器,每个Pod还包含一个或多个紧密相关的用户业务容器。
- Label
- Label(标签)是Kubernetes系统中另外一个核心概念。一个Label是一个key-value的键值对,其中key与value由用户自己指定。Label可以附加到各种资源对象上,例如Node、Pod、Service、RC等。一个资源对象可以定义任意数量的Label,同一个Label也可以被添加到任意数量的资源对象中,也可以在对象创建后动态添加或者删除。
- 另外可以通过给指定的资源对象捆绑一个或多个不同的Label,来实现多纬度的资源分组管理功能,以便于灵活、方便地进行资源分配、调度、配置、部署等管理工作。给某个资源对象定义一个Label,就相当于给它打了一个标签,随后可以通过Label selector标签选择器查询和筛选拥有某些Label的资源对象,Kubernetes通过这种方式实现了类似SQL的简单又通用的对象查询机制。
- ReplicaSet
- Replication Controller(复制控制器,RC)是Kubernetes 集群中最早的保证Pod高可用的API 对象。通过监控运行中的Pod 来保证集群中运行指定数目的Pod 副本。指定的数目可以是1 个或多个;如果少于指定数目,RC 就会运行新的Pod 副本。如果多于指定数目,RC 就会杀死多余的Pod 副本。即使在数目为1 的情况下,通过RC 运行Pod也比直接运行Pod 更明智,因为RC 可以发挥它高可用的能力,保证永远有1个Pod在运行。RC是K8s 较早期的技术概念,只适用于长期伺服型的业务类型。
- RC 与RS (ReplicaSet)唯一区别就是label selector 支持不同,RS 支持新的基于集合的标签,RC仅支持基于等式的标签。推荐使用RS,后面RC将可能被淘汰。
- Deployment
- Deployment(部署)表示用户对K8s 集群的一次更新操作,可以是创建一个新的服务,更新一个新的服务,也可以是滚动升级一个服务。滚动升级一个服务,实际是创建一个新的RS,然后逐渐将新RS中副本数增加到理想状态,将旧RS中的副本数减小到0的复合操作;这样一个复合操作用一个RS是不太好描述的,需要用一个更通用的Deployment来描述。未来对所有长期服务型的的业务的管理,都会通过Deployment来管理。
- Service
- RC 和Deployment 只是保证了支撑Service(服务)的微服务Pod的数量,但是没有解决如何访问这些服务的问题。一个Pod只是一个运行服务的实例,随时可能在一个节点上停止,在另一个节点以一个新的IP地址启动一个新的Pod,因此不能以固定的IP地址和端口号提供服务。要稳定地提供服务,需要服务发现和负载均衡能力。服务发现完成的工作,是针对客户端访问的服务,找到对应的的后端服务实例。
- 在K8s 集群中,客户端需要访问的服务就是Service对象。每个Service 会对应一个集群内部有效的虚拟IP,集群内部通过虚拟IP访问一个服务。在K8s集群中微服务的负载均衡是由Kube-proxy实现的。Kube-proxy是K8s集群内部的负载均衡器。它是一个分布式代理服务器,在K8s 的每个Node节点上都会运行一个Kube-proxy 组件;这一设计体现了它的伸缩性优势,需要访问服务的节点越多,提供负载均衡能力的Kube-proxy 就越多,高可用节点也随之增多。与之相比,通过在服务器端部署反向代理做负载均衡,还需要进一步解决反向代理的负载均衡和高可用问题。
- Job
- Job 是Kubernetes 用来控制批处理型任务的API对象。批处理业务与长期服务业务的主要区别是批处理业务的运行有头有尾,而长期伺服业务在用户不停止的情况下永远运行。根据用户的设置,Job 管理的Pod 把任务成功完成就自动退出了。成功完成的标志根据不同的spec.completions 策略而不同:单Pod 型任务有一个Pod 成功就标志完成;定数成功型任务保证有N 个任务全部成功;工作队列型任务根据应用确认的全局成功而标志成功。
- DaemonSet
- 后台支撑型服务的核心关注点在Kubernetes 集群中的节点(物理机或虚拟机),DaemonSet(守护程序集)确保所有或某些节点运行同一个Pod,要保证每个节点上都有一个此类Pod 运行。节点可能是所有集群节点也可能是通过nodeSelector 选定的一些特定节点。典型的后台支撑型服务包括,存储,日志和监控等在每个节点上支持K8s 集群运行的服务。
- Volume
- 数据卷,共享Pod 中容器使用的数据。
- Namespace
- 命名空间将对象逻辑上分配到不同namespace,可以是不同的项目、用户等区分管理,并设定控制策略,从而实现多租户。命名空间也成为虚拟集群。
- StatefulSet
- StatefulSet 适合持久性的应用程序,有唯一的网络标志符(IP),持久存储,有序的部署、扩展、删除和滚动更新。
-
2.7、Kubernetes原理图解
集群有两种角色:Master和Node(也叫worker)。
- Master 是集群的"大脑",负责管理整个集群:像应用的调度、更新、扩缩容等。
- Node 就是具体"干活"的,一个Node一般是一个虚拟机或物理机,它上面事先运行着 docker 服务和 kubelet 服务(Kubernetes 的一个组件,Master派来用于响应任务的小弟),当接收到master下发的"任务"后,Node就要去完成任务(用docker运行一个指定的应用)
Deployment - 应用管理者
当我们拥有一个 Kubernetes 集群后,就可以在上面跑我们的应用了,前提是我们的应用必须支持在 docker 中运行,也就是我们要事先准备好docker镜像。
有了镜像之后,一般我们会通过Deployment 配置文件去描述应用,比如应用叫什么名字、使用的镜像名字、要运行几个实例、需要多少的内存资源、cpu 资源等等。
有了配置文件就可以通过Kubernetes提供的命令行客户端kubectl 去管理这个应用了。kubectl 会跟 Kubernetes 的 master 先通过验证及授权连接上API在和RestAPI通信,最终完成应用的管理。
比如我们刚才配置好的 Deployment 配置文件叫 app.yaml,我们就可以通过"kubectl create -f app.yaml" 来创建这个应用,之后就由 Kubernetes 来保证我们的应用处于运行状态,当某个实例运行失败了或者运行着应用的 Node 突然宕机了,Kubernetes 会自动发现并在新的 Node 上调度出一个新的实例,保证我们的应用始终达到我们预期的结果。
Pod - Kubernetes最小调度单位
其实在上一步创建完 Deployment之后,Kubernetes 的 Node 做的事情并不是简单的docker run 一个容器。出于易用性、灵活性、稳定性等的考虑,Kubernetes 提出了一个叫做 Pod 的东西,作为 Kubernetes 的最小调度单位。所以我们的应用在每个 Node 上运行的其实是一个 Pod。Pod 也只能运行在 Node 上。如下图:
那么什么是 Pod 呢?Pod 是一组容器(当然也可以只有一个)。容器本身就是一个小盒子了,Pod 相当于在容器上又包了一层小盒子。这个盒子里面的容器有什么特点呢?
- 可以直接通过 volume 共享存储。
- 有相同的网络空间,通俗点说就是有一样的ip地址,有一样的网卡和网络设置。
- 多个容器之间可以“了解”对方,比如知道其他人的镜像,知道别人定义的端口等。
Service - 服务发现 - 找到每个Pod
上面的 Deployment 创建了,Pod 也运行起来了。如何才能访问到我们的应用呢?
最直接想到的方法就是直接通过 Pod-ip+port 去访问,但如果实例数很多呢?先拿到所有的 Pod-ip 列表,再配置到负载均衡器中,轮询访问。但上面我们说过,Pod 可能会死掉,甚至 Pod 所在的 Node 也可能宕机,Kubernetes 会自动帮我们重新创建新的Pod。再者每次更新服务的时候也会重建 Pod。而每个 Pod 都有自己的 ip。所以 Pod 的ip 是不稳定的,会经常变化的。
面对这种变化我们就要借助另一个概念:Service。它就是来专门解决这个问题的。不管Deployment的Pod有多少个,不管它是更新、销毁还是重建,Service总是能发现并维护好它的ip列表。Service对外也提供了多种入口:
- ClusterIP:Service 在集群内的唯一 ip 地址,我们可以通过这个 ip,均衡的访问到后端的 Pod,而无须关心具体的 Pod。
- NodePort:Service 会在集群的每个 Node 上都启动一个端口,我们可以通过任意Node 的这个端口来访问到 Pod。
- LoadBalancer:在 NodePort 的基础上,借助公有云环境创建一个外部的负载均衡器,并将请求转发到 NodeIP:NodePort。
- ExternalName:将服务通过 DNS CNAME 记录方式转发到指定的域名(通过 spec.externlName 设定)。
看似服务访问的问题解决了。但大家有没有想过,Service是如何知道它负责哪些 Pod 呢?是如何跟踪这些 Pod 变化的?
最容易想到的方法是使用 Deployment 的名字。一个 Service 对应一个 Deployment 。当然这样确实可以实现。但kubernetes 使用了一个更加灵活、通用的设计 Label 标签,通过给 Pod 打标签,Service 可以只负责一个Deployment 的 Pod 也可以负责多个 Deployment 的 Pod 了。Deployment 和 Service 就可以通过 Label 解耦了。
RollingUpdate - 滚动升级
滚动升级是Kubernetes中最典型的服务升级方案,主要思路是一边增加新版本应用的实例数,一边减少旧版本应用的实例数,直到新版本的实例数达到预期,旧版本的实例数减少为0,滚动升级结束。在整个升级过程中,服务一直处于可用状态。并且可以在任意时刻回滚到旧版本。
管理员--->kubectl--->deployment(nginx.yaml)--->label--->service--->Pod--->container