Kubernetes 系列(三):剖析Pod对象

 

什么是Pod

Pod直译为“豆荚” ,你可以把它想象成一个“豆荚” ,然后里面包着一组有关联关系的“豆子”(容器)。
一个豆荚里的豆子,它们共同吸收着同一个养分,Pod也是如此,里面的容器共有着同一组资源。
K8S官方文档对Pod的描述是:A Pod is the basic building block of Kubernetes–the smallest and simplest unit in the Kubernetes object model that you create or deploy. A Pod represents processes running on your Cluster. 翻译成中文:Pod是K8S的基本构建模块,它是你在K8S集群里能创建或部署的最小和最简单的单元。

 

Pod中的多个容器共享网络( Network Namespace)和文件系统, 因此处于一个Pod中的多个容器共享以下资源:

  • PID命名空间:Pod中不同的应用程序可以看到其他应用程序的进程ID。
  • network命名空间:Pod中多个容器处于同一个网络命名空间,因此能够访问的IP和端口范围都是相同的。也可以通过localhost相互访问。
  • IPC命名空间:Pod中的多个容器共享Inner-process Communication命名空间,因此可以通过SystemV IPC或POSIX进行进程间通信。
  • UTS命名空间:Pod中的多个容器共享同一个主机名。
  • Volumes:Pod中各个容器可以共享在Pod中定义分存储卷(Volume)。

Pod 是 Kubernetes 里的原子调度单位。这就意味着,Kubernetes 项目的调度器,是统一按照 Pod 而非容器的资源需求进行计算的。

如果你能把 Pod 看成传统环境里的“机器”、把容器看作是运行在这个“机器”里的“用户程序”,那么很多关于 Pod 对象的设计就非常容易理解了。比如,凡是调度、网络、存储,以及安全相关的属性,基本上是 Pod 级别的。这些属性的共同特征是,它们描述的是“机器”这个整体,而不是里面运行的“程序”。比如,配置这个“机器”的网卡(即:Pod 的网络定义),配置这个“机器”的磁盘(即:Pod 的存储定义),配置这个“机器”的防火墙(即:Pod 的安全定义)。更不用说,这台“机器”运行在哪个服务器之上(即:Pod 的调度)。

Pod 本质:实际上是在扮演传统基础设施里“虚拟机”的角色;而容器,则是这个虚拟机里运行的用户程序。

最后,拿K8S跟OpenStack对比就明白了:

  K8S  OpenStack
  Pod  VM
  Docker  application 应用、进程

 

OpenStack管理的VM可以说是OpenStack里的最小单元,虚拟机我们知道有隔离性,里面部署的应用只跑在虚拟机里面,他们共享这个VM的CPU、Mem、网络、存储资源。
那么,同理Pod也是如此,Pod里面的容器共享着Pod里面的CPU、Mem、网络和存储资源。

Pod的特点

1.K8S集群的最小单元是Pod,而不是容器;K8S直接管理的也是Pod,而不是容器:

2.Pod里可以运行一个或多个容器:

你也可以把一组有关联关系的容器放在一个Pod里面,这些容器共享着同一组网络命名空间和存储卷。

Pod里的容器共用相同的网络和存储资源:

3.每个Pod会被分配唯一的IP地址,然后里面的容器都会共享着这个网络空间,这个网络空间包含了IP地址和网络端口。Pod容器内部通信用的是localhost,如果要跟外面通信,就需要用到共享的那个IP和端口。

Pod可以指定共享的存储Volume,然后Pod里的所有容器都有权限访问这个Volume。Volume是持久化数据用的,Pod的重启都不会影响Volume里的数据。

Pod实例

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80

在生产环境中,推荐使用诸如Deployment,StatefulSet,Job或者CronJob等控制器来创建Pod,而不是直接创建。

1.将上述pod描述文件保存为nginx-pod.yaml,使用kubectl create命令运行pod

kubectl create -f nginx-pod.yaml

2.查看kubectl get 指令的作用,就是从 Kubernetes 里面获取(GET)指定的 API 对象(通过标签查找Pod)。在这里我还加上了一个 -l 参数,即获取所有匹配 app: nginx 标签的 Pod。

kubectl get pods -l app=nginx

