首页 > 技术文章 > Pod详解

zhangyupengzuishuai 2021-11-04 17:26 原文

Pod基础概念:

Pod是kubernetes中最小的资源管理组件,Pod也是最小化运行容器化应用的资源对象。一个Pod代表着集群中运行的一个进程。kubernetes中其他大多数组件都是围绕着Pod来进行支撑和扩展Pod功能的,例如,用于管理Pod运行的StatefulSet和Deployment等控制器对象,用于暴露Pod应用的Service和Ingress对象,为Pod提供存储的PersistentVolume存储资源对象等。

在Kubrenetes集群中Pod有如下两种使用方式:

一个Pod中运行一个容器

“每个Pod中一个容器”的模式是最常见的用法;在这种使用方式中,你可以把Pod想象成是单个容器的封装,kuberentes管理的是Pod而不是直接管理容器。

在一个Pod中同时运行多个容器

一个Pod中也可以同时封装几个需要紧密耦合互相协作的容器,它们之间共享资源。这些在同一个Pod中的容器可以互相协作成为一个service单位,比如一个容器共享文件,另一个“sidecar”容器来更新这些文件。Pod将这些容器的存储资源作为一个实体来管理。

 

一个Pod下的容器必须运行于同一节点上。现代容器技术建议一个容器只运行一个进程,该进程在容器中PID命令空间中的进程号为1,可直接接收并处理信号,进程终止时容器生命周期也就结束了。若想在容器内运行多个进程,需要有一个类似Linux操作系统init进程的管控类进程,以树状结构完成多进程的生命周期管理。运行于各自容器内的进程无法直接完成网络通信,这是由于容器间的隔离机制导致,k8s中的Pod资源抽象正是解决此类问题,Pod对象是一组容器的集合,这些容器共享Network、UTS及IPC命令空间,因此具有相同的域名、主机名和网络接口,并可通过IPC直接通信。

Pod资源中针对各容器提供网络命令空间等共享机制的是底层基础容器pause,基础容器(也可称为父容器)pause就是为了管理Pod容器间的共享操作,这个父容器需要能够准确地知道如何去创建共享运行环境的容器,还能管理这些容器的生命周期。为了实现这个父容器的构想,kubernetes中,用pause容器来作为一个Pod中所有容器的父容器。这个pause容器有两个核心的功能,一是它提供整个Pod的Linux命名空间的基础。二来启用PID命名空间,它在每个Pod中都作为PID为1进程(init进程),并回收僵尸进程。

pasue容器使得Pod中的所有容器可以共享两种资源:

网络:

每个Pod都会被分配一个唯一的IP地址。Pod中的所有容器共享网络空间,包括IP地址和端口。Pod内部的容器可以使用localhost互相通信。Pod中的容器与外界通信时,必须分配共享网络资源(例如使用宿主机的端口映射)。

存储:

可以Pod指定多个共享的Volume。Pod中的所有的容器都可以访问共享的Volume。Volume也可以用来持久化Pod中的存储资源,以防容器重启后文件丢失。

总结:

每个Pod都有一个特殊的被称为“基础容器”的Pause容器。Pause容器对应的镜像属于Kubernetes平台的一部分,除了Pause容器,每个Pod还包含一个或者多个紧密相关的用户应用容器。

kubernetes中的pause容器主要为每个容器提供以下功能:

  • 在pod中担任Linux命名空间(如网络命令空间)共享的基础
  • 启用PID命名空间,开启init进程

Kubernetes设计这样的Pod概念和特殊组成结构有什么用意?

  • 原因一:在一组容器作为一个单元的情况下,难以对整体的容器简单的进行判断及有效地进行行动。比如,一个容器死亡了,此时是算整体挂了吗?那么引入与业务无关的Pause容器作为Pod的基础容器,以他的状态代表着整个容器组的状态,这样就可以解决该问题。
  • 原因二:Pod里的 多个应用容器共享Pause容器的IP,共享Pause容器挂载的Volume,这样简化了应用容器之间的通信问题,也解决了容器之间的文件共享问题。

 通常把Pod分为两类:

