Kubernetes(常简称为K8s)是一个开源的平台,用于自动化部署、扩展和管理容器化应用程序。
官方文档:https://kubernetes.io/zh-cn/docs/concepts/overview/components/
一、入门
(一)Kubernetes是什么?
随着Docker等容器技术的流行,越来越多的企业开始采用容器来打包和部署应用程序。
例如,一家电商平台可能会将它的前端应用、后端服务、数据库以及缓存系统分别打包成独立的容器。这样做不仅提高了开发效率,还使得各个组件可以独立部署和扩展。
然而,随着容器数量的增长,手动管理和调度这些容器变得复杂且容易出错。
当这家电商平台在促销活动期间(如黑色星期五),需要临时增加多个实例来应对流量高峰。
随着容器数量的增加,确保所有服务能够正确地找到并通信变得更加困难。
当某个容器由于软件错误或硬件故障而崩溃时,如果没有自动化的监控和恢复机制,运维人员需要手动重启或重新部署受影响的服务。

面对这一挑战,企业迫切需要一种能够自动化管理大规模容器集群的解决方案。
谷歌,作为互联网巨头之一,早在十几年前就开始使用内部开发的系统------Borg来应对类似的挑战。Borg是一个基于容器技术的大规模集群管理系统,旨在实现资源管理的自动化,并最大化跨多个数据中心的资源利用率。
通过Borg,谷歌成功地管理了其庞大的应用程序集群,显著提升了资源利用效率和服务可靠性。然而,由于严格的保密协议,外界对Borg的了解非常有限,这使得其他公司无法借鉴谷歌的成功经验。
为了填补这一市场空白并分享其宝贵经验,谷歌在2015年决定将Borg的设计理念与最佳实践开源,推出了Kubernetes项目。
Kubernetes汲取了Borg过去十年间的经验与教训,提供了一套强大的工具集来自动化部署、扩展和管理容器化应用。它不仅简化了容器编排的复杂性,还提高了系统的可维护性和资源利用率。
因此,自发布以来,Kubernetes迅速获得了社区的广泛认可和支持,成为了容器编排领域的事实标准。
通过Kubernetes,各种规模的企业都能够高效地管理和扩展自己的容器化应用,极大地促进了DevOps实践的发展。
(二)为什么使用K8S?
使用Kubernetes(K8s)可以为企业和个人开发者带来多方面的优势和好处,特别是在现代云计算和微服务架构的背景下。

1.应用部署复杂性
想象一下,你正在运营一家网店,每当有新的促销活动时,你需要更新网站上的产品信息和页面设计。如果手动进行这些操作,不仅耗时还容易出错,比如忘记更新某个页面或配置错误。
使用Kubernetes,你可以将你的应用(如网店前端和后端服务)容器化,并通过简单的命令或者界面上的操作来自动化整个更新过程。就像有了一个智能助手,它能按照预设的步骤帮你完成所有工作,而且几乎不会出错。
2.服务中断与高可用性
假设你的网店在黑色星期五期间突然遭遇服务器故障,导致网站无法访问,这会直接影响到销售业绩。
Kubernetes就像是你的IT支持团队,一旦检测到某个容器出现问题,它会自动重启这个容器或将它移动到另一个健康的服务器上,确保你的网店一直在线,顾客可以随时访问。
3.流量高峰应对不足
在节假日期间,你的网店可能会遇到前所未有的访客数量激增,如果没有准备充分,网站可能会变得非常缓慢甚至崩溃。
Kubernetes可以根据当前的负载情况自动增加更多的实例(想象成增加了更多的收银台),这样即使是在高峰期,你的网站也能保持流畅运行,满足更多用户的需求。
4.负载均衡配置繁琐
当你的网店规模扩大,可能需要在不同的服务器上运行多个副本以分散流量,但这意味着你需要设置复杂的负载均衡规则。
Kubernetes内置了负载均衡功能,不需要你做额外的设置,它就能智能地分配来访的请求到各个实例上,就像有一个无形的手在幕后协调一切,保证每个请求都能得到及时处理。
5.存储管理不便
如果你需要保存用户的订单记录、评论等数据,如何安全高效地管理这些数据就成了一个问题。
Kubernetes可以帮助你轻松挂载所需的存储系统,无论是本地硬盘还是云存储,都像是给你的数据找到了一个可靠的"保险箱",让你不用担心数据丢失或损坏的问题。
6.跨环境迁移困难
随着业务增长,你可能想要把网店从自己的服务器迁移到云端,但担心过程中会有各种兼容性问题。
Kubernetes提供了一致的操作体验,无论是在本地还是云端,都可以使用相同的方式管理和部署你的应用,就像无论你搬家到哪里,家具都能无缝适应新家一样。
(三)核心特性
以下是Kubernetes的一些核心特性:
- 自动化部署和回滚:Kubernetes支持自动化的应用部署和更新,可以控制滚动升级的速度,并在必要时自动回滚更改。
- 服务发现与负载均衡:无需修改代码即可自动暴露容器化的应用程序,同时Kubernetes会自动分配IP地址并进行负载均衡。
- 水平扩缩容:根据资源使用情况,如CPU使用率,或自定义度量标准,实现应用程序实例的自动增加或减少。
- 自我修复:如果一个容器失败了,Kubernetes能够自动重启该容器,或者重新调度它们到其他可用节点上。
- 存储编排:自动挂载所选存储系统,无论是本地存储、公有云提供商提供的存储还是网络存储都可支持。
- 秘钥与配置管理:让使用者可以在不重建容器镜像的情况下,安全地更新秘钥和应用程序配置。
Kubernetes已经成为容器编排领域的事实标准,被广泛应用于各种规模的企业中,用以提高开发效率和运维的灵活性。
二、K8S的基本架构
Kubernetes架构可简单分为主(Master)节点、从(工作/Worker/Node)节点和数据库Etcd。