3.你还可以使用 kubectl describe 命令,查看一个 API 对象的细节

kubectl describe pod  NAME

4.更新yaml文件后,如何替换正在运行的pod:

kubectl replace -f nginx-pod.yaml

但是在这里推荐使用kubectl apply 命令来统一进行 Kubernetes 对象的创建和更新操作

kubectl apply -f nginx-pod.yaml

5.如何进入到pod中

kubectl exec -it NAME -- /bin/bash

6.如何删除Kubernetes 集群中删除这个 Nginx pod,也可以下面命令列表的命令:

kubectl delete -f nginx-pod.yaml

下面简要分析一下上面的Pod定义文件:

  • apiVersion: 使用哪个版本的Kubernetes API来创建此对象,此处值是v1,这个版本号需要根据安装的Kubernetes版本和资源类型进行变化,记住不是写死的。
  • kind:要创建的对象类型,根据实际情况,此处资源类型可以是Deployment、Job、Ingress、Service等。
  • metadata:用于唯一区分对象的元数据,包括:API对象的name,UID和namespace、标签等信息
  • labels:是一个个的key/value对,定义这样的label到Pod后,其他控制器对象可以通过这样的label来定位到此Pod,从而对Pod进行管理。(参见Deployment等控制器对象)
  • spec:定义pod及pod下容器的相关配置,包括一些container,storage,volume以及其他Kubernetes需要的参数,以及诸如是否在容器失败时重新启动容器的属性。

如果我们希望从外部访问这nginx应用,那么我们还需要创建Service对象来暴露IP和port。

Pod基本操作命令

说明

具体命令

创建

kubectl create -f xxx.yaml

查询

kubectl get pod PodName / kubectl describe pod PodName

删除

kubectl delete pod PodName

更新

kubectl replace/apply  /path/to/newPodName.yaml (当然也可以加--force 强制替换)

查看logs输出

kubectl logs PodName

 

Pod内部机制

在 Kubernetes 项目里,Pod 的实现需要使用一个中间容器,这个容器叫作 Infra 容器(也可以叫pause容器)。在这个 Pod 中,Infra 容器永远都是第一个被创建的容器,而其他用户定义的容器,则通过 Join Network Namespace 的方式,与 Infra 容器关联在一起。

所有处于该Pod中的容器在启动时都会添加诸如--net=container:pause --ipc=contianer:pause --pid=container:pause的启动参数,因此pause容器成为Pod内共享命名空间的基础。所有容器共享pause容器的IP地址,也被称为Pod IP。

这样的组织关系,可以用下面这样一个示意图来表达:如上图所示,这个 Pod 里有两个用户容器 A 和 B,还有一个 Infra 容器。很容易理解,在 Kubernetes 项目里,Infra 容器一定要占用极少的资源,所以它使用的是一个非常特殊的镜像,叫作:k8s.gcr.io/pause。这个镜像是一个用汇编语言编写的、永远处于“暂停”状态的容器,解压后的大小也只有 100~200 KB 左右。而在 Infra 容器“Hold 住”Network Namespace 后,用户容器就可以加入到 Infra 容器的 Network Namespace 当中了。所以,如果你查看这些容器在宿主机上的 Namespace 文件(这个 Namespace 文件的路径,我已经在前面的内容中介绍过),它们指向的值一定是完全一样的。

这也就意味着,对于 Pod 里的容器 A 和容器 B 来说

  • 它们可以直接使用 localhost 进行通信;
  • 它们看到的网络设备跟 Infra 容器看到的完全一样;
  • 一个 Pod 只有一个 IP 地址,也就是这个 Pod 的 Network Namespace 对应的 IP 地址;
  • 当然,其他的所有网络资源,都是一个 Pod 一份,并且被该 Pod 中的所有容器共享;
  • Pod 的生命周期只跟 Infra 容器一致,而与容器 A 和 B 无关。
  • 而对于同一个 Pod 里面的所有用户容器来说,它们的进出流量,也可以认为都是通过 Infra 容器完成的。

