4.Kubernetes Pod

Kubernetes Pod

Pod介绍

  1. Pod是kubernetes中最小部署单元;也是最简的单位。一个Pod代表着集群中运行的一个进程。
  2. Pod中封装着应用的容器,也可以是一组容器的集合;kubernetes中应用的一个实例,可能由一个或者多个容器组合在一起共享资源。

在Kubrenetes集群中Pod有如下两种使用方式:

  • Pod中运行一个容器

    这种模式是最常见的用法;在这种使用方式中,你可以把Pod想象成是单个容器的封装,kuberentes管理的是Pod而不是直接管理容器。

  • Pod中同时运行多个容器

    Pod中同时封装几个需要紧密耦合互相协作的容器,彼此之间共享资源。这些在同一个Pod中的容器可以互相协作成为一个service单元,即一个容器共享文件,另一个sidecar容器来更新这些文件。Pod将这些容器的存储资源作为一个实体来管理

  1. Pod存在存储、独立的网络IP,管理容器如何运行的策略选项。

Pod中共享的环境包括Linux的namespacecgroup和其他可能的隔绝环境,这与Docker容器一致。在Pod的环境中,每个容器中可能还有更小的子隔离环境。

Pod中可以共享两种资源:网络和存储。

  • 共享网络

Pod中的容器共享IP地址和端口号,Pod之中的容器通过localhost互相发现;并通过lo网卡进行通信。Pod中的容器与外界通信时,必须分配共享网络资源(例如使用宿主机的端口映射)。

例如SystemV信号或者POSIX共享内存。不同Pod之间的容器具有不同的IP地址,不能直接通过IPC通信。

  • 共享存储

Pod中的容器也有访问共享volume的权限,这些volume会被定义成pod的一部分并挂载到应用容器的文件系统中。Volume也可以用来持久化Pod中的存储资源,以防容器重启后文件丢失。

  1. 每个应用容器,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

  1. 用户通过Kubectl发送创建Pod的请求,提交至API-server;
  2. API-server会尝试将Pod信息写入到etcd中,待写入操作完成,API-server会将结果返回至用户;
  3. 此时API-server会开始反映etcd的状态变化;
  4. 所有的Kubernetes组件都是用watcher机制来跟踪检查API-server上的变化;
  5. Scheduler调度器会通过watch探测到API-server的Pod信息,但还未绑定到工作节点;
  6. Scheduler会选择一个合适的工作点,将信息同步到API-server;
  7. 调度信息由API-server写入到到etcd中去,而且API-server也开始反映该Pod的调度信息;
  8. Pod被调度到的工作节点的kubelet会在当前节点尝试调用Docker启动器,并将容器的结果状态返回至API-server;
  9. API-server将Pod状态信息写入到etcd中;
  10. 在etcd写入完成之后,API-server会将确认信息返回至kubelet;

当Pod创建成功之后,Pod可能会有多个状态,涉及到Pod生命周期的多个重要行为。

Pod的生命周期

它的大致生命周期过程如下图所示:

它主要包含以下几个重要的行为:

  • 初始化容器
  • 存活性探测
  • 就绪型探测
  • 生命周期钩子函数

初始化容器

初始化容器(init container)即应用程序的主容器启动之前要运行的容器,常用于为主容器执行一些预置操作,它们具有两种典型特征。

  1. 初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么Kubernetes需要重启它直到成功完成
  2. 每个初始化容器都必须按定义的顺序串行运行。

初始化容器的典型应用场景有:

  • 用于运行特定的工具程序,出于安全等方面的原因,这些程序不适于包含在主容器镜像中。
  • 提供主容器镜像中不具备的工具程序或自定义代码。
  • 为容器镜像的构建和部署人员提供了分离、独立工作的途径,使得他们不必协同起来制作单个镜像文件。
  • 初始化容器和主容器处于不同的文件系统视图中,因此可以分别安全地使用敏感数据,例如Secrets资源。
  • 初始化容器要先于应用容器串行启动并运行完成,因此可用于延后应用容器的启动直至其依赖的条件得到满足。

就绪型探测和存活性探测

在说容器探测之前,先说一下Kubernetes的探测方式,K8s包含三大类探测方式:

  1. ExecAction:在容器中执行一个命令,并根据其返回的状态码进行诊断的操作称为Exec探测,状态码为0表示成功,否则即为不健康状态。
  2. TCPSocketAction:通过与容器的某TCP端口尝试建立连接进行诊断,端口能够成功打开即为正常,否则为不健康状态。
  3. HTTPGetAction:通过向容器IP地址的某指定端口的指定path发起HTTP GET请求进行诊断,响应码为2xx或3xx时即为成功,否则为失败。

任何一种探测方式都可能存在三种结果:“Success”(成功)、“Failure”(失败)或“Unknown”(未知),只有第一种结果表示成功通过检测。

kubelet可在活动容器上执行两种类型的检测:存活性检测(livenessProbe)和就绪性检测(readinessProbe

存活性检测:用于判定容器是否处于“运行”(Running)状态;一旦此类检测未通过,kubelet将杀死容器并根据其restartPolicy决定是否将其重启;未定义存活性检测的容器的默认状态为“Success”。

就绪性检测:用于判断容器是否准备就绪并可对外提供服务;未通过检测的容器意味着其尚未准备就绪,端点控制器(如Service对象)会将其IP从所有匹配到此Pod对象的Service对象的端点列表中移除;检测通过之后,会再次将其IP添加至端点列表中。

生命周期钩子函数

从上图可以看出钩子函数也是Pod生命周期中的一个事件。
Kubernetes为容器提供了两种生命周期钩子。

  1. Start:于容器初始化创建完成之后立即运行的钩子处理器(handler),不过Kubernetes无法确保它一定会于容器中的ENTRYPOINT之前运行。
  2. Stop:于容器终止操作之前立即运行的钩子处理器,它以同步的方式调用,因此在其完成之前会阻塞删除容器的操作的调用。

Pod的终止过程

因为Pod作为在集群的节点上运行的进程,所以在不再需要的时候能够优雅的终止掉是十分必要的(比起使用发送KILL信号这种暴力的方式)。用户需要能够放松删除请求,并且知道它们何时会被终止,是否被正确的删除。用户想终止程序时发送删除pod的请求,在pod可以被强制删除前会有一个宽限期,会发送一个TERM请求到每个容器的主进程。一旦超时,将向主进程发送KILL信号并从API server中删除。如果kubelet或者container manager在等待进程终止的过程中重启,在重启后仍然会重试完整的宽限期。

示例流程如下:

  1. 用户发送删除pod的命令,默认宽限期是30秒;
  2. 在Pod超过该宽限期后API server就会更新Pod的状态为“dead”;
  3. 在客户端命令行上显示的Pod状态为“terminating”;
  4. 跟第三步同时,当kubelet发现pod被标记为“terminating”状态时,开始停止pod进程:
    • 如果在pod中定义了preStop hook,在停止pod前会被调用。如果在宽限期过后,preStop hook依然在运行,第二步会再增加2秒的宽限期;
    • 向Pod中的进程发送TERM信号;
  5. 跟第三步同时,该Pod将从该service的端点列表中删除,不再是replication controller的一部分。关闭的慢的pod将继续处理load balancer转发的流量;
  6. 过了宽限期后,将向Pod中依然运行的进程发送SIGKILL信号而杀掉进程。
  7. 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状态,不过在被强制删除之前依然有一小段优雅删除周期。

posted @ 2020-05-25 15:04  Gmiao  阅读(301)  评论(0编辑  收藏  举报