由图可知,其中主节点为集群的控制单元,一般不会运行业务应用程序,主要包含的组件有Kube-APIServer、Kube-ControllerManager、Kube-Scheduler。
从节点为工作节点,也就是部署应用程序容器的节点,主要包含的组件有Kubelet、Kube-Proxy,当然如果Master节点也要部署容器,也会包含这两个组件。
同时,可以看出一个集群中可以有很多Node节点,用以保证集群容器的分布式部署用于实现业务的高可用性,也可以有很多Master节点,之后通过一个负载均衡保证集群控制节点的高可用。
Etcd集群可以和Master节点部署在同一个宿主机,也可以单独部署,生产环境建议部署大于3的奇数台Etcd节点实现Etcd集群的高可用。
在官方文档中,又将Kubernetes架构分为两大部分:控制平面(Control Plane) 和 工作节点(Node)。
(一)控制平面(Control Plane)
控制平面负责管理集群的状态,并做出全局决策,如调度、扩展等。
它包括以下几个关键组件:
- API Server:作为Kubernetes集群的前端接口,所有对集群的操作都必须通过API Server进行。它是集群中唯一可以直接访问etcd数据库的组件,确保了数据的一致性和安全性。
- etcd:这是一个高可用的键值存储系统,用于保存集群的所有状态数据。Etcd不仅存储配置信息,还维护着集群的当前状态。
- Scheduler:负责决定将新的Pod放置在哪个节点上运行。它根据资源需求、硬件/软件策略等因素做出最佳选择,以确保集群资源的有效利用。
- Controller Manager:包含多个控制器,每个控制器负责特定的任务,例如Node控制器监控Node的健康状态,Replication控制器确保指定数量的Pod副本正在运行。它们共同作用来维持集群的期望状态。
1.Master节点
控制平面是一系列组件的集合,它们被部署在Master节点上。
Master节点是物理或虚拟机器,在上面部署了控制平面的所有组件。换句话说,Master节点是承载控制平面组件的实际硬件或虚拟资源。控制平面的功能就是由部署在Master节点上的这些组件实现的。
Master节点是Kubernetes集群的控制节点,在生产环境中不建议部署集群核心组件外的任何容器,公司业务程序的容器更是不建议部署到Master节点上,以免升级或者维护时对业务造成影响。
2.API Server
如何高效地管理和协调大规模分布式系统中的资源?
假设你在运营一个电商网站,在促销活动期间(如黑色星期五),你需要快速部署和管理大量的应用实例。
如果没有一个集中化的管理系统,你可能需要手动登录到每台服务器上进行部署操作,这不仅耗时而且容易出错。此外,跟踪每个应用的状态(是否正在运行、健康状况如何)也变得极其困难。
Kubernetes引入了API Server
作为集群的统一入口点。所有对集群的操作和查询都必须通过API Server进行,它提供了RESTful API接口,使得外部系统可以通过编程方式与Kubernetes交互。这不仅简化了操作流程,还确保了数据的一致性和安全性。
现在,你不再需要手动登录到每台服务器上进行部署操作,而是可以通过API Server集中管理所有的应用实例。
3.etcd
如何确保集群中所有节点的状态一致性和安全性?
在一个大型企业环境中,多个团队可能同时对同一个Kubernetes集群进行操作。如果缺乏统一的数据存储机制,不同的团队成员可能基于过时的信息做出决策,导致配置冲突或安全漏洞。
例如,一个团队可能无意中覆盖了另一个团队设置的关键配置参数,影响系统的正常运行。
为了保证集群状态的一致性,Kubernetes使用了一个高可用的键值存储系统------etcd。

Etcd
用于存储集群的所有配置数据和状态信息,确保即使在高并发情况下也能保持数据的一致性和可靠性。
通过etcd,所有团队成员都可以基于最新的集群状态做出决策,减少了配置冲突或安全漏洞的风险。
4.Scheduler
如何合理分配有限的计算资源给不同的应用程序?
当你有多个不同类型的应用程序需要在同一组物理或虚拟机上运行时,如果没有智能调度机制,你可能会遇到资源分配不均的问题。
比如,某些服务器可能因为运行了过多的高负载应用而过载,而其他服务器则未充分利用,造成资源浪费。
Kubernetes引入了Scheduler
组件,负责决定将新的Pod放置在哪个节点上运行。Scheduler会分析每个节点的资源使用情况(如CPU、内存),并根据预设的策略选择最适合的节点来部署新Pod。
现在,你可以更高效地利用集群资源,避免某些节点过载而其他节点未充分利用的问题。
5.Controller Manager
如何自动恢复集群至预期状态?
如果你为某个关键服务设置了三个副本以提高可用性,但其中一个副本因硬件故障或其他原因崩溃了,如果没有自动化工具,你需要手动检测故障并重新启动该服务副本。
对于拥有成百上千个Pod的大规模系统来说,这种手动干预几乎是不可能完成的任务。
Kubernetes的Controller Manager
包含多个控制器,每个控制器专注于维持特定类型的资源的期望状态。
例如,Replication Controller确保指定数量的Pod副本正在运行;Node Controller监控节点健康状况,并在节点故障时采取措施恢复服务。
当某个关键服务的副本崩溃时,Replication Controller会自动检测到这种情况并重新启动该服务副本,无需人工干预。
(二)节点(Node)
节点是Kubernetes的工作机器,可以是物理机或虚拟机。

