Pod 概述
如果说容器是未来云计算系统中的进程,容器镜像是这个系统里的安装包,那么Kubernetes就是云计算系统的操作系统。而Pod就是基于容器技术的操作系统里的"进程组",负责一个或多个容器的管理。
Pod 是Kubernetes中可以创建、调度和部署的最小也是最简单的单元。一个Pod里可以封装一个容器或多个容器(多个容器间可能存在拓扑关系–也即依赖关系),Pod里的容器共享存储、网络、容器运行配置等。同一个Pod里的所有容器都被统一安排和调度,所以可将Pod看成是对容器的编排。
每一个Pod都会被指派一个唯一的IP地址,在Pod中的每一个容器共享网络命名空间,包括IP地址和网络端口。在同一个Pod中的容器可以通过locahost进行互相通信。当Pod中的容器需要与Pod外的实体进行通信时,则需要通过端口等共享的网络资源进行通信。
在Pod中所有的容器能够访问共享存储卷,允许这些容器共享数据。
因为Pod生命周期是短暂的,一旦运行完成则立即回收,且涉及Pod的创建、自愈等操作比较复杂,所以很少在Kubernetes中直接使用Pod。而是使用更高级的称为Controller的抽象层,来管理Pod实例。所以从这个角度上来说,Controller可以看成是Pod运维的最佳实践。
静态Pod和普通Pod
根据Pod是否通过API Server方式创建,可以将Pod分为两类:静态Pod和普通Pod。具体定义如下:所有以非API Server方式创建的Pod都叫作静态Pod(Static Pod)。反之,通过API Server方式创建的Pod叫做普通Pod。注意,如无特殊说明,Pod均指普通Pod,如果是静态Pod,则会在使用时特别指出。
静态Pod是指在指定的节点上由kubelet守护进程直接管理,不需要API Server器监管。kubelet监视每个静态Pod(在它失败之后重新启动)。所以,静态Pod始终都会绑定到特定节点的Kubelet上。kubelet会尝试通过 API Server 为每个静态Pod自动创建一个镜像Pod(Mirror Pod)。这意味着节点上运行的静态Pod对API Server来说是可见的,但是不能通过API Server来控制。
常见的静态Pod创建方式有两种:
(1) 静态Pod配置文件:kubelet通过启动参数–config指定目录下的Pod YAML文件(默认目录为/etc/kubernetes/manifests/),kubelet会持续监控指定目录下的文件变化,以创建或删除Pod。这种类型的Pod没有通过kube-controller-manager管理。另外,可以 通过启动参数–file-check-frequency设置检查该目录的时间间隔,默认为20秒。
(2) HTTP端点(URL):通过–manifest-url参数设置,通过–http- check-frequency设置检查该HTTP端点数据的时间间隔,默认为 20 秒。
kubelet将静态 Pod的状态汇报给API Server,API Server为该静态 Pod创建一个镜像 Pod与其匹配。镜像 Pod的状态将真实反映静态 Pod的状态。当静态 Pod被删除时,与之相对应的镜像 Pod也会被删除。
普通Pod则是由API Server创建,且kubelet会定期向API Server汇报其状态。此外,kubelet也通过API Server的Watch接口监听Pod信息,如果监听到新的Pod副本被调度绑定到本节点,则执行Pod对应的容器创建和启动逻辑;如果监听到Pod对象被删除,则删除本节点上相应的Pod容器;如果监听到修改Pod的信息,kubelet就会相应地修改本节点的Pod容器。
Pod 组成原理
一个Pod里可以封装一个容器或多个容器,可将Pod看成是对容器的编排。对于多容器场景,为了简化容器间通信模型,并没有在容器间建立通信,而是定义了一个中间容器——Pause容器(也称Infra容器、根容器)。这样在Pod中的容器创建后,只需通过Join Network Namespace的方式与Pause容器关联在—起,就可以间接建立与Pod中其他容器的关联。所以,在Pod中Pause容器永远是第一个被创建出来的容器。
因为每个Pod都有Pause容器,所以Pause容器在设计时尽量占用极少的资源。Pause容器使用名为"k8s.gcr.io/pause"的镜像。这个镜像是一个用汇编语言编写、永远处于"暂停"状态的容器,解压后的大小也只有700KB左右。
因为Pod中Pause容器的存在,所以Pod里的其他容器可以通过Pause实现存储、网络、容器运行配置等资源的共享。每个Pod都分配了唯一的IP地址,称之为Pod IP,这个IP也是Paus容器的IP。Pod里的多个业务容器共享Pause容器的IP,共享Pause容器挂接的存储卷,这样既简化了密切关联的业务容器之间的通信问题,也很好地解决了它们之间的文件共享问题。
Pod组成示意图如下:
可以看到,上图中容器中的应用APP1和APP2分别通过Join Network Namespace的方式,实现本地通信(通过localhost)。同时,Pause容器挂载的其他资源,如存储卷等,也可以被Pod中的其他容器共享。
网络
Kubernetes网络模型设计的一个基础原则是:每个Pod都拥有一个独立的IP地址,并假定所有Pod都在一个可以直接连通的、扁平的网络空间中。所以不管这些Pod是否运行在同一个Node中,都要求它们可以直接通过对方的IP进行访问。基于这个原则设计的原因是,用户不需要额外考虑如何建立Pod之间的连接,也不需要考虑如何将容器端口映射到主机端口等问题。
在Kubernetes中IP是以Pod为单位进行分配的。一个Pod内部的所有容器共享一个网络堆栈(相当于一个网络命名空间,它们的IP地址、网络设备、配置等都是共享的)。按照这个网络原则抽象出来的为每个Pod都设置一个IP地址的模型也被称作IP-per-Pod模型。
IP-per-Pod模型是一个简单的网络模型。从该模型的网络的端口分配、域名解析、服务发现、负载均衡、应用配置和 迁移等角度来看,Pod都能够被看作一台独立的虚拟机或物理机。按照这个网络抽象原则,所有Pod都可以在不用NAT的方式下同别的Pod通信。
为每个Pod都设置一个IP地址的模型还有另外一层含义,那就是同一个Pod内的不同容器会共享同一个网络命名空间,也就是同一个Linux网络协议栈。这就意味着同一个Pod内的不同容器可以通过localhost连接对方的端口。由于Pod内的容器是共享部分资源的,所以可以通过共享资源相互通信,这显然更加容易和高效。但是也要注意这会损失了一定程度的隔离性,对需要网络隔离的应用还需特殊处理。
根据组件的类型,可将Kubernetes中的Pod网络通信分为如下几类:(1) 同一个Pod不同容器间的通信;(2) 同一个Node上不同Pod间的通信;(3) 同一个集群,同一个Namespace,不同Node上Pod间的通信;(4) 同一个集群,不同Namespace,不同Node上Pod间的通信;(5) 不同集群间的通信。由于这里重点关注Pod自身的情况,所以主要介绍下同一个Pod不同容器间的通信这一类别。其他类别可以参考Kubernetes网络模型概述一文。
同一个Pod不同容器间通信
在Kubernetes中IP是以Pod为单位进行分配的。一个Pod内部的所有容器共享一个网络命名空间。所谓的网络命名空间包括了网卡(network interface)、回环设备(loopback device)、路由表(routing table)和iptables规则。对于一个进程来说,这些要素构成了它发起和响应网络请求的基本环境。
由于一个Pod内的容器(在同一个Node上)共享一个网络命名空间,所以对于网络的各类操作,就如同在同一台主机上一样,比如可以用localhost访问彼此的端口。这么做的结果是简单、安全和高效,也能减少将已存在的程序从物理机或者虚拟机中移植到容器下运行的难度。示例如下:
从上图可知,APP1和APP2(以容器形式存在)因为在同一个Pod中,所以共享一个网络命名空间。因此可以直接用localhost访问彼此的端口,从而实现通信。这和传统的一组普通程序运行的环境是完全一样的,传统程序不需要针对网络做特别的修改就可以移植,它们之间的相互访问只需使用localhost就可以。
存储
容器内部存储的生命周期是短暂的,会随着容器环境的销毁而销毁,具有不稳定性。如果多个容器希望共享同一份存储,则仅仅依赖容器是很难实现的。所以在Kubernetes中,把所有的存储资源抽象成存储卷(Volume)并将其设计在Pod层级来解决这个问题。
存储卷是与Pod绑定的,且与Pod具有相同生命周期的资源对象。Pod通过Pause容器可以实现存储卷的共享。所以Pod的存储卷是被定义在Pod上,然后被各个容器挂载到自己的文件系统中的。同一个Pod中的多个容器能够共享Pod级别的存储卷。
可以将存储卷的内容理解为目录或文件,存储卷具体是什么类型以及由哪个系统提供,对容器应用来说是透明的。Kubernetes目前支持的存储卷类型包括:Kubernetes的内部资源对象类型(如ConfigMap、Secret等)、宿主机本地存储存储类型(如EmptyDir)、持久化存储类型(如)、网络共享存储类型(如CephFS)、存储厂商提供的存储卷类型(如DellEMC提供的ScaleIO Volumes)和公有云提供的存储卷类型(如Azure公有云提供的Disk–AzureDisk)等。简言之,存储卷可以被定义为各种类型,多个容器各自进行挂载操作,将一个存储卷挂载为容器内部需要的目录。使用示例如下:
apiVersion: v1
kind: Pod
metadata:
name: vol-test-Pod
namespace: test
labels:
name: vol-test-Pod
spec:
containers:
- name: test-Pod1
image: test-image
resources:
requests:
memory: 128Mi
cpu: 500m
limits:
memory: 200Mi
cpu: 1000m
ports:
- containerPort: 80
volumeMounts:
- name: vol-test-data
mountPath: /usr/share/test # 在容器中使用存储卷,指定容器的数据目录
volumes: # 在Pod中定义存储卷,这里volumes和container处于同一层级
- name: vol-test-data
hostPath:
path: /data/test-data # 挂载宿主机目录
如上述Pod配置文件所述,就将宿主机的/data/test-data目录挂载到了容器/usr/share/test目录。
Pod中使用Secret
Secret是一种包含少量敏感信息例如密码、令牌或密钥的资源对象。使用Secret意味着不需要在应用程序代码中包含机密数据。
由于创建Secret可以独立于使用它们的Pod,因此在创建、查看和编辑Pod的工作流程中暴露Secret(及其数据)的风险较小。此外,Kubernetes和在集群中运行的应用程序也可以对Secret采取额外的预防措施,例如避免将敏感数据写入非易失性存储。
但是,也要注意的是,默认情况下,Secret是以未加密的方式存储在API Server的底层数据存储(etcd)中。任何拥有API访问权限的人都可以检索或修改Secret,任何有权访问etcd的人也可以。此外,任何有权限在命名空间中创建Pod的人都可以使用该访问权限读取该命名空间中的任何Secret;这包括间接访问,如创建 Deployment 的能力。所以,为了安全的使用Secret,还需对Secret的管理进行额外的增强。
这里仅介绍Secret资源对象的简单使用,更多知识可以参考官网。
Secret 的类型
创建 Secret 时,你可以使用 Secret 资源的 type 字段,或者与其等价的 kubectl 命令行参数(如果有的话)为其设置类型。 Secret 类型有助于对 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类型时(type 值为空字符串),则被视为Opaque类型。Kubernetes也支持自定义Secret类型。 不过,如果要使用内置类型之一,则必须满足为该类型所定义的所有要求。
Secret创建及使用
在使用Secret前,先创建Secret。可以通过YAML文件或者直接使用kubectl create secret命令行的方式来创建Secret资源对象,这里重点介绍基于yaml文件创建Secret资源对象的的方式。定义名为secret.yaml文件(也可以命名自己感兴趣的名字),示例如下:
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
上述yaml声明了一个Secret资源对象,并定义了两个键值对。接下来就可运行kubectl create命令来创建该Secret。
$ kubectl create -f secret.yaml
secret/app-secret created
Secret对象创建好后,就可以使用资源查看命令,查看已声明内容。示例如下:
$ kubectl describe secret app-secret -o yaml
完成了Secret的创建,接下来就可在Pod中使用Secret。Secret的使用方式主要分为两种:存储卷挂载、环境变量注入。
(1) 存储卷挂载
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-Pod
spec:
containers:
- name: demo
image: alpine
volumeMounts:
- name: test-app-secret # 引用volume的名称
mountPath: "/config" # 挂载到容器内的目录
readOnly: true # 设置内容只读
volumes:
- name: test-app-secret # 定义创建volume的名称
secret:
secretName: app-secret # 引用需要挂载的secret的元数据名称
(2) 环境变量注入
这种方式比较简单,就是在Pod的yaml中将Secret中的配置数据通过ENV的方式注入到Pod中,供Pod直接使用。这种比较简单,示例如下:
apiVersion: v1
kind: Pod
metadata:
name: secret-demo-Pod
spec:
containers:
- name: demo
image: alpine
env:
# 定义环境变量
- name: KEY_1 # 定义环境变量的名称,注意可以这里和 Secret 中的键名是不一样的,需要遵循环境变量的命名规则
valueFrom:
secretKeyRef:
name: app-secret # 引用需要挂载的Secret的元数据名称
key: username # 根据key获取Secret指定的配置
Pod中使用ConfigMap
ConfigMap是一种存储键值对的资源对象。在Pod中使用ConfigMap时,可以将其作为环境变量、命令行参数或者存储卷中的配置文件。ConfigMap将配置和Pod解耦,更易于配置文件的更改和管理。注意,相比Secret用于存储重要数据,ConfigMap并不提供保密或者加密功能。
在使用ConfigMap前,先创建ConfigMap。可以通过YAML文件或者直接使用kubectl create configmap命令行的方式来创建ConfigMap资源对象,这里重点介绍基于yaml文件创建ConfigMap资源对象的的方式。定义名为configmap.yaml文件(可以命名自己感兴趣的名字),示例如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
key1: hello
key2: world
上述yaml声明了一个ConfigMap资源对象,并定义了两个键值对。接下来就可运行kubectl create命令来创建该ConfigMap。
$ kubectl create -f configmap.yaml
configmap "app-config" created
ConfigMap对象创建好后,就可以使用资源查看命令,查看已声明内容。示例如下:
$ kubectl describe configmap app-config -o yaml
在Pod中使用ConfigMap
完成了ConfigMap的创建,接下来就可在Pod中使用ConfigMap。ConfigMap的使用方式主要分为两种:存储卷挂载、环境变量注入。这两种方式都是通过Pod的yaml文件中配置ConfigMap引用而实现的。
(1) 存储卷挂载
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-Pod
spec:
containers:
- name: demo
image: alpine
volumeMounts:
- name: test-app-config # 引用volume的名称
mountPath: "/config" # 挂载到容器内的目录
readOnly: true # 设置内容只读
volumes:
- name: test-app-config # 定义创建volume的名称
configMap:
name: app-config # 引用需要挂载的configMap的元数据名称
items:
- key: key1 # 根据key获取configMap指定的配置
path: custom-key-1
(2) 环境变量注入
这种方式比较简单,就是在Pod的yaml中将ConfigMap中的配置数据通过ENV的方式注入到Pod中,供Pod直接使用。这种比较简单,示例如下:
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-Pod
spec:
containers:
- name: demo
image: alpine
env:
# 定义环境变量
- name: KEY_1 # 定义环境变量的名称,注意可以这里和 ConfigMap 中的键名是不一样的,需要遵循环境变量的命名规则
valueFrom:
configMapKeyRef:
name: app-config # 引用需要挂载的configMap的元数据名称
key: key1 # 根据key获取configMap指定的配置
Kubernetes从1.6版本开始引入了一个新的字段envFrom,实现了在Pod环境中将ConfigMap(也可用于Secret资源对象)中所有定义的key=value自动生成为环境变量。示例如下:
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-Pod
spec:
containers:
- name: demo
image: alpine
envFrom:
configMapKeyRef:
name: app-config # 引用需要挂载的configMap的元数据名称,会根据app-config中的key=value自动生成环境变量
需要说明的是,上面只是介绍如何在Pod中使用Configmap。而 ConfigMap也可被系统的其他组件使用,不一定只暴露给Pod。如ConfigMap可以保存系统中其他组件要使用的配置数据。
在使用ConfigMap资源对象时,要注意其使用限制条件,常见的有:
(1) ConfigMap必须在Pod之前创建,Pod才能引用它。
(2) 如果Pod使用envFrom基于ConfigMap定义环境变量,则无效的环境变量名称(例如名称以数字开头)将被忽略,并在事件中被记录为 InvalidVariableNames。
(3) ConfigMap受命名空间限制,只有处于相同命名空间中的Pod才可以引用它。
(4) ConfigMap无法用于静态Pod。
在容器内获取Pod信息 (DownwardAPI)
Kubernetes在成功创建Pod之后,会为Pod和容器设置一些额外的信息,如Pod级别的Pod名称、Pod IP、Node IP、Label、Annotation、容器级别的资源限制等。在很多应用场景中,这些信息对容器内的应用来说都很有用,如使用Pod名称作为日志记录的一个字段用于标识日志来源。为了在容器内获取Pod级别的这些信息,Kubernetes提供了Downward API机制来将Pod和容器的某些元数据信息注入容器环境内,供容器应用方便地使用。
Downward API可以通过以下两种方式将Pod和容器的元数据信息注入容器内部:
(1) 环境变量:将Pod或Container信息设置为容器内的环境变量。
(2) 存储卷挂载:将Pod或Container信息以文件的形式挂载到容器内部。
环境变量方式
通过环境变量的方式可以将Pod信息或Container信息注入容器运行环境中。示例如下:
apiVersion: v1
kind: Pod
metadata:
name: metadata-demo-Pod
spec:
containers:
- name: demo
image: alpine
resources:
requests:
memory: "32Mi"
cpu: "125m"
limits:
memory: "64Mi"
cpu: "250m"
env:
# 定义环境变量
- name: POD_NAME # 定义环境变量的名称
valueFrom:
fieldRef:
fieldPath: metadata.name # 引用需要挂载的Pod元数据名称
- name: MEM_REQUEST
valueFrom:
resourceFieldRef:
containerName: demo # 指定待获取值所在容器的名称
resource: requests.memory # 引用需要挂载的容器元数据名称
- name: MEM_LIMIT
valueFrom:
resourceFieldRef:
containerName: demo
resource: limits.memory
存储卷挂载
通过存储卷挂载的方式可以将Pod信息或Container信息挂载为容器内的文件。示例如下:
apiVersion: v1
kind: Pod
metadata:
name: metadata-demo-Pod
spec:
containers:
- name: demo
image: alpine
resources:
requests:
memory: "32Mi"
cpu: "125m"
limits:
memory: "64Mi"
cpu: "250m"
volumeMounts:
- name: Podinfo # 引用volume的名称
mountPath: /etc # 挂载到容器内的目录
readOnly: false # 设置内容只读
volumes:
- name: Podinfo # 定义创建volume的名称
downwardAPI: # 使用Downward API的方法引用Pod的元数据信息
items:
- path: "Pod-name"
fieldRef:
fieldPath: metadata.name
- path: "Pod-name"
fieldRef:
fieldPath: metadata.name # 引用需要挂载的Pod元数据名称
- path: "mem-request"
resourceFieldRef:
containerName: demo # 指定待获取值所在容器的名称
resource: requests.memory # 引用需要挂载的容器元数据名称
- path: MEM_LIMIT
resourceFieldRef:
containerName: demo
resource: limits.memory
Pod 生命周期
Pod的生命周期主要有以下四个状态:Pending(挂起)、 Running(运行中)、Succeeded(成功)、Failed(失败)。同时,为了标识Pod所在Node上的kubelet无法汇报给Kubernetes的API Server其状态,还额外引入Unknown(未知)。所以Pod的生命周期一共有五个状态。各状态的详细说明如下:
(1) Pending(挂起)。这个状态意味着Pod的YAML文件已经提交给了Kubernetes API,该Pod已经被创建并保存到etcd当中。但是,但有一个或者多个容器镜像尚未创建。有可能是等待创建,包括调度Pod的时间和通过网络下载镜像的时间,这可能需要花点时间。也有可能是有些容器因为某种原因不能被顺利创建,如"调度不成功"。如果某个Pod在期望的时间范围外仍处于该状态,则有必要进一步排除问题原因,而不是一直等待下去。
(2) Running(运行中)。这个状态下,Pod已经调度成功,跟—个具体的Node绑定。它包含的容器都已经创建成功,并且至少有一个容器正在运行,或者正处于启动或重启状态。
(3) Succeeded(成功)。这个状态意味着Pod里的所有容器都正常运行完毕,并且已经正常退出了(返回码是0)或者系统终止。这种情况在运行一次性的任务时最为常见。
(4) Failed(失败)。这个状态下,Pod中的所有容器都已经终止,并且至少有一个容器因为失败终止。也就是说,Pod里至少有一个容器以不正常的状态(非0的返回码)退出。出现这个状态意味着需要想办法调试这个容器的应用,比如查看Pod的Events或日志。
(5) Unknown(未知)。这是一个异常状态,意味着Pod的状态不能持续地被kubelet汇报给Kubernetes的API Server,这很有可能是主从节点(Master和kubelet)间的通信出现了问题。
Pod的生命周期示意图如下:
在Pod的API对象中,使用PodStatus对象(对应status字段)描述Pod的状态。其中phase字段用来表示Pod生命周期的当前阶段(Pending、Running、Succeeded、Failed、Unknown)。
进一步来说,Pod对象的Status字段还可以细分出一组Conditions。这些细分状态的值包括:PodScheduled、Ready、Initialized以及Unschedulable。它们主要用于描述造成当前Status的具体原因是什么。比如,Pod当前的Status是Pending,对应的Condition是Unschedulable,这就意味着它的调度出现了问题。示例如下:
apiVersion: v1
kind: Pod
...
status:
conditions:
- lastProbeTime: null
lastTransitionTime: "2024-01-22T17:30:37Z"
status: "True"
type: Initialized #conditions状态2
- lastProbeTime: null
lastTransitionTime: "2024-01-22T17:30:51Z"
status: "True"
type: Ready #conditions状态4
- lastProbeTime: null
lastTransitionTime: "2024-01-22T17:30:51Z"
status: "True"
type: ContainersReady #conditions状态3
- lastProbeTime: null
lastTransitionTime: "2024-01-22T17:30:37Z"
status: "True"
type: PodScheduled #conditions状态1
phase: Running # 标识Pod当前生命阶段
qosClass: BestEffort
startTime: "2024-01-22T17:30:37Z"
注意,Pod的状态是一个变化值,无需用户维护,用户无需在Pod的API对象中设置其值。
Pod的状态信息是我们判断应用运行状况的重要标准,尤其是在Pod进入非"Running"状态后,一定要能尽早做出反应,根据它所代表的异常情况开始跟踪和定位,而不是手忙脚乱地查阅文档。
在Pod的生命周期过程中,还可执行一些可选操作,具体有:使用Init Container、执行容器健康检查、使用Pod Hook等。各操作在Pod生命周期的时序如下图所示:
此外,针对多个Pod中的容器需要配置的相同信息,Kube还提供了Pod Preset来减少多个Pod上的相同配置。需要说明的是,尽管Preset可以减少相同信息在多个Pod的配置,但是后期的维护不好划分,应谨慎使用。
健康检查
Kubernetes对Pod的健康状态可以通过三类探针来检查:LivenessProbe(存活探针)、ReadinessProbe(就绪探针)及StartupProbe(启动探针)。探针是由 kubelet 对容器执行的定期诊断。每次探测都将获得以下三种结果之一:Success(成功,表示容器通过了诊断)、Failure(失败,容器未通过诊断)、Unknown(未知,诊断失败,不会采取任何行动)。
LivenessProbe指示容器是否正在运行。如果存活态探测失败,则kubelet会杀死容器,并且容器将根据其重启策略来进一步处理。如果容器不提供存活探针,则默认状态为Success。
ReadinessProbe指示容器是否准备好为请求提供服务(准备接收流量)。如果就绪态探测失败,Endpoint Controller将从与Pod匹配的所有服务的Endpoint列表中删除该Pod的IP地址。如果容器不提供就绪态探针,则默认状态为Success。
StartupProbe指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被禁用,直到此探针成功为止。如果启动探测失败,kubelet将杀死容器,而容器依其重启策略来进一步处理。如果容器没有提供启动探针,则默认状态为Success。
使用探针来检查容器目前有四种不同的方法。每个探针都必须准确定义为这四种机制中的一种:
(1) exec方式。在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
(2) grpc方式(v1.27稳定支持)。使用 gRPC 执行一个远程过程调用。目标应该实现gRPC健康检查。如果响应的状态是"SERVING",则认为诊断成功。
(3) httpGet方式。对容器的IP地址上指定端口和路径执行HTTP GET请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
(4) tcpSocket方式。对容器的IP地址上的指定端口执行TCP检查。如果端口打开,则诊断被认为是成功的。如果远程系统(容器)在打开连接后立即将其关闭,也将其定义为健康。
此外,为了自定义Pod的可用性探测方式,Kubernetes在1.14版本引入Pod Readiness Gates。Pod Readiness Gates给予了Pod之外的组件控制某个Pod就绪的能力,通过Pod Readiness Gates机制,用户可以设置自定义的Pod可用性探测方式来告诉Kubernetes某个Pod是否可用,具体使用方式是用户提供一个外部的控制器(Controller)来设置相应Pod的可用性状态。
存活探针的常见模式是为就绪探针使用相同的低成本 HTTP 端点,但具有更高的 failureThreshold。 这样可以确保在硬性终止 Pod 之前,将观察到 Pod 在一段时间内处于非就绪状态。
启用就绪态探针意味着Pod将在启动阶段不接收任何数据,并且只有在就绪探针探测成功后才开始接收数据。这可以帮助避免将流量导向只能返回错误信息的Pod。如应用在启动时可能需要加载大量的数据或配置文件,或是启动后要依赖等待外部服务。在这种情况下,既不想杀死应用,也不想给它发送请求。
对于所包含的容器需要较长时间才能启动就绪的Pod而言,启动探针是有用的。这样一来,就不再需要配置一个较长的存活态探测时间间隔,只需要设置另一个独立的配置,对启动期间的容器执行探测,从而允许使用远远超出存活态时间间隔所允许的时长。
这里以存活探针的httpGet方式为例进行简单的使用说明,更多的使用示例可以参考官网。示例代码如下:
apiVersion: v1
kind: Pod
metadata:
name: probe-demo-http
spec:
containers:
- name: liveness-demo
image: registry.k8s.io/liveness-demo
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30 # 延迟加载时间
periodSeconds: 3 # 重试时间间隔
timeoutSeconds: 10 # 探测超时限制,到了超时时间,如果探测还没返回结果说明失败
failureThreshold: 3 # 检测失败3次,就表示未就绪
successThreshold: 2 # 检查成功2次,就表示就绪
在上述配置中,Pod中只有一个容器。其中periodSeconds 字段指定了 kubelet 每隔 3 秒执行一次存活探测。 initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 3 秒。 kubelet 会向容器内运行的服务(服务在监听 8080 端口)发送一个 HTTP GET 请求来执行探测。 如果服务器上 /healthz 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。 如果处理程序返回失败代码,则 kubelet 会杀死这个容器并将其重启。对于HttpGet请求来说,返回大于或等于 200 并且小于 400 的任何代码都表示成功,其它返回代码都表示失败。
Init Container(初始化容器)
在很多应用场景中,需要在启动应用容器(app container)之前启动一个或多个初始化容器,完成应用容器所需的预置条件。Kubernetes 引入Init Container实现该功能。
Init Container与应用容器在本质上是一样的,但它们是仅运行一次就结束的任务,并且必须在成功运行完成后,系统才能继续执行下一个容器。Init Container中定义的容器,都会比spec.containers定义的应用容器先启动。如果为一个 Pod 指定了多个 Init Container,那些这个容器会按顺序逐一启动,而直至都启动并且退出了,应用容器才会启动。
如果Pod的Init Container失败,Kubernetes会不断地重启该Pod,直到Init Container成功为止。然而,如果Pod对应的restartPolicy为Never,它不会重新启动。
Init Container具有与应用容器分离的单独镜像,所以它们的启动相关代码具有如下优势:
(1) Init Container使用 Linux Namespace,所以相对应用容器来说具有不同的文件系统视图。因此,它们能够具有访问 Secret 的权限,而应用容器则不能。
(2) Init Container必须在应用容器启动之前运行完成,而应用容器是并行运行的,所以Init Container能够提供一种简单的阻塞或延迟应用容器的启动的方法,直到满足了一组先决条件。
Pod Hook
Pod Hook(钩子)是由kubelet发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。可以同时为 Pod 中的所有容器都配置 hook。
kubelet在容器创建后立即发送postStart事件。但是,不能保证在调用容器的入口点之前调用postStart处理程序。postStart处理程序相对于容器的代码异步运行,但kubelet对容器的管理将被阻止,直到postStart处理程序完成。在postStart处理程序完成之前,容器的状态不会设置为RUNNING。
preStop在容器终止之前被同步阻塞调用,常用于在容器结束前优雅的释放资源。
如果postStart或者preStop Hook执行失败,将会终止容器。
使用示例如下:
apiVersion: v1
kind: Pod
metadata:
name: hook-demo
spec:
containers:
- name: hook-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler> /usr/share/message"]
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
注意,Hook调用的日志没有暴露给Pod的event,所以只能通过 kubectl describe 命令来获取,如果有错误将可以看到FailedPostStartHook或FailedPreStopHook这样的event。
Pod Preset
Preset 就是预设,有时候想要让一批容器在启动的时候就注入一些信息,比如 secret、volume、volume mount 和环境变量,而又不想一个一个的改这些 Pod 的 template,这时候就可以用到 PodPreset 这个资源对象了。这里面涉及到一个权衡,尽管Preset可以减少相同信息在多个Pod的配置,但是后期的维护不好划分,应谨慎使用。
为使用Preset,Kubernetes提供了一个准入控制器(PodPreset),当其启用时,Pod Preset会将应用创建请求传入到该控制器上。当有 Pod 创建请求发生时,系统将执行以下操作:
(1) 检索所有可用的 PodPresets。
(2) 检查 PodPreset 标签选择器上的标签,看看其是否能够匹配正在创建的 Pod 上的标签。
(3) 尝试将由 PodPreset 定义的各种资源合并到正在创建的 Pod 中。
(4) 出现错误时,在该 Pod 上引发记录合并错误的事件,PodPreset 不会注入任何资源到创建的 Pod 中。
(5) 注释刚生成的修改过的 Pod spec,以表明它已被 PodPreset 修改过。
每个 Pod 可以匹配零个或多个 Pod Prestet;并且每个 PodPreset 可以应用于零个或多个 Pod。 PodPreset 应用于一个或多个 Pod 时,Kubernetes 会修改 Pod Spec。对于 Env、EnvFrom 和 VolumeMounts 的更改,Kubernetes 修改 Pod 中所有容器的容器 spec;对于 Volume 的更改,Kubernetes 修改 Pod Spec。
Pod控制器
在Kubernetes平台上,基本不需要直接创建一个Pod,在大多数情况下会通过RC(Replication Controller)、Deployment、StatefulSet、DaemonSet、Job、Cronjob等控制器完成对一组Pod副本的创建、调度及全生命周期的自动控制任务。
Deployment或RC
在最早的Kubernetes版本里只有一个Pod副本控制器RC,这个控制器是这样设计实现的:RC独立于所控制的Pod,并通过Label标签这个松耦合关联关系控制目标Pod实例的创建和销毁,随着Kubernetes的发展,RC也出现了新的继任者——Deployment,用于更加自动地完成Pod副本的部署、版本更新、回滚等功能。
严谨地说,RC的继任者其实并不是Deployment,而是ReplicaSet, 因为ReplicaSet进一步增强了RC标签选择器的灵活性。之前RC的标签选择器只能选择一个标签,而ReplicaSet拥有集合式的标签选择器,可以选择多个Pod标签。
DaemonSet:在每个Node上都调度一个Pod
DaemonSet 用于管理在集群中的每个Node上仅运行一份Pod的副本实例。这种用法适合有这种需求的应用:
(1) 在每个Node上都运行一个日志采集程序。
(2) 在每个Node上都运行一个性能监控程序,采集该Node的运行性能数据。
Job:批处理调度
Kubernetes Job资源对象可以用来定义并启动一个批处理任务。批处理任务通常并行(或者串行)启动多个计算进程去处理一批工作项(Work item),处理完成后,整个批处理任务结束。
Cronjob:定时任务
Kubernetes Cronjob 类似Linux Cron的定时任务Cron Job,用来定义需要每个一段时间执行的定时任务。
NodeSelector
Kubernetes Master上的Scheduler服务(kube-scheduler进程)负责实现Pod的调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod都计算出一个最佳的目标节点,这一过程是自动完成的,通常无法知道Pod最终会被调度到哪个节点上。在实际情况下,也可能需要将Pod调度到指定的一些Node上,可以通过Node的标签(Label)和Pod 的nodeSelector属性相匹配,来达到上述目的。
NodeAffinity:Node亲和性调度
NodeAffinity意为Node亲和性的调度策略,是用于替换NodeSelector的全新调度策略。
NodeAffinity规则设置的注意事项如下:
(1) 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能最终运行在指定的Node上。
(2) 如果nodeAffinity指定了多个nodeSelectorTerms,那么其中一个能匹配成功即可。
(3) 如果在nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该Pod。
PodAffinity:Pod亲和与互斥调度策略
在实际的生产环境中有一类特殊的Pod调度需求:存在某些相互依赖、频繁调用的Pod,它们需要被尽可能地部署在同一个Node节点、机 架、机房、网段或者区域(Zone)内,这就是Pod之间的亲和性;反之,出于避免竞争或者容错的需求,也可能使某些Pod尽可能地远离某些特定的Pod,这就是Pod之间的反亲和性或者互斥性。
Pod间的亲和性与反亲和性调度策略,是相关联的两种或多种Pod是否可以在同一个拓扑域中共存或者互斥,前者被称为Pod Affinity,后者被称为Pod Anti Affinity。
所谓拓扑域,是由一些Node节点组成,这些Node节点通常有相同的地理空间坐标,比如在同一个机架、机房或地区,一般用region表示机架、机房等的拓扑区域,用Zone表示地区这样跨度更大的拓扑区域。在极端情况下,也可以认为一个Node就是一个拓扑区域。
Taints和Tolerations (污点和容忍)
如果说NodeAffinity节点亲和性,是让Pod能够被调度到某些Node上运行(优先选择或强制要求)。Taint 则正好相反,它让Node拒绝Pod的运行。简单地说,被标记为Taint的节点就是存在问题的节点,比如磁盘要满、资源不足、存在安全隐患要进行升级维护,希望新的Pod不会被调度过来,但被标记为Taint的节点并非故障节点,仍是有效的工作节点,所以仍需将某些Pod调度到这些节点上时,可以通过使用Toleration属性来实现。
Pod Priority Preemption:Pod优先级调度
对于运行各种负载(如Service、Job)的中等规模或者大规模的集群来说,出于各种原因,需要尽可能提高集群的资源利用率。而提高资源利用率的常规做法是采用优先级方案,即不同类型的负载对应不同的优先级,同时允许集群中的所有负载所需的资源总量超过集群可提供的资源,在这种情况下,当发生资源不足的情况时,系统可以选择释放一些不重要的负载(优先级最低的),保障最重要的负载能够获取足够的资源稳定运行。
Kubernetes 1.8版本引入了基于Pod优先级抢占(Pod Priority Preemption)的调度策略,此时Kubernetes会尝试释放目标节点上低优先级的Pod,以腾出空间(资源)安置高优先级的 Pod,这种调度方式被称为“抢占式调度”。该功能在Kubernetes 1.14版本中正式发布。
优先级抢占调度策略的核心行为分别是驱逐(Eviction)与抢占 (Preemption),这两种行为的使用场景不同,效果相同。Eviction是 kubelet进程的行为,即当一个Node资源不足(under resource pressure) 时,该节点上的kubelet进程会执行驱逐动作,此时kubelet会综合考虑Pod的优先级、资源申请量与实际使用量等信息来计算哪些Pod需要被驱 逐;当同样优先级的Pod需要被驱逐时,实际使用的资源量超过申请量 最大倍数的高耗能Pod会被首先驱逐。对于QoS等级为“Best Effort”的Pod来说,由于没有定义资源申请(CPU/Memory Request),所以它们实际 使用的资源可能非常大。
Preemption则是Scheduler执行的行为,当一个新的Pod因为资源无法满足而不能被调度时,Scheduler可能(有权决定)选择驱逐部分低优先级的Pod实例来满足此Pod的调度目标,这就是Preemption机制。
高优先级Pod抢占节点并驱逐低优先级的Pod,这个问题对于普通的服务型的Pod来说问题不大,但对于执行批处理任务的Pod来说就可能是个灾难,当一个高优先级的批处理任务的Pod创建后,正在执行批处理任务的某个低优先级的Pod可能因为资源不足而被驱逐,从而导致对应的批处理任务被搁置。为了避免这个问题发生,PriorityClass增加了一 个新的属性——preemptionPolicy,当它的值为 preemptionLowerPriorty(默认)时,就执行抢占功能,当它的值被设置为Never时,就默认不抢占资源,而是静静地排队,等待自己的调度机会。
Horizontal Pod Autoscaler(HPA):自动扩缩容
Kubernetes提供Horizontal Pod Autoscaler(HPA)的控制器,用于实现基于CPU使用率进行自动Pod扩缩容的功能。HPA控制器周期性地监测目标Pod的资源性能指标,并与HPA资源对象中的扩缩容条件进行对比,在满足条件时对Pod副本数量进行调整。
自定义调度器
如果Kubernetes调度器的众多特性还无法满足调度需求,还可以用自己开发的调度器进行调度。从1.6版本开始, Kubernetes的多调度器特性也进入了快速发展阶段。
一般情况下,每个新Pod都会由默认的调度器进行调度。但是如果在Pod中提供了自定义的调度器名称,那么默认的调度器会忽略该Pod,转由指定的调度器完成Pod的调度。
参考
《Kubernetes权威指南 从Docker到Kubernetes实践全接触》 龚正 吴治辉 闫健勇 编著
《深入剖析Kubernetes》 张磊 著
https://lib.jimmysong.io/kubernetes-handbook/objects/Pod-overview/ Pod 概览
https://www.ianlewis.org/en/almighty-pause-container The Almighty Pause Container
https://www.cnblogs.com/guigujun/p/10556508.html kubernetes中的Pause容器如何理解
https://blog.csdn.net/u010026928/article/details/127283580 Kubernetes 进阶之容器组(Pod)
https://andblog.cn/3027 k8s学习:从 container 到 pod
https://lib.jimmysong.io/kubernetes-handbook/objects/pod-lifecycle/ Pod 的生命周期
https://www.cnblogs.com/linuxk/p/9569618.html Kubernetes学习之路(十一)之Pod状态和生命周期管理
https://cloud.tencent.com/developer/article/2242216 理清 K8s 中 Pod 的 phase 和 conditions
https://lib.jimmysong.io/kubernetes-handbook/objects/pod/ Pod 解析
https://kubernetes.io/blog/2015/06/the-distributed-system-toolkit-patterns/ The Distributed System ToolKit: Patterns for Composite Containers
https://developer.aliyun.com/article/1398059 k8s学习-ConfigMap
https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/ Secret
https://jimmysong.io/kubernetes-handbook/guide/secret-configuration.html Secret
https://lib.jimmysong.io/kubernetes-handbook/storage/secret/ Secret
https://www.linuxdevops.cn/2021/07/kubernetes-secret-concept-configuration kubernetes Secret 概念配置
https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/ Pod Lifecycle
https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ 配置存活、就绪和启动探针
https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/#container-probes 容器探针
https://imroc.cc/kubernetes/best-practices/configure-healthcheck/ 健康检查配置
https://developer.aliyun.com/article/1141637 Kubernetes的健康监测机制
https://zhuanlan.zhihu.com/p/610560146 kubernetes中配置gRPC服务健康检查
https://www.cnblogs.com/fengjian2016/p/17078670.html K8S Pod健康检查正确配置姿势
https://www.cnblogs.com/cheyunhua/p/15945284.html Kubernetes 探针/健康检查
https://lib.jimmysong.io/kubernetes-handbook/objects/init-containers/ Init 容器
https://lib.jimmysong.io/kubernetes-handbook/objects/pod-hook/ Pod Hook
https://blog.csdn.net/m0_45406092/article/details/118997434 Pod状态和生命周期管理