深入解析Pod对象

269 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第32天,点击查看活动详情

Pod对象的基本概念

Pod,而非容器,才是Kubernetes的最小编排单位。将该设计落实到API对象,容器(Container)就成了Pod属性里的一个普通的字段。

那到底哪些属性属于Pod对象,哪些属于Container?

Pod扮演的是传统部署环境里“VM”的角色。这设计是为了使用户从传统环境(VM环境)向Kubernetes(容器环境)迁移更平滑。

若你能把Pod看成传统环境里的“机器”、把容器看作是运行在这个“机器”里的“用户程序”,那很多关于Pod对象的设计就容易理解。

凡是调度、网络、存储及安全相关的属性,基本是Pod级。这些属性的共同特征是,它们描述的是“机器”整体,而非里面运行的“程序”。如:

  • 配置这个“机器”的网卡(即Pod的网络定义)
  • 配置这个“机器”的磁盘(即Pod的存储定义)
  • 配置这个“机器”的防火墙(即Pod的安全定义)
  • 这台“机器”运行在哪个服务器之上(即Pod的调度)

介绍Pod中几个重要字段。

1 NodeSelector

供用户将Pod与Node进行绑定:

apiVersion: v1
kind: Pod
...
spec:
 nodeSelector:
   disktype: ssd

意味着该Pod永远只能运行在携带“disktype: ssd”标签(Label)的节点;否则,它将调度失败。

2 NodeName

该字段被赋值,Kubernetes就会被认为该Pod已经过调度,调度结果就是赋值的节点名字。所以,该字段一般由调度器设置,但用户也可设置它来“骗过”调度器,当然这做法一般在测试或调试才用到。

3 HostAliases

定义了Pod的hosts文件(比如/etc/hosts)里的内容:

apiVersion: v1
kind: Pod
...
spec:
  hostAliases:
  - ip: "10.1.2.3"
    hostnames:
    - "foo.remote"
    - "bar.remote"
...

设置了一组IP和hostname的数据。该Pod启动后,/etc/hosts文件的内容将如下:

cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
...
10.244.135.10 hostaliases-pod
10.1.2.3 foo.remote
10.1.2.3 bar.remote

最下面两行记录,就是通过HostAliases字段为Pod设置的。Kubernetes中,若要设置hosts文件里的内容,一定要通过这方法。若直接修改hosts文件,在Pod被删除重建后,kubelet会自动覆盖掉被修改的内容。

凡和容器的Linux Namespace相关的属性,也一定Pod级。Pod的设计,就是要让它里面的容器尽可能共享Linux Namespace,仅保留必要的隔离和限制能力。这样,Pod模拟出的效果就跟VM里程序间的关系很类似。

如下,在下面这个Pod的YAML文件,定义了shareProcessNamespace=true:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  shareProcessNamespace: true
  containers:
  - name: nginx
    image: nginx
  - name: shell
    image: busybox
    stdin: true
    tty: true

即该Pod里的容器要共享PID Namespace。

而在该YAML文件中,还定义了两个容器:

  • nginx容器

  • 开启了tty和stdin的shell容器

    Pod的YAML文件里声明开启它们俩,等同设置docker run里的-it(-i即stdin,-t即tty)参数。tty就是Linux给用户提供的一个常驻小程序,用于接收用户的标准输入,返回操作系统的标准输出。当然,为了能够在tty中输入信息,你还需要同时开启stdin(标准输入流)。

该Pod被创建后,使用shell容器的tty跟该容器交互:

$ kubectl create -f nginx.yaml

接下来,我们使用kubectl attach命令,连接到shell容器的tty上:

$ kubectl attach -it nginx -c shell

就能在shell容器里执行ps指令,查看所有正在运行的进程:

$ kubectl attach -it nginx -c shell
/ # ps ax

该容器里不仅能看到它本身的ps ax指令,还能看到nginx容器的进程及Infra容器的/pause进程。即整个Pod里的每个容器的进程,对所有容器来说都可见:它们共享同一个PID Namespace。

凡Pod中的容器要共享宿主机的Namespace,也一定是Pod级别的定义,如:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  hostNetwork: true
  hostIPC: true
  hostPID: true
  containers:
  - name: nginx
    image: nginx
  - name: shell
    image: busybox
    stdin: true
    tty: true