每个节点上运行以下组件:
- Kubelet:每个节点上的代理,负责与Master节点通信并管理该节点上的容器。它会定期向API Server报告节点状态,并根据Master的指示启动或停止容器。
- Kube-proxy:负责维护网络规则以便于Pod之间的通信。它确保网络流量能够正确地路由到相应的容器,同时也支持服务发现和负载均衡。
- 容器运行时:如Docker或containerd,负责拉取镜像、启动/停止容器等。它是实际执行容器化应用的地方。
1.Kubelet
如何在每个工作节点上管理容器的生命周期?
在一个动态变化的环境中,随着业务需求的变化,你可能需要频繁地更新或替换运行在各个节点上的容器。
如果没有一种机制可以自动执行这些任务,你将不得不手动登录到每个节点上去执行更新命令,这不仅效率低下,还增加了人为错误的风险。
每个节点上都有一个Kubelet
代理,它与Master节点通信,并根据接收到的指令管理该节点上的容器。Kubelet定期向API Server报告节点状态,并根据需要启动或停止容器。
现在,你可以自动化地执行容器的更新或替换任务,大大提高了效率并减少了人为错误的风险。
2.Service和Kube-proxy
如何实现跨多个Pod的服务发现和负载均衡?
假设你的电商网站由多个微服务组成,每个微服务都可能有多个实例分布在不同的节点上。
为了让用户请求能够正确路由到相应的微服务实例,你需要一种可靠的方法来进行服务发现和负载均衡。否则,你将面临请求无法到达正确的服务实例,或者由于流量过于集中在某些实例而导致性能瓶颈的问题。
Kubernetes引入了Service
概念,为一组Pod提供一个稳定的IP地址和DNS名称,并通过Kube-proxy
实现服务发现和负载均衡。Kube-proxy在每个节点上维护网络规则,确保请求能够正确路由到相应的Pod。
无论用户请求到达哪个节点,都能正确地路由到相应的微服务实例,实现了高效的服务发现和负载均衡。
3.容器运行时(Container runtime)
如何简化容器化应用的部署流程?
每次发布新版本的应用程序时,你需要先构建新的容器镜像,然后将其推送到镜像仓库,最后手动在各个节点上拉取镜像并启动容器。
这一系列步骤既繁琐又容易出错,尤其是在需要频繁更新的情况下。理想情况下,你希望能够一键完成整个部署过程,从构建镜像到最终上线。
Kubernetes通过Deployment控制器简化了应用的部署和更新流程。
你可以定义一个YAML
文件来描述应用的期望状态,Deployment控制器会自动处理从构建镜像到最终上线的所有步骤。
此外,Container Runtime
(如Docker或containerd)负责实际拉取镜像和启动容器。
现在,你可以一键完成整个部署过程,极大地简化了操作流程,减少了出错的可能性。
(三)运作流程示例

1.部署应用
用户通过kubectl命令行工具或其他客户端向API Server发送请求,提交想要部署的应用描述(通常为YAML文件格式)。
2.调度决策
Scheduler接收到新Pod需要被调度的通知后,分析集群内各节点的资源使用情况,选择最适合部署该Pod的节点。
3.执行部署
一旦确定了目标节点,Kubelet会接到指示,与容器运行时合作下载所需的容器镜像并启动容器。
4.服务发现与负载均衡
Service定义了一组Pod的访问策略,为这些Pod提供了一个稳定的IP地址和DNS名称。同时,Kube-proxy确保了流量能正确分发到相关的Pod。
5.自我修复与扩展
如果某个Pod失败或节点出现故障,Controller Manager会检测到这些问题,并采取措施恢复服务,例如重新创建Pod或将它们迁移到其他健康的节点上。此外,还可以基于CPU使用率或其他指标自动调整Pod的数量以应对负载变化。
三、基本概念
Kubernetes中的大部分概念如Node、Pod、Replication Controller、Service等都可以被看作一种资源对象,几乎所有资源对象都可以通过Kubernetes提供的kubectl工具(或者API编程调用)执行增、删、改、查等操作并将其保存在etcd中持久化存储。
从这个角度来看,Kubernetes其实是一个高度自动化的资源控制系统,它通过跟踪对比etcd库里保存的"资源期望状态"与当前环境中的"实际资源状态"的差异来实现自动控制和自动纠错的高级功能。
(一)Node
除了Master,Kubernetes集群中的其他机器被称为Node。
Node是Kubernetes集群中的工作负载节点,每个Node都会被Master分配一些工作负载(Docker容器),当某个Node宕机时,其上的工作负载会被Master自动转移到其他节点上。
Node可以在运行期间动态增加到Kubernetes集群中,在默认情况下Node上的kubelet代理会向Master注册自己,这也是Kubernetes推荐的Node管理方式。
一旦Node被纳入集群管理范围,kubelet进程就会定时向Master汇报自身的情报,例如操作系统、Docker版本、机器的CPU和内存情况,以及当前有哪些Pod在运行等,这样Master就可以获知每个Node的资源使用情况,并实现高效均衡的资源调度策略。
而某个Node在超过指定时间不上报信息时,会被Master判定为"失联",Node的状态被标记为不可用(Not Ready),随后Master会触发"工作负载大转移"的自动流程。
(二)Pod
在容器环境中,你可能需要多个紧密协作的容器一起工作,那么你将遇到如下问题:
例如,一个Web应用程序可能需要一个Nginx容器作为前端服务器和一个Node.js容器处理业务逻辑。如果这些容器被分散到不同的机器上运行,它们之间的通信会变得复杂且效率低下。
另外,如图的Java容器需要访问Mysql容器,由于Mysql容器可能被删除、重启,虽然mysql挂载了数据卷不会导致存储消失,但会造成网络发生变化,直接绑定的网络访问地址不再适用。
而两个java应用容器可能想访问同一个存储卷,实现读写分离,这个需要怎么配置?