自主式Pod:

这种Pod本身是不能自我修复的,当当Pod被创建后(不论是由你直接创建还是被其他Controller),都会被Kuberentes调度到集群的Node上。直到Pod的进程终止、被删掉、因为缺少资源而被驱逐、或者Node故障之前这个Pod都会一直保持在那个Node上。Pod不会自愈。如果Pod运行的Node故障,或者是调度器本身故障,这个Pod就会被删除。同样的,如果Pod所在Node缺少资源或者Pod处于维护状态,Pod也会被驱逐。

控制器管理的Pod

Kubernetes使用更高级的称为Controller的抽象层,来管理Pod实例。Controller可以创建和管理多个Pod,提供副本管理、滚动升级和集群级别的自愈能力。例如,如果一个Node故障,Controller就能自动将该节点上的Pod调度到其他健康的Node上。虽然可以直接使用Pod,但是在Kubernetes中通常是使用Controller来管理Pod的。

Pod容器的分类

1、基础容器

  • 维护整个Pod网络和存储空间
  • node节点中操作
  • 启动一个容器时,k8s会自动启动一个基础容器
  • 每次创建Pod时候就会创建,运行的每一个容器都有一个pause-amd64的基础容器自动会运行,对于用户是透明的

2、初始化容器(initcontainers)

Init容器必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以Init容器能够提供了一种简单的阻塞或延迟应用容器的启动的方法。

Init容器与普通的容器肥成像,除了一下两点:

  • Init容器总是运行到成功完成为止
  • 每个Init容器都必须在下一个Init容器启动之前成功完成启动和退出:如果Pod的Init容器失败,k8s会不断地重启该Pod,直到Init容器成功为止。然而,如果Pod对象的重启策略(restartPolicy)为Never,它不会重新启动

Init的容器的作用

因为init容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:

  • Init容器可以包含一些安装过程应用容器中不存在的实用工具或个性化代码。例如,没有必要仅为了在安装过程中使用类似sed、awk、python或dig这样的工具而去FROM一个镜像来生成一个新的镜像。
  • Init 容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。
  • 应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像。
  • Init 容器能以不同于Pod内应用容器的文件系统视图运行。因此,Init容器可具有访问 Secrets 的权限,而应用容器不能够访问。
  • 由于 Init 容器必须在应用容器启动之前运行完成,因此 Init 容器提供了一种机制来阻塞或延迟应用容器的启动,
    直到满足了一组先决条件。一旦前置条件满足,Pod内的所有的应用容器会并行启动。

3、应用容器(Maincontainer)

 ##并行启动

apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: busybox:1.28
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']

这个例子是定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice 启动, 第二个等待 mydb 启动。 一旦这两个 Init容器都启动完成,Pod 将启动 spec 中的应用容器。

特别说明:

  • 在Pod启动过程中,Init容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。
  • 如果由于运行时或失败退出,将导致容器启动失败,它会根据Pod的restartPolicy指定的策略进行重试。然而,如果Pod的restartPolicy设置为Always,Init容器失败时会使用RestartPolicy策略。
  • 在所有的Init容器没有成功之前,Pod将不会变成Ready状态。Init容器的端口将不会在Service中进行聚集。正在初始化中的Pod处于Pending状态,但应该会将Initializing状态设置为true。
  • 如果Pod重启,所有Init容器必须重新执行。
  • 对Init容器spec的修改被限制在容器image字段,修改其他字段都不会生效。更改Init容器的image字段,等价于重启该Pod。
  • Init容器具有应用容器的所有字段。除了readinessProbe,因为Init容器无法定义不同于完成(completion)的就绪(readiness)之外的其他状态。这会在验证过程中强制执行。
  • 在Pod中的每个app和Init容器的名称必须唯一;与任何其它容器共享同一个名称,会在验证时抛出错误。

镜像拉取策略(image PullPolicy):

