Kubernetes Pod
Pod介绍
- Pod是kubernetes中最小部署单元;也是最简的单位。一个Pod代表着集群中运行的一个进程。
- Pod中封装着应用的容器,也可以是一组容器的集合;kubernetes中应用的一个实例,可能由一个或者多个容器组合在一起共享资源。
在Kubrenetes集群中Pod有如下两种使用方式:
-
Pod中运行一个容器
这种模式是最常见的用法;在这种使用方式中,你可以把Pod想象成是单个容器的封装,kuberentes管理的是Pod而不是直接管理容器。
-
Pod中同时运行多个容器
Pod中同时封装几个需要紧密耦合互相协作的容器,彼此之间共享资源。这些在同一个Pod中的容器可以互相协作成为一个
service
单元,即一个容器共享文件,另一个sidecar
容器来更新这些文件。Pod将这些容器的存储资源作为一个实体来管理
- Pod存在存储、独立的网络IP,管理容器如何运行的策略选项。
Pod中共享的环境包括Linux的namespace
,cgroup
和其他可能的隔绝环境,这与Docker容器一致。在Pod的环境中,每个容器中可能还有更小的子隔离环境。
Pod中可以共享两种资源:网络和存储。
- 共享网络
Pod中的容器共享IP地址和端口号,Pod之中的容器通过localhost
互相发现;并通过lo网卡进行通信。Pod中的容器与外界通信时,必须分配共享网络资源(例如使用宿主机的端口映射)。
例如SystemV信号或者POSIX共享内存。不同Pod之间的容器具有不同的IP地址,不能直接通过IPC通信。
- 共享存储
Pod中的容器也有访问共享volume的权限,这些volume会被定义成pod的一部分并挂载到应用容器的文件系统中。Volume也可以用来持久化Pod中的存储资源,以防容器重启后文件丢失。
- 每个应用容器,pod被认为是临时实体,有自己的生命周期。pod被创建后,被分配一个唯一的ID(UID),调度到节点上,并一致维持期望的状态直到被终结(根据重启策略)或者被删除。
例如:某node节点宕机,宕机node上的pod,在经一段超时时间后,会被重新调度到其他node节点上;一个给定的pod(如UID定义的)不会被重新调度到新的节点上,而是被一个同样的pod取代,如果期望的话甚至可以是相同的名字,但是会有一个新的UID(查看replication controller获取详情)。
Pod中多个容器
Pod中可以同时运行多个进程(作为容器运行)协同工作。同一个Pod中的容器会自动的分配到同一个 node 上。同一个Pod中的容器共享资源、网络环境和依赖,它们总是被同时调度。
注意:在一个Pod中同时运行多个容器是一种比较高级的用法。只有当你的容器需要紧密配合协作的时候才考虑用这种模式。
例如,你有一个容器作为web服务器运行,需要用到共享的volume,有另一个
sidecar
容器来从远端获取资源更新这些文件
Pod中容器分类
大体上来说容器可分为如下三类
容器分类 | |
---|---|
Infrastructure Container | 基础容器,维护整个Pod网络空间,对用户不可见 |
InitContainers | 初始化容器,先于业务容器开始执行,一般用于业务容器的初始化工作 |
Containers | 业务容器,具体跑应用程序的镜像 |
Pause容器
每个Pod都有一个特殊的被称为“根容器”的Pause 容器,全称infrastucture container(又叫infra)基础容器。
Pause容器对应的镜像属于Kubernetes平台的一部分,除了Pause容器,每个Pod还包含一个或者多个紧密相关的用户业务容器。
当我们检查kubernetes集群的node节点时,我们使用docker ps查看时会发现一些名为pause“的容器在节点上运行。
# docker ps
......
8d7d2c61978b registry.aliyuncs.com/google_containers/pause:3.1 "/pause" 6 days ago Up 6 days k8s_POD_coredns-58cc8c89f4-j9m59_kube-system_46fb3943-eea0-4ab2-a62a-714c3c4c199d_0
64f9fe1121ef registry.aliyuncs.com/google_containers/pause:3.1 "/pause" 6 days ago Up 6 days k8s_POD_dashboard-metrics-scraper-566cddb686-mvq2s_kubernetes-dashboard_d80b23de-7046-4329-8775-c93f3191d2b1_0
78b6551f072e registry.aliyuncs.com/google_containers/pause:3.1 "/pause" 6 days ago Up 6 days k8s_POD_kubernetes-dashboard-7b5bf5d559-h6vh4_kubernetes-dashboard_9df74d76-a6d9-493b-b939-a186a8425e62_0
4d65cac0b5df registry.aliyuncs.com/google_containers/pause:3.1 "/pause" 6 days ago Up 6 days k8s_POD_coredns-58cc8c89f4-dhcqh_kube-system_16ba2b78-1e66-4416-84b4-ebc07ce17167_0
......
kubernetes中的pause容器主要为每个业务容器提供以下功能:
- 在pod中担任Linux命名空间共享的基础;
- 启用pid命名空间,开启init进程。
首先我们创建一个pause容器。
docker run -d --name pause -p 8080:80 gcr.io/google_containers/pause-amd64:3.0
然后我们可以运行其他容器来组成我们的pod。先运行nginx,为localhost:2368创建一个代理。
注意,我们也将本机的8080端口代理到pause容器的80端口,而不是代理到nginx容器,这是因为pause容器初始化了网络命名空间,nginx容器将会加入这个命名空间。
$ cat <<EOF >> nginx.conf
> error_log stderr;
> events { worker_connections 1024; }
> http {
> access_log /dev/stdout combined;
> server {
> listen 80 default_server;
> server_name example.com www.example.com;
> location / {
> proxy_pass http://127.0.0.1:2368;
> }
> }
> }
> EOF
$ docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf --net=container:pause --ipc=container:pause --pid=container:pause nginx
进而我们再创建ghost 容器,这是一个博客程序,作为我们的服务端。
$ docker run -d --name ghost --net=container:pause --ipc=container:pause --pid=container:pause ghost
通过以上的方法,我们就可以通过访问http://localhost:8080/来查看我们的博客了。
解析
pause容器将内部的80端口映射到宿主机的8080端口,pause容器在宿主机上设置好了网络namespace后,nginx容器加入到该网络namespace中,我们看到nginx容器启动的时候指定了
--net=container:pause
,ghost容器同样加入到了该网络namespace中,这样三个容器就共享了网络,互相之间就可以使用localhost
直接通信,--ipc=contianer:pause --pid=container:pause
就是三个容器处于同一个namespace中,init进程为pause
,这时我们进入到ghost容器中查看进程情况。
[root@k8s-node01 ~]# docker exec -it ghost /bin/bash
root@d3057ceb54bc:/var/lib/ghost# ps axu
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1012 4 ? Ss 03:48 0:00 /pause
root 6 0.0 0.0 32472 780 ? Ss 03:53 0:00 nginx: master process nginx -g daemon off;
systemd+ 11 0.0 0.1 32932 1700 ? S 03:53 0:00 nginx: worker process
node 12 0.4 7.5 1259816 74868 ? Ssl 04:00 0:07 node current/index.js
root 77 0.6 0.1 20240 1896 pts/0 Ss 04:29 0:00 /bin/bash
root 82 0.0 0.1 17496 1156 pts/0 R+ 04:29 0:00 ps axu
在ghost容器中同时可以看到pause和nginx容器的进程,并且pause容器的PID是1。而在kubernetes中容器的PID=1的进程即为容器本身的业务进程。
init容器
Pod 能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的 Init 容器。
Init 容器与普通的容器非常像,除了如下两点:
- Init 容器总是运行到成功完成为止。
- 每个 Init 容器都必须在下一个 Init 容器启动之前成功完成。
如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的 restartPolicy
为 Never,它不会重新启动。
Pod对象的生命周期
Pod 的 status
在信息保存在 PodStatus 中定义,其中有一个 phase
字段。
Pod 的相位(phase)是 Pod 在其生命周期中的简单宏观概述。该阶段并不是对容器或 Pod 的综合汇总,也不是为了做为综合状态机。
Pod 相位的数量和含义是严格指定的。除了本文档中列举的状态外,不应该再假定 Pod 有其他的 phase
值。
下面是 phase
可能的值:
- 挂起(Pending):API Server创建了Pod资源对象并已经存入了etcd中,但是它并未被调度完成,或者仍然处于从仓库下载镜像的过程中。
- 运行中(Running):Pod已经被调度到某节点之上,并且所有容器都已经被kubelet创建完成。
- 成功(Succeeded):Pod 中的所有容器都被成功终止,并且不会再重启。
- 失败(Failed):Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。
- 未知(Unknown):因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败。
下图是Pod的生命周期示意图,从图中可以看到Pod状态的变化。
Pod的创建过程
先看下创建一个Pod的工作流程:
create pod -> apiserver -> write etcd -> scheduler -> bind pod to node -> write etcd -> kubelet( apiserver get pod) -> dcoekr api,create container -> apiserver -> update pod status to etcd -> kubectl get pods
- 用户通过Kubectl发送创建Pod的请求,提交至API-server;
- API-server会尝试将Pod信息写入到etcd中,待写入操作完成,API-server会将结果返回至用户;
- 此时API-server会开始反映etcd的状态变化;
- 所有的Kubernetes组件都是用watcher机制来跟踪检查API-server上的变化;
- Scheduler调度器会通过watch探测到API-server的Pod信息,但还未绑定到工作节点;
- Scheduler会选择一个合适的工作点,将信息同步到API-server;
- 调度信息由API-server写入到到etcd中去,而且API-server也开始反映该Pod的调度信息;
- Pod被调度到的工作节点的kubelet会在当前节点尝试调用Docker启动器,并将容器的结果状态返回至API-server;
- API-server将Pod状态信息写入到etcd中;
- 在etcd写入完成之后,API-server会将确认信息返回至kubelet;
当Pod创建成功之后,Pod可能会有多个状态,涉及到Pod生命周期的多个重要行为。
Pod的生命周期
它的大致生命周期过程如下图所示:
它主要包含以下几个重要的行为:
- 初始化容器
- 存活性探测
- 就绪型探测
- 生命周期钩子函数
初始化容器
初始化容器(init container)即应用程序的主容器启动之前要运行的容器,常用于为主容器执行一些预置操作,它们具有两种典型特征。
- 初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么Kubernetes需要重启它直到成功完成
- 每个初始化容器都必须按定义的顺序串行运行。
初始化容器的典型应用场景有:
- 用于运行特定的工具程序,出于安全等方面的原因,这些程序不适于包含在主容器镜像中。
- 提供主容器镜像中不具备的工具程序或自定义代码。
- 为容器镜像的构建和部署人员提供了分离、独立工作的途径,使得他们不必协同起来制作单个镜像文件。
- 初始化容器和主容器处于不同的文件系统视图中,因此可以分别安全地使用敏感数据,例如Secrets资源。
- 初始化容器要先于应用容器串行启动并运行完成,因此可用于延后应用容器的启动直至其依赖的条件得到满足。
就绪型探测和存活性探测
在说容器探测之前,先说一下Kubernetes的探测方式,K8s包含三大类探测方式:
ExecAction
:在容器中执行一个命令,并根据其返回的状态码进行诊断的操作称为Exec探测,状态码为0表示成功,否则即为不健康状态。TCPSocketAction
:通过与容器的某TCP端口尝试建立连接进行诊断,端口能够成功打开即为正常,否则为不健康状态。HTTPGetAction
:通过向容器IP地址的某指定端口的指定path发起HTTP GET请求进行诊断,响应码为2xx或3xx时即为成功,否则为失败。
任何一种探测方式都可能存在三种结果:“
Success
”(成功)、“Failure
”(失败)或“Unknown
”(未知),只有第一种结果表示成功通过检测。
kubelet可在活动容器上执行两种类型的检测:存活性检测(livenessProbe
)和就绪性检测(readinessProbe
)
存活性检测:用于判定容器是否处于“运行”(Running)状态;一旦此类检测未通过,kubelet将杀死容器并根据其restartPolicy决定是否将其重启;未定义存活性检测的容器的默认状态为“Success”。
就绪性检测:用于判断容器是否准备就绪并可对外提供服务;未通过检测的容器意味着其尚未准备就绪,端点控制器(如Service对象)会将其IP从所有匹配到此Pod对象的Service对象的端点列表中移除;检测通过之后,会再次将其IP添加至端点列表中。
生命周期钩子函数
从上图可以看出钩子函数也是Pod生命周期中的一个事件。
Kubernetes为容器提供了两种生命周期钩子。
- Start:于容器初始化创建完成之后立即运行的钩子处理器(handler),不过Kubernetes无法确保它一定会于容器中的ENTRYPOINT之前运行。
- Stop:于容器终止操作之前立即运行的钩子处理器,它以同步的方式调用,因此在其完成之前会阻塞删除容器的操作的调用。
Pod的终止过程
因为Pod作为在集群的节点上运行的进程,所以在不再需要的时候能够优雅的终止掉是十分必要的(比起使用发送KILL信号这种暴力的方式)。用户需要能够放松删除请求,并且知道它们何时会被终止,是否被正确的删除。用户想终止程序时发送删除pod的请求,在pod可以被强制删除前会有一个宽限期,会发送一个TERM请求到每个容器的主进程。一旦超时,将向主进程发送KILL信号并从API server中删除。如果kubelet或者container manager在等待进程终止的过程中重启,在重启后仍然会重试完整的宽限期。
示例流程如下:
- 用户发送删除pod的命令,默认宽限期是30秒;
- 在Pod超过该宽限期后API server就会更新Pod的状态为“dead”;
- 在客户端命令行上显示的Pod状态为“terminating”;
- 跟第三步同时,当kubelet发现pod被标记为“terminating”状态时,开始停止pod进程:
- 如果在pod中定义了preStop hook,在停止pod前会被调用。如果在宽限期过后,preStop hook依然在运行,第二步会再增加2秒的宽限期;
- 向Pod中的进程发送TERM信号;
- 跟第三步同时,该Pod将从该service的端点列表中删除,不再是replication controller的一部分。关闭的慢的pod将继续处理load balancer转发的流量;
- 过了宽限期后,将向Pod中依然运行的进程发送SIGKILL信号而杀掉进程。
- Kublete会在API server中完成Pod的的删除,通过将优雅周期设置为0(立即删除)。Pod在API中消失,并且在客户端也不可见。
删除宽限期默认是30秒。 kubectl delete
命令支持 —grace-period=
选项,允许用户设置自己的宽限期。如果设置为0将强制删除pod。在kubectl>=1.5版本的命令中,你必须同时使用 --force
和 --grace-period=0
来强制删除pod。
Pod的强制删除是通过在集群和etcd中将其定义为删除状态。当执行强制删除命令时,API server不会等待该pod所运行在节点上的kubelet确认,就会立即将该pod从API server中移除,这时就可以创建跟原pod同名的pod了。这时,在节点上的pod会被立即设置为terminating状态,不过在被强制删除之前依然有一小段优雅删除周期。