Kubernetes引入了Pod的概念。Pod是Kubernetes中最小的可调度单元,它可以包含一个或多个共享网络和存储资源的容器。
"Pod"有豆荚的意思,在英文中把鲸鱼聚在一起的小团体称之为"Pod",就像班级里因共同语言而聚在一起的四五人的小集体,Kubernetes中引申为一系列相关的Docker容器的集合。
1.Pause容器
每个Pod都有一个特殊的被称为"根容器"的Pause容器。
Pause容器对应的镜像属于Kubernetes平台的一部分,除了Pause容器,每个Pod还包含一个或多个紧密相关的用户业务容器。
Pod里的多个业务容器共享Pause容器的IP,共享Pause容器挂接的Volume,这样既简化了密切关联的业务容器之间的通信问题,也很好地解决了它们之间的文件共享问题。
Kubernetes要求底层网络支持集群内任意两个Pod之间的TCP/IP直接通信,这通常采用虚拟二层网络技术来实现,因此在Kubernetes里,一个Pod里的容器与另外主机上的Pod容器能够直接通信。
如下图所示,整个Pod共享主机命名空间(Hostname),共享进程(PID),共享IP(Network),共享进程间通信(IPC)。

在此之前,在一组容器作为一个单元的情况下,我们难以简单地对"整体"进行判断及有效地行动。比如,一个容器死亡了,此时算是整体死亡么?是N/M的死亡率么?
引入业务无关并且不易死亡的Pause容器作为Pod的根容器,以它的状态代表整个容器组的状态,就简单、巧妙地解决了这个难题。
这样,上述例子中的Nginx和Node.js容器可以放在同一个Pod里,确保它们总是在同一台机器上运行,并且可以直接通过localhost相互通信。
2.Pod的运作流程
Pod其实有两种类型:普通的Pod及静态Pod(Static Pod)。
后者比较特殊,它并没被存放在Kubernetes的etcd存储里,而是被存放在某个具体的Node上的一个具体文件中,并且只在此Node上启动、运行。
而普通的Pod一旦被创建,就会被放入etcd中存储,随后会被Kubernetes Master调度到某个具体的Node上并进行绑定(Binding),随后该Pod被对应的Node上的kubelet进程实例化成一组相关的Docker容器并启动。

在默认情况下,当Pod里的某个容器停止时,Kubernetes会自动检测到这个问题并且重新启动这个Pod(重启Pod里的所有容器),如果Pod所在的Node宕机,就会将这个Node上的所有Pod重新调度到其他节点上。
(三)控制器
Replication Controller(复制控制器,RC)和ReplicaSet(复制集,RS)是两种简单部署Pod的方式。
1.Replication Controller
Replication Controller
可以确保Pod副本数达到期望值,也就是RC定义的数量。
换句话说,Replication Controller可以确保一个Pod或一组同类Pod总是可用的。如果存在的Pod大于设定的值,则Replication Controller将终止额外的Pod。如果太小,Replication Controller将启动更多的Pod用于保证达到期望值。
如图,假如在我们的RC里定义redis-slave这个Pod需要保持两个副本,系统将可能在其中的两个Node上创建Pod。
假设Node 2上的Pod 2意外终止,则根据RC定义的replicas数量2,Kubernetes将会自动创建并启动一个新的Pod,以保证在整个集群中始终有两个redis-slavePod运行。

与手动创建Pod不同的是,用Replication Controller维护的Pod在失败、删除或终止时会自动替换。
因此,即使应用程序只需要一个Pod,也应该使用ReplicationController或其他方式管理。
Replication Controller类似于进程管理程序,但是Replication Controller不是监视单个节点上的各个进程,而是监视多个节点上的多个Pod。
2.Replica Set
Replica Set
是支持基于集合的标签选择器的下一代Replication Controller,它主要用作Deployment协调创建、删除和更新Pod,和Replication Controller唯一的区别是,ReplicaSet支持标签选择器。
可以理解为Replica Set是对Replication Controller的一次升级。

在实际应用中,虽然ReplicaSet可以单独使用,但是一般建议使用Deployment来自动管理ReplicaSet,除非自定义的Pod不需要更新或有其他编排等。
Replication Controller和ReplicaSet的创建、删除和Pod并无太大区别,Replication Controller目前几乎已经不在生产环境中使用。
ReplicaSet也很少单独被使用,都是使用更高级的资源Deployment、DaemonSet、StatefulSet管理Pod。
(四)无状态应用管理Deployment
前面提到的ReplicaSet可以确保在任何给定时间运行的Pod副本达到指定的数量,但是Deployment
是一个更高级的概念,它管理ReplicaSet并为Pod和ReplicaSet提供声明性更新以及许多其他有用的功能。
Deployment一般用于部署公司的无状态服务,这个也是最常用的控制器,因为企业内部现在都是以微服务为主,而微服务实现无状态化也是最佳实践,可以利用Deployment的高级功能做到无缝迁移、自动扩容缩容、自动灾难恢复、一键回滚等功能。