Pod 的核心是运行容器,必须指定容器引擎,比如 Docker,启动容器时,需要拉取镜像,k8s 的镜像拉取策略可以由用户指定:

  • IfNotPresent:在镜像已经存在的情况下,kubelet 将不再去拉取镜像,仅当本地缺失时才从仓库中拉取,默认的镜像拉取策略
  • Always:每次创建 Pod 都会重新拉取一次镜像;
  • Never:Pod 不会主动拉取这个镜像,仅使用本地镜像。

注意:对于标签为“:latest”的镜像文件,其默认的镜像获取策略即为“Always”;而对于其他标签的镜像,其默认策略则为“IfNotPresent”。

 修改文件

 总结:

pod    运行方式: 自主式pod:没有自愈能力     控制器管理pod:有自愈能力(pod被删除后会重启拉起新的pod)

pod三种容器:

  • 基础容器(pause):初始化容器环境,开启pid=1的Init进程来管理其他容器的生命周期:提供网络和存储空间的共享环境基础
  • init容器:是在基础容器之后,应用容器之前运行的容器,多个init容器是串行运行,Init容器必须在上一个init容器成功运行和退出后才会运行
  • 应用容器(main c):运行业务的容器,在Init容器都成功运行和退出后运行的,多个应用器是并行运行的

注:在一个pod中,init容器和应用容器的名称都是惟一的

pod镜像拉取策略imagePullPolicy配置containers字段下面一层

  • IfNotPresent:是带有指定标签的镜像的默认拉取策略。本地有则用本地进行,本地没有则从仓库拉取镜像
  • Always:是没有标签的镜像或者使用latest标签的镜像的默认拉取策略。创建Pod总是从仓库拉取镜像
  • Never:不从仓库拉取镜像,仅使用本地镜像

pod重启策略restartPolicy配置跟containers字段同一层

  • Always:默认的重启策略,容器退出时,总是重启容器
  • Nerver:容器退出,从不重启容器
  • OnFailure:只有容器异常退出(非0状态码退出)时,才会重启容器

Pod进阶:

资源限制:

当定义Pod时可以选择性的伪每个容器设定所需要的资源数量,最常见的可设定资源是CPU和内存大小,以及其他类型的资源。

当为Pod中的容器指定request资源时,调度器就使用该信息来决定将Pod调度到哪个节点上。当还为容器指定了limit资源时,kubelet就会确保运行的容器不会使用超出所设的limit资源量。kubelet还会为容器预留所设定的request资源量,供该容器使用。

如果Pod运行所在的节点具有足够的可用资源,容器可以使用超出所设置的request资源量。不过,容器不可以使用超出所设置的limit资源量。

如果给容器设置了内存的limit值,但未设置内存的request值,Kubernetes会自动为其设置与内存limit相匹配的request值。类似的,如果给容器设置了CPU的limit值但未设置CPU的request值,则Kubernetes自动为其设置CPU的request值,并使之与CPU的limit值匹配。

CPU 资源单位

CPU资源的request和limit以cpu为单位。Kubernetes中的一个cpu相当于1个VCPU (1个超线程)

Kubernetes也支持带小数CPU的请求。spec.containers [].resources.requests.cpu为0.5的容器能够获得一个cpu的一半CPU资源(类似于cgroup对CPU资源的时间分片)。表达式0.1等价于表达式100m (毫核) ,表示每1000毫秒内容器可以使用的CPU时间总量为0.1*1000毫秒。

内存资源单位

内存的request和limit以字节为单位。可以以整数表示,或者以10为底数的指数的单位(E、P,T,G,M,K)来表示,或者以2为底数的指数的单位(Ei, Pi,Ti,Gi、Mi, Ki)来表示。

如: 1KB-10^3-1000, 1MB-10^6-1000000-1000KB, 1GB=10^9-1000000000-1000MB

1KiB-2^10-1024, 1MiB-2 20-1048576-1024KiB

