Pod,而不是容器,才是 Kubernetes 项目中的最小编排单位。将这个设计落实到 API 对象上,容器(Container)就成了 Pod 属性里的一个普通的字段。那么,一个很自然的问题就是:
到底哪些属性属于 Pod 对象,而又有哪些属性属于 Container 呢?
把 Pod 看成传统环境里的“机器”、把容器看作是运行在这个“机器”里的“用户程序”,那么很多关于 Pod 对象的设计就非常容易理解了。比如,凡是调度、网络、存储,以及安全相关的属性,基本上是 Pod 级别的。
这些属性的共同特征是,它们描述的是“机器”这个整体,而不是里面运行的“程序”。
- 配置这个“机器”的网卡(即:Pod 的网络定义)
- 配置这个“机器”的磁盘(即:Pod 的存储定义)
- 配置这个“机器”的防火墙(即:Pod 的安全定义)
- 这台“机器”运行在哪个服务器之上(即:Pod 的调度)
POD的字段
NodeSelector:是一个供用户将 Pod 与 Node 进行绑定的字段,用法如下所示:
---
apiVersion: v1
kind: Pod
...
spec:
nodeSelector:
disktype: ssd
这样的一个配置,意味着这个 Pod 永远只能运行在携带了“disktype: ssd”标签(Label)的节点上;否则,它将调度失败。
NodeName:一旦 Pod 的这个字段被赋值,Kubernetes 项目就会被认为这个 Pod 已经经过了调度,调度的结果就是赋值的节点名字。所以,这个字段一般由调度器负责设置,但用户也可以设置它来“骗过”调度器,当然这个做法一般是在测试或者调试的时候才会用到。
HostAliases:定义了 Pod 的 hosts 文件(比如 /etc/hosts)里的内容,用法如下:
---
apiVersion: v1
kind: Pod
...
spec:
hostAliases:
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
...
在这个 Pod 的 YAML 文件中,设置了一组 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
在 Kubernetes 项目中,如果要设置 hosts 文件里的内容,一定要通过这种方法。否则,如果直接修改了 hosts 文件的话,在 Pod 被删除重建之后,kubelet 会自动覆盖掉被修改的内容。
凡是跟容器的 Linux Namespace 相关的属性,也一定是 Pod 级别的
这个原因也很容易理解:Pod 的设计,就是要让它里面的容器尽可能多地共享 Linux Namespace,仅保留必要的隔离和限制能力。这样,Pod 模拟出的效果,就跟虚拟机里程序间的关系非常类似了。
举个例子,在下面这个 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
kubectl create -f nginx.yaml
# 进入POD
kubectl attach -it nginx -c shell
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 /pause
6 root 0:00 nginx: master process nginx -g daemon off;
34 101 0:00 nginx: worker process
35 root 0:00 sh
40 root 0:00 ps aux
/ # exit
在这个容器里,不仅可以看到它本身的 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 通信、看到宿主机里正在运行的所有进程。
Containers的字段
Kubernetes 项目中对 Container 的定义,和 Docker 相比并没有什么太大区别。
Image 镜像
Command 启动命令
workingDir 容器的工作目录
Ports 容器要开发的端口
volumeMounts 容器要挂载的 Volume
ImagePullPolicy 字段
定义了镜像拉取的策略。之所以是一个 Container 级别的属性,是因为容器镜像本来就是 Container 定义中的一部分。
默认值是 Always,即每次创建 Pod 都重新拉取一次镜像。当容器的镜像是类似于 nginx 或者 nginx:latest 这样的名字时,ImagePullPolicy 也会被认为 Always。
Never 或者 IfNotPresent,则意味着 Pod 永远不会主动拉取这个镜像,或者只在宿主机上不存在这个镜像时才拉取。
Lifecycle 字段
它定义的是 Container Lifecycle Hooks。顾名思义,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"]
postStart 是在容器启动后,立刻执行一个指定的操作。需要明确的是,postStart 定义的操作,虽然是在 Docker 容器 ENTRYPOINT 执行之后,但它并不严格保证顺序。也就是说,在 postStart 启动时,ENTRYPOINT 有可能还没有结束。如果 postStart 执行超时或者错误,Kubernetes 会在该 Pod 的 Events 中报出该容器启动失败的错误信息,导致 Pod 也处于失败的状态。
preStop 是容器被杀死之前(比如,收到了 SIGKILL 信号)。需要明确的是,preStop 操作的执行,是同步的。所以,它会阻塞当前的容器杀死流程,直到这个 Hook 定义操作完成之后,才允许容器被杀死,这和 postStart 不一样。
Pod 对象在 Kubernetes 中的生命周期
Pod 生命周期的变化,主要体现在 Pod API 对象的 Status 部分,这是它除了 Metadata 和 Spec 之外的第三个重要字段。其中,pod.status.phase,就是 Pod 的当前状态,有如下几种可能的情况:
Pending Pod 的 YAML 文件已经提交给了 Kubernetes,API 对象已经被创建并保存在 Etcd 当中。但是,这个 Pod 里有些容器因为某种原因而不能被顺利创建。比如,调度不成功。
Running Pod 已经调度成功,跟一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。
Succeeded Pod 里的所有容器都正常运行完毕,并且已经退出了。这种情况在运行一次性任务时最为常见。
Failed Pod 里至少有一个容器以不正常的状态(非 0 的返回码)退出。这个状态的出现,意味着得想办法 Debug 这个容器的应用,比如查看 Pod 的 Events 和日志。
Unknown 这是一个异常状态,意味着 Pod 的状态不能持续地被 kubelet 汇报给 kube-apiserver,这很有可能是主从节点(Master 和 Kubelet)间的通信出现了问题。
更进一步地,Pod 对象的 Status 字段,还可以再细分出一组 Conditions。它们主要用于描述造成当前 Status 的具体原因是什么。
这些细分状态的值包括
正常的状态
PodScheduled pod正处于调度中,刚开始调度的时候,hostip还没绑定上,持续调度之后,有合适的节点就会绑定hostip,然后更新etcd数据;
ContainersReady pod中的容器都已经启动;
Ready 意味着 Pod 不仅已经正常启动(Running 状态),而且已经可以对外提供服务了;
Initialized 所有pod 中的初始化容器已经完成了;
异常的状态
Unschedulable 限制不能被调度,譬如现在资源不足。Pod 当前的 Status 是 Pending,对应的 Condition 是 Unschedulable,这就意味着它的调度出现了问题。
Running 和 Ready 两者的区别?
POD的直议是豆荚,豆荚中的一个或者多个豆属于同一个家庭,共享一个物理豆荚(可以共享调度、网络、存储,以及安全),每个豆虽然有自己的空间,但是由于之间的缝隙,可以近距离无缝沟通(Linux Namespace相关的属性)。