如图,当你想要更新一个服务的新版本时,Deployment可以先启动一个Pod,完成启动后关闭掉旧版本的Pod,而不会导致服务中断,注意这并不是删掉旧版本Pod。
同时,若想回滚到旧版本的Pod,就先启动旧版本的Pod,再关闭新版本的Pod,实现轻松地进行应用版本的升级和降级。
(五)有状态应用管理StatefulSet
前面Pod的管理对象Deployment是面向无状态的服务,但现实中有很多服务是有状态的,特别是一些复杂的中间件集群,例如MySQL集群、MongoDB集群、Redis集群、ZooKeeper集群等。

一般StatefulSet用于有以下一个或者多个需求的应用程序:
- 需要稳定的独一无二的网络标识符。
- 需要持久化数据。
- 需要有序的、优雅的部署和扩展。
- 需要有序的自动滚动更新。
1.稳定的网络标识
每个Pod都会获得一个基于StatefulSet名称和序号(如web-0, web-1, web-2)的固定DNS名称。这使得Pod之间可以通过固定的名称进行通信,即使它们被重启或重新调度。
如果通过RC或Deployment控制Pod副本数量来实现上述有状态的集群,就会发现这点是无法满足的,因为Pod的名称是随机产生的,Pod的IP地址也是在运行期才确定且可能有变动的,我们事先无法为每个Pod都确定唯一不变的ID。
2.持久化存储
StatefulSet允许为每个Pod分配独立的持久卷声明(Persistent Volume Claim)。即使Pod被删除,其关联的持久卷也不会被删除,从而保证了数据的持久性。
每个Pod都可以拥有自己的持久卷,这些卷在Pod被重新调度后依然存在,确保了数据的一致性和可靠性,删除Pod时默认不会删除与StatefulSet相关的存储卷(为了保证数据的安全)。
3.有序部署和扩展
Pods是按照顺序创建和终止的,StatefulSet控制的Pod副本的启停顺序是受控的。
例如,在扩容时,按顺序增加新的Pod,操作第n个Pod时,前n-1个Pod已经是运行且准备好的状态。在缩容时,会先删除编号较大的Pod。这种有序性对于某些有状态应用(如数据库集群)非常重要。
4.Headless Service(无头服务)
StatefulSet目前使用Headless Service(无头服务)负责Pod的网络身份和通信,需要提前创建此服务。
StatefulSet除了要与PV卷捆绑使用以存储Pod的状态数据,还要与HeadlessService配合使用,即在每个StatefulSet定义中都要声明它属于哪个HeadlessService。
Headless Service与普通Service的关键区别在于,它没有Cluster IP,如果解析Headless Service的DNS域名,则返回的是该Service对应的全部Pod的Endpoint列表。
StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod实例都创建了一个DNS域名,这个域名的格式为:
bash
$(podname).$(headless service name)
比如一个3节点的Kafka的StatefulSet集群对应的Headless Service的名称为kafka,StatefulSet的名称为kafka,则StatefulSet里的3个Pod的DNS名称分别为kafka-0.kafka、kafka-1.kafka、kafka-3.kafka,这些DNS名称可以直接在集群 的配置文件中固定下来。
(六)守护进程集DaemonSet
DaemonSet(守护进程集,缩写为ds)和守护进程类似,它在符合匹配条件的节点上均部署一个Pod。
什么是DaemonSet?
有时候我们需要在每个Kubernetes节点或符合条件的节点上都部署某个应用,那么就可以使用Kubernetes的DaemonSet调度Pod。
DaemonSet确保全部(或者某些符合条件)节点上运行一个Pod副本。当有新节点加入集群时,也会为它们新增一个Pod,当节点从集群中移除时,这些Pod也会被回收,删除DaemonSet将会删除它创建的所有Pod。
- 系统程序:例如,在每个节点上运行Glusterd、Ceph等。
- 日志收集:例如,每个节点运行Fluentd、Logstash等。
- 系统监控:比如,Prometheus Node Exporter、Collectd、Datadog代理、New Relic代理或Ganglia gmond。
在生产环境中,公司业务的应用程序一般无须使用DaemonSet部署,一般情况下只有像Fluentd(日志收集)、Ingress(集群服务入口)、Calico(集群网络组件)、Node-Exporter(监控数据采集)等才需要使用DaemonSet部署到每个节点。
(七)Job
批处理任务通常并行(或者串行)启动多个计算进程去处理一批工作项(workitem),在处理完成后,整个批处理任务结束。
从1.2版本开始,Kubernetes支持批处理类型的应用,我们可以通过Kubernetes Job这种新的资源对象定义并启动一个批处理任务Job
。
与RC、Deployment、ReplicaSet、DaemonSet类似,Job也控制一组Pod容器。从这个角度来看,Job也是一种特殊的Pod副本自动控制器,同时Job控制Pod副本与RC等控制器的工作机制有以下重要差别。
1.短暂运行
Job所控制的Pod副本是短暂运行的,可以将其视为一组Docker容器,其中的每个Docker容器都仅仅运行一次。
当Job控制的所有Pod副本都运行结束时,对应的Job也就结束了。Job在实现方式上与RC等副本控制器不同,Job生成的Pod副本是不能自动重启的,对应Pod副本的RestartPoliy都被设置为Never。
因此,当对应的Pod副本都执行完成时,相应的Job也就完成了控制使命,即Job生成的Pod在Kubernetes中是短暂存在的。
2.并行计算
Job所控制的Pod副本的工作模式能够多实例并行计算,以TensorFlow框架为例,可以将一个机器学习的计算任务分布到10台机器上,在每台机器上都运行一个worker执行计算任务,这很适合通过Job生成10个Pod副本同时启动运算。
3.定时任务------CronJob
Kubernetes在1.5版本之后又提供了类似crontab的定时任务------CronJob
,解决了某些批处理任务需要定时反复执行的问题。
CronJob(计划任务,缩写为cj)用于以时间为基准周期性地执行任务,这些自动化任务和运行在Linux或UNIX系统上的CronJob一样。
CronJob对于创建定期和重复任务非常有用,例如执行备份任务、周期性调度程序接口、发送电子邮件等。
(八)Service
使用虚拟机或者裸容器部署应用时,程序之前的互相访问一般都是使用宿主机的IP地址和端口号进行访问的,因为宿主机的IP地址一般不会轻易改变,所以IP+端口的方式并没有什么大的问题。
而使用Kubernetes的Pod部署应用时,Pod大部分都是随机地部署至最佳的节点上,并且经常被删除重建,所以Pod的IP地址并不是一成不变的,这样的话服务之前的访问就不能使用IP+端口的方式,所以Service应运而生。

