持续创作,加速成长!这是我参与「掘金日新计划 · 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当前状态:
- Pending。Pod的YAML文件已提交给Kubernetes,API对象已被创建并保存在Etcd。但该Pod里有些容器因某种原因而不能被顺利创建。如调度不成功。
- Running。Pod已调度成功,跟一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。
- Succeeded。Pod里的所有容器都正常运行完毕,并且已经退出了。这种情况在运行一次性任务时最为常见。
- Failed。Pod里至少有一个容器以不正常的状态(非0的返回码)退出。这个状态的出现,意味着你得想办法Debug这个容器的应用,比如查看Pod的Events和日志。
- 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部分的内容,争取精通常用字段及其作用。