简介
Kubernetes这个名字源于希腊语,意为"舵手"或"飞行员”。k8s这个缩写是因为k和s之间有八个字符。Google在 2014年开源了Kubernetes项目,Kubernetes是一个用于自动化部署、扩展和管理容器化应用程序的开源系统。同样类似的容器编排工具还有docker swarm/mesos等,但kubernetes应用最为广泛,社区更为活跃。
kubernetes主要包含了以下特性:
- 自动化上线和回滚
Kubernetes会分步骤地对应用或配置的更改进行更新,同时监视应用程序运行状况以确保不会同时终止所有实例,如果出现问题,Kubernetes会回滚所作更改。
- 服务发现与负载均衡
无需修改应用程序即可使用陌生的服务发现机制,Kubernetes为容器提供了自己的IP地址和一个DNS名称,并且可以在它们之间实现负载均衡。
- 存储编排
自动挂载所选存储系统,支持本地存储(如GCP或AWS之类公有云提供商所提供的存储)或者网络存储(如NFS、iSCSI、Gluster、Ceph、Cinder、Flocker等)系统。
- Secret和配置管理
部署或者更新Secrets和应用程序的配置不必重新构建容器镜像,并且不会将软件堆栈配置中的秘密信息暴露出来。
- 自动装箱
根据资源需求和其他约束自动放置容器,同时不影响可用性,将不同类型的工作负载进行分类或者混合放置,以提高资源利用率,节省资源。
- 批量执行
除了服务之外,Kubernetes还可以管理批处理和CI工作负载,替换掉失效的容器,或者持续集成部署。
- IPv4/IPv6双协议栈
为Pod和Service分配IPv4和IPv6地址
- 水平扩缩
支持使用一个简单的命令、一个UI或基于CPU使用情况自动对应用程序进行扩缩。
- 自我修复
重新启动失败的容器,在节点死亡时替换并重新调度容器,杀死不响应用户定义的健康检查的容器,并且在服务准备好之前不会公布给客户端。
- 为扩展性设计
无需更改上游源码即可扩展Kubernetes集群。
发展历史
谈起kubernetes的发展历史,首先回顾一下项目部署的发展历程,分布经历了物理机、虚拟机、容器化三个阶段的演变。
- 传统部署时代
早期,应用程序直接在物理服务器上运行,无法为物理服务器中的应用程序定义资源边界,这会导致资源分配问题。例如,如果在物理服务器上运行多个应用程序,则可能会出现一个应用程序占用大部分资源的情况,结果可能导致其他应用程序的性能下降。一种解决方案是在不同的物理服务器上运行每个应用程序,但是由于资源利用不足而无法扩展,并且维护许多物理服务器的成本很高。
- 虚拟化部署时代
为了解决物理机存在的弊端,引入了虚拟化技术,支持在单个物理服务器的CPU上运行多个虚拟机(VM),每个VM是一台完整的计算机,在虚拟化硬件之上运行所有组件,包括其自己的操作系统。虚拟化技术允许应用程序在VM之间隔离,提供一定程度的安全,一个应用程序的信息不能被另一应用程序随意访问。虚拟化技术能够更好地利用物理服务器上的资源,由于可轻松地添加或更新应用程序而实现更好的可伸缩性,降低了硬件成本。
- 容器部署时代
容器类似于VM,但是具有被放宽的隔离属性,可以在应用程序之间共享操作系统,因此容器被认为是轻量级的隔离。容器与VM类似,具有自己的文件系统、CPU、内存、进程空间等,由于它们与基础架构分离,因此可以跨云和OS发行版本进行移植。
随着微服务、容器化等技术的发展,解决了资源利用率不高的问题,但是随之而来却是如何进行容器管理,过多的容器使得运维工作也成为了一种负担,因此容器编排对于大型依托于容器化部署的分布式系统至关重要。在容器编排方面,Kubernetes所扮演的角色如下图所示:
Kubernetes相当于在容器之上的API,对服务器资源以及容器调度等过程进行管理,其诞生发展过程中版本演进阶段如下:
- 2014年
- Kubernetes诞生,其前身为Google内部运行多年的Brog系统
- 2018年
- Kubernetes 1.10: 容器存储接口( CSI ) 进入 beta 测试阶段
- Kubernetes 1.11: CoreDNS 做为集群 dns 附加选项也已经 GA
- Kubernetes 1.13: 容器存储接口( CSI ) 进入 GA 阶段
- 2019年
- Kubernetes 1.14: Windows 节点支持进入 GA 阶段
- Kubernetes 1.15: CSI 改善、性能优化
- Kubernetes 1.16: CRD GA
- Kubernetes 1.17: 特性优化
- 2020年
- Kubernetes 1.18: 围绕 beta 和稳定功能改进
- Kubernetes 1.19: Ingress API 正式进入 GA ;遵循 N-3 支持
- Kubernetes 1.20 : 宣布弃用Dockershim
- 更多
- 关于Kubernetes的生态,更多内容参考CNCF LandScape。
使用范围
kubernetes功能很强大,可以做很多的事情,Kubernetes不是传统的、包罗万象的PaaS(平台即服务)系统,构建了开发人员平台的基础,在重要的地方保留了用户的选择和灵活性。
- 可以做什么
基于容器化部署方式,Kubernetes可以提供:
- 服务发现和负载均衡
Kubernetes可以使用DNS名称或自己的IP地址公开容器,如果进入容器的流量很大,Kubernetes可以负载均衡并分配网络流量,从而使部署稳定。
- 存储编排
Kubernetes允许自动挂载选择的存储系统,例如本地存储、公共云提供商等。
- 自动部署和回滚
使用Kubernetes对象描述已部署容器的所需状态,它可以以受控的速率将实际状态更改为期望状态。例如,自动化为部署创建新容器,删除现有容器并将它们的所有资源用于新容器。
- 自动完成装箱计算
Kubernetes允许指定每个容器所需CPU和内存。当容器指定了资源请求时,Kubernetes可以做出更好的决策来管理容器的资源。
- 自我修复
Kubernetes重新启动失败的容器、替换容器、杀死不响应用户定义的运行状况检查的容器,并且在准备好服务之前不将其通告给客户端。
- 密钥与配置管理
Kubernetes可以存储和管理敏感信息,例如密码、OAuth令牌和ssh密钥,可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置,也无需在堆栈配置中暴露密钥。
- 应用边界
Kubernetes在容器级别而不是在硬件级别运行,它提供了PaaS产品共有的一些普遍适用的功能,例如部署、扩展、负载均衡、日志记录和监视。但是,Kubernetes不是单体系统,默认解决方案都是可选和可插拔的,在应用过程中需要明确操作边界:
- 不限制支持的应用程序类型
Kubernetes旨在支持极其多种多样的工作负载,包括无状态、有状态和数据处理工作负载,如果应用程序可以在容器中运行,那么它就可以在Kubernetes上很好地运行。
- 不部署源代码,也不构建应用程序
持续集成(CI)、交付和部署(CI/CD)工作流取决于组织的文化和偏好以及技术要求。
- 不提供应用程序级别的服务作为内置服务
例如中间件(消息中间件)、数据处理框架(Spark)、数据库(MySQL)、缓存(Redis)、集群存储系统(Ceph),这些组件可以在Kubernetes上运行,或者可以由运行在Kubernetes上的应用程序通过可移植机制(开放服务代理)来访问。
- 不要求日志记录、监视或警报解决方案
它提供了一些集成作为概念证明,并提供了收集和导出指标的机制。
- 不提供或不要求配置语言/系统
提供了声明性API,该声明性API可以由任意形式的声明性规范所构成。
- 不提供也不采用任何全面的机器配置、维护、管理或自我修复系统
Kubernetes不仅仅是一个编排系统,实际上它消除了编排的需要。编排的技术定义是执行已定义的工作流程,例如,首先执行A,然后执行B,再执行C。相比之下,Kubernetes包含一组独立的、可组合的控制过程,这些过程连续地将当前状态驱动到所提供的所需状态。如何从A到C的方式无关紧要,也不需要集中控制,这使得系统更易于使用且功能更强大、系统更健壮、更为弹性和可扩展。
设计架构
kubernetes中包含了很多新的概念,但是这些新的概念都不是凭空产生的,对应应用部署发展过程,几乎每个组件或者流程都有传统方式中的步骤或工具与之对应,kubernetes通过整合资源,提供了一套便于应用部署的方案及实现,极大减少了系统运维的工作量。参考官方架构设计图:
kubernetes通常是集群化部署,一个Kubernetes集群由一组被称作节点(Node)的机器组成,一个节点可以理解为一台服务器,这些节点上运行Kubernetes所管理的容器化应用。集群具有至少一个控制节点(MasterNode)和若干工作节点(WorkNode),节点可以是物理机或者虚拟机。
控制节点包含了控制平面(Contro Plane),控制平面中的组件用来管理整个集群,工作节点用来托管对应的工作负载Pod。一般来说节点上都会包含kubelet、kube-proxy等组件以及容器运行时。
Kubernetes中包含了众多组件,通过watch的机制进行每个组件的协作,每个组件之间的设计实现了解耦其工作流程如下图所示:
以创建Pod为例:
- 集群管理员或者开发人员通过kubectl或者客户端等构建REST请求,经由apiserver进行鉴权认证(使用kubeconfig文件),验证准入信息后将请求数据(metadata)写入etcd中;
- ControllerManager(控制器组件)通过watch机制发现创建Pod的信息,并将整合信息通过apiservre写入etcd中,此时Pod处于可调度状态
- Scheduler(调度器组件)基于watch机制获取可调度Pod列表信息,通过调度算法(过滤或打分)为待调度Pod选择最适合的节点,并将创建Pod信息写入etcd中,创建请求发送给节点上的kubelet;
- kubelet收到Pod创建请求后,调用CNI接口为Pod创建网络环境,调用CRI接口创建Pod内部容器,调用CSI接口对Pod进行存储卷的挂载;
- 等待Pod内部运行环境创建完成,基于探针或者健康检查监测业务运行容器启动状态,启动完成后Pod处于Running状态,Pod进入运行阶段。
说明:在整个集群交互过程中,组件之间无法直接通信,通过apiserver进行请求调用。
概念
基本概念
通过对Kubernetes有一个初步认识后,接下来详细介绍使用过过程中的一些基本概念。
集群
集群是一组相互独立的、通过高速网络互联的计算机,它们构成了一组服务环境。kubernetes由于其易用性及高可用性,方便的伸缩一般也都是集群配置。kubernetes集群常见两种方式:
- 一主多从:一台master节点和多台node节点,搭建比较简单,但是有可能出现master单机故障
- 多主多从:多台master节点和多台node节点,搭建比较麻烦,可用性高,可以避免单机故障的场景。
Kubernetes集群创建后会生成kubeconfig文件,默认情况下,kubectl在$HOME/.kube目录下查找名为config的文件,也可以通过设置KUBECONFIG环境变量或者设置--kubeconfig参数来指定其他kubeconfig文件。
kubeconfig文件用来组织有关集群、用户、命名空间和身份认证机制的信息,kubectl命令行工具使用kubeconfig文件来查找选择集群所需的信息,并与集群的apiserver进行通信。
说明:用于配置集群访问的文件称为kubeconfig文件,这是引用配置文件的通用方法,并不表明有一个名为kubeconfig的文件。
DNS子域名
资源类型名称一般使用DNS子域名规范来命名,DNS子域名的定义可参考RFC 1123。 名称必须满足如下规则:
- 不能超过253个字符
- 只能包含小写字母、数字,以及'-' 和 '.'
- 须以字母数字开头
- 须以字母数字结尾
RFC 1123标签名
某些资源类型需要其名称遵循RFC 1123所定义的DNS标签标准,命名必须满足如下规则:
- 最多63个字符
- 只能包含小写字母、数字,以及 '-'
- 须以字母数字开头
- 须以字母数字结尾
RFC 1035标签名
某些资源类型需要其名称遵循RFC 1035所定义的DNS标签标准,命名必须满足如下规则:
- 最多63个字符
- 只能包含小写字母、数字,以及 '-'
- 须以字母开头
- 须以字母数字结尾
路径分段名称
某些资源类型要求名称能被安全地用作路径中的片段,其名称不能是 .、..,也不可以包含 / 或 % 这些字符。下面是一个名为nginx-demo的Pod的配置清单:
apiVersion: v1
kind: Pod
metadata:
name: nginx-demo
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
某些资源类型可能具有额外的命名约束。
UIDs
Kubernetes系统生成的字符串,唯一标识对象,在Kubernetes集群的整个生命周期中创建的每个对象都有一个不同的uid,它旨在区分类似实体的历史事件。Kubernetes UIDs是全局唯一标识符(也叫 UUIDs)。 UUIDs 是标准化的,见 ISO/IEC 9834-8 和 ITU-T X.667.
名称空间
通常在一个集群内部,按照功能会将kubernetes中的工作负载划分在多个名称空间(Namespace或者ns)中,名称空间的主要功能是进行资源隔离。Kubernetes集群会默认创建四个初始名称空间:
- default:没有指明使用其它名字空间的对象所使用的默认名字空间
- kube-system:Kubernetes集群系统创建对象所使用的名字空间,运行系统资源级别的工作负载
- kube-public:所有用户(包括未经过身份验证的用户)都可以访问,以防某些资源在整个集群中应该是可见和可读的。这个名称空间的公共方面只是一种约定,而不是要求。
- kube-node-lease:此名称空间用于与各个节点相关的租约(Lease)对象。节点租期允许kubelet发送心跳,由此控制面能够检测到节点故障。
对象
在Kubernetes系统中,Kubernetes对象是持久化的实体,Kubernetes使用这些实体去表示整个集群的状态。它们描述了如下信息:
- 哪些容器化应用在运行(以及在哪些节点上)
- 可以被应用使用的资源
- 关于应用运行时表现的策略,比如重启策略、升级策略,以及容错策略
对象规约(Spec)与状态(Status)是几乎每个Kubernetes对象包含两个嵌套的对象字段。spec必须在创建对象时设置,描述希望对象所具有的特征;status描述了对象当前状态(CurrentState),由Kubernetes系统和组件设置并更新。
在任何时刻,Kubernetes控制平面都一直积极地管理着对象的实际状态,以使之与实际状态相匹配。
例:Kubernetes中的Deployment对象可以表示运行在集群中的应用。当创建Deployment时,设置Deployment的spec,指定该应用需要有3个副本运行。Kubernetes读取Deployment规约,创建所期望的3个应用实例并更新状态与规约相匹配。如果这些实例中有的失败了(一种状态变更),Kubernetes通过执行修正操作来响应规约和状态间的不一致,在这里意味着它会启动一个新的实例替换故障实例,以满足规约中所定义的3个实例。
更多参考:https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/kubernetes-objects/
标签
标签(Labels)是附加到Kubernetes对象(比如Pods)上的键值对。标签用于指定对用户有意义且相关的对象标识属性,对核心系统没有直接语义。
标签可以用于组织和选择对象的子集,可以在创建时附加到对象,也可以随后添加和修改。每个对象都可以定义一组键/值标签,每个键对于给定对象必须是唯一的。
更多参考:https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/labels/
注解
可以使用标签或注解(Annotation)将元数据附加到Kubernetes对象。标签可以用来选择对象和查找满足某些条件的对象集合,注解不用于标识和选择对象。
注解中的元数据,可以很小,也可以很大,可以是结构化的,也可以是非结构化的,能够包含标签不允许的字符。注解和标签一样,是键/值对:
"metadata": {
"annotations": {
"key1" : "value1",
"key2" : "value2"
}
}
更多参考:https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/annotations/
字段选择器
字段选择器(Fieldselectors)允许根据一个或多个资源字段的值筛选Kubernetes资源,其本质上是资源过滤器(Filters)。默认情况下,字段选择器/过滤器是未被应用的,这意味着指定类型的所有资源都会被筛选出来。
更多参考:https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/field-selectors/
亲和性
Affinity翻译成中文是"亲和性",对应的是Anti-Affinity,翻译为"互斥"。这两个词比较形象,可以把Pod选择Node的过程类比成磁铁的吸引和互斥,不同的是除了简单的正负极之外,Pod和Node的吸引和互斥是可以灵活配置的。Kubernetes中使用亲和性这个对象字段来控制Pod和Node之间的调度关系。
污点
污点(Taint)可以理解为一种标记,使用kubectl taint命令可以给某个Node节点设置污点,Node被设置上污点之后就和Pod之间存在了一种相斥的关系,可以让Node拒绝Pod的调度执行,甚至将Node已经存在的Pod驱逐出去。使用key=value:effect形式来配置污点,有如下几种策略:
- NoSchedule:表示kubernetes将不会将Pod调度到具有该污点的Node上
- PreferNoSchedule:表示kubernetes将尽量避免将Pod调度到具有该污点的Node上
- NoExecute:表示kubernetes将不会将Pod调度到具有该污点的Node上,同时会将Node上已经存在的Pod驱逐出去
排空
kubernetes手动标记节点(Node)为不可调度,将节点上的Pod进行驱逐,可以使用以下两种方式来实现:
- kubectl cordon :标记节点为不可调度(但对其上的pod不做任何事)
- kubectl drain :标记节点为不可调度,随后疏散其上所有pod
在用kubectl uncordon 解除节点的不可调度之前,不会有新pod被调度到该节点上。
组件
ControlPlane控制组件
控制平面组件是kubernetes控制节点的核心组件,可以对集群做出全局决策(比如调度)、检测和响应集群事件(例如,当不满足部署的replicas字段时,启动新的Pod)。
控制平面组件可以在集群中的任何节点上运行。通常设置脚本通常会在同一个服务器节点上启动所有控制平面组件,一般称这个节点为Master节点,并且不会在此节点上运行用户容器。作为主控制节点,就要考虑其高可用性。
kube-apiserver
apiserver是Kubernetes控制平面的组件,该组件公开了Kubernetes API。apiserver验证并配置API对象的数据, 这些对象包括Pods、Services、Replicationcontrollers 等。apiseerver为REST操作提供服务,并为集群的共享状态提供前端, 所有其他组件都通过该前端进行交互。
apiserver设计上考虑了水平伸缩,它可通过部署多个实例进行伸缩,可以运行apiserver的多个实例,并在这些实例之间平衡流量。
etcd
etcd是兼具一致性和高可用性的键值数据库,可以作为保存Kubernetes所有集群数据的后台数据库。集群之间传递需要保存的数据都会存储在etcd中。
kube-scheduler
控制平面组件,负责监视新创建的、未指定运行节点(node)的Pods,选择节点让Pod在上面运行。调度决策考虑的因素包括单个Pod和Pod集合的资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰和最后时限。
Scheduler负责Pod的调度,其作用是按照特定的调度算法和策略,将Pod绑定到集群中某个合适的Node上,工作流程大概如下:
- Scheduler的默认调度流程
- 预选调度策略
- 优选调度策略
- 调度并创建Pod的工作流程
- 为待调度Pod选择最合适的Node
- 将Pod与Node的绑定信息写入Etcd
- Kubelet组件监听到Pod绑定事件后,创建Pod
kube-controller-manager
Kubernetes使用了很多控制器,每个控制器管理集群状态的一个特定方面,运行控制器进程的控制平面组件。从逻辑上讲,每个控制器都是一个单独的进程,但是为了降低复杂性,它们都被编译到同一个可执行文件,并在一个进程中运行。
这些控制器包括:
- 节点控制器(NodeController):负责在节点出现故障时进行通知和响应
- 任务控制器(Jobcontroller):监测代表一次性任务的Job对象,然后创建Pods来运行这些任务直至完成
- 端点控制器(EndpointsController):填充端点(Endpoints)对象(即加入Service与Pod)
- 服务帐户和令牌控制器(ServiceAccount&TokenControllers):为新的命名空间创建默认帐户和API访问令牌
cloud-controller-manager
云控制器管理器是指嵌入特定云的控制逻辑的控制平面组件。云控制器管理器可以将集群连接到云提供商的API之上,并将与该云平台交互的组件同集群交互的组件分离开来。
cloud-controller-manager仅运行特定于云平台的控制回路。如果在自己的环境中运行Kubernetes,或者在本地计算机中运行学习环境,所部署的环境中不需要云控制器管理器。
与kube-controller-manager类似,cloud-controller-manager将若干逻辑上独立的控制回路组合到同一个可执行文件中,供你以同一进程的方式运行。可以对其执行水平扩容(运行不止一个副本)以提升性能或者增强容错能力。
下面的控制器都包含对云平台驱动的依赖:
- 节点控制器(NodeController):用于在节点终止响应后检查云提供商以确定节点是否已被删除
- 路由控制器(RouteController):用于在底层云基础架构中设置路由
- 服务控制器(ServiceController):用于创建、更新和删除云提供商负载均衡器
Node组件
节点组件在每个节点上运行,维护运行的Pod并提供Kubernetes运行环境。
kubelet
一个在集群中每个节点(node)上运行的代理。它保证容器(containers)都运行在Pod中。kubelet接收一组通过各类机制提供给它的PodSpecs,确保这些PodSpecs中描述的容器处于运行状态且健康。kubelet不会管理不是由Kubernetes创建的容器。
Kubelet 是Worker 上容器的管理 agent,是连接 Master 和各个Worker 节点之间的桥梁,其工作职责如下:
- 节点管理
- Pod管理
- 健康检查
- 资源监控
分别对接不同的后端,实现业务逻辑:
- CRI(Container Runtime Interface):容器运行时接口,提供计算资源
- CNI(Container Network Interface):容器网络接口,提供网络资源
- CSI(Container Storage Interface:容器存储接口,提供存储资源
kube-proxy
kube-proxy是集群中每个节点上运行的网络代理,实现Kubernetes服务(Service)概念的一部分。维护节点上的网络规则,这些网络规则允许从集群内部或外部的网络会话与Pod进行网络通信。
如果操作系统提供了数据包过滤层并可用的话,kube-proxy会通过它来实现网络规则。否则,kube-proxy仅转发流量本身。
可以看作Service的透明代理及负载均衡器,核心功能是将到某个Service的访问请求转发到后端的多个Pod实例上。kube-proxy主要有两种模式iptables和ipvs。
容器运行时(ContainerRuntime)
Kubernetes支持容器运行时,例如Docker、containerd、CRI-O以及KubernetesCRI(容器运行环境接口)的其他任何实现。
Addons
kubernetes提供了插件(Addons)的功能,插件使用Kubernetes资源(DaemonSet、Deployment等)实现集群功能。因为这些插件提供集群级别的功能,插件中命名空间域的资源属于kube-system命名空间。
下面描述众多插件中的几种。有关可用插件的完整列表,参考插件(Addons)。以下为常用的插件:
- CoreDNS负责为整个集群提供DNS服务
- 网络插件flannel、calico、canal、cilium
- Ingress Controller(traefik)为服务提供外网入口
- Dashboard提供GUI
DNS
尽管其他插件都并非严格意义上的必需组件,但几乎所有Kubernetes集群都应该有集群DNS,很多都需要DNS服务。
集群DNS是一个DNS服务器,和环境中的其他DNS服务器一起工作,它为Kubernetes服务提供DNS记录。Kubernetes启动的容器自动将此DNS服务器包含在其DNS搜索列表中。
Web界面(仪表盘)
Dashboard是Kubernetes集群的通用的、基于Web的用户界面。它使用户可以管理集群中运行的应用程序以及集群本身并进行故障排除。
容器资源监控
容器资源监控将关于容器的一些常见的时间序列度量值保存到一个集中的数据库中,并提供用于浏览这些数据的界面。
集群层面日志
集群层面日志机制负责将容器的日志数据保存到一个集中的日志存储中,该存储能够提供搜索和浏览接口。