Service主要用于Pod之间的通信,对于Pod的IP地址而言,Service是提前定义好并且是不变的资源类型,在生产环境中最佳的实践方式就是每个应用互相调用时使用Service的名字进行连接,而非IP地址。
1.Service的基本概念
Kubernetes的Pod具有生命周期的概念,它可以被创建、删除、销毁,一旦被销毁就意味着生命周期的结束。
通过ReplicaSet能够动态地创建和销毁Pod,例如进行扩缩容和执行滚动升级。
每个Pod都会获取到它自己的IP地址,但是这些IP地址不总是稳定和可依赖的,这样就会导致一个问题:在Kubernetes集群中,如果一组Pod(比如后端的Pod)为其他Pod(比如前端的Pod)提供服务,那么如果它们之间使用Pod的IP地址进行通信,在Pod重建后,将无法再进行连接。
为了解决上述问题,Kubernetes引用了Service这样一种抽象概念:逻辑上的一组Pod,即一种可以访问Pod的策略,通常被称为微服务。

假设有一个用作图片处理的backend(后端),运行了3个副本,每个副本具有一个app=backend的Label,这些副本是可互换(无状态)的,之后创建一个Service指定Selector为app=backend,那么frontend(前端)即可通过这个Service的名称直接访问backend,所以frontend不需要关心它调用了哪个backend副本。
即使组成这一组backend程序的Pod发生了变化,frontend也没有必要知道,而且也不需要跟踪这一组backend的状态,因为Service能够解耦这种关联。
2.微服务
通过分析、识别并建模系统中的所有服务为微服务
------Kubernetes Service。
我们的系统最终由多个提供不同业务能力而又彼此独立的微服务单元组成的,服务之间通过TCP/IP进行通信,从而形成了强大而又灵活的弹性网格,拥有强大的分布式能力、弹性扩展能力、容错能力,程序架构也变得简单和直观许多。

既然每个Pod都会被分配一个单独的IP地址,而且每个Pod都提供了一个独立的Endpoint(Pod IP+ContainerPort)以被客户端访问,现在多个Pod副本组成了一个集群来提供服务,那么客户端如何来访问它们呢?
一般的做法是部署一个负载均衡器(软件或硬件),为这组Pod开启一个对外的服务端口如8000端口,并且将这些Pod的Endpoint列表加入8000端口的转发列表,客户端就可以通过负载均衡器的对外IP地址+服务端口来访问此服务。
客户端的请求最后会被转发到哪个Pod,由负载均衡器的算法所决定。

