简介
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的用户界面。它使用户可以管理集群中运行的应用程序以及集群本身并进行故障排除。
容器资源监控
容器资源监控将关于容器的一些常见的时间序列度量值保存到一个集中的数据库中,并提供用于浏览这些数据的界面。
集群层面日志
集群层面日志机制负责将容器的日志数据保存到一个集中的日志存储中,该存储能够提供搜索和浏览接口。
资源
在kubernetes中包含很多中类型的资源,所有内容都可以抽象为资源,资源实例化之后称为对象。资源按照不同的类型可以划分为集群类、名称空间类、元数据类资源。
集群级资源
集群级别的资源是所有名称空间之间可以共享的资源。
Namespace
Kubernetes中,名称空间(Namespace)提供一种机制,将同一集群中的资源划分为相互隔离的组。 同一名称空间内的资源名称要唯一。名称空间作用域仅针对带有名称空间的对象,例如Deployment、Service等, 这种作用域对集群访问的对象不适用,例如StorageClass、Node、PersistentVolume等。
如何使用
名称空间属于Kubernetes中一种重量级隔离方案,是在多个用户之间划分集群资源的一种方法(通过资源配额)适用于存在很多跨多个团队或项目的用户的场景。对于只有几到几十个用户的集群,根本不需要创建或考虑名称空间。
使用kubectl可以对名称空间进行操作,查看当前集群所包含的名称空间信息
[root@master ~]# kubectl get ns
# 输出结果
NAME STATUS AGE
default Active 9d
ingress-nginx Active 9d
kube-node-lease Active 9d
kube-public Active 9d
kube-system Active 9d
kuboard Active 9d
lens-metrics Active 7d19h
也可以使用describe命令来查看某个名称空间的详细信息
[root@master ~]# kubectl describe ns kube-public
# 输出结果
Name: kube-public
Labels: kubernetes.io/metadata.name=kube-public
Annotations:
Status: Active
No resource quota.
No LimitRange resource.
在使用kubectl发送请求时也可以使用--namespace参数来进行某个名称空间的操作。
[root@master ~]# kubectl get pods
# 输出结果
NAME READY STATUS RESTARTS AGE
docker-learn-server-7597c4ddf6-ctvq6 1/1 Running 0 28h
docker-learn-server-7597c4ddf6-d99wk 1/1 Running 0 28h
docker-learn-server-7597c4ddf6-dpm4f 1/1 Running 0 31h
docker-learn-server-7597c4ddf6-nwl8j 1/1 Running 0 28h
docker-learn-server-7597c4ddf6-sp4ph 1/1 Running 0 28h
netchecker-agent-b2fx8 1/1 Running 0 9d
netchecker-agent-hostnet-6vgg5 1/1 Running 0 9d
netchecker-agent-hostnet-clvhd 1/1 Running 0 9d
netchecker-agent-hostnet-rh5x6 1/1 Running 0 9d
netchecker-agent-l6stj 1/1 Running 0 9d
netchecker-agent-mh4wh 1/1 Running 0 9d
netchecker-server-59fcd6bf86-8hj27 2/2 Running 1 (9d ago) 9d
# 指定--namespace=ingress-nginx即可只查看此名称空间下的pods
[root@master ~]# kubectl get pods --namespace=ingress-nginx
# 输出结果
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-ingress-nginx-j675b 0/1 Completed 0 9d
ingress-nginx-admission-patch-ingress-nginx-jtfsv 0/1 Completed 0 9d
ingress-nginx-controller-ingress-nginx-6cc7cfdbff-6cb47 1/1 Running 0 9d
ingress-nginx-controller-ingress-nginx-6cc7cfdbff-tlk29 1/1 Running 0 9d
使用kubecel创建名称空间test-ns
[root@master ~]# kubectl create namespace test-ns
namespace/test-ns created
删除某个名称空间
[root@master ~]# kubectl delete ns test-ns
namespace "test-ns" deleted
并非所有的对象都在名称空间内,大多数 kubernetes 资源(例如 Pod、Service、副本控制器等)都位于某个名称空间中,但名称空间资源本身并不在名称空间中,而且底层资源,例如节点(Node)和持久化卷(Volume)也不属于任何名字空间。
查看哪些Kubernetes资源在名称空间中,哪些不在名称空间中:
# 位于名字空间中的资源
kubectl api-resources --namespaced=true
# 不在名字空间中的资源
kubectl api-resources --namespaced=false
标签功能
Kubernetes控制平面会为所有名字空间设置一个不可变更的标签kubernetes.io/metadata.name,只要 NamespaceDefaultLabelName这一特性被启用,标签的值是名字空间的名称。
删除
删除名称空间会删除其包含的所有内容,删除过程是异步的,在删除动作完成之前或出现异常状况名称空间会一直处于Terminating状态。
在删除名称空间时会做如下操作,查看官方关于名称空间删除的源码片段
// NamespacedResourcesDeleterInterface is the interface to delete a namespace with all resources in it.
type NamespacedResourcesDeleterInterface interface {
Delete(nsName string) error
}
定义了NamespacedResourcesDeleterInterface接口来删除一个名称空间下的所有资源。
// Delete deletes all resources in the given namespace.
// Before deleting resources:
// * It ensures that deletion timestamp is set on the
// namespace (does nothing if deletion timestamp is missing).
// * Verifies that the namespace is in the "terminating" phase
// (updates the namespace phase if it is not yet marked terminating)
// After deleting the resources:
// * It removes finalizer token from the given namespace.
//
// Returns an error if any of those steps fail.
// Returns ResourcesRemainingError if it deleted some resources but needs
// to wait for them to go away.
// Caller is expected to keep calling this until it succeeds.
func (d *namespacedResourcesDeleter) Delete(nsName string) error {
// Multiple controllers may edit a namespace during termination
// first get the latest state of the namespace before proceeding
// if the namespace was deleted already, don't do anything
namespace, err := d.nsClient.Get(context.TODO(), nsName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
if namespace.DeletionTimestamp == nil {
return nil
}
klog.V(5).Infof("namespace controller - syncNamespace - namespace: %s, finalizerToken: %s", namespace.Name, d.finalizerToken)
// ensure that the status is up to date on the namespace
// if we get a not found error, we assume the namespace is truly gone
namespace, err = d.retryOnConflictError(namespace, d.updateNamespaceStatusFunc)
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
// the latest view of the namespace asserts that namespace is no longer deleting..
if namespace.DeletionTimestamp.IsZero() {
return nil
}
// return if it is already finalized.
if finalized(namespace) {
return nil
}
// there may still be content for us to remove
estimate, err := d.deleteAllContent(namespace)
if err != nil {
return err
}
if estimate > 0 {
return &ResourcesRemainingError{estimate}
}
// we have removed content, so mark it finalized by us
_, err = d.retryOnConflictError(namespace, d.finalizeNamespace)
if err != nil {
// in normal practice, this should not be possible, but if a deployment is running
// two controllers to do namespace deletion that share a common finalizer token it's
// possible that a not found could occur since the other controller would have finished the delete.
if errors.IsNotFound(err) {
return nil
}
return err
}
return nil
}
根据源码及其注释说明,名称空间删除整体调用流程大致如下:
当出现一直处于Terminating状态时,需要根据kubernetes日志具体的原因来解决。例如:使用命令查看名称空间信息
[root@master ~]# kubectl get ns ingress-nginx -o yaml
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: "2022-03-31T08:53:41Z"
labels:
kubernetes.io/metadata.name: ingress-nginx
name: ingress-nginx
resourceVersion: "123397"
uid: 48c4ec7a-10bb-4829-ba44-9c52c163fc54
spec:
finalizers:
- kubernetes
status:
phase: Active
根据message提示信息,查看具体的资源状态,手动清理或者重置资源状态后重新进行删除。
如果是finalizer导致的名称空间删除一直处于Terminating状态,可以查看namespace资源对象的spec.finalizer[] 列表情况,如果不为空,手动进行编辑清空数组,再重试。
[root@master ~]# kubelet edit ns #将metadata.finalizer[]列表删除
Node
Kubernetes中,Pod运行在节点(Node)上,每个节点包含运行Pod所需的服务,这些节点由控制平面组件负责管理。节点上的组件包括 kubelet、 容器运行时(CRI)以及 kube-proxy。
使用kubectl查看集群中节点信息,这些信息中通常会显示节点的ROLES(比如控制节点、工作节点)、NAME、节点的状态、存活时间及集群管理使用版本:
[root@master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready control-plane,master 10d v1.23.1
node1 Ready 10d v1.23.1
node2 Ready 10d v1.23.1
管理
节点的添加通过apiserver来添加,可以通过kubelet自动注册或者手动进行添加,也可以使用kubelet对节点进行修改。
- 自动注册
当kubelet设置--register-node为true(默认)时,它会尝试向apiserver注册。对于自动注册模式,kubelet使用下列参数启动:
- --kubeconfig:用于向apiserver表明身份的凭据路径
- --cloud-provider:与某云驱动进行通信以读取与自身相关的元数据的方式
- --register-node:是否自动向apiserver注册
- --register-with-taints:使用所给的污点列表注册节点。 当 register-node为false 时无效
- --node-ip:节点 IP 地址
- --node-labels:在集群中注册节点时要添加的标签
- --node-status-update-frequency:指定kubelet向控制平面发送状态的频率
- 手动管理
手动加入节点时,需要确保节点已经安装必要Node组件,并且需要设置kubelet属性--register-node=false,生成节点加入集群的认证命令
# 在master节点执行
[root@master ~]# kubeadm token create --print-join-command
然后将节点加入到集群中
kubeadm join 127.0.0.1:6443 --token 0nqoof.k8n2cnz0ld1hlnm1 --discovery-token-ca-cert-hash sha256:25aaa4aba8ed96818ebfed5f02a7cda3f44de5e8952c762c9e11949165f14f9c
如果无法通信检查网络插件是否安装,如果未安装推荐使用calico,安装后尝试重新加入集群,使用calico注意端口是否开放。
export POD_SUBNET=10.100.0.0/16
kubectl apply -f https://kuboard.cn/install-script/v1.21.x/calico-operator.yaml
wget https://kuboard.cn/install-script/v1.21.x/calico-custom-resources.yaml
sed -i "s#192.168.0.0/16#${POD_SUBNET}#" calico-custom-resources.yaml
kubectl apply -f calico-custom-resources.yaml
使用下面命令可以将节点标记为不可调度
kubectl cordon $NODENAME
说明:如果标记节点为不可调度(unschedulable),将阻止新Pod调度到该节点之上,但不会影响任何已经在其上的Pod,这是重启节点或者执行其他维护操作之前的一个有用的准备步骤。
- 优雅退出
Kubernetes在V1.21[beta]版本提供了如何优雅的退出一个节点
- 清空节点,参考官方文档
- 腾空节点,参考官方文档
状态
一个节点(Node)会包含地址、状态、容量及可分配空间、内核版本基本信息这四部分内容,使用kubectl查看节点的描述信息:
kubectl describe node <节点名称>
# 输出结果
Name: node1
Roles:
Labels:
......
Annotations:
......
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
NetworkUnavailable False Thu, 31 Mar 2022 16:28:14 +0800 Thu, 31 Mar 2022 16:28:14 +0800 CalicoIsUp Calico is running on this node
Ready True Sun, 10 Apr 2022 12:40:29 +0800 Wed, 30 Mar 2022 22:01:18 +0800 KubeletReady kubelet is posting ready status
Addresses:
InternalIP: 10.0.4.5
Hostname: node1
Capacity:
cpu: 2
ephemeral-storage: 61860632Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 3826332Ki
pods: 110
Allocatable:
cpu: 1900m
ephemeral-storage: 57010758357
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 3461788Ki
pods: 110
System Info:
Machine ID: 5e629d3c708849d29bbc2a68d1d884a8
System UUID: 5e629d3c-7088-49d2-9bbc-2a68d1d884a8
Boot ID: 5219e8f9-7310-48b5-994d-3ae2c7d90d6e
Kernel Version: 4.18.0-305.10.2.el8_4.x86_64
OS Image: CentOS Linux 8 (Core)
Operating System: linux
Architecture: amd64
Container Runtime Version: containerd://1.5.8
Kubelet Version: v1.23.1
Kube-Proxy Version: v1.23.1
PodCIDR: 10.234.2.0/23
PodCIDRs: 10.234.2.0/23
Non-terminated Pods: (22 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age
--------- ---- ------------ ---------- --------------- ------------- ---
......
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 857m (45%) 2770m (145%)
memory 1659537408 (46%) 2560321280 (72%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-1Gi 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
Events:
......
- 地址(Addresses)主要包含以下配置信息
- HostName:由节点的内核设置,可以通过kubelet的--hostname-override参数覆盖
- ExternalIP:通常是节点的可外部路由(从集群外可访问)的IP地址
- InternalIP:通常是节点仅可在集群内部路由的IP地址
- 状态(Conditions)conditions 字段描述了节点的状态,状况的示例包括
- Ready:如节点是健康的并已经准备好接收Pod则为True,False表示节点不健康而且不能接收Pod, Unknown表示节点控制器在node-monitor-grace-period期间(默认 40 秒)没有收到节点的消息
- DiskPressure:True表示节点存在磁盘空间压力,即磁盘可用量低, 否则为False
- MemoryPressure:True表示节点存在内存压力,即节点内存可用量低,否则为False
- PIDPressure:True表示节点存在进程压力,即节点上进程过多,否则为False
- NetworkUnavailable:True表示节点网络配置不正确,否则为False
- 容量及分配空间(Capacity、Allocated resources)
- 描述节点上的可用资源:CPU、内存和可以调度到节点上的Pod的个数上限。
- capacity块中的字段标示节点拥有的资源总量, allocatable块指示节点上可供普通Pod消耗的资源量。
- 基本信息(System Info)
- 描述节点的一般信息,如内核版本、Kubernetes版本(kubelet和kube-proxy版本)、 容器运行时详细信息,以及节点使用的操作系统, kubelet从节点收集这些信息并将其发布到Kubernetes API。
说明:如果Ready状况的status值处于Unknown或者False状态的时间超过了pod-eviction-timeout值,默认超时时长为5分钟,节点控制器会对节点上的所有Pod触发apiserver发起的驱逐。当节点不可达时,apiserver不能和kubelet通信,删除Pod的决定会直到kubelet重新建立与apiserver的连接再执行,在建立连接的过程中,被计划删除的Pod可能会继续在游离的节点上运行。
节点控制器在确认Pod停止运行前,不会强制删除它们,这些在无法访问的节点上运行的Pod处于Terminating或者 Unknown状态。 如果kubernetes不能基于下层基础设施推断出某节点是否已经永久离开了集群, 集群管理员可能需要手动删除节点对象。从Kubernetes删除节点对象将导致apiserver删除节点上所有运行的Pod对象。
节点生命周期控制器会自动创建代表状况的污点,当调度器将Pod指派给某节点时,会考虑节点上的污点,Pod则可以通过容忍度(Toleration)表达所能容忍的污点。
心跳
Kubernetes中通过心跳来确定集群中每个节点的可用性,在新版本中,集群创建后会默认初始化一个kube-node-lease名称空间,存放节点心跳相关的内容。
对于节点有两种形式的心跳,并且集群会在检测到故障时采取相应的措施:
- 更新节点的.status
- 更新Lease对象的信息(其中Lease对象就存储在kube-node-lease中,每个节点关联一个对象)
与Node的.status更新相比,Lease是一种轻量级资源,使用Leases心跳在大型集群中可以减少这些更新对性能的影响。kubelet负责创建和更新节点的.status,以及更新它们对应的Lease对象。
- 当状态发生变化时,或者在配置的时间间隔内没有更新事件时,kubelet会更新.status。.status更新的默认间隔为5分钟(比不可达节点的40秒默认超时时间长很多)。
- kubelet会每10秒(默认更新间隔时间)创建并更新其Lease对象,Lease更新独立于NodeStatus更新发生, 如果Lease的更新操作失败,kubelet会采用指数回退机制,从200毫秒开始重试,最长重试间隔为7秒钟。
API
概述
REST API是Kubernetes的基本结构,所有操作和组件之间的通信及外部用户命令都是调用apiserver处理的REST API。Kubernetes平台视一切皆为API对象,且它们在API中有相应的定义。关于API版本的说明:
- Alpha:
- 版本名称包含alpha(例如,v1alpha1)。
- 软件可能会有Bug,启用某个特性可能会暴露出Bug,某些特性可能默认禁用。
- 对某个特性的支持可能会随时被删除,恕不另行通知。
- API可能在以后的软件版本中以不兼容的方式更改,恕不另行通知。
- 由于缺陷风险增加和缺乏长期支持,建议该软件仅用于短期测试集群。
- Beta:
- 版本名称包含 beta (例如, v2beta3)。
- 软件被很好的测试过,启用某个特性被认为是安全的,特性默认开启。
- 尽管一些特性会发生细节上的变化,但它们将会被长期支持。
- 在随后的Beta版或稳定版中,对象的模式和(或)语义可能以不兼容的方式改变。 当这种情况发生时,将提供迁移说明。模式更改可能需要删除、编辑和重建 API 对象。编辑过程可能并不简单。对于依赖此功能的应用程序,可能需要停机迁移。
- 该版本的软件不建议生产使用,后续发布版本可能会有不兼容的变动,如果你有多个集群可以独立升级,可以放宽这一限制。
- Stable:
- 版本名称如 vX,其中X为整数。
- 特性的稳定版本会出现在后续很多版本的发布软件中。
访问控制
Kubernetes提供了完善的访问控制策略,用户使用kubectl、客户端库或构造REST请求来访问 Kubernetes API。 用户和Kubernetes服务账户都可以被鉴权访问API。API请求要么与某普通用户相关联,要么与某服务账号相关联,或者被视作匿名请求。这意味着集群内外的每个进程在向apiserver发起请求时都必须通过身份认证,否则会被视作匿名用户。这里的进程可以是在某工作站上输入kubectl命令的操作人员,也可以是节点上的kubelet组件,还可以是控制平面的组件。
当请求到达API时,它会经历多个阶段,如下图所示:
用户分类
所有Kubernetes集群都有两类用户,由Kubernetes管理的服务账号和普通用户。Kubernetes假定普通用户是由一个与集群无关的服务通过以下方式之一进行管理的:
- 负责分发私钥的管理员
- 类似Keystone或者Google Accounts这类用户数据库
- 包含用户名和密码列表的文件
Kubernetes中并不包含用来代表普通用户账号的对象,普通用户的信息无法通过API调用添加到集群中。尽管无法通过API调用来添加普通用户,Kubernetes仍然认为能够提供由集群的证书机构签名的合法证书的用户是通过身份认证的用户,Kubernetes使用证书中的 'subject' 的通用名称(Common Name)字段(例如,"/CN=bob")来确定用户名,基于角色访问控制(RBAC)子系统会确定用户是否有权针对某资源执行特定的操作。
与此不同,服务账号是Kubernetes所管理的用户。它们被绑定到特定的名称空间,由apiserver自动创建,或者通过API调用创建。服务账号与一组以Secret保存的凭据相关,这些凭据会被挂载到Pod中,从而允许集群内的进程访问Kubernetes API。
认证流程
Kubernetes中的访问控制主要包含下面的流程或者特性:
- 传输安全
Kubernetes集群中,apiserver在443端口上提供服务,受TLS保护。apiserver出示证书,该证书可以使用私有证书颁发机构(CA)签名,也可以基于公认的CA的公钥基础架构签名。如果集群使用私有证书颁发机构,需要在客户端的 ~/.kube/config文件中提供该CA证书的副本,以便可以信任该连接不被拦截。
- 认证
如上图步骤1所示,建立TLS后,HTTP请求将进入认证(Authentication)步骤。 集群创建脚本或者集群管理员配置apiserver,使之运行一个或多个身份认证组件。认证步骤的输入整个HTTP请求,通常组件只检查头部或客户端证书。认证模块包含客户端证书、密码、普通令牌、引导令牌和JSON Web令牌(JWT,用于服务账户)。可以指定多个认证模块,在这种情况下,服务器依次尝试每个验证模块,直到其中一个成功。如果请求认证不通过,服务器将以HTTP状态码401拒绝该请求。反之,该用户被认证为特定的username,并且该用户名可用于后续步骤以在其决策中使用。
- 鉴权
如上图的步骤2所示,将请求验证为来自特定的用户后,请求必须被鉴权。请求必须包含请求者的用户名、请求的行为以及受该操作影响的对象。如果现有策略声明用户有权完成请求的操作,那么该请求鉴权通过。例如,如果用户Bob有以下策略,那他只能在projectCaribou名称空间中读取Pod。
{
"apiVersion": "abac.authorization.kubernetes.io/v1beta1",
"kind": "Policy",
"spec": {
"user": "bob",
"namespace": "projectCaribou",
"resource": "pods",
"readonly": true
}
}
如果Bob执行以下请求,那么请求会被鉴权,因为允许他读取projectCaribou名称空间中的对象。
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"spec": {
"resourceAttributes": {
"namespace": "projectCaribou",
"verb": "get",
"group": "unicorn.example.org",
"resource": "pods"
}
}
}
如果Bob在projectCaribou名字空间中请求写(create 或 update)对象,其鉴权请求将被拒绝。如果Bob在诸如projectFish这类其它名字空间中请求读取(get)对象,其鉴权也会被拒绝。
Kubernetes支持多种鉴权模块,例如ABAC模式、RBAC模式和Webhook模式等。管理员创建集群时,他们配置应在API服务器中使用的鉴权模块。如果配置了多个鉴权模块,则Kubernetes会检查每个模块,任意一个模块鉴权该请求,请求即可继续。如果所有模块拒绝了该请求,请求将会被拒绝(HTTP 状态码 403)。
- 准入控制
准入控制模块是可以修改或拒绝请求的软件模块。 除鉴权模块可用的属性外,准入控制模块还可以访问正在创建或修改的对象的内容。准入控制器对创建、修改、删除或(通过代理)连接对象的请求进行操作。 准入控制器不会对仅读取对象的请求起作用。有多个准入控制器被配置时,服务器将依次调用它们。这一操作如上图的步骤3所示。与身份认证和鉴权模块不同,如果任何准入控制器模块拒绝某请求,则该请求将立即被拒绝。除了拒绝对象之外,准入控制器还可以为字段设置复杂的默认值。可用的准入控制模块在准入控制器中进行了描述。请求通过所有准入控制器后,将使用检验例程检查对应的 API 对象,然后将其写入对象存储(如步骤 4 所示)。
- 审计
Kubernetes审计提供了一套与安全相关的、按时间顺序排列的记录,其中记录了集群中的操作序列。 集群对用户、使用Kubernetes API的应用程序以及控制平面本身产生的活动进行审计。
- apiserver端口和IP
默认情况下,Kubernetes apiserver在2个端口上提供HTTP服务:
- localhost端口
- 用于测试和引导,以及主控节点上的其他组件(调度器,控制器管理器)与API通信
- 没有TLS,默认为端口8080,默认IP为localhost,使用--insecure-bind-address进行更改
- 请求绕过身份认证和鉴权模块,由准入控制模块处理的请求,受需要访问主机的保护
- 安全端口
- 尽可能使用,使用TLS,用--tls-cert-file设置证书,用--tls-private-key-file设置密钥
- 默认端口6443,使用--secure-port更改,默认IP是第一个非本地网络接口,使用--bind-address更改
- 请求须经身份认证和鉴权组件处理,须经准入控制模块处理,身份认证和鉴权模块运行
RBAC
基于角色(Role)的访问控制(RBAC)是一种基于组织中用户的角色来调节控制对计算机或网络资源的访问的方法。RBAC鉴权机制使用rbac.authorization.k8s.io API组来驱动鉴权决定,允许通过Kubernetes API动态配置策略。在启动apiserver时将--authorization-mode参数设置为一个逗号分隔的列表并确保其中包含RBAC来启用RBAC功能。
kube-apiserver --authorization-mode=Example,RBAC --<其他选项> --<其他选项>
- Role
RBAC的Role或ClusterRole中包含一组代表权限的规则。这些权限是累加的(不存在拒绝某操作的规则)。Role用在某个名称空间内设置访问权限,在创建Role时,必须指定该Role所属的名称空间。ClusterRole则是一个集群作用域的资源。Role或ClusterRole 对象的名称必须是合法的路径分段名称。
如果希望在名称空间内定义角色,应该使用Role,如果定义集群范围的角色,应该使用ClusterRole。下面是一个位于 "default" 名称空间的Role的示例,可用来授予对Pods的读访问权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" 标明 core API 组
resources: ["pods"]
verbs: ["get", "watch", "list"]
- ClusterRole
ClusterRole可以和Role相同完成授权,因为ClusterRole属于集群范围,所以它也可以为以下资源授予访问权限:
- 集群范围资源(比如节点)
- 非资源端点(比如/healthz)
- 跨名称空间访问的作用域的资源,例:允许某特定用户执行kubectl get pods --all-namespaces
下面是一个ClusterRole的示例,可用来为任一特定名字空间中的Secret授予读访问权限,或者跨名称空间的访问权限(取决于该角色是如何绑定的):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# "namespace" 被忽略,因为 ClusterRoles 不受名字空间限制
name: secret-reader
rules:
- apiGroups: [""]
# 在 HTTP 层面,用来访问 Secret 对象的资源的名称为 "secrets"
resources: ["secrets"]
verbs: ["get", "watch", "list"]
- RoleBinding/ClusterRoleBinding
角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户。 它包含若干主体(用户、组或服务账户)的列表和对这些主体所获得的角色的引用。RoleBinding在指定的名称空间中执行授权,ClusterRoleBinding 在集群范围执行授权。
一个RoleBinding可以引用同一的名称空间中的任何Role。或者,一个RoleBinding可以引用某ClusterRole并将该 ClusterRole绑定到RoleBinding所在的名称空间。如果希望将某ClusterRole绑定到集群中所有名称空间,需要使用ClusterRoleBinding。
RoleBinding或ClusterRoleBinding对象的名称必须是合法的路径分段名称。
- RoleBinding示例
下面的例子中的RoleBinding将"pod-reader" Role授予在"default"名称空间中的用户"jane"。这样用户"jane"就具有了读取"default"名字空间中Pods的权限。
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定允许 "jane" 读取 "default" 名字空间中的 Pods
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
# 你可以指定不止一个“subject(主体)”
- kind: User
name: jane # "name" 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
# "roleRef" 指定与某 Role 或 ClusterRole 的绑定关系
kind: Role # 此字段必须是 Role 或 ClusterRole
name: pod-reader # 此字段必须与你要绑定的 Role 或 ClusterRole 的名称匹配
apiGroup: rbac.authorization.k8s.io
RoleBinding也可以引用ClusterRole,以将对应ClusterRole中定义的访问权限授予RoleBinding所在名称空间的资源。这种引用使得可以跨整个集群定义一组通用的角色,之后在多个名称空间中复用。
例如,尽管下面的RoleBinding引用的是一个ClusterRole,"dave"(这里的主体, 区分大小写)只能访问 "development"名称空间中的Secrets对象,RoleBinding所在的名字空间(由metadata决定)是 "development"。
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定使得用户 "dave" 能够读取 "development" 名字空间中的 Secrets
# 你需要一个名为 "secret-reader" 的 ClusterRole
kind: RoleBinding
metadata:
name: read-secrets
# RoleBinding 的名字空间决定了访问权限的授予范围。
# 这里隐含授权仅在 "development" 名字空间内的访问权限。
namespace: development
subjects:
- kind: User
name: dave # 'name' 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
- ClusterRoleBinding示例
要跨整个集群完成访问权限的授予,可以使用ClusterRoleBinding。下面的ClusterRoleBinding允许 "manager"组内的所有用户访问任何名字空间中的Secrets。
apiVersion: rbac.authorization.k8s.io/v1
# 此集群角色绑定允许 “manager” 组中的任何人访问任何名字空间中的 secrets
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager # 'name' 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
创建了绑定之后,不能再修改绑定对象所引用的Role或ClusterRole。试图改变绑定对象的roleRef将导致合法性检查错误。命令kubectl auth reconcile可以创建或者更新包含RBAC对象的清单文件,并且在必要的情况下删除和重新创建绑定对象,以改变所引用的角色。
如果想要改变现有绑定对象中roleRef字段的内容,必须删除重新创建绑定对象。这种限制有两个主要原因:
- 针对不同角色的绑定是完全不一样的绑定,要求通过删除/重建绑定来更改roleRef,这样可以确保要赋予绑定的所有主体会被授予新的角色(而不是在允许或者不小心修改了roleRef的情况下导致所有现有主体未经验证即被授予新角色对应的权限)。
- 将roleRef设置为不可以改变,这使得可以为用户授予对现有绑定对象的update权限, 这样可以让他们管理主体列表,同时不能更改被授予这些主体的角色。
ABAC
基于属性的访问控制(Attribute-based access control - ABAC)定义了访问控制范例,其中通过使用将属性组合在一起的策略来向用户授予访问权限。
基于ABAC模式,可以指定策略文件--authorization-policy-file=SOME_FILENAME。文件格式为JSON Lines,不应存在外层的列表或映射,每行应只有一个映射。每一行都是一个策略对象,策略对象是具有以下属性的映射:
- 版本控制属性:
- apiVersion,字符串类型:有效值为abac.authorization.kubernetes.io/v1beta1,允许对策略格式进行版本控制和转换
- kind,字符串类型:有效值为Policy,允许对策略格式进行版本控制和转换
- spec 配置为具有以下映射的属性:
- 主体匹配属性:
- user,字符串类型:来自--token-auth-file的用户字符串,如果指定 user,它必须与验证用户的用户名匹配。
- group,字符串类型:如果指定 group,必须与经过身份验证的用户组匹配,system:authenticated匹配所有经过身份验证的请求,system:unauthenticated匹配所有未经过身份验证的请求。
- 主体匹配属性:
- 资源匹配属性:
- apiGroup,字符串类型:一个API组。例: extensions,通配符,*匹配所有 API 组。
- namespace,字符串类型:一个命名空间。例:kube-system,通配符,*匹配所有资源请求。
- resource,字符串类型:资源类型。例:pods,通配符:*匹配所有资源请求。
- 非资源匹配属性:
- nonResourcePath,字符串类型;非资源请求路径。
- 例如:/version或 /apis
- 通配符:* 匹配所有非资源请求,foo/* 匹配 /foo/ 的所有子路径。
- nonResourcePath,字符串类型;非资源请求路径。
- readonly,键入布尔值,如果为 true,则表示该策略仅适用于 get、list 和 watch 操作。
说明:属性未设置等效于属性被设置为对应类型的零值( 例如空字符串、0、false),然而,出于可读性考虑,应尽量选择不设置这类属性。在将来,策略可能以 JSON 格式表示,并通过 REST 界面进行管理。
名称空间级资源
名称空间在kubernetes中主要的作用是做资源隔离,因此名称空间级别的资源只在当前名称空间下有效。
工作负载型资源
工作负载(workload)是在Kubernetes上运行的应用程序,无论负载是单一组件还是由多个一同工作的组件构成,工作负载都可以在一组Pods中运行。
Pod
Pod是Kubernetes中可以创建和管理的最小可部署计算单元。Pod中包含了一组(一个或多个)容器,这些容器不仅指Docker,实现了CRI的容器都可以作为kubernetes的运行时,如containerd等。从Docker出发来说,Pod类似于共享名称空间(Namespace)和文件系统卷(Volume)的一组Docker容器。
说明:Pod天生地为其成员容器提供了两种共享资源:网络和存储
- 网络
Pod中的每个容器共享网络资源,包括IP地址和网络端口,每个Pod在每个地址族中拥有唯一的IP地址,没有特殊配置就不能使用IPC进行通信。Pod内的容器可以使用localhost互相通信,当Pod中的容器与Pod之外的实体通信时,可以通过IP联网的方式实现,但是需要协调如何使用共享的网络资源(例如端口),Pod中的容器所看到的系统主机名与为Pod配置的name属性值相同。
- 存储
一个Pod可以设置一组共享的存储卷(Volume)。Pod中的所有容器都可以访问该共享卷,从而允许这些容器共享数据。卷还允许Pod中的持久数据保留下来,即使其中的容器需要重新启动。
如何使用
在kubernetes中通常不会直接创建Pod,一般使用诸如Deployment或Job这类工作负载资源来创建Pod,这些Pod都是无状态的。如果Pod需要跟踪状态,可以使用StatefulSet资源来创建。Kubernetes集群中的Pod主要有两种用法:
- 运行单个容器的Pod:"每个Pod一个容器"模型是最常见的Kubernetes用例,这种情况下,可以将Pod看作单个容器的包装器,并且Kubernetes直接管理Pod,而不是容器。
- 运行多个协同工作的容器的Pod:Pod可能封装由多个紧密耦合且需要共享资源的共处容器组成的应用程序。这些位于同一位置的容器形成单个内聚的服务单元,Pod将这些容器和存储资源打包为一个可管理的实体。
说明:将多个并置、同管的容器组织到一个Pod中是一种相对高级的使用场景,只有在一些场景中,容器之间紧密关联时你才应该使用这种模式。
Pod被设计成支持形成内聚服务单元的多个协作过程(形式为容器)。Pod中的容器被自动安排到集群中的同一物理机或虚拟机上,并可以一起进行调度。容器之间可以共享资源和依赖、彼此通信、协调何时以及何种方式终止自身。
例如,在一个Pod中包含两个容器,一个容器,为共享卷中的文件提供Web服务器支持,另一个单独的容器负责从远端更新这些文件,如下图所示:
生命周期
Pod遵循预定义的生命周期,起始于Pending阶段,如果至少其中有一个主要容器正常启动,则进入Running,之后取决于Pod中是否有容器以失败状态结束而进入Succeeded或者Failed阶段。
- 概述
Pod生命周期中只会被调度一次,和容器一样,Pod也被认为是相对临时性(而不是长期存在)的实体,Pod会被创建、赋予一个唯一的ID(UID),一旦Pod被调度(分派)到某个节点,Pod会一直在该节点运行,直到Pod停止或者被终止。任何给定的Pod(由UID定义)从不会被重新调度(rescheduled)到不同的节点,如有必要,这一Pod可以被一个新的、几乎完全相同的Pod替换掉,新Pod的名字可以不变,但是其UID会不同。如果存储卷生命期与某Pod相同,这就意味着该对象在此Pod(UID亦相同)存在期间也一直存在。如果Pod因为任何原因被删除,甚至某完全相同的替代Pod被创建时,这个相关的对象(例如这里的卷)也会被删除并重建。
Pod自身不具有自愈能力,如果Pod被调度到某节点(Node)而该节点之后失效,调度到该节点的Pod也会在给定的超时期限结束后删除。同时,Pod无法在因节点资源耗尽或者节点维护而被驱逐期间继续存活。kubernetes使用一种高级抽象(控制器)来管理这些相对而言可随时丢弃的Pod实例。
- Pod阶段
Pod的status字段是一个PodStatus对象,其中包含一个phase字段。Pod的阶段(Phase)是Pod在其生命周期中所处位置的简单宏观概述,该阶段并不是对容器或Pod状态的综合汇总。Pod阶段的数量和含义是严格定义的,下面是phase可能的值:
取值 | 描述 |
---|---|
Pending | Pod已被Kubernetes系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待Pod被调度的时间和通过网络下载镜像的时间。 |
Running | Pod已经绑定到了某个节点,Pod中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。 |
Succeeded | Pod中的所有容器都已成功终止,并且不会再重启。 |
Failed | Pod中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。 |
Unknown | 因为某些原因无法取得Pod的状态。这种情况通常是因为与Pod所在主机通信失败。 |
- Pod内容器状态
kubernetes还会跟踪Pod中每个容器的状态,就像它跟踪Pod总体上的阶段一样,可以使用容器生命周期回调在容器生命周期中的特定时间点触发事件。一旦调度器将Pod分派给某个节点,kubelet就通过容器运行时开始为Pod创建容器。容器状态有三种:Waiting(等待)、Running(运行中)、Terminated(终止)。可以使用命令检查Pod中容器的状态。
[root@master ~]# kubectl describe pod docker-learn-server-7597c4ddf6-jnfpr
# 输出结果
Name: docker-learn-server-7597c4ddf6-jnfpr
Namespace: default
Priority: 0
Node: node1/10.0.4.5
Start Time: Fri, 01 Apr 2022 01:14:07 +0800
Labels: k8s.kuboard.cn/layer=svc
k8s.kuboard.cn/name=docker-learn-server
pod-template-hash=7597c4ddf6
Annotations: cni.projectcalico.org/containerID: 332efea7ce7d4d93d9dd5b1b265a921197c2f66a09dfe30ce8526aaf2111430e
cni.projectcalico.org/podIP: 10.234.168.26/32
cni.projectcalico.org/podIPs: 10.234.168.26/32
Status: Running
IP: 10.234.168.26
IPs:
IP: 10.234.168.26
Controlled By: ReplicaSet/docker-learn-server-7597c4ddf6
Containers:
docker-learn-server:
Container ID: containerd://ff4d17115f8b2c02db6a485ecda67c74fc91cb3fc73cc62c1e7ab92dc288a500
Image: ccr.ccs.tencentyun.com/dockerpracticesig/docker_practice:vuepress
Image ID: ccr.ccs.tencentyun.com/dockerpracticesig/docker_practice@sha256:2d74d38991a79c96c14782502e3b7aeb2dacab1a2c2f61ab6974160178466472
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Fri, 01 Apr 2022 01:14:08 +0800
Ready: True
Restart Count: 0
Environment:
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-mzmw5 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-mzmw5:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional:
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors:
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
输出了Pod的详细声明式信息,其输出中包含Pod中每个容器的状态。每种状态都有特定的含义:
- Waiting
如果容器不处在Running或Terminated状态之一,它就处在Waiting状态,处于Waiting状态的容器仍在运行它完成启动所需要的操作。例如,从某个容器镜像仓库拉取容器镜像,或者向容器应用Secret数据等。使用kubectl来查询包含Waiting状态的容器的Pod时,会看到一个Reason字段,其中给出了容器处于等待状态的原因。
- Running
Running状态表明容器正在执行状态并且没有问题发生。如果配置了postStart回调,那么该回调已经执行且已完成。如果使用kubectl来查询包含Running状态的容器的Pod时,会看到关于容器进入Running状态的信息。
- Terminated
处于Terminated状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。如果使用kubectl来查询包含Terminated状态的容器的Pod时,会看到容器进入此状态的原因、退出代码以及容器执行期间的起止时间。如果容器配置了preStop回调,则该回调会在容器进入Terminated状态之前执行。
- Pod重启策略
Pod的spec中包含一个restartPolicy字段,其可能取值包括Always、OnFailure和Never。默认值是Always。该策略适用于Pod中的所有容器。restartPolicy仅针对同一节点上kubelet的容器重启动作。当Pod中的容器退出时,kubelet会按指数回退方式计算重启的延迟(10s、20s、40s、...),其最长延迟为5分钟。一旦某容器执行了10分钟并且没有出现问题,kubelet对该容器的重启回退计时器执行重置操作。
- Pod状况
Pod有一个PodStatus对象,其中包含一个PodConditions数组。Pod可能通过也可能未通过其中的一些状况测试。主要包含以下几种状态
- PodScheduled:Pod已经被调度到某节点
- ContainersReady:Pod中所有容器都已就绪
- Initialized:所有的Init容器都已成功完成
- Ready:Pod可以为请求提供服务,并且应该被添加到对应服务的负载均衡池中
对象中包含的属性信息如下表所示:
字段名称 | 描述 |
---|---|
type | Pod状况的名称 |
status | 表明该状况是否适用,可能的取值有"True","False"或"Unknown" |
lastProbeTime | 上次探测Pod状况时的时间戳 |
lastTransitionTime | Pod上次从一种状态转换到另一种状态时的时间戳 |
reason | 机器可读的、驼峰编码(UpperCamelCase)的文字,表述上次状况变化的原因 |
message | 人类可读的消息,给出上次状态转换的详细信息 |
kubernetes在v1.14 stable后引入就绪态概念。应用可以向PodStatus中注入额外的反馈信号:PodReadiness(Pod就绪态)。要使用这一特性,可以设置Pod规约中的readinessGates列表,为kubelet提供一组额外的状况供其评估Pod就绪态时使用。
就绪态基于Pod的status.conditions字段的当前值来做决定。如果Kubernetes无法在status.conditions字段中找到某状况,则该状况的状态值默认为"False"。
kind: Pod
...
spec:
readinessGates:
- conditionType: "www.example.com/feature-1"
status:
conditions:
- type: Ready # 内置的 Pod 状况
status: "False"
lastProbeTime: null
lastTransitionTime: 2018-01-01T00:00:00Z
- type: "www.example.com/feature-1" # 额外的 Pod 状况
status: "False"
lastProbeTime: null
lastTransitionTime: 2018-01-01T00:00:00Z
containerStatuses:
- containerID: docker://abcd...
ready: true
...
命令kubectl patch不支持修改对象的状态。如果需要设置Pod的status.conditions,应用或者Operators需要使用PATCH操作。可以使用Kubernetes客户端库之一来编写代码,针对Pod就绪态设置定制的Pod状况。对于使用定制状况的Pod而言,只有当下面的陈述都适用时,该Pod才会被评估为就绪:
- Pod中所有容器都已就绪;
- readinessGates中的所有状况都为True值。
当Pod容器已就绪,但至少一个定制状况没有取值或值为False,kubelet将Pod的状况设置为ContainersReady。
Pause容器
Pause容器,也被称为infra容器,用作Pod中所有容器的父容器,Pod是Kubernetes设计的精髓,而pause容器则是Pod网络模型的精髓。
在Pod的生命周期中,创建Pod时Kubelet先调用CRI接口RuntimeService.RunPodSandbox来创建一个沙箱环境,为Pod设置网络(例如:分配 IP)等基础运行环境。当Pod沙箱(Pod Sandbox,Pod 这种在统一隔离环境里资源受限的一组容器)建立起来后,Kubelet 就可以在里面创建用户容器。当到删除 Pod 时,Kubelet 会先移除 Pod Sandbox 然后再停止里面的所有容器。
Kubernetes的Pod抽象基于Linux的namespace和cgroups,为一组容器共同提供了隔离的运行环境。从网络的角度看,同一个Pod 中的不同容器犹如在运行在同一个专有主机上,可以通过 localhost 进行通信。原则上,任何人都可以配置Docker来控制容器组之间的共享级别,只需创建一个父容器,并创建与父容器共享资源的新容器,然后管理这些容器的生命周期。在Kubernetes中,pause容器被当作Pod中所有容器的父容器并为每个业务容器提供以下功能:
- 在Pod中它作为共享Linux Namespace(Network、UTS 等)的基础
- 启用PID Namespace共享,它为每个Pod提供1号进程,并收集Pod内的僵尸进程
Init容器
有些Pod中具有Init容器,Init容器是一种特殊容器,Init容器会在启动应用容器之前运行并完成。Init容器可以包括一些应用镜像中不存在的实用工具和安装脚本。Init容器与普通的容器非常相似,与应用容器相比有两点不同:
- Init容器总是运行到完成为止。
- 每个Init容器都必须在下一个Init容器启动之前成功完成(串行)。
Pod规约中添加initContainers字段设置Init容器,该字段为Container类型对象数组形式,和应用的containers数组同级。Init容器的状态在status.initContainerStatuses字段中为容器状态数组(类似status.containerStatuses字段)。因为Init容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:
- Init容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。例:没有必要仅为了在安装过程中使用类似sed、awk、python或dig这样的工具而去FROM一个镜像来生成一个新的镜像
- Init容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低
- 应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像
- Init容器能以不同于Pod内应用容器的文件系统视图运行。因此,Init容器可以访问应用容器不能访问的Secret的权限
- 由于Init容器必须在应用容器启动之前运行完成,因此Init容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。一旦前置条件满足,Pod内的所有的应用容器会并行启动
Pod重启会导致Init容器重新执行,主要有如下几个原因:
- Pod的基础设施容器(如pause容器)被重启。这种情况不多见,必须由具备root权限访问节点的人员来完成。
- 当restartPolicy设置为"Always",Pod中所有容器会终止而强制重启。由于垃圾收集机制的原因,Init容器的完成记录将会丢失。
如果Pod的Init容器启动失败,kubelet会不断地重启该Init容器直到该容器成功为止。如果Pod对应的restartPolicy值为"Never",并且Pod的Init容器失败,则Kubernetes会将整个Pod状态设置为失败。
当Init容器的镜像发生改变或者Init容器的完成记录因为垃圾收集等原因被丢失时,Pod不会被重启。这一行为适用于Kubernetesv1.20及更新版本。
Pod的终止
Pod所代表的是集群在节点上运行的进程,当不再需要这些进程时,一般不应武断地使用KILL信号终止,会导致这些进程没有机会完成清理操作。
当请求删除某个Pod时,设计的目标是不仅能够删除进程,并且知道进程何时被终止,同时也能够确保删除操作终将完成。集群会记录并跟踪Pod的终止周期,而不是直接强制杀死Pod,在存在强制关闭设施前提下, kubelet会尝试体面地终止Pod。
- 正常终止流程
通常情况下,容器运行时会发送一个TERM信号到每个容器中的主进程,很多容器运行时都能够注意到容器镜像中STOPSIGNAL的值,并发送该信号而不是TERM,一旦超出了体面终止限期,容器运行时会向所有剩余进程发送KILL信号,之后 Pod 就会被从apiserver上移除。如果kubelet或者容器运行时的管理服务在等待进程终止期间被重启, 集群会从头开始重试,赋予Pod完整的体面终止限期。
下面是一个终止流程的例子:
- 使用kubectl工具手动删除某个特定的Pod,而该Pod的体面终止限期是默认值(30 秒)。
- apiserver中的Pod对象被更新,记录涵盖体面终止限期在内Pod的最终死期,超出所计算时间点则认为Pod 已死(dead)。 如果你使用kubectl describe来查验你正在删除的Pod,该Pod会显示为"Terminating"(正在终止)。 在Pod运行所在的节点上,kubelet一旦看到Pod被标记为正在终止(已经设置了体面终止限期),kubelet即开始本地的Pod关闭过程。
- 如果Pod中的容器之一定义了preStop回调, kubelet开始在容器内运行回调逻辑。如果超出体面终止限期时,preStop回调逻辑仍在运行,kubelet会请求给予该Pod的宽限期一次性增加2秒钟。如果 preStop回调所需要的时间长于默认的体面终止限期,你必须修改terminationGracePeriodSeconds属性值来使其正常工作。
- kubelet接下来触发容器运行时发送TERM信号给每个容器中的进程 。Pod中的容器会在不同时刻收到 TERM信号,接收顺序也是不确定的, 如果关闭的顺序很重要,可以考虑使用preStop回调逻辑来协调。
- 与此同时,kubelet启动体面关闭逻辑,控制面会将Pod从对应的端点列表(以及端点切片列表, 如果启用了的话)中移除,过滤条件是Pod被对应的服务以某选择算符选定。 ReplicaSets和其他工作负载资源不再将关闭进程中的Pod视为合法的、能够提供服务的副本。关闭动作很慢的Pod也无法继续处理请求数据,因为负载均衡器(例如服务代理)已经在终止宽限期开始的时候将其从端点列表中移除。
- 超出终止宽限期限时,kubelet会触发强制关闭过程。容器运行时会向Pod中所有容器内仍在运行的进程发送 SIGKILL信号,如果容器运行时使用了pause容器,kubelet也会清理隐藏的pause容器。
- kubelet触发强制从API服务器上删除Pod对象的逻辑,并将体面终止限期设置为0(这意味着马上删除)。
- apiserver删除Pod的API对象,从任何客户端都无法再看到该对象。
- **强制终止Pod **
对于某些工作负载及其Pod而言,强制删除很可能会带来某种破坏。默认情况下,所有的删除操作都会附有30秒钟的宽限期限。 kubectl delete命令支持--grace-period= 选项,允许你重载默认值, 设定自己希望的期限值。将宽限期限强制设置为0意味着立即从apiserver删除Pod。 如果Pod 仍然运行于某节点上,强制删除操作会触发kubelet立即执行清理操作。必须在设置 --grace-period=0 的同时额外设置 --force 参数才能发起强制删除请求。
执行强制删除操作时,apiserver不再等待来自kubelet的关于Pod已经在原来运行的节点上终止执行的确认消息。 apiserver直接删除Pod对象,这样新的与之同名的Pod即可以被创建。 在节点侧,被设置为立即终止的 Pod仍然会在被强行杀死之前获得一点点的宽限时间。
- **失效Pod的垃圾收集 **
对于已失败的Pod而言,对应的API对象仍然会保留在集群的apiserver上,直到用户或者控制器进程显式地将其删除。
控制面组件会在 Pod 个数超出所配置的阈值 (根据 kube-controller-manager 的 terminated-pod-gc-threshold 设置)时 删除已终止的 Pod(阶段值为 Succeeded 或 Failed)。 这一行为会避免随着时间演进不断创建和终止 Pod 而引起的资源泄露问题。
容器探针
探针(probe)是由kubelet对容器执行的定期诊断。要执行诊断,kubelet既可以在容器内执行代码,也可以发出一个网络请求,下面从几个方面来说明容器探针。
- 检查机制
使用探针来检查容器有四种不同的方法。每个探针都必须准确定义为这四种机制中的一种:
- exec
在容器内执行指定命令。如果命令退出时返回码为0则认为诊断成功。
- grpc
使用gRPC执行一个远程过程调用,目标应该实现gRPC健康检查。如果响应的状态是"SERVING",则认为诊断成功。gRPC探针是一个alpha特性,只有在你启用了"GRPCContainerProbe"特性门控时才能使用。
- httpGet
对容器的IP地址上指定端口和路径执行HTTPGET请求。如果响应的状态码大于等于200且小于400,则诊断被认为是成功的。
- tcpSocket
对容器的IP地址上的指定端口执行TCP检查。如果端口打开,则诊断被认为是成功的。如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。
- 探测结果
每次探测都将获得以下三种结果之一:
- Success(成功)容器通过了诊断。
- Failure(失败)容器未通过诊断。
- Unknown(未知)诊断失败,因此不会采取任何行动。
- 探测类型
针对运行中的容器,kubelet可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:
- livenessProbe
指示容器是否正在运行。如果存活态探测失败,则kubelet会杀死容器,并且容器将根据其重启策略决定未来。如果容器不提供存活探针,则默认状态为Success。
- readinessProbe
指示容器是否准备好为请求提供服务。如果就绪态探测失败,端点控制器将从与Pod匹配的所有服务的端点列表中删除该Pod的IP地址。初始延迟之前的就绪态的状态值默认为Failure。如果容器不提供就绪态探针,则默认状态为Success。
- startupProbe
指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被禁用,直到此探针成功为止。如果启动探测失败,kubelet将杀死容器,而容器依其重启策略进行重启。如果容器没有提供启动探测,则默认状态为Success。
- 探针类型
容器探针按照不同探测维度可以划分为存活态、就绪态、启动探针几种,每种有各自的使用场景,下面进行简单介绍:
- 何时该使用存活态探针,特性状态:Kubernetesv1.0[stable]
如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活态探针,kubelet将根据Pod的restartPolicy自动执行修复操作。如果希望容器在探测失败时被杀死并重新启动,那么需要指定一个存活态探针,并指定restartPolicy为"Always"或"OnFailure"。
- 何时该使用就绪态探针,特性状态:Kubernetesv1.0[stable]
如果要仅在探测成功时才开始向Pod发送请求流量,需要指定就绪态探针。在这种情况下,就绪态探针可能与存活态探针相同,但是规约中的就绪态探针的存在意味着Pod将在启动阶段不接收任何数据,并且只有在探针探测成功后才开始接收数据。如果希望容器能够自行进入维护状态,也可以指定一个就绪态探针,检查某个特定于就绪态的且不同于存活态探测的端点。
如果应用程序对后端服务有严格的依赖性,可以同时实现存活态和就绪态探针。当应用程序本身是健康的,存活态探针检测通过后,就绪态探针会额外检查每个所需的后端服务是否可用。可以避免将流量导向只能返回错误信息的Pod。
如果容器需要在启动期间加载大型数据、配置文件或执行迁移,你可以使用启动探针。然而,如果想区分已经失败的应用和仍在处理其启动数据的应用,可能更倾向于使用就绪探针。
说明:如果只是想在Pod被删除时能够排空请求,则不一定需要使用就绪态探针;在删除Pod时,Pod会自动将自身置于未就绪状态,无论就绪态探针是否存在。等待Pod中的容器停止期间,Pod会一直处于未就绪状态。
- 何时该使用启动探针,特性状态:Kubernetesv1.18[beta]
对于包含的容器需要较长时间才能启动就绪的Pod而言,启动探针是有用的。不再需要配置一个较长的存活态探测时间间隔,只需要设置另一个独立的配置选定,对启动期间的容器执行探测,从而允许使用远远超出存活态时间间隔所允许的时长。
如果容器启动时间通常超出initialDelaySeconds+failureThreshold×periodSeconds总值,应该设置一个启动探测,对存活态探针所使用的同一端点执行检查。periodSeconds的默认值是10秒,应该将其failureThreshold设置得足够高,以便容器有充足的时间完成启动,并且避免更改存活态探针所使用的默认值。并且这一设置有助于减少死锁状况的发生。
ReplicaSet
ReplicaSet(RS)的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。 因此,它通常用来保证给定数量的、完全相同的Pod的可用性。
在新版本的Kubernetes中建议使用RS来取代ReplicationController(RC)。ReplicaSet跟RC没有本质的不同,只是名字不一样,但ReplicaSet支持集合式selector。其工作原理如下图所示:
说明:在初始阶段定了一个RS需要维护保证集群中存在12个Pod副本,这三个副本分布在集群的三个节点中,由于某种不可控(如网络中断或者断电)因素,节点(Node)不可用,为了保障维持12个副本,RS会在其余可用节点中创建新的Pod,保障其可用性。
虽然ReplicaSets可以独立使用,但如今它主要被Deployments用作协调Pod的创建、删除和更新的机制。当使用 Deployment时,不必担心还要管理它们创建的ReplicaSet,Deployment会拥有并管理ReplicaSet。
ReplicaSet通过一组字段来定义的,包括一个用来识别可获得的Pod的集合的选择算符、一个用来标明应该维护的副本个数的数值、一个用来指定应该创建新Pod以满足副本个数条件时要使用的Pod模板等。 每个ReplicaSet都通过根据需要创建和删除Pod以使得副本个数达到期望值, 进而实现其存在价值。当ReplicaSet需要创建新的 Pod时,会使用所提供的Pod模板。
Deployment
Deployment是最常用工作负载,是ReplicaSet的更上一层的抽象概念。Deployment提供了Pod和ReplicaSet声明式创建和更新的能力,不要管理Deployment所拥有的ReplicaSet。
- 工作原理示意
Deployment控制器以受控速率更改实际状态,使其变为期望状态。可以定义Deployment以创建新的RS,或删除现有Deployment,并通过新的Deployment收养其资源。
以下是 Deployments 的典型用例:
- 创建 Deployment以将ReplicaSet上线,ReplicaSet在后台创建Pods。检查ReplicaSet的上线状态,查看其是否成功。
- 通过更新Deployment的PodTemplateSpec,声明Pod的新状态。新的ReplicaSet会被创建,Deployment以受控速率将Pod从旧ReplicaSet迁移到新ReplicaSet。每个新的ReplicaSet都会更新Deployment的修订版本。
- 如果Deployment的当前状态不稳定,回滚到较早的Deployment版本。每次回滚都会更新Deployment的修订版本。
- 扩大Deployment规模以承担更多负载。
- 暂停Deployment以应用对PodTemplateSpec所作的多项修改,然后恢复其执行以启动新的上线版本。
- 使用Deployment状态来判定上线过程是否出现停滞。
- 清理较旧的不再需要的ReplicaSet。
图片说明:一个节点中定义了四个Pod副本,假定按照预定策略每次进行50%的变更进行滚动更新,升级前版本为V1,升级后版本为v2。
- 第一阶段:创建两个新的Pod,当这两个副本变为Running状态后替换原来副本中的两个;
- 第二阶段:进行剩余副本的创建、替换过程,到此整个Devolopment更新过程完成,实现滚动更新。
在这个过程中不会让用户出现明显的服务中断感知,进行版本的回退,原理同样类似,只不过是创建历史v1版本资源定义的Pod来替换v2版本的Pod。
- 如何使用
下面是一个Deployment示例。其中创建了一个ReplicaSet,负责启动三个nginx Pods:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
在该例中:
- 创建名为nginx-deployment(由 .metadata.name 字段标明)的Deployment。
- 该 Deployment 创建三个(由 replicas 字段标明)Pod 副本。
- selector 字段定义 Deployment 如何查找要管理的 Pods。 在这里,你选择在 Pod 模板中定义的标签(app: nginx)。 不过,更复杂的选择规则是也可能的,只要 Pod 模板本身满足所给规则即可。
操作过程中可能用到的命令:
- 创建Development
kubectl apply -f <定义Development资源的yaml文件>
- 查看运行情况
kubectl get deployments
# 输出结果
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 0/3 0 0 1s
说明:在检查集群中的Deployment时,所显示的字段有
- NAME列出了集群中Deployment的名称
- READY显示应用程序的可用的“副本”数,显示的模式是“就绪个数/期望个数”
- UP-TO-DATE显示为了达到期望状态已经更新的副本数
- AVAILABLE显示应用可供用户使用的副本数
- AGE显示应用程序运行的时间
请注意期望副本数是根据.spec.replicas字段设置3。
- 查看Deployment创建的RS
kubectl get rs
# 输出结果
NAME DESIRED CURRENT READY AGE
nginx-deployment-75675f5897 3 3 3 18s
说明:ReplicaSet输出中包含以下字段,注意ReplicaSet的名称始终被格式化为[Deployment名称]-[随机字符串]。 其中的随机字符串是使用pod-template-hash作为种子随机生成的。
- NAME列出名字空间中ReplicaSet的名称
- DESIRED显示应用的期望副本个数,即在创建 Deployment 时所定义的值,此为期望状态
- CURRENT显示当前运行状态中的副本个数
- READY显示应用中有多少副本可以为用户提供服务
- AGE显示应用已经运行的时间长度
- 查看每个Pod自动生成的标签
kubectl get pods --show-labels
# 输出结果
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-75675f5897-7ci7o 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
nginx-deployment-75675f5897-kzszj 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
nginx-deployment-75675f5897-qqcnn 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
StatefulSet
StatefulSet是用来管理有状态应用的工作负载 API 对象。StatefulSet用来管理某Pod集合的部署和扩缩, 并为这些Pod提供持久存储和持久标识符。
与Deployment类似,StatefulSet管理基于相同容器规约的一组Pod。但和Deployment不同的是,StatefulSet为它们的每个Pod维护了一个有粘性的ID,这些Pod是基于相同的规约来创建的,但是不能相互替换,无论怎么调度,每个Pod都有一个永久不变的ID。
如果希望使用存储卷为工作负载提供持久存储,可以使用StatefulSet作为解决方案的一部分。 尽管StatefulSet中的单个Pod仍可能出现故障,但持久的Pod标识符使得将现有卷与替换已失败Pod的新Pod相匹配变得更加容易。
StatefulSets 对于需要满足以下一个或多个需求的应用程序很有价值:
- 稳定的、唯一的网络标识符。
- 稳定的、持久的存储。
- 有序的、优雅的部署和缩放。
- 有序的、自动的滚动更新。
在上面描述中,“稳定的”意味着Pod调度或重调度的整个过程是有持久性的。 如果应用程序不需要任何稳定的标识符或有序的部署、删除或伸缩,则应该使用 由一组无状态的副本控制器提供的工作负载来部署应用程序,比如 Deployment或者ReplicaSet可能更适用于无状态应用部署需要。下面是一个创建例子:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # has to match .spec.template.metadata.labels
serviceName: "nginx"
replicas: 3 # by default is 1
template:
metadata:
labels:
app: nginx # has to match .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: k8s.gcr.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi
上述例子中:
- 名为nginx的Headless Service用来控制网络域名。
- 名为 web 的 StatefulSet 有一个 Spec,它表明将在独立的 3 个 Pod 副本中启动 nginx 容器。
- volumeClaimTemplates 将通过 PersistentVolumes 驱动提供的PersistentVolumes来提供稳定的存储。
StatefulSet 的命名需要遵循DNS 子域名规范。
DaemonSet
DaemonSet确保全部(或者某些)节点上运行一个Pod的副本。当有节点加入集群时,也会为新增一个Pod。当有节点从集群移除时,这些Pod也会被回收。删除DaemonSet将会删除它创建的所有Pod。
DaemonSet 的一些典型用法:
- 在每个节点上运行集群守护进程
- 在每个节点上运行日志收集守护进程
- 在每个节点上运行监控守护进程
一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个DaemonSet。一个稍微复杂的用法是为同一种守护进程部署多个DaemonSet,每个具有不同的标志,并且对不同硬件类型具有不同的内存、CPU 要求。
DaemonSet与Deployments非常类似,它们都能创建Pod,并且Pod中的进程都不希望被终止(例如,Web 服务器、存储服务器)。
建议为无状态的服务使用Deployments,比如前端服务。对这些服务而言,对副本的数量进行扩缩容、平滑升级,比精确控制Pod运行在某个主机上要重要得多。当需要Pod副本总是运行在全部或特定主机上,并且当该 DaemonSet提供了节点级别的功能(允许其他 Pod 在该特定节点上正确运行)时, 应该使用DaemonSet。
例如,网络插件通常包含一个以DaemonSet运行的组件。 这个DaemonSet组件确保它所在的节点的集群网络正常工作。
Job
Job会创建一个或者多个Pods,并将继续重试Pods的执行,直到指定数量的Pods成功终止。随着Pods成功结束,Job跟踪记录成功完成的Pods个数。当数量达到指定的成功个数阈值时,任务(即Job)结束。删除Job的操作会清除所创建的全部Pods。挂起Job的操作会删除Job的所有活跃Pod,直到Job被再次恢复执行。
适合以 Job 形式来运行的任务主要有三种:
- 非并行 Job:
- 通常只启动一个 Pod,除非该 Pod 失败。
- 当 Pod 成功终止时,立即视 Job 为完成状态。
- 具有 确定完成计数 的并行 Job:
- .spec.completions 字段设置为非 0 的正数值。
- Job 用来代表整个任务,当成功的 Pod 个数达到 .spec.completions 时,Job 被视为完成。
- 当使用 .spec.completionMode="Indexed" 时,每个 Pod 都会获得一个不同的 索引值,介于 0 和 .spec.completions-1 之间。
- 带 工作队列 的并行 Job:
- 不设置 spec.completions,默认值为 .spec.parallelism。
- 多个 Pod 之间必须相互协调,或者借助外部服务确定每个 Pod 要处理哪个工作条目。 例如,任一 Pod 都可以从工作队列中取走最多 N 个工作条目。
- 每个 Pod 都可以独立确定是否其它 Pod 都已完成,进而确定 Job 是否完成。
- 当 Job 中 任何 Pod 成功终止,不再创建新 Pod。
- 一旦至少 1 个 Pod 成功完成,并且所有 Pod 都已终止,即可宣告 Job 成功完成。
- 一旦任何 Pod 成功退出,任何其它 Pod 都不应再对此任务执行任何操作或生成任何输出。 所有 Pod 都应启动退出过程。
创建示例
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
CronJob
CronJob创建基于时隔重复调度的Jobs,Kubernetes v1.21 [stable]引入特性。一个CronJob对象就像crontab (cron table) 文件中的一行,它用Cron格式进行编写,并周期性地在给定的调度时间执行Job。
CronJob用于执行周期性的动作,例如备份、报告生成等。这些任务中的每一个都应该配置为周期性重复的(例如:每天/每周/每月一次); 你可以定义任务开始执行的时间间隔。创建一个CronJob示例:
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox:1.28
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
ReplicationController
Replication Controller(RC)保证了在所有时间内,都有特定数量的Pod副本正在运行,如果太多了,RC就杀死几个,如果太少了,RC会新建几个,和直接创建的pod不同的是,RC会替换掉那些删除的或者被终止的pod,不管删除的原因是什么(维护、更新RC都不关心)。基于这个理由,建议即使是只创建一个Pod,也要使用RC。RC就像一个进程管理器,监管着不同node上的多个pod,而不是单单监控一个Node上的Pod,RC会委派本地容器来启动一些节点上服务(Kubelet 、Docker)。
说明:在v1.11版本废弃,由RS来替代。
服务发现及负载均衡型资源
Kubernetes除了容器编排、弹性伸缩资源之外,另一强大之处在于提供了一套自己的网络模型,很好的解决了服务间网络通信和负载问题。
Kubernetes中每一个Pod都有它自己的IP地址。从端口分配、命名、服务发现、 负载均衡、应用配置和迁移的角度来看, Pod可以被视作虚拟机或者物理主机,这将形成一个干净的、向后兼容的模型,这个模型里,不需要显式地在Pod之间创建连接,不需要处理容器端口到主机端口之间的映射。这个模型不仅不复杂,还与Kubernetes实现从虚拟机向容器平滑迁移的初衷相符, 如果从虚拟机单一IP和其他虚拟机通信的角度来说,Pod和虚拟机的模型是基本相同的。
Kubernetes强制要求所有网络设施都满足以下基本要求(从而排除了有意隔离网络的策略):
- 节点上的Pod可以不通过NAT和其他任何节点上的Pod通信
- 节点上的代理(比如:系统守护进程、kubelet)可以和节点上的所有Pod通信
Kubernetes中通过Service提供在Node本身请求端口,并用这类端口转发到对应的Pod(称之为主机端口), 这是一个很特殊的操作,转发方式如何实现也是容器运行时的细节,Pod自己并不知道这些主机端口的存在。Kubernetes网络解决四方面的问题:
- 一个Pod中的容器之间通过本地回路(loopback)通信
- 集群网络在不同Pod之间提供通信
- Service资源允许对外暴露Pod中运行的应用程序, 以支持来自于集群外部的访问
- 可以使用Services来发布仅供集群内部使用的服务
Service
Kubernetes中工作负载运行在Pod之内,并且提供了将运行在一组Pods上的应用程序公开为网络服务,这种抽象称之为Service。也可以简称为svc。
Pod是非永久性资源,如果使用Deployment来运行应用程序,可以动态创建和销毁Pod。每个Pod都有自己的IP地址,但是在Deployment中,在同一时刻运行的Pod集合可能与稍后运行该应用程序的Pod集合不同。这导致了一个问题: 如果一组Pod(如"后端")为集群内的其他Pod(如"前端")提供功能, 那么前端如何找出并跟踪要连接的IP地址,以便前端可以使用提供工作负载的后端部分?
Service就提供了这样一种能力,无需调用着关注IP地址的变化,通过服务名称的映射就可以调用到真实服务,并且当被调用的Pod存在多个副本时,Service还提供了一种负载的能力。
如何定义
Service是一个REST对象,基于POST方式,向apiserver发送REST请求创建服务对象。在Kubernetes中提供了几种常用的服务发布类型,通过ServiceTypes来指定服务类型:
- ClusterIP:通过集群的内部IP暴露服务,只能够在集群内部访问,默认的ServiceType类型。
- NodePort:通过每个节点上的IP和静态端口(NodePort)暴露服务,NodePort服务会路由到自动创建的 ClusterIP服务,通过请求 <节点IP>:<节点端口>,可以从集群的外部(例如公网)访问一个NodePort服务。
- LoadBalancer:使用云提供商的负载均衡器向外部暴露服务,外部负载均衡器可以将流量路由到自动创建的 NodePort服务和ClusterIP服务上。
- ExternalName:通过返回CNAME和对应值,将服务映射到externalName字段的内容(例fexample.com),无需创建任何类型代理。
说明:也可以使用Ingress来暴露自己的服务,Ingress不是一种服务类型,但它充当集群的入口点,它可以将路由规则整合到一个资源中,因此可以在同一IP地址下公开多个服务。
查看如下创建Service(服务)的yaml文件
---
apiVersion: v1
kind: Service
metadata:
annotations: {}
labels:
k8s.kuboard.cn/layer: svc
k8s.kuboard.cn/name: docker-learn-server
name: docker-learn-server
namespace: default
resourceVersion: '189188'
spec:
clusterIP: 10.233.154.14
clusterIPs:
- 10.233.154.14
externalTrafficPolicy: Cluster
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- name: ibsday
nodePort: 32601
port: 80
protocol: TCP
targetPort: 80
selector:
k8s.kuboard.cn/layer: svc
k8s.kuboard.cn/name: docker-learn-server
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}
Kubernetes为该服务分配一个IP地址(有时称为 "集群IP"),该IP地址由服务代理使用,会将请求代理到端口为80的并且便签为docker-learn-server的容器服务,该NodePort类型的服务对外公开的端口为32601。
说明:Service能够将一个接收port映射到任意的targetPort,默认情况下,targetPort将被设置为与port字段相同的值。
多端口
对于某些服务,需要公开多个端口,Kubernetes允许在Service对象上配置多个端口定义。 当服务使用多个端口时,必须提供所有端口名称,以使它们无歧义。 例如:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
说明:与一般的Kubernetes名称一样,端口名称只能包含小写字母数字字符和-。 端口名称还必须以字母数字字符开头和结尾。例如,名称123-abc和web有效,但是123_abc和-web无效。
自定义IP
在Service创建的请求中,可以通过设置spec.clusterIP字段来指定集群IP地址。 比如,希望替换一个已经已存在的DNS条目,或者遗留系统已经配置了一个固定的IP且很难重新配置。
用户选择的IP地址必须合法,并且这个IP地址在service-cluster-ip-range CIDR范围内, 这对apiserver来说是通过一个标识来指定的。 如果IP地址不合法,apiserver会返回HTTP状态码 422,表示值不合法。
流量策略
- **外部流量策略 **
通过设置spec.externalTrafficPolicy字段来控制来自于外部的流量如何路由,可选值有Cluster和Local。字段设为 Cluster会将外部流量路由到所有就绪的端点,设为Local会只路由到当前节点上就绪的端点;如果流量策略设置为Local,而且当前节点上没有就绪的端点,kube-proxy不会转发请求相关服务的任何流量。
说明:如果启用了kube-proxy的ProxyTerminatingEndpoints特性,kube-proxy会检查节点是否有本地的端点,以及是否所有的本地端点都被标记为终止中。
如果本地有端点,而且所有端点处于终止中的状态,那么kube-proxy会忽略任何设为Local的外部流量策略。在所有本地端点处于终止中的状态的同时,kube-proxy将请求指定服务的流量转发到位于其它节点的 状态健康的端点,如同外部流量策略设为 Cluster。
针对处于正被终止状态的端点这一转发行为使得外部负载均衡器可以优雅地排出由NodePort服务支持的连接,就算是健康检查节点端口开始失败也是如此。否则,当节点还在负载均衡器的节点池内,在Pod终止过程中的流量会被丢掉,并且可能会丢失。
- **内部流量策略 **
Kubernetes在v1.22 [beta]引入的特性,设置spec.internalTrafficPolicy字段来控制内部来源的流量如何转发,可设置的值有Cluster和Local。将字段设置为Cluster会将内部流量路由到所有就绪端点,设置为Local只会路由到当前节点上就绪的端点。如果流量策略是Local,而且当前节点上没有就绪的端点,那么kube-proxy会丢弃流量。
服务发现
Kubernetes支持两种基本的服务发现模式,环境变量和DNS。
- 环境变量
当Pod运行在Node上,kubelet会为每个活跃的Service添加一组环境变量。它同时支持Docker links兼容变量,简单的{SVCNAME}_SERVICE_HOST和{SVCNAME}_SERVICE_PORT变量。这里Service的名称需大写,横线被转换成下划线。
例如:一个名称为redis-master的Service暴露了TCP端口6379,同时给它分配了Cluster IP地址10.0.0.11,这个 Service生成了如下环境变量:
REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
说明:当具有需要访问服务的Pod时,并且正在使用环境变量方法将端口和集群IP发布到客户端Pod时,必须在客户端Pod出现之前创建服务。 否则,这些客户端Pod将不会设定其环境变量。如果仅使用DNS查找服务的集群IP,则无需担心此设定问题。
- DNS
Kubernetes集群范围内使用DNS服务来完成服务名到ClusterIP的解析,一般应该使用附加组件为Kubernetes集群设置DNS服务,从Kubernetes v1.12开始,CoreDNS是推荐的DNS服务器,取代了kube-dns。支持集群的DNS服务器(例如CoreDNS)监视Kubernetes API中的新服务,并为每个服务创建一组DNS记录,如果在整个集群中都启用了DNS,则所有Pod都应该能够通过其DNS名称自动解析服务。
由于Pod的地址可变性,通过创建Service资源,绑定Service IP,将其代理到后端Pod,通过暴露Service资源的固定地址(集群IP),来解决以上Pod资源变化产生的IP变动问题,简单概括Service解决了Pod的服务发现问题。需要注意的是Service资源也会被创建和销毁,并且也绑定了唯一IP,同样存在类似于Pod服务发现的问题,如果将Service资源名称和Service暴露的网络IP通过类似域名与IP的关系进行绑定,那么只需要根据服务名就能自动匹配服务IP,解决了Service服务发现问题。
CoreDNS就是为了解决以上问题,在集群内部做了ServiceName和ServiceIP之间的自动映射,使得不需要记录Service的IP地址,只需要通过ServiceName就能访问Pod。
无头服务
如果不需要进行负载均衡,以及提供单独的Service IP,可以通过指定Cluster IP(spec.clusterIP)的值为 "None" 来创建无头服务(Headless Service)。对于Headless Service并不会分配Cluster IP,kube-proxy不会处理它们, 而且平台也不会为它们进行负载均衡和路由。
DNS如何实现自动配置,依赖于 Service 是否定义了选择算符:
- 带选择算符的服务
对定义了选择算符的无头服务,Endpoint控制器在API中创建了Endpoints记录,并且修改DNS配置返回A记录,通过这个地址直接到达Service的后端Pod上。
- 无选择算符的服务
对没有定义选择算符的无头服务,Endpoint控制器不会创建Endpoints记录。 然而DNS系统会查找和配置,无论是:
- 对于ExternalName类型的服务,查找其CNAME记录
- 对所有其他类型的服务,查找与Service名称相同的任何Endpoints的记录
代理模式
在Kubernetes集群中,每个Node运行一个kube-proxy进程。kube-proxy负责为Service实现了一种VIP(虚拟 IP)的形式,而不是ExternalName的形式。
Kubernetes依赖代理将入站流量转发到后端,为什么不使用DNS轮询? 例如:是否可以配置具有多个A值(或 IPv6为AAAA)的DNS记录,并依靠轮询名称解析?
使用服务代理有以下几个原因:
- DNS实现的历史由来已久,它不遵守记录TTL,并且在名称查找结果到期后对其进行缓存。
- 有些应用程序仅执行一次DNS查找,并无限期地缓存结果。
- 即使应用和库进行了适当的重新解析,DNS记录上的TTL值低或为零也可能会给DNS带来高负载,从而使管理变得困难。
- userspace
kube-proxy会监视Kubernetes控制平面对Service对象和Endpoints对象的添加和移除操作。对每个Service,它会在本地Node上打开一个随机端口。任何连接到"代理端口"的请求,都会被代理到Service后端Pods中的某个上面。使用哪个后端Pod,是kube-proxy基于SessionAffinity来确定的,默认情况下,用户空间模式下的kube-proxy通过轮转算法选择后端。
- iptables
kube-proxy会监视Kubernetes控制节点对Service对象和Endpoints对象的添加和移除。使用iptables处理流量具有较低的系统开销,因为流量由Linux netfilter处理,无需在用户空间和内核空间之间切换,这种方法也更可靠:
- 对每个Service,kube-proxy会配置iptables规则,从而捕获到达该Service的clusterIP和端口的请求,进而将请求重定向到Service的一组后端中的某个Pod上面。
- 对于每个Endpoints对象,kube-proxy也会配置iptables规则,这个规则会选择一个后端组合。默认的策略是,kube-proxy在iptables模式下随机选择一个后端。
如果kube-proxy在iptables模式下运行,且所选的第一个Pod没有响应,则连接失败。与userspace模式不同。这种情况下,kube-proxy将检测到与第一个Pod的连接已失败,并会自动使用其他后端Pod重试。可以使用Pod就绪探测器验证后端Pod可以正常工作,以便iptables模式下的kube-proxy仅看到测试正常的后端。这样做可以避免将流量通过kube-proxy发送到已知失败的Pod。
- ipvs
在ipvs模式下,kube-proxy监视Kubernetes服务和端点,调用netlink接口相应地创建IPVS规则,并定期将IPVS规则与Kubernetes服务和端点同步。该控制循环可确保IPVS状态与所需状态匹配。访问服务时,IPVS将流量定向到后端Pod之一。
IPVS代理模式基于类似于iptables模式的netfilter挂钩函数,但是使用哈希表作为基础数据结构,并且在内核空间中工作。这意味着,与iptables模式下的kube-proxy相比,IPVS模式下的kube-proxy重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。与其他代理模式相比,IPVS模式还支持更高的网络流量吞吐量。IPVS提供了更多选项来平衡后端Pod的流量:
- rr:轮替(Round-Robin)
- lc:最少链接(Least Connection),即打开链接数量最少者优先
- dh:目标地址哈希(Destination Hashing)
- sh:源地址哈希(Source Hashing)
- sed:最短预期延迟(Shortest Expected Delay)
- nq:从不排队(Never Queue)
服务类型
- NodePort类型
如果将type字段设置为NodePort,Kubernetes会在--service-node-port-range标志指定的范围内分配端口(默认值:30000-32767),通过.spec.ports[].nodePort字段指定端口,每个节点将那个端口(每个节点上的相同端口号)代理到服务中。
使用NodePort可以自由设置自己的负载均衡解决方案,配置Kubernetes不完全支持的环境,甚至直接暴露一个或多个节点的IP。Service能够通过 :spec.ports[ ].nodePort和spec.clusterIp:spec.ports[*].port对外可见。 如果设置了kube-proxy的--nodeport-addresses参数或kube-proxy配置文件中的等效字段, 将被过滤NodeIP。
如果要指定特定的IP代理端口,可以设置kube-proxy中的--nodeport-addresses参数或者将kube-proxy配置文件 中的等效nodePortAddresses字段设置为特定的IP块。标志采用逗号分隔的IP块列表(10.0.0.0/8,192.0.2.0/25)来指定kube-proxy认为是此节点的IP地址范围。
如果使用--nodeport-addresses=127.0.0.0/8标志启动kube-proxy,则kube-proxy仅选择NodePort Services的本地回路接口。--nodeport-addresses的默认值是一个空列表,这意味着kube-proxy应该考虑NodePort的所有可用网络接口。(这也与早期的 Kubernetes 版本兼容)。
如果需要特定的端口号,可以在nodePort字段中指定一个值。控制平面将分配该端口或报告API事务失败。这意味着需要注意可能发生的端口冲突。必须使用有效的端口号,该端口号在配置用于NodePort的范围内。下面是一个创建NodeType类型Service的实例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app: MyApp
ports:
# 默认情况下,为了方便起见,`targetPort` 被设置为与 `port` 字段相同的值。
- port: 80
targetPort: 80
# 可选字段
# 默认情况下,为了方便起见,Kubernetes 控制平面会从某个范围内分配一个端口号(默认:30000-32767)
nodePort: 30007
- LoadBalancer类型
如果需要使用支持外部负载均衡器的云提供商服务,设置type的值为"LoadBalancer", 将为Service提供负载均衡器。负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过Service的status.loadBalancer字段发布出去。如下是创建LoadBalancer类型的Service示例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 192.0.2.127
来自外部负载均衡器的流量将直接重定向到后端Pod上,不过实际它们是如何工作的,这要依赖于云提供商。某些云提供商允许设置loadBalancerIP,在这些情况下,将根据用户设置的loadBalancerIP来创建负载均衡器。如果没有设置loadBalancerIP字段,将会给负载均衡器指派一个临时IP;如果设置了loadBalancerIP,但云提供商并不支持这种特性,那么设置的loadBalancerIP值将会被忽略掉。
Ingress
Ingress是在kubernetes v1.19[stable]版本引入,Ingress是对集群中服务的外部访问进行管理的API对象,典型的访问方式是HTTP。
什么是Ingress
Ingress公开了从集群外部到内部服务的HTTP和HTTPS路由,为Service提供外部可访问的URL、负载均衡流量,流量路由由Ingress资源上定义的规则控制,也可以配置边缘路由或其他前端帮助处理流量。Ingress不会公开任意端口或协议,将HTTP或HTTPS以外的服务公开到Internet时,通常使用NodePort或LoadBalancer类型的Service。其工作过程如下图所示:
如何使用
为了让Ingress资源工作,集群必须有一个运行状态的Ingress控制器。与作为kube-controller-manager可执行文件的一部分运行的其他类型的控制器不同,Ingress控制器不是随集群自动启动的。
使用Ingress类可以在集群中部署任意数量的Ingress控制器。使用.metadata.name字段的值来设置Ingress对象的ingressClassName字段,ingressClassName是之前的注解做法的替代。
如果没有指定一个IngressClass,并且集群中只有一个IngressClass被标记为了默认,Kubernetes会应用此默认IngressClass。 也可以将ingressclass.kubernetes.io/is-default-class注解的值设置为"true"把一个IngressClass标记为集群默认。如下所示为一个最小的Ingress资源示例:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
作用域
Ingress的作用域取决于Ingress控制器,作用域范围可以是集群也可以是名称空间,IngressClass的参数默认是集群范围的。
- 集群范围
如果设置了.spec.parameters字段且未设置.spec.parameters.scope字段,或是将.spec.parameters.scope字段设为了Cluster,那么该IngressClass所指代的即是一个集群作用域的资源。 参数的kind(和apiGroup一起)指向一个集群作用域的API(可能是一个定制资源Custom Resource),而它的name则为此API确定了一个具体的集群作用域的资源。
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: external-lb-1
spec:
controller: example.com/ingress-controller
parameters:
# 此 IngressClass 的配置定义在一个名为 “external-config-1” 的
# ClusterIngressParameter(API 组为 k8s.example.net)资源中。
# 这项定义告诉 Kubernetes 去寻找一个集群作用域的参数资源。
scope: Cluster
apiGroup: k8s.example.net
kind: ClusterIngressParameter
name: external-config-1
- 名称空间范围
Kubernetes v1.23[stable]版本引入该特性,如果设置了.spec.parameters字段且将.spec.parameters.scope字段设为了Namespace,那么该IngressClass将会引用一个名称空间作用域的资源。.spec.parameters.namespace必须和此资源所处的名称空间相同。
参数的kind(和apiGroup一起)指向一个名称空间作用域的API(例如:ConfigMap),而它的name则确定了一个位于指定的名称空间中的具体的资源。
名称空间作用域的参数帮助集群操作者将控制细分到用于工作负载的各种配置中(比如:负载均衡设置、API 网关定义)。如果使用集群作用域的参数,那么必须从以下两项中选择一项执行:
- 每次修改配置,集群操作团队需要批准其他团队的修改。
- 集群操作团队定义具体的准入控制,比如RBAC角色与角色绑定,以使得应用程序团队可以修改集群作用域的配置参数资源。
IngressClass API本身是集群作用域的。下面是一个引用名称空间作用域的配置参数的IngressClass的示例:
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: external-lb-2
spec:
controller: example.com/ingress-controller
parameters:
# 此 IngressClass 的配置定义在一个名为 “external-config” 的
# IngressParameter(API 组为 k8s.example.com)资源中,
# 该资源位于 “external-configuration” 命名空间中。
scope: Namespace
apiGroup: k8s.example.com
kind: IngressParameter
namespace: external-configuration
name: external-config
扇出
过多的Ingress类资源会难以管理,因此Ingress提供了一种方案,Ingress允许降低负载均衡器的数量,Ingress可以HTTP URI将来自同一IP地址的流量路由到多个Service,这种方式称为扇出(fanout)可以简单理解为对所有的客户端请求提供一个入口点,并根据URI分发至各自请求的后端。如图所示:
下面是一个配置示例:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: simple-fanout-example
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
pathType: Prefix
backend:
service:
name: service1
port:
number: 4200
- path: /bar
pathType: Prefix
backend:
service:
name: service2
port:
number: 8080
虚拟托管
基于名称的虚拟主机支持将针对多个主机名的HTTP流量路由到同一IP地址上。
以下Ingress让后台负载均衡器基于host 头部字段来路由请求。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: name-virtual-host-ingress
spec:
rules:
- host: foo.bar.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service1
port:
number: 80
- host: bar.foo.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service2
port:
number: 80
如果创建的Ingress资源没有在rules中定义的任何hosts,则可以匹配指向Ingress控制器IP地址的任何网络流量,而无需基于名称的虚拟主机。
以下Ingress会将请求first.bar.com的流量路由到service1,将请求second.bar.com的流量路由到 service2,而所有其他流量都会被路由到service3。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: name-virtual-host-ingress-no-third-host
spec:
rules:
- host: first.bar.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service1
port:
number: 80
- host: second.bar.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service2
port:
number: 80
- http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service3
port:
number: 80
Ingress-Nginx
Kubernetes作为一个项目,目前支持和维护AWS、 GCE和Nginx Ingress控制器。其中常用的是基于Nginx的Ingress-Nginx,这里的Nginx是经过改造适配于Ingress的。
这里不做过多介绍,详细内容可以查看官网或者Github地址,官方网站:
- https://kubernetes.github.io/ingress-nginx/
Github地址:
- https://github.com/kubernetes/ingress-nginx
配置与存储型资源
卷(Volume)
无论是Docker还是Kubernetes中其他CRI,都存在着一个问题,容器中的文件是临时存在的,会随着容器重启、销毁、或者故障等造成数据丢失,此外在Pod中如果存在多个容器共享文件,也需要提供共享空间。Docker也有卷(Volume)的概念,但对它只有少量且松散的管理。Docker卷是磁盘上或者另外一个容器内的一个目录,提供卷驱动程序,但是其功能非常有限。Kubernetes Volume(数据卷)主要解决了如下两方面问题:
- 数据持久性:通常情况下,容器运行起来之后,写入到其文件系统的文件暂时性的。当容器崩溃后,kubelet 将会重启该容器,此时原容器运行后写入的文件将丢失,因为容器将重新从镜像创建
- 数据共享:同一个 Pod(容器组)中运行的容器之间,经常会存在共享文件/文件夹的需求
卷的核心是一个目录,其中可能存有数据,Pod中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。使用卷时,在.spec.volumes字段中设置为Pod提供的卷,并在.spec.containers[*].volumeMounts字段中声明卷在容器中的挂载位置。容器中的进程看到的文件系统视图是由它们的容器镜像的初始内容以及挂载在容器中的卷(如果定义了的话)所组成的。 其中根文件系统同容器镜像的内容相吻合, 任何在该文件系统下的写入操作,如果被允许的话,都会影响接下来容器中进程访问文件系统时所看到的内容。
Kubernetes支持很多类型的卷,Pod可以同时使用任意数目的卷类型。临时卷类型的生命周期与Pod相同,但持久卷可以比Pod的存活期长;当Pod不再存在时,Kubernetes也会销毁临时卷;不过Kubernetes不会销毁持久卷。对于给定Pod中任何类型的卷,在容器重启期间数据都不会丢失。卷挂载在镜像中的指定路径下,Pod配置中的每个容器必须独立指定各个卷的挂载位置,卷不能挂载到其他卷之上(不过存在一种使用 subPath 的相关机制),也不能与其他卷有硬链接。
持久卷
存储的管理是一个与计算实例的管理完全不同的问题。Kubernetes中为用户和管理员提供了一组API,将存储如何供应的细节从其如何被使用中抽象出来,为了实现这点,引入了两个新的API资源:
- 持久卷(PersistentVolume,PV)
PV是集群中的一块存储,可由管理员事先供应,或使用存储类(Storage Class)动态供应。持久卷是集群资源,就像节点也是集群资源一样。PV和普通的Volume一样,也是使用卷插件来实现的,只是它们拥有独立于任何使用PV的Pod的生命周期,此API对象中记述了存储的实现细节,无论其背后是NFS、iSCSI还是特定于云平台的存储系统。
- 持久卷申领(PersistentVolumeClaim,PVC)
PVC表达的是用户对存储的需求,概念上与Pod类似。Pod会耗用节点资源,而PVC会耗用PV资源。Pod可以请求特定数量的资源(CPU 和内存);同样PVC也可以请求特定的大小和访问模式 (例如,可以要求PV卷能够以ReadWriteOnce、ReadOnlyMany或ReadWriteMany模式之一来挂载)。
PVC允许用户消耗抽象的存储资源,常见的情况是针对不同场景用户需要具有不同属性(如:性能)的PV卷。集群管理员需要能够提供不同性质的PV,并且这些PV卷之间的差别不仅限于卷大小和访问模式,同时又不能将卷是如何实现的这些细节暴露给用户,为了满足这类需求,就有了存储类(StorageClass) 资源。
PV卷的供应有两种方式:静态供应、动态供应。
- 静态供应
集群管理员创建若干PV卷。这些卷对象带有真实存储的细节信息,并且对集群用户可用(可见)。PV卷对象存在于Kubernetes API中,可供用户使用。
- 动态供应
如果管理员所创建的所有静态PV卷都无法与用户的PVC匹配,集群可以尝试为该PVC动态供应一个存储卷。这一供应操作是基于StorageClass来实现的。
说明:PVC必须请求某个存储类,同时集群管理员必须已经创建并配置了该类,这样动态供应卷的动作才会发生。如果PVC指定存储类为 "",则相当于为自身禁止使用动态供应的卷。
为了基于存储类完成动态的存储供应,集群管理员需要在apiserver上启用DefaultStorageClass准入控制器。例:通过保证DefaultStorageClass出现在apiserver组件的--enable-admission-plugins标志值中实现这点,该标志的值可以是逗号分隔的有序列表。
关于PV、PVC以及StorageClass之间关系示意图如下所示:
Pod将PVC当做存储卷来使用,集群会检视PVC,找到所绑定的卷,并为Pod挂载该卷。对于支持多种访问模式的卷,用户要在Pod中以卷的形式使用PVC时需指定期望的访问模式。一旦用户有了PVC对象并且该对象已被绑定,则所绑定的PV卷在用户需要期间一直属于该用户。用户通过在Pod的volumes块中包含PVC节区来调度Pod,访问所申领的PV卷。
说明:用户创建一个带有特定存储容量和特定访问模式的PVC对象,在动态供应场景下,这个PVC对象可能已经创建完毕。主控节点中的控制回路监测新的PVC对象,寻找与之匹配的PV卷,并将二者绑定到一起。如果为了新的PVC动态供应了PV卷,则控制回路总是将该PV卷绑定到这一PVC。否则,用户总是能够获得他们所请求的资源,只是所获得的PV卷可能会超出所请求的配置。无论该PVC是如何与PV卷建立的绑定关系,一旦绑定关系建立,则PVC绑定就是排他性的。PVC与PV卷之间的绑定是一种一对一的映射,实现上使用ClaimRef来记述PV卷与PVC申领间的双向绑定关系。
如果找不到匹配的PV卷,PVC会无限期地处于未绑定状态。当匹配的PV卷可用时,PVC会被绑定。例:即使某集群上供应了很多50 Gi大小的PV卷,也无法与请求100 Gi大小的存储的PVC匹配。当新的 100 Gi PV卷被加入到集群时,该PVC才有可能被绑定。
投射卷
投射卷(Projected Volumes),一个Projected Volumes可以将若干现有的卷源映射到同一个目录之上。所有的卷源都要求处于Pod所在的同一个名称空间内。目前以下类型的卷源可以被投射:
- secret
- downwardAPI
- configMap
- serviceAccountToken
- 带有Secret、DownwardAPI和ConfigMap的配置示例
apiVersion: v1
kind: Pod
metadata:
name: volume-test
spec:
containers:
- name: container-test
image: busybox:1.28
volumeMounts:
- name: all-in-one
mountPath: "/projected-volume"
readOnly: true
volumes:
- name: all-in-one
projected:
sources:
- secret:
name: mysecret
items:
- key: username
path: my-group/my-username
- downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "cpu_limit"
resourceFieldRef:
containerName: container-test
resource: limits.cpu
- configMap:
name: myconfigmap
items:
- key: config
path: my-group/my-config
- 带有非默认权限模式设置的Secret的配置示例
apiVersion: v1
kind: Pod
metadata:
name: volume-test
spec:
containers:
- name: container-test
image: busybox:1.28
volumeMounts:
- name: all-in-one
mountPath: "/projected-volume"
readOnly: true
volumes:
- name: all-in-one
projected:
sources:
- secret:
name: mysecret
items:
- key: username
path: my-group/my-username
- secret:
name: mysecret2
items:
- key: password
path: my-group/my-password
mode: 511
每个被投射的卷源都列举在规约中的sources下面。参数几乎相同,只有两个例外:
- 对于Secret,secretName字段被改为name以便于ConfigMap的命名一致;
- defaultMode只能在投射层级设置,不能在卷源层级设置,也可以显式地为每个投射单独设置mode属性。
临时卷
临时卷(Ephemeral Volume)区别于持久卷,有的应用程序需要额外存储,但并不关心数据在重启后是否存在。例如,缓存服务经常受限于内存大小,将不常用的数据转移到比内存慢、但对总体性能的影响很小的存储中。另外,有的应用程序需要以文件形式注入的只读数据,比如配置数据或密钥。
临时卷就是为上述类似用例设计的,遵从Pod的生命周期,与Pod一起创建和删除,停止和重新启动Pod时,不会受持久卷在何处可用的限制。临时卷在Pod规范中以内联方式定义,简化了应用程序的部署和管理。Kubernetes为不同的场景,支持几种不同类型的临时卷:
- emptyDir: Pod 启动时为空,存储空间来自本地的kubelet根目录(通常是根磁盘)或内存
- configMap、downwardAPI、secret: 将不同类型的Kubernetes数据注入到Pod中
- CSI临时卷: 类似于前面的卷类型,但由专门支持此特性的指定CSI驱动程序提供
- 通用临时卷: 它可以由所有支持持久卷的存储驱动程序提供
emptyDir、configMap、downwardAPI、secret是作为本地临时存储提供的,它们由各个节点上的kubelet管理。
CSI临时卷必须由第三方CSI存储驱动程序提供。通用临时卷可以由第三方CSI存储驱动程序提供,也可以由支持动态配置的任何其他存储驱动程序提供。一些专门为CSI临时卷编写的CSI驱动程序,不支持动态供应,因此这些驱动程序不能用于通用临时卷。使用第三方驱动程序的优势在于,它们可以提供Kubernetes本身不支持的功能,例如,与kubelet管理的磁盘具有不同运行特征的存储,或者用来注入不同的数据。
存储类
StorageClass资源为管理员提供了描述存储"类"的方法。不同的类型可能会映射到不同的服务质量等级或备份策略,或是由集群管理员制定的任意策略。Kubernetes本身并不清楚各种类代表的什么,这个类的概念在其他存储系统中有时被称为 "配置文件"。
每个StorageClass都包含provisioner、parameters和reclaimPolicy字段,这些字段会在StorageClass需要动态分配PV时会使用到。
StorageClass对象的命名很重要,用户使用这个命名来请求生成一个特定的类。当创建StorageClass对象时,管理员设置StorageClass对象的命名和其他参数,一旦创建了对象就不能再对其更新。
创建StorageClass示例:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
- debug
volumeBindingMode: Immediate
健康监测
CSI卷健康监测支持CSI驱动从底层存储系统着手,探测异常卷状态,并以事件形式上报到PVC或Pod。Kubernetes卷健康监测是Kubernetes容器存储接口(CSI)实现的一部分。卷健康监测特性由外部健康监测控制器kubelet两个组件实现。如果CSI驱动器通过控制器的方式支持卷健康监测特性,那么只要在CSI卷上监测到异常卷状态,就会在 PVC中上报一个事件。
外部健康监测控制器也会监测节点失效事件。如果要启动节点失效监测功能,可以设置标志enable-node-watcher为true。当外部健康监测器检测到节点失效事件,控制器会报送一个事件,该事件会在 PVC 上继续上报,以表明使用此PVC的Pod正位于一个失效的节点上。如果CSI驱动程序支持节点测的卷健康检测,那当在CSI卷上检测到异常卷时,会在使用该PVC的每个Pod上触发一个事件。
CSI
CSI卷克隆功能增加了通过在dataSource字段中指定存在的PVC,来表示用户想要克隆的卷。克隆(Clone),意思是为已有的Kubernetes卷创建副本,它可以像任何其它标准卷一样被使用。唯一的区别就是配置后,后端设备将创建指定完全相同的副本,而不是创建一个"新的"空卷。
从Kubernetes API的角度看,克隆的实现只是在创建新的PVC时,增加了指定一个现有PVC作为数据源的能力。源PVC必须是bound状态且可用的(不在使用中)。用户在使用该功能时,需要注意以下事项:
- 克隆支持(VolumePVCDataSource)仅适用于CSI驱动
- 克隆支持仅适用于动态供应器
- CSI驱动可能实现,也可能未实现卷克隆功能
- 仅当PVC与目标PVC存在于同一命名空间(源和目标PVC必须在相同的命名空间)时,才可以克隆PVC
- 仅在同一存储类中支持克隆。
- 目标卷必须和源卷具有相同的存储类
- 可以使用默认的存储类并且storageClassName字段在规格中忽略了
- 克隆只能在两个使用相同VolumeMode设置的卷中进行(如果请求克隆一个块存储模式的卷,源卷必须也是块存储模式)。
克隆卷与其他任何PVC一样配置,除了需要增加dataSource来引用同一命名空间中现有的PVC。创建示例:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: clone-of-pvc-1
namespace: myns
spec:
accessModes:
- ReadWriteOnce
storageClassName: cloning
resources:
requests:
storage: 5Gi
dataSource:
kind: PersistentVolumeClaim
name: pvc-1
说明:必须为spec.resources.requests.storage指定一个值,并且指定的值必须大于或等于源卷的值。
创建结果是一个名称为clone-of-pvc-1的新PVC与指定的源pvc-1拥有相同的内容。一旦新的PVC可用,被克隆的PVC像其他PVC一样被使用。可以预期的是,新创建的PVC是一个独立的对象。 可以独立使用、克隆、快照或删除它,而不需要考虑它的原始数据源PVC。这也意味着,源没有以任何方式链接到新创建的PVC,它也可以被修改或删除,而不会影响到新创建的克隆。
DownwardAPI
有两种方式可以将Pod和Container字段呈现给运行中的容器:
- 环境变量:用于单个变量,可以将Pod信息和容器信息直接注入容器内部
- 卷文件:将Pod信息生成为文件,直接挂载到容器内部中去
这两种呈现Pod和Container字段的方式都称为Downward API。
更多内容查看:Downward API
ConfigMap
ConfigMap是一种配置类资源,用来将非机密性的数据保存到键值对中。Pods可以将其用作环境变量、命令行参数或者存储卷中的配置文件。ConfigMap主要作用把环境配置信息和容器镜像解耦,便于应用配置的修改。在设计上ConfigMap不是用来保存大量数据的,其保存的数据不可超过1M。如果需要保存超出此尺寸限制的数据,可以考虑挂载存储卷或者使用独立的数据库或者文件服务。
说明:ConfigMap并不提供保密或加密功能。如果存储的数据是机密的,使用Secret或者使用第三方工具来保证数据的私密性。
ConfigMap也可以看作一个API对象,可以存储其他对象所需要使用的配置。和其他Kubernetes对象都有一个spec不同的是,ConfigMap使用data和binaryData字段,能够接收"键-值"对作为其取值。data和binaryData字段都是可选的。data字段设计用来保存UTF8字符串,binaryData则被设计用来保存二进制数据作为base64编码的字串。
ConfigMap 的名字必须是一个合法的DNS子域名。data或binaryData字段下面的每个键的名称都必须由字母数字字符或者-、_或.组成。在data下保存的键名不可以与在binaryData下出现的键名有重叠。
如何使用
ConfigMap可以作为数据卷挂载,也可被系统的其他组件使用, 而不一定直接暴露给Pod。最常见的用法是为同一命名空间里某Pod中运行的容器执行配置,也可单独使用,比如基于ConfigMap调整其行为插件或Operator。在一个Pod的存储卷中使用ConfigMap,一般流程如下:
- 创建一个ConfigMap对象或者使用现有的ConfigMap对象,多个Pod可以引用同一个ConfigMap
- 修改Pod定义,在spec.volumes[]下添加一个卷,为该卷设置任意名称,将spec.volumes[].configMap.name字段设置为对ConfigMap对象的引用
- 为每个需要该ConfigMap的容器添加一个.spec.containers[].volumeMounts[]字段,设置字段的值如下:
- 将.spec.containers[].volumeMounts[].readOnly值设置为true
- 将.spec.containers[].volumeMounts[].mountPath设置为一个未使用的目录名,ConfigMap的内容将出现在该目录中。
- 更改镜像或者命令行,以便程序能够从该目录中查找文件,ConfigMap中的每个data键会变成mountPath下面的一个文件名。
下面是一个将ConfigMap以卷的形式进行挂载的Pod示例:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
configMap:
name: myconfigmap
说明:如果Pod中有多个容器,每个容器都需要自己的volumeMounts块,每个ConfigMap,只需设置一个spec.volumes块,被挂载的ConfigMap内容会被自动更新。当卷中使用的ConfigMap被更新时,所投射的键最终也会被更新。kubelet组件会在每次周期性同步时检查所挂载的ConfigMap是否为最新。kubelet使用的是本地的高速缓存来获得ConfigMap当前值,缓存类型通过KubeletConfiguration结构ConfigMapAndSecretChangeDetectionStrategy字段来配置。
ConfigMap既可通过watch实现内容传播(默认形式),也可实现基于TTL的缓存,还可直接经过所有请求重定向到apiserver。从ConfigMap被更新的那一刻算起,到新的主键被投射到Pod中去,这一时间跨度可能与kubelet的同步周期加上高速缓存的传播延迟相等。这里的传播延迟取决于所选的高速缓存类型 (分别对应watch操作的传播延迟、高速缓存的TTL时长或者0)。以环境变量方式使用的ConfigMap数据不会被自动更新。更新这些数据需要重新启动 Pod。使用ConfigMap作为subPath卷挂载的容器将不会收到ConfigMap的更新。
某些情况下,可以写一个引用ConfigMap的Pod的spec,并根据ConfigMap中的数据在该Pod中配置容器,这个Pod和ConfigMap必须要在同一个名称空间中。静态Pod中的spec字段不能引用ConfigMap或任何其他API对象。
这是一个ConfigMap的示例,它的一些键只有一个值,其他键的值看起来像是配置的片段格式。
apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
# 类属性键;每一个键都映射到一个简单的值
player_initial_lives: "3"
ui_properties_file_name: "user-interface.properties"
# 类文件键
game.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
可以使用四种方式来使用ConfigMap配置Pod中的容器:
- 在容器命令和参数内
- 容器的环境变量
- 在只读卷里面添加一个文件,让应用来读取
- 编写代码在Pod中运行,使用Kubernetes API来读取ConfigMap
不同的方法适用于不同的数据使用方式。前三种方法,kubelet使用ConfigMap中的数据在Pod中启动容器。第四种方法必须编写代码才能读取ConfigMap数据,由于直接使用Kubernetes API,只要ConfigMap发生更改, 应用就能够通过订阅来获取更新,并且在这样的情况发生的时候做出反应。通过直接进入Kubernetes API,也可以获取到不同的名称空间里的ConfigMap。下面是一个Pod示例,通过使用game-demo中的值来配置一个Pod:
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-pod
spec:
containers:
- name: demo
image: alpine
command: ["sleep", "3600"]
env:
# 定义环境变量
- name: PLAYER_INITIAL_LIVES # 请注意这里和 ConfigMap 中的键名是不一样的
valueFrom:
configMapKeyRef:
name: game-demo # 这个值来自 ConfigMap
key: player_initial_lives # 需要取值的键
- name: UI_PROPERTIES_FILE_NAME
valueFrom:
configMapKeyRef:
name: game-demo
key: ui_properties_file_name
volumeMounts:
- name: config
mountPath: "/config"
readOnly: true
volumes:
# 你可以在 Pod 级别设置卷,然后将其挂载到 Pod 内的容器中
- name: config
configMap:
# 提供你想要挂载的 ConfigMap 的名字
name: game-demo
# 来自 ConfigMap 的一组键,将被创建为文件
items:
- key: "game.properties"
path: "game.properties"
- key: "user-interface.properties"
path: "user-interface.properties"
ConfigMap不会区分单行属性值和多行类似文件的值,重要的是Pods和其他对象如何使用这些值。示例定义了一个卷并将它作为/config文件夹挂载到demo容器内,创建两个文件,/config/game.properties和 /config/user-interface.properties,尽管ConfigMap中包含了四个键。这是因为Pod定义中在volumes节指定了一个items数组。如果完全忽略items数组,则ConfigMap中的每个键都会变成一个与该键同名的文件,会得到四个文件。
不可变更选项
从v1.19开始,可以添加一个immutable字段到ConfigMap定义中,创建不可变更的ConfigMap。Kubernetes特性 Immutable Secret和ConfigMaps提供了一种将各个Secret和ConfigMap设置为不可变更的选项。对于大量使用 ConfigMap的集群 (至少有数万个各不相同的ConfigMap给Pod挂载)而言,禁止更改ConfigMap的数据有以下好处:
- 保护应用,使之免受意外(不想要的)更新所带来的负面影响。
- 大幅降低对kube-apiserver的压力提升集群性能,系统会关闭对已标记为不可变更的ConfigMap的监视操作。
此功能特性由ImmutableEphemeralVolumes控制,将immutable字段设置为true创建不可变更的 ConfigMap。 例如:
apiVersion: v1
kind: ConfigMap
metadata:
...
data:
...
immutable: true
一旦某ConfigMap被标记为不可变更,则无法逆转这一变化,也无法更改data或binaryData字段的内容,只能删除并重建ConfigMap,现有的Pod会维护一个已被删除的ConfigMap的挂载点,建议重新创建这些Pods。
Secret
Secret是一种包含少量敏感信息例如密码、令牌或密钥的对象,这些信息可能会放在Pod规约中或镜像中,这样不需要在应用程序代码中包含机密数据。Secret类似于ConfigMap但专门用于保存机密数据。由于创建Secret可以独立于使用它的Pod,因此在创建、查看和编辑Pod的工作流程中暴露Secret的风险较小。在集群中运行的应用程序也可以对Secret采取额外的预防措施,例如避免将机密数据写入非易失性存储。
说明:默认情况下Secret未加密地存储在apiserver的底层数据存储etcd中,任何拥有API或etcd访问权限的人都可以检索或修改Secret。此外,任何有权限在命名空间中创建Pod的人都可以使用该访问权限读取该命名空间中的任何Secret,包括间接访问。例:创建Deployment的能力,为了安全地使用Secret,需至少执行以下步骤:
- 为Secret启用静态加密
- 启用或配置RBAC规则来限制读取和写入Secret的数据,被准许创建Pod的人也隐式地被授权获取Secret内容
- 在适当的情况下,也可以使用RBAC等机制来限制允许哪些主体创建新Secret或替换现有Secret
类型
创建Secret时,可以使用Secret资源的type字段,或者与其等价的kubectl命令行参数为其设置类型,类型有助于对Secret数据进行编程处理。Kubernetes提供若干内置类型,用于一些常见的使用场景,不同类型Kubernetes执行的合法性检查操作以及对其所实施的限制各不相同。
内置类型 | 用法 |
---|---|
Opaque | 用户定义的任意数据 |
kubernetes.io/service-account-token | 服务账号令牌 |
kubernetes.io/dockercfg | ~/.dockercfg 文件的序列化形式 |
kubernetes.io/dockerconfigjson | ~/.docker/config.json 文件的序列化形式 |
kubernetes.io/basic-auth | 用于基本身份认证的凭据 |
kubernetes.io/ssh-auth | 用于 SSH 身份认证的凭据 |
kubernetes.io/tls | 用于 TLS 客户端或者服务器端的数据 |
bootstrap.kubernetes.io/token | 启动引导令牌数据 |
通过为Secret对象的type字段设置一个非空的字符串值,也可以定义并使用自己的Secret类型。如果type值为空字符串,则被视为Opaque类型。Kubernetes并不对类型的名称作任何限制。如果使用内置类型, 必须满足为该类型定义的所有要求。
如果要定义一种公开使用的Secret类型,需要遵守Secret类型的约定和结构,在类型名签名添加域名,并用 / 隔开。 例如:cloud-hosting.example.net/cloud-api-credentials。
更多内容参考:https://kubernetes.io/zh/docs/concepts/configuration/secret/#secret-types
创建
Secret的创建可以使用以下几种形式:
- 使用kubectl命令来创建
- 基于配置文件来创建
- 使用kustomize来创建
下面主要介绍前两种创建Secret的方式,第三种可以参考官方文档。
- 基于Kubectl创建
一个Secret可以包含Pod访问数据库所需的用户凭证。例如由用户名和密码组成的数据库连接字符串,可以在本地计算机上,将用户名存储在文件 ./username.txt 中,将密码存储在文件 ./password.txt 中。
echo -n 'admin' > ./username.txt
echo -n '1f2d1e2e67df' > ./password.txt
命令中,-n标志确保生成的文件在文本末尾不包含额外的换行符。当kubectl读取文件并将内容编码为base64字符串时,多余的换行符也会被编码。
使用命令将这些文件打包成一个Secret并在apiserver上创建对象。
# 执行命令
kubectl create secret generic db-user-pass \
--from-file=./username.txt \
--from-file=./password.txt
# 输出
secret/db-user-pass created
默认密钥名称是文件名,可以选择使用--from-file=[key=]source设置密钥名称
kubectl create secret generic db-user-pass \
--from-file=username=./username.txt \
--from-file=password=./password.txt
不需要对文件中包含的密码字符串中的特殊字符进行转义。使用--from-literal= = 标签提供Secret数据,可以多次使用此标签,提供多个键值对。特殊字符(例如: \(,\,*,= 和 !)由所使用shell解释执行,而且需要转义。 大多数shell中,转义密码最简便的方法是用单引号括起来。如果密码是S!B\*d\)zDsb=,可以如下执行命令:
kubectl create secret generic db-user-pass \
--from-literal=username=devuser \
--from-literal=password='S!B\*d$zDsb='
查看secret是否已创建:
kubectl get secrets
# 输出
NAME TYPE DATA AGE
db-user-pass Opaque 2 51s
查看Secret的描述:
kubectl describe secrets/db-user-pass
# 输出
Name: db-user-pass
Namespace: default
Labels:
Annotations:
Type: Opaque
Data
====
password: 12 bytes
username: 5 bytes
kubectl get和kubectl describe命令默认不显示Secret的内容,防止Secret被意外暴露或存储在终端日志中。要查看创建的Secret的内容,运行以下命令:
kubectl get secret db-user-pass -o jsonpath='{.data}'
# 输出结果
{"password":"MWYyZDFlMmU2N2Rm","username":"YWRtaW4="}
使用base64解码password的数据:
echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
# 输出结果
1f2d1e2e67df
删除创建的Secret:
kubectl delete secret db-user-pass
- 基于配置文件创建
使用配置文件,可以先用JSON或YAML格式在文件中创建Secret,然后创建该对象。Secret资源包含2个键值对,data和stringData。data字段用来存储base64编码的任意数据,提供stringData字段是为了方便,它允许Secret使用未编码的字符串。data和stringData的键必须由字母、数字、-,_ 或 . 组成。例如,要使用Secret的data字段存储两个字符串,需先将字符串转换为base64 ,如下所示:
echo -n 'admin' | base64
YWRtaW4=
echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm
编写一个Secret配置文件,如下所示:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
说明:Secret对象的名称必须是有效的DNS子域名。Secret数据的JSON和YAML序列化结果是以base64编码的。换行符在这些字符串中无效,必须省略。在Darwin/macOS上使用base64工具时,用户不应该使用-b选项分割长行。Linux用户应该在base64地命令中添加-w 0选项,或者在-w选项不可用的情况下,输入base64 | tr -d '\n'。
对于某些场景,可能希望使用stringData字段,可以将一个非base64编码的字符串直接放入Secret中,当创建或更新该Secret时,此字段将被编码。
上述用例的实际场景可能为:当部署应用时,使用Secret存储配置文件,希望在部署过程中,填入部分内容到该配置文件。例如,应用程序使用以下配置文件:
apiUrl: "https://my.api.com/api/v1"
username: ""
password: ""
可以使用以下定义将其存储在Secret中:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
stringData:
config.yaml: |
apiUrl: "https://my.api.com/api/v1"
username:
password:
然后使用kubectl apply命令创建Secret:
kubectl apply -f ./secret.yaml
# 输出结果
secret/mysecret created
stringData字段是只写的,获取Secret时,此字段永远不会输出。 例如,运行以下命令:
kubectl get secret mysecret -o yaml
# 输出结果 yaml格式
apiVersion: v1
data:
config.yaml: YXBpVXJsOiAiaHR0cHM6Ly9teS5hcGkuY29tL2FwaS92MSIKdXNlcm5hbWU6IHt7dXNlcm5hbWV9fQpwYXNzd29yZDoge3twYXNzd29yZH19
kind: Secret
metadata:
creationTimestamp: 2018-11-15T20:40:59Z
name: mysecret
namespace: default
resourceVersion: "7225"
uid: c280ad2e-e916-11e8-98f2-025000000001
type: Opaque
命令kubectl get和kubectl describe默认不显示Secret的内容。防止Secret意外地暴露给旁观者或者保存在终端日志中。如果在data和stringData中都指定了一个字段,例如username,字段值为stringData,如下Secret定义:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
stringData:
username: administrator
输出结果生成以下Secret:
apiVersion: v1
data:
username: YWRtaW5pc3RyYXRvcg== # 其中 YWRtaW5pc3RyYXRvcg== 解码成administrator。
kind: Secret
metadata:
creationTimestamp: 2018-11-15T20:46:46Z
name: mysecret
namespace: default
resourceVersion: "7579"
uid: 91460ecb-e917-11e8-98f2-025000000001
type: Opaque
使用
Secret可以以数据卷的形式挂载,也可以作为环境变量暴露给Pod中的容器使用。Secret也可用于系统中的其他部分,而不是一定要直接暴露给Pod,比如包含系统中其他部分在与外部系统交互时需要的凭证数据。Kubernetes控制平面也使用Secret,例如引导令牌Secret是一种帮助自动化节点注册的机制:
- 在Pod中以文件形式使用
- 以环境变量形式使用
- 在静态Pod使用
- 容器镜像拉取使用
说明:Kubernetes会检查Secret的卷数据源,确保所指定的对象引用确实指向类型为Secret的对象。如果Pod依赖于某Secret,该Secret必须先于Pod被创建。
如果Secret内容无法取回(可能因为Secret尚不存在或者临时性地出现apiserver网络连接问题),kubelet会周期性地重试Pod运行操作。kubelet也会为该Pod报告Event事件,给出读取Secret时遇到的问题细节。
详细内容:https://kubernetes.io/zh/docs/concepts/configuration/secret/#working-with-secrets
元数据型资源
HPA
HPA全称HorizontalPodAutoscaler,Pod水平自动扩缩,可以根据CPU利用率自动扩缩RC、Deployment、RS或StatefulSet中的Pod数量,目的是自动扩缩工作负载以满足需求。
水平扩缩意味着对增加的负载的响应是部署更多的Pod。 与"垂直(Vertical)"扩缩不同,对于Kubernetes,垂直扩缩意味着将更多资源(例如:内存或 CPU)分配给已经为工作负载运行的Pod。如果负载减少,并且Pod的数量高于配置的最小值,HPA会指示工作负载资源(Deployment、StatefulSet或其他类似资源)缩减。水平Pod自动扩缩不适用于无法扩缩的对象(例如:DaemonSet)
- 如何工作
HorizontalPodAutoscaler被实现为Kubernetes API资源和控制器。HPA的工作示意图如下所示:
HPA控制Deployment及其RS的规模,Kubernetes将水平Pod自动扩缩实现为一个间歇运行的控制回路,它不是一个连续的过程。间隔由kube-controller-manager的--horizontal-pod-autoscaler-sync-period参数设置(默认间隔为15秒)。
每个时间段内,控制器管理器根据每个HPA定义指定的指标查询资源利用率。控制器管理器找到由scaleTargetRef定义的目标资源,然后根据目标资源的.spec.selector标签选择Pod,并从资源指标API(针对每个Pod的资源指标)或自定义指标获取指标API(适用于所有其他指标)。
- 对于按Pod统计的资源指标,如CPU,控制器从资源指标API中获取每个HPA指定的Pod的度量值,如果设置了目标使用率,控制器获取每个Pod中的容器资源使用情况,并计算资源使用率。如果设置了target值,将直接使用原始数据,不再计算百分比。控制器根据平均的资源使用率或原始值计算出扩缩的比例,进而计算出目标副本数。如果Pod某些容器不支持资源采集,那么控制器将不会使用该Pod的CPU使用率。
- 如果Pod使用自定义指示,控制器机制与资源指标类似,区别在于自定义指标只使用原始值,而不是使用率。
- 如果Pod使用对象指标和外部指标,每个指标描述一个对象信息,这个指标将直接根据目标设定值相比较,生成一个扩缩比例。在autoscaling/v2beta2版本API中,这个指标也可以根据Pod数量平分后计算。
HPA的常见用途是将其配置为从聚合API(metrics.k8s.io、custom.metrics.k8s.io 或 external.metrics.k8s.io)获取指标。metrics.k8s.io API通常由名为Metrics Server的插件提供,需要单独启动。
- 实现细节
从最基本的角度来看,Pod水平自动扩缩控制器根据当前指标和期望指标来计算扩缩比例。
说明:期望副本数 = ceil[当前副本数 * (当前指标 / 期望指标)]
例如,如果当前指标值为200m,而期望值为100m,则副本数将加倍, 因为200.0 / 100.0 == 2.0如果当前值为 50m,则副本数将减半,因为50.0 / 100.0 == 0.5。如果比率足够接近1.0(在全局可配置的容差范围内,默认为 0.1), 则控制平面会跳过扩缩操作。
在检查容差并决定最终值之前,控制平面还会考虑是否缺少任何指标,以及有多少Pod已就绪。所有设置了删除时间戳的Pod(带有删除时间戳的对象正在关闭/移除的过程中)都会被忽略,所有失败的Pod都会被丢弃。
如果某个Pod缺失度量值,将会被搁置,只在最终确定扩缩数量时再考虑。如果HPA指定的是targetAverageValue或targetAverageUtilization,那么它会将指定Pod度量值的平均值当做currentMetricValue。当使用CPU指标来扩缩时,任何还未就绪(还在初始化,或者可能是不健康的)状态的Pod或最近的指标度量值采集于就绪状态前的Pod,该Pod也会被搁置。在排除掉被搁置的Pod后,扩缩比例就会根据currentMetricValue/desiredMetricValue计算出来。
由于技术限制,HPA控制器在确定是否保留某些CPU指标时无法准确确定Pod首次就绪的时间(默认值为30秒,该值使用--horizontal-pod-autoscaler-initial-readiness-delay标志配置),如果Pod未准备好并在其启动后的一个可配置的短时间窗口内转换为未准备好,它会认为Pod尚未准备好。 一旦Pod准备就绪,如果发生在自启动后较长的可配置的时间内(默认5分钟,该值由-horizontal-pod-autoscaler-cpu-initialization-period标志配置),它就会认为任何向准备就绪的转换都是第一个。
如果缺失某些度量值,控制平面会更保守地重新计算平均值,在需要缩小时假设这些Pod消耗目标值的100%, 需要放大时假设这些Pod消耗了0%目标值,这可以在一定程度上抑制扩缩的幅度。此外,如果存在任何尚未就绪的Pod,工作负载会在不考虑遗漏指标或尚未就绪的Pod的情况下进行扩缩, 控制器保守地假设尚未就绪的Pod消耗了期望指标的0%,从而进一步降低了扩缩的幅度。
考虑到尚未准备好的Pod和缺失的指标后,控制器会重新计算使用率。如果新的比率与扩缩方向相反,或者在容差范围内,则控制器不会执行任何扩缩操作。在其他情况下,新比率用于决定对Pod数量的任何更改。平均利用率的原始值是通过HPA状态体现的,而不考虑尚未准备好的Pod或缺少的指标,即使使用新的使用率也是如此。
如果创建HPA时指定了多个指标,那么会按照每个指标分别计算扩缩副本数,取最大值进行扩缩。如果任何一个指标无法顺利地计算出扩缩副本数(比如,通过API获取指标时出错),并且可获取的指标建议缩容,那么本次扩缩会被跳过。如果一个或多个指标给出的desiredReplicas值大于当前值,HPA仍然能实现扩容。
在HPA控制器执行扩缩操作之前,会记录扩缩建议信息。控制器会在操作时间窗口中考虑所有的建议信息,并从中选择得分最高的建议。这个值可通过kube-controller-manager服务的启动参数--horizontal-pod-autoscaler-downscale-stabilization进行配置,默认值为5分钟。这个配置可以让系统更为平滑地进行缩容操作,从而消除短时间内指标值快速波动产生的影响。
更多内容参考:
- Pod水平自动扩缩
- Horizontal Pod Autoscaler 演练
如下为HPA的官方使用示例:
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
# HPA的伸缩对象描述,HPA会动态修改该对象的pod数量
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
# HPA的最小pod数量和最大pod数量
minReplicas: 1
maxReplicas: 10
# 监控的指标数组,支持多种类型的指标共存
metrics:
# Object类型的指标
- type: Object
object:
metric:
# 指标名称
name: requests-per-second
# 监控指标的对象描述,指标数据来源于该对象
describedObject:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
name: main-route
# Value类型的目标值,Object类型的指标只支持Value和AverageValue类型的目标值
target:
type: Value
value: 10k
# Resource类型的指标
- type: Resource
resource:
name: cpu
# Utilization类型的目标值,Resource类型的指标只支持Utilization和AverageValue类型的目标值
target:
type: Utilization
averageUtilization: 50
# Pods类型的指标
- type: Pods
pods:
metric:
name: packets-per-second
# AverageValue类型的目标值,Pods指标类型下只支持AverageValue类型的目标值
target:
type: AverageValue
averageValue: 1k
# External类型的指标
- type: External
external:
metric:
name: queue_messages_ready
# 该字段与第三方的指标标签相关联,(此处官方文档有问题,正确的写法如下)
selector:
matchLabels:
env: "stage"
app: "myapp"
# External指标类型下只支持Value和AverageValue类型的目标值
target:
type: AverageValue
averageValue: 30
PodTemplate
负载资源的控制器通常使用Pod模板(Pod Template) 来创建Pod并管理它们。Pod模板是包含在工作负载对象中的规范,用来创建Pod。这类负载资源包括Deployment、Job和DaemonSets等。工作负载的控制器会使用负载对象中的PodTemplate来生成实际的Pod。PodTemplate是用来运行应用时指定的负载资源的目标状态的一部分。
下面的示例是一个简单的Job的清单,其中的template指示启动一个容器。该Pod中的容器会打印一条消息之后暂停。
apiVersion: batch/v1
kind: Job
metadata:
name: hello
spec:
template:
# 这里是 Pod 模版
spec:
containers:
- name: hello
image: busybox
command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
restartPolicy: OnFailure
# 以上为 Pod 模版
修改Pod模版或者切换到新的Pod模版都不会对已经存在的Pod起作用。Pod不会直接收到模版的更新。相反,新的Pod会被创建出来,与更改后的Pod模版匹配。
例如,Deployment控制器针对每个Deployment对象确保运行中的Pod与当前的Pod模版匹配。如果模版被更新,则Deployment必须删除现有的Pod,基于更新后的模版创建新的Pod。每个工作负载资源都实现了自己的规则,用来处理对Pod模版的更新。
在节点上,kubelet并不直接监测或管理与Pod模版相关的细节或模版的更新,这些细节都被抽象出来。这种抽象和关注点分离简化了整个系统的语义,并且使得用户可以在不改变现有代码的前提下就能扩展集群的行为。
LimitRange
LimitRange从字面意义上指的是对范围进行限制,实际上是对CPU和内存资源使用范围的限制。如果在一个设置有默LimitRange控制的名称空间创建容器,且该容器没有声明自己的内存限制时,会被指定默认内存限制。
下面给出一个限制范围对象的配置文件,该配置声明了默认的内存请求和默认的内存限制:
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
spec:
limits:
- default:
memory: 512Mi
defaultRequest:
memory: 256Mi
type: Container
选择一个名称空间(default-mem-example)创建限制范围:
kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults.yaml --namespace=default-mem-example
如果在default-mem-example名称空间创建容器,并且该容器没有声明自己的内存请求和限制值,它将被指定默认的内存请求256M和默认的内存限制512M。
下面是具有一个容器的Pod的配置文件,容器未指定内存请求和限制。
apiVersion: v1
kind: Pod
metadata:
name: default-mem-demo
spec:
containers:
- name: default-mem-demo-ctr
image: nginx
创建Pod
kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults-pod.yaml --namespace=default-mem-example
查看Pod详情:输出内容显示该Pod的容器有256M的内存请求和512M的内存限制。这些都是LimitRange设置的默认值。
kubectl get pod default-mem-demo --output=yaml --namespace=default-mem-example
# 输出结果
containers:
- image: nginx
imagePullPolicy: Always
name: default-mem-demo-ctr
resources:
limits:
memory: 512Mi
requests:
memory: 256Mi
更多内容查看:LimitRange
参考资料:
- https://kuboard.cn
- https://kubernetes.io
- https://github.com/kubernetes
- https://kubernetes.io/zh/docs/home/