PS:在买硬盘的时候,操作系统报的数量要比产品标出或商家号称的小一些,主要原因是标出的是以MB, GB为单位的, 1GB

就是1,000,000, 000Bte ,而操作系统是以2进制为处理单位的,因此检查硬盘容量时是以MiB, GiB为单位, 1GB-2^30-1,073, 741, 824,相比较而言,1GiB要比1GB多出1,073, 741, 824-1, 000, 000, 000-73, 741, 824Byte,所以检测实际结果要比标出的少一些。

Pod 和 容器的资源请求和限制

spec.containers[].resources. requests.cpu               #定义创建容器时预分配的CPU资源

spec.containers[].resources.requests.memory        #定义创建容器时预分配的内存资源

spec.containers[].resources.limits.cpu                     #定义 cpu 的资源上限

spec.containers[].resources.limits.memory             #定义内存的资源上限

示例

示例1∶exec方式

  • initialDelaySeconds∶指定 kubelet 在执行第一次探测前应该等待5秒,即第一次探测是在容器启动后的第6秒才开始执行。默认是 0 秒,最小值是 0。
  • periodSeconds∶指定了 kubelet 应该每 5 秒执行一次存活探测。默认是 10 秒。最小值是 1。
  • failureThreshold∶当探测失败时,Kubernetes 将在放弃之前重试的次数。存活探测情况下的放弃就意味着重新启动容器。就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。
  • timeoutSeconds∶探测超时后等待多少秒。默认值是 1 秒。最小值是 1。(在 Kubernetes 1.20 版本之前,exec 探针会忽略timeoutSeconds 探针会无限期地持续运行,甚至可能超过所配置的限期,直到返回结果为止。)

可以看到 Pod 中只有一个容器。kubelet 在执行第一次探测前需要等待 5 秒,kubelet 会每 5 秒执行一次存活探测。kubelet在容器内执行命令 cat /tmp/healthy 来进行探测。
如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。当到达第31 秒时,这个命令返回非 0 值,kubelet会杀死这个容器并重新启动它。

 

示例2∶httpGet方式

在这个配置文件中, 可以看到 Pod 也只有一个容器。initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 3秒。periodSeconds 字段指定了 kubelet 每隔 3 秒执行一次存活探测。 timeoutSeconds字段指定了超时等待时间为10S,kubelet 会向容器内运行的服务(服务会监听 80端口)发送一个HTTP GET 请求来执行探测。如果服务器上/index.html路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。
任何大于或等于 200 并且小于 400 的返回代码标示成功,其它返回代码都标示失败。

示例3tcpSocket方式

 启动退出动作:

 

 

 

 

 

 整个pod生命周期当中,init容器是先运行的,等init容器运行退出后,业务容器才会启动,业务容器会发送postStart(启动动作事件),也不会影响容器的运行状态,当容器退出之前,k8s也会发出一个poststop事件,容器才能退出。

探针:

 

 

3种探针

  •  livenessProbe(存活探针):判断容器是否正常运行,如果失败则杀掉容器(不是Pod),再根据重启策略是否重启容器
  • readinessProbe(就绪探针):判断容器是否能够进入ready状态,探针失败则进入noready状态,并从service的endpoints中剔除此容器
  • startupProbe:判断容器内的应用是否启动成功,在success状态前,其它探针都处于无效状态

3种检查方式:

  • exec:使用command字段设置命令,在容器中执行此命令。如果命令返回状态码为0,则认为探测成功
  • httpget:通过访问指定端口和url路径执行http get访问。如果返回的http状态码为大于等于200且小于400则认为成功
  • tcpsocket:通过tcp连接pod(IP)和指定端口,如果端口无误且tcp连接成功,则认为探测成功

探针可选参数

  • initialDelaySeconds:容器启动多少秒后开始执行探测
  • periodSeconds:探测的周期盘频率,每多少秒执行一次探测
  • failureThreshold:探测失败后,允许再试几次
  • timeoutSeconds:探测等待超时的时间

推荐阅读