Kubernetes也遵循上述常规做法,运行在每个Node上的kube-proxy进程其实就是一个智能的软件负载均衡器,负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。
3.Cluster IP
但Kubernetes发明了一种很巧妙又影响深远的设计:Service没有共用一个负载均衡器的IP地址,每个Service都被分配了一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP
。
这样一来,每个服务就变成了具备唯一IP地址的通信节点,服务调用就变成了最基础的TCP网络通信问题。
我们知道,Pod的Endpoint地址会随着Pod的销毁和重新创建而发生改变,因为新Pod的IP地址与之前旧Pod的不同。而Service一旦被创建,Kubernetes就会自动为它分配一个可用的Cluster IP,而且在Service的整个生命周期内,它的Cluster IP不会发生改变。
于是,服务发现这个棘手的问题在Kubernetes的架构里也得以轻松解决:只要用Service的Name与Service的Cluster IP地址做一个DNS域名映射即可完美解决问题。
Kubernetes里其实可以分为3种IP:
- Cluster IP:Service的IP地址。
- Node IP:Node的IP地址。
- Pod IP:Pod的IP地址。
(1)Cluster IP
Service的Cluster IP,它也是一种虚拟的IP,但更像一个"伪造"的IP网络。它具有如下特点:
- Cluster IP仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配IP地址(来源于Cluster IP地址池)。◎ Cluster IP无法被Ping,因为没有一个"实体网络对象"来响应。
- Cluster IP只能结合Service Port组成一个具体的通信端口,单独的ClusterIP不具备TCP/IP通信的基础,并且它们属于Kubernetes集群这样一个封闭的空间,集群外的节点如果要访问这个通信端口,则需要做一些额外的工作。
- 在Kubernetes集群内,Node IP网、Pod IP网与Cluster IP网之间的通信,采用的是Kubernetes自己设计的一种编程方式的特殊路由规则,与我们熟知的IP路由有很大的不同。
(2)Node IP
首先,Node IP是Kubernetes集群中每个节点的物理网卡的IP地址,是一个真实存在的物理网络,所有属于这个网络的服务器都能通过这个网络直接通信,不管其中是否有部分节点不属于这个Kubernetes集群。
这也表明在Kubernetes集群之外的节点访问Kubernetes集群之内的某个节点或者TCP/IP服务时,都必须通过Node IP通信。
(3)Pod IP
其次,Pod IP是每个Pod的IP地址,它是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络,前面说过,Kubernetes要求位于不同Node上的Pod都能够彼此直接通信,所以Kubernetes里一个Pod里的容器访问另外一个Pod里的容器时,就是通过Pod IP所在的虚拟二层网络进行通信的,而真实的TCP/IP流量是通过Node IP所在的物理网卡流出的。
4.服务发现机制
任何分布式系统都会涉及"服务发现"这个基础问题,大部分分布式系统都通过提供特定的API接口来实现服务发现功能,但这样做会导致平台的侵入性比较强,也增加了开发、测试的难度。
Kubernetes则采用了直观朴素的思路去解决这个棘手的问题。
首先,每个Kubernetes中的Service都有唯一的Cluster IP及唯一的名称,而名称是由开发者自己定义的,部署时也没必要改变,所以完全可以被固定在配置中。接下来的问题就是如何通过Service的名称找到对应的Cluster IP。
(1)基于环境变量的服务发现
最早时Kubernetes采用了Linux环境变量解决这个问题,即每个Service都生成一些对应的Linux环境变量(ENV),并在每个Pod的容器启动时自动注入这些环境变量。
js
TOMCAT_SERVICE_SERVICE_HOST=169.169.41.218
TOMCAT_SERVICE_SERVICE_PORT_SERVICE_PORT=8080
TOMCAT_SERVICE_SERVICE_PORT_SHUTDOWN_PORT=8005
TOMCAT_SERVICE_SERVICE_PORT=8080
TOMCAT_SERVICE_PORT_8005_TCP_PORT=8005
TOMCAT_SERVICE_PORT=tcp://169.169.41.218:8080
TOMCAT_SERVICE_PORT_8080_TCP_ADDR=169.169.41.218
TOMCAT_SERVICE_PORT_8080_TCP=tcp://169.169.41.218:8080
TOMCAT_SERVICE_PORT_8080_TCP_PROTO=tcp
TOMCAT_SERVICE_PORT_8080_TCP_PORT=8080
TOMCAT_SERVICE_PORT_8005_TCP=tcp://169.169.41.218:8005
TOMCAT_SERVICE_PORT_8005_TCP_ADDR=169.169.41.218
TOMCAT_SERVICE_PORT_8005_TCP_PROTO=tcp
在上述环境变量中,比较重要的是前3个环境变量。可以看到,每个Service的IP地址及端口都有标准的命名规范,遵循这个命名规范,就可以通过代码访问系统环境变量来得到所需的信息,实现服务调用。
考虑到通过环境变量获取Service地址的方式仍然不太方便、不够直观,后来Kubernetes通过Add-On增值包引入了DNS系统,把服务名作为DNS域名,这样程序就可以直接使用服务名来建立通信连接了。
(2) 基于DNS的服务发现
Kubernetes进行服务发现的另一种方式是基于Kubernetes内部的DNS记录,新版Kubernetes默认使用CoreDNS作为集群的内部DNS,一般CoreDNS的Service地址为Service网段的第10个IP。
比如10.96.0.10,端口53,集群内Pod可通过该地址和端口进行Kubernetes内部的服务解析。DNS服务器监听Kubernetes创建的Service,然后给每个Service添加一组DNS记录,集群中的Pod就能通过Kubernetes内部的DNS解析到Service的IP,也就是Service的ClusterIP。
yaml
# kubectl run redis --image=redis:3.2.10-alpine
# kubectl exec -ti redis-7cf489c77d-h2t4t -- nslookup kube-dns.kube-system
nslookup: can't resolve '(null)': Name does not resolve
Name: kube-dns.kube-system
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Kubernetes目前支持环境变量和DNS两种简单的服务发现,两种方式各有优缺点,其中环境变量形式需要开发人员自行实现变量的解析,而基于DNS的服务发现不需要多余的代码,直接通过http://SERVICE_NAME:SERVICE_PORT即可调用其他组件,但是会有一些网络上的消耗。
相对于Kubernetes的服务发现,业界更常用的可能是基于Spring Cloud的Eureka,或者是Consul等工具实现微服务的自动发现,不过并不是所有的项目都是基于Spring Cloud全家桶进行开发的,而使用其他工具或Kubernetes环境变量进行服务发现,也会带来实现服务发现逻辑代码时间上的消耗,所以可能基于Kubernetes DNS的服务发现更为简单高效。
(九)Ingress(入口)
Ingress为Kubernetes集群中的服务提供了入口,可以提供负载均衡、SSL终止和基于名称(域名)的虚拟主机、应用的灰度发布等功能,在生产环境中常用的Ingress有Treafik、Nginx、HAProxy、Istio等。
在没有使用Kubernetes的传统架构中,部署一个应用并使用域名进行发布的流程如下:
- 1)部署应用至宿主机,启动应用并暴露一个端口号。
- 2)通过Nginx或其他代理工具配置域名,并代理至后端应用。
- 3)在域名厂商进行域名解析,设置DNS记录至Nginx或其他代理工具(Nginx的前面可能还有其他代理服务器,在此不进行说明)的服务器上,之后就能通过域名访问我们的应用。
在使用Kubernetes时,部署一个应用并使用域名进行发布的流程如下:
- 1)部署应用至Kubernetes集群,容器内定义了程序启动的方式。
- 2)配置ClusterIP类型的Service,通过Selector链接到容器内的应用。
- 3)配置Ingress链接到该应用的Service,之后将域名解析到Ingress(Ingress前面可能还有其他代理服务器)对应的宿主机上即可。
此时在Kubernetes中对应的架构图如图所示:
通俗来讲,Ingress和之前提到的Service、Deployment等类似,也是一个Kubernetes的资源对象,Deployment是用来部署应用的,Ingress就是实现用域名的方式访问应用。
Ingress实现的方式有很多,比如Nginx、HAProxy、Treafik等,就Nginx而言,和上述提到的传统服务架构用Nginx类似。