该Pod中,定义了共享宿主机的Network、IPC和PID Namespace。即该Pod里的所有容器会直接使用宿主机的网络、直接与宿主机进行IPC通信、看到宿主机里正在运行的所有进程。

4 Containers

Containers、Init Containers这两个字段都属于Pod对容器的定义,内容也完全相同,只是Init Containers的生命周期,会先于所有Containers,且严格按定义顺序执行。

Kubernetes对Container的定义,和Docker相比无大区别。

Image(镜像)、Command(启动命令)、workingDir(容器的工作目录)、Ports(容器要开发的端口)及volumeMounts(容器要挂载的Volume)都是构成Kubernetes中的Container的主要字段。

ImagePullPolicy字段

定义了镜像拉取的策略。Container级别的属性,因为容器镜像本就是Container定义中的一部分。

ImagePullPolicy默认Always,即每次创建Pod都重新拉取一次镜像。当容器的镜像是类似于nginx或nginx:latest这样的名字时,ImagePullPolicy也会被认为Always。

而若值被定义为Never或IfNotPresent,则:

  • Pod永远不会主动拉取这个镜像
  • 或只在宿主机上不存在这个镜像时才拉取

Lifecycle字段

定义Container Lifecycle Hooks,在容器状态发生变化时触发一系列“钩子”。

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-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"]

来自Kubernetes官方文档的Pod的YAML文件。定义了一个nginx镜像的容器。YAML文件的容器(Containers)部分,分别设置postStart和preStop参数。

postStart

容器启动后,立刻执行一个指定操作。

postStart定义的操作,虽是在Docker容器ENTRYPOINT执行之后,但不严格保证顺序。即postStart启动时,ENTRYPOINT可能还没结束。

若postStart执行超时或者错误,Kubernetes会在该Pod的Events中报出该容器启动失败的错误信息,导致Pod也处于失败状态。

preStop

发生时机则是容器被杀死前(如收到SIGKILL信号)。preStop操作的执行是同步的。会阻塞当前的容器杀死流程,直到该Hook定义操作完成后,才允许容器被杀死。

所以,在这个例子中,在容器成功启动后,在/usr/share/message里写入一句“欢迎信息”(即postStart定义的操作)。而在容器被删除前,先调用nginx的退出指令(即preStop定义的操作),实现容器的“优雅退出”。

5 Pod对象在Kubernetes中的生命周期

Pod生命周期的变化,体现在Pod API对象的Status部分,除了Metadata和Spec之外的第三个重要字段。pod.status.phase即Pod当前状态:

  1. Pending。Pod的YAML文件已提交给Kubernetes,API对象已被创建并保存在Etcd。但该Pod里有些容器因某种原因而不能被顺利创建。如调度不成功。
  2. Running。Pod已调度成功,跟一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。
  3. Succeeded。Pod里的所有容器都正常运行完毕,并且已经退出了。这种情况在运行一次性任务时最为常见。
  4. Failed。Pod里至少有一个容器以不正常的状态(非0的返回码)退出。这个状态的出现,意味着你得想办法Debug这个容器的应用,比如查看Pod的Events和日志。
  5. Unknown。异常状态,意味着Pod的状态不能持续地被kubelet汇报给kube-apiserver,这很有可能是主从节点(Master和Kubelet)间的通信出现了问题。

Conditions

Pod对象的Status字段还可再细分出一组Conditions。这些细分状态的值包括:PodScheduled、Ready、Initialized,以及Unschedulable。主要描述造成当前Status的具体原因。

如Pod当前Status=Pending,对应Condition=Unschedulable,即它调度出现问题。

Ready细分状态

Pod不仅已正常启动(Running状态),且已可对外提供服务。这两者之间(Running和Ready)是有区别的。

Pod的这些状态信息,是判断应用运行情况的重要标准,尤其是Pod进入了非“Running”状态后,要能迅速做出反应,根据它所代表的异常情况开始跟踪和定位,而非手忙脚乱查文档。

6总结

Pod API对象是整个Kubernetes体系中最核心的一个概念,也是后面我讲解各种控制器时都要用到的。

仔细阅读$GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/api/core/v1/types.go里,type Pod struct ,尤其是PodSpec部分的内容,争取精通常用字段及其作用。