这一点很重要,因为将来如果你要为 Kubernetes 开发一个网络插件时,应该重点考虑的是如何配置这个 Pod 的 Network Namespace,而不是每一个用户容器如何使用你的网络配置,这是没有意义的。这就意味着,如果你的网络插件需要在容器里安装某些包或者配置才能完成的话,是不可取的:Infra 容器镜像的 rootfs 里几乎什么都没有,没有你随意发挥的空间。当然,这同时也意味着你的网络插件完全不必关心用户容器的启动与否,而只需要关注如何配置 Pod,也就是 Infra 容器的 Network Namespace 即可。

 

Pause容器除了为其他容器提供Linux namespace基础,还有其他功能,那就是扮演PID 1的角色,处理僵尸进程。

在一个PID Namespace下,Infra 作为PID为1的进程存在于一个Pod里,其他的业务容器都挂载这个Infra 进程下面。这样,一个PID Namespace下的进程就会以Pause作为根,呈树状的结构存在一个Pod下。

Pause这个容器代码是用C写的(代码见下),其中Pause的代码里,有个无限循环的for(;;)函数,函数里面执行的是pause( )函数,pause() 函数本身是在睡眠状态的, 直到被信号(signal)所中断。因此,正是因为这个机制,Pause容器会一直等待SIGCHLD信号,一旦有了SIGCHLD信号(进程终止或者停止时会发出这种信号),Pause就会启动sigreap方法,sigreap方法里就会调用waitpid获取其子进程的状态信息,这样自然就不会在Pod里产生僵尸进程了。

最后总结Paused的作用,它主要有两方面的作用:

  • 扮演PID 1的角色,处理僵尸进程;
  • 在Pod里为其他容器共享Linux namespace的基础。

 Pod的生命周期

Pod的生命周期是Replication Controller进行管理的。一个Pod的生命周期过程包括:

  • 通过yaml或json对Pod进行描述
  • apiserver(运行在Master主机)收到创建Pod的请求后,将此Pod对象的定义存储在etcd中
  • scheduler(运行在Master主机)将此Pod分配到Node上运行
  • Pod内所有容器运行结束后此Pod也结束

在整个过程中,Pod的状态是PodStatus对象里的phase字段来表示的,这个phase字段有以下一些值:

  • Pending:Pod定义正确,提交到Master,但其所包含的容器镜像还未完全创建。通常,Master对Pod进行调度需要一些时间,Node进行容器镜像的下载也需要一些时间,启动容器也需要一定时间。(写数据到etcd,调度,pull镜像,启动容器)。
  • Running:Pod已经被分配到某个Node上,并且所有的容器都被创建完毕,至少有一个容器正在运行中,或者有容器正在启动或重启中。
  • Succeeded:Pod中所有的容器都成功运行结束,并且不会被重启。这是Pod的一种最终状态
  • Failed:Pod中所有的容器都运行结束了,其中至少有一个容器是非正常结束的(exit code不是0)。这也是Pod的一种最终状态。
  • Unknown:无法获得Pod的状态,通常是由于无法和Pod所在的Node进行通信。

 Restart policy

定义Pod时,可以指定restartPolicy字段,表明此Pod中的容器在何种条件下会重启。restartPolicy拥有三个候选值:

  • Always:只要退出就重启
  • OnFailure:失败退出时(exit code不为0)才重启
  • Never:永远不重启

Pod 生命周期控制方法

Pod本身不具备容错性,这意味着如果Pod运行的Node宕机了,那么该Pod无法恢复。因此推荐使用Deployment等控制器来创建Pod并管理。

一般来说,Pod不会自动消失,只能手动销毁或者被预先定义好的controller销毁。但有一种特殊情况,当Pod处于Succeeded或Failed阶段,并且超过一定时间后(由master决定),会触发超时过期从而被销毁。

三种类型的控制器控制Pod生命周期的方法:

  • Job:适用于一次性任务如批量计算,任务结束后Pod会被此类控制器清除。Job的重启策略只能是"OnFailure"或者"Never"。
  • ReplicationController, ReplicaSet, or Deployment,此类控制器希望Pod一直运行下去,它们的restart policy只能是"always"。
  • DaemonSet:每个node一个Pod,很明显此类控制器的restart policy应该是"always"。
posted @ 2020-09-08 11:44  -零  阅读(815)  评论(0编辑  收藏  举报