Ingress控制器在每个符合条件的宿主机上部署一个Pod,这个Pod里面运行的就是Nginx进程,里面的实现逻辑和宿主机部署Nginx的方式并无太大区别,关键区别是宿主机部署的Nginx需要更改Nginx的配置文件配置域名,而Ingress则和其他Kubernetes资源文件一样,使用YAML文件进行配置,之后Ingress控制器根据YAML文件定义的内容自动生成对应的配置文件。
在Kubernetes v1.1版中正式引用Ingress的概念,用于从集群外部到集群内部Service的HTTP和HTTPS路由,可以配置提供服务外部访问的URL、负载均衡和终止SSL,并提供基于域名的虚拟主机。
流量从Internet到Ingress再到Services最后到Pod上,通常情况下,Ingress部署在所有的Node节点上,暴露443和80端口(一般通过hostNetwork的方式部署Ingress),之后再通过F5或公有云LB代理到对应的Ingress节点上,之后将域名解析到F5或公有云LB即可实现基于域名的服务发布。
(十)Volumn(存储卷)
Volume(存储卷)是Pod中能够被多个容器访问的共享目录。
Kubernetes的Volume概念、用途和目的与Docker的Volume比较类似,但两者不能等价。
首先,Kubernetes中的Volume被定义在Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下;其次,Kubernetes中的Volume与Pod的生命周期相同,但与容器的生命周期不相关,当容器终止或者重启时,Volume中的数据也不会丢失。
最后,Kubernetes支持多种类型的Volume,例如GlusterFS、Ceph等先进的分布式文件系统。
(十一)Persistent Volume
之前提到的Volume是被定义在Pod上的,属于计算资源的一部分,而实际上,网络存储是相对独立于计算资源而存在的一种实体资源。
比如在使用虚拟机的情况下,我们通常会先定义一个网络存储,然后从中划出一个"网盘"并挂接到虚拟机上。
Persistent Volume(PV)和与之相关联的Persistent Volume Claim(PVC)也起到了类似的作用。
PV可以被理解成Kubernetes集群中的某个网络存储对应的一块存储,它与Volume类似,但有以下区别。
- PV只能是网络存储,不属于任何Node,但可以在每个Node上访问。
- PV并不是被定义在Pod上的,而是独立于Pod之外定义的。
- PV目前支持的类型包括:gcePersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC(Fibre Channel)、Flocker、NFS、iSCSI、RBD(Rados Block Device)、CephFS、Cinder、GlusterFS、VsphereVolume、Quobyte Volumes、VMware Photon、PortworxVolumes、ScaleIO Volumes和HostPath(仅供单机测试)。
(十二)ConfigMap
我们知道,Docker通过将程序、依赖库、数据及配置文件"打包固化"到一个不变的镜像文件中的做法,解决了应用的部署的难题,但这同时带来了棘手的问题,即配置文件中的参数在运行期如何修改的问题。
我们不可能在启动Docker容器后再修改容器里的配置文件,然后用新的配置文件重启容器里的用户主进程。
为了解决这个问题,Docker提供了两种方式:
- 在运行时通过容器的环境变量来传递参数;
- 通过Docker Volume将容器外的配置文件映射到容器内。
这两种方式都有其优势和缺点,在大多数情况下,后一种方式更合适我们的系统,因为大多数应用通常从一个或多个配置文件中读取参数。
但这种方式也有明显的缺陷:我们必须在目标主机上先创建好对应的配置文件,然后才能映射到容器里。
上述缺陷在分布式情况下变得更为严重,因为无论采用哪种方式,写入(修改)多台服务器上的某个指定文件,并确保这些文件保持一致,都是一个很难完成的目标。
此外,在大多数情况下,我们都希望能集中管理系统的配置参数,而不是管理一堆配置文件。
针对上述问题,Kubernetes给出了一个很巧妙的设计实现,如下所述。
首先,把所有的配置项都当作key-value字符串,当然value可以来自某个文本文件,比如配置项password=123456、user=root、host=192.168.8.4用于表示连接FTP服务器的配置参数。
这些配置项可以作为Map表中的一个项,整个Map的数据可以被持久化存储在Kubernetes的Etcd数据库中,然后提供API以方便Kubernetes相关组件或客户应用CRUD操作这些数据,上述专门用来保存配置参数的Map就是Kubernetes ConfigMap资源对象。
接下来,Kubernetes提供了一种内建机制,将存储在etcd中的ConfigMap通过Volume映射的方式变成目标Pod内的配置文件,不管目标Pod被调度到哪台服务器上,都会完成自动映射。
进一步地,如果ConfigMap中的key-value数据被修改,则映射到Pod中的"配置文件"也会随之自动更新。
于是,KubernetesConfigMap就成了分布式系统中最为简单(使用方法简单,但背后实现比较复杂)且对应用无侵入的配置中心。
ConfigMap配置集中化的一种简单方案如图所示: