Kubernetes-Pod状态和生命周期管理

Kubernetes-Pod 状态和探针及生命周期管理

什么是 Pod?

  • Pod 是 kubernetes 中你可以创建和部署的最小也是最简的单位。一个 Pod 代表着集群中运行的一个进程。
  • Pod 中封装着应用的容器(有的情况下是好几个容器),存储、独立的网络IP,管理容器如何运行的策略选项。Pod 代表着部署的一个单位:kubernetes 中应用的一个实例,可能由一个或者多个容器组合在一起共享资源。
  • 在 Kubrenetes 集群中 Pod 有如下两种使用方式:
    • 一个 Pod 中运行一个容器。"每个 Pod 中一个容器"的模式是最常见的用法;在这种使用方式中,你可以把 Pod 想象成是单个容器的封装,kuberentes 管理的是 Pod 而不是直接管理容器。
    • 在一个 Pod 中同时运行多个容器。一个 Pod 中也可以同时封装几个需要紧密耦合互相协作的容器,它们之间共享资源。这些在同一个 Pod 中的容器可以互相协作成为一个 service 单位--一个容器共享文件,另一个 "sidecar" 容器来更新这些文件。Pod 将这些容器的存储资源作为一个实体来管理。
  • Pod 中共享的环境包括 Linux 的 namespace,cgroup 和其他可能的隔绝环境,这一点跟 Docker 容器一致。在 Pod 的环境中,每个容器中可能还有更小的子隔离环境。
  • Pod 中的容器共享 IP 地址和端口号,它们之间可以通过 localhost 互相发现。它们之间可以通过进程间通信,需要明白的是同一个 Pod 下的容器是通过lo网卡进行通信。例如 SystemV 信号或者 POSIX 共享内存。不同 Pod 之间的容器具有不同的IP地址,不能直接通过 IPC 通信。
  • Pod 中的容器也有访问共享 volume 的权限,这些 volume 会被定义成 pod 的一部分并挂载到应用容器的文件系统中。
  • 就像每个应用容器,pod 被认为是临时实体。在 Pod 的生命周期中,pod 被创建后,被分配一个唯一的 ID(UID),调度到节点上,并一致维持期望的状态直到被终结(根据重启策略)或者被删除。如果 node 死掉了,分配到了这个 node 上的 pod,在经过一个超时时间后会被重新调度到其他 node 节点上。一个给定的 pod(如 UID 定义的)不会被"重新调度"到新的节点上,而是被一个同样的 pod 取代,如果期望的话甚至可以是相同的名字,但是会有一个新的 UID(查看 replication controller 获取详情)。

Pod 中如何管理多个容器?

  • Pod 中可以同时运行多个进程(作为容器运行)协同工作。同一个 Pod 中的容器会自动的分配到同一个 node 上。同一个 Pod 中的容器共享资源、网络环境和依赖,它们总是被同时调度。
  • 注意在一个 Pod 中同时运行多个容器是一种比较高级的用法。只有当你的容器需要紧密配合协作的时候才考虑用这种模式。例如,你有一个容器作为 web 服务器运行,需要用到共享的 volume,有另一个"sidecar"容器来从远端获取资源更新这些文件,如下图所示:

mark

  • Pod中可以共享两种资源:网络和存储
    • 网络
    • 每个 Pod 都会被分配一个唯一的 IP 地址。Pod 中的所有容器共享网络空间,包括 IP 地址和端口。Pod 内部的容器可以使用 localhost 互相通信。Pod 中的容器与外界通信时,必须分配共享网络资源(例如使用宿主机的端口映射)。
    • 存储
    • Pod 可以指定多个共享的 Volume。Pod 中的所有容器都可以访问共享的 Volume。Volume 也可以用来持久化 Pod 中的存储资源,以防容器重启后文件丢失。

使用 Pod

  • 通常把Pod分为两类:

    • 自主式 Pod :这种 Pod 本身是不能自我修复的,当 Pod 被创建后(不论是由你直接创建还是被其他 Controller),都会被 Kuberentes 调度到集群的 Node 上。直到 Pod 的进程终止、被删掉、因为缺少资源而被驱逐、或者 Node 故障之前这个 Pod 都会一直保持在那个 Node上。Pod 不会自愈。如果 Pod 运行的 Node 故障,或者是调度器本身故障,这个 Pod 就会被删除。同样的,如果 Pod 所在 Node 缺少资源或者 Pod 处于维护状态,Pod 也会被驱逐。
    • 控制器管理的 Pod:Kubernetes 使用更高级的称为 Controller 的抽象层,来管理 Pod 实例。Controller 可以创建和管理多个 Pod,提供副本管理、滚动升级和集群级别的自愈能力。例如,如果一个 Node 故障, Controller 就能自动将该节点上的 Pod 调度到其他健康的 Node 上。虽然可以直接使用 Pod,但是在 Kubernetes 中通常是使用 Controller 来管理 Pod 的。如下图:
  • 每个 Pod 都有一个特殊的被称为"根容器"的 Pause 容器。 Pause 容器对应的镜像属于 Kubernetes 平台的一部分,除了 Pause 容器,每个 Pod 还包含一个或者多个紧密相关的用户业务容器

mark

  • Kubernetes 设计这样的 Pod 概念和特殊组成结构有什么用意?????

  • 原因一:在一组容器作为一个单元的情况下,难以对整体的容器简单地进行判断及有效地进行行动。比如,一个容器死亡了,此时是算整体挂了么?那么引入与业务无关的 Pause 容器作为 Pod 的根容器,以它的状态代表着整个容器组的状态,这样就可以解决该问题。

  • 原因二:Pod 里的多个业务容器共享 Pause 容器的 IP,共享 Pause 容器挂载的 Volume,这样简化了业务容器之间的通信问题,也解决了容器之间的文件共享问题。

Pod 的持久性和终止

Pod 持久性

  • Pod 在设计之处就不是作为持久化实体的。在调度失败、节点故障、缺少资源或者节点维护的状态下都会死掉会被驱逐。

  • 通常,用户不需要手动直接创建 Pod,而是应该使用 controller(例如 Deployments ),即使是在创建单个 Pod 的情况下。Controller 可以提供集群级别的自愈功能、复制和升级管理。

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 进程:
    • 1.如果在 pod 中定义了 preStop hook,在停止 pod 前会被调用。如果在宽限期过后,preStop hook 依然在运行,第二步会再增加 2 秒的宽限期;
    • 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=<seconds> 选项,允许用户设置自己的宽限期。如果设置为 0 将强制删除 pod。在 kubectl>=1.5 版本的命令中,你必须同时使用 --force--grace-period=0 来强制删除 pod。

  • Pod 的强制删除是通过在集群和 etcd 中将其定义为删除状态。当执行强制删除命令时,API server 不会等待该 pod 所运行在节点上的 kubelet 确认,就会立即将该 pod 从 API server 中移除,这时就可以创建跟原 pod 同名的 pod 了。这时,在节点上的 pod 会被立即设置为 terminating 状态,不过在被强制删除之前依然有一小段优雅删除周期。

Pause容器

  • Pause 容器,又叫 Infra 容器。我们检查 node 节点的时候会发现每个 node 上都运行了很多的 pause 容器,例如如下。
root@node-1:~# swapoff -a
root@node-1:~# docker ps | grep pause
9ebbe07cb572        registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1   "/pause"                 53 minutes ago      Up 53 minutes                           k8s_POD_pod-demo_default_6354a50d-0ec7-4741-93c3-b90b294c043b_0
c9eb2c969041        registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1   "/pause"                 3 hours ago         Up 3 hours                              k8s_POD_net-test2-8456fd74f7-ckpsr_default_a0f5fb1e-da73-412f-a4f6-d27ba5924733_5
3ce1ddf4347f        registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1   "/pause"                 3 hours ago         Up 3 hours                              k8s_POD_coredns-7f9c544f75-h9s2f_kube-system_252a6f3a-53b3-4d0d-a0a7-ea1e945b3058_6
f3095425a86b        registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1   "/pause"                 3 hours ago         Up 3 hours                              k8s_POD_kube-proxy-9gqxn_kube-system_f42aea7c-cc7b-4f5d-9bfa-1dfb0519d742_2
4be80d796ea9        registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1   "/pause"                 3 hours ago         Up 3 hours                              k8s_POD_kube-flannel-ds-amd64-bs7rw_kube-system_fda78b3d-3cb1-4bbb-a8e2-8e7c41a2b484_4
  • kubernetes 中的 pause 容器主要为每个业务容器提供以下功能:

    • 在 pod 中担任 Linux 命名空间共享的基础;
    • 启用 pid 命名空间,开启 init 进程
  • 如图

mark

root@node-1:~# docker run -d --name pause -p 8880:80 k8s.gcr.io/pause:3.1
d3057ceb54bc6565d28ded2c33ad2042010be73d76117775c130984c3718d609
root@node-1:~# 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
root@node-1:~# docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf --net=container:pause --ipc=container:pause --pid=container:pause nginx
d04f848b7386109085ee350ebb81103e4efc7df8e48da18404efb9712f926082
root@node-1:~#  docker run -d --name ghost --net=container:pause --ipc=container:pause --pid=container:pause ghost
332c86a722f71680b76b3072e85228a8d8e9608456c653edd214f06c2a77f112
  • 解析
  • pause 容器将内部的80端口映射到宿主机的 8880 端口,pause 容器在宿主机上设置好了网络 namespace 后, nginx 容器加入到该网络 namespace 中,我们看到 nginx 容器启动的时候指定了 --net=container:pause,ghost 容器同样加入到了该网络 namespace 中,这样三个容器就共享了网络,互相之间就可以使用 localhost 直接通信,--ipc=contianer:pause --pid=container:pause 就是三个容器处于同一个 namespace 中,init 进程为 pause,这时我们进入到 ghost 容器中查看进程情况。
root@node-1:~# 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 phase (Pod 的相位)

  • Pod 的 status 在信息保存在 PodStatus 中定义,其中有一个 phase 字段。

  • Pod 的相位(phase)是 Pod 在其生命周期中的简单宏观概述。该阶段并不是对容器或 Pod 的综合汇总,也不是为了做为综合状态机。

  • Pod 相位的数量和含义是严格指定的。除了本文档中列举的状态外,不应该再假定 Pod 有其他的 phase 值。

  • 下面是 phase 可能的值:

    • 第一阶段:
    • 挂起 (Pending):#正在创建 Pod 但是 Pod 中的容器还没有全部被创建完成,处于此状态的 Pod 应该检查 Pod 依赖的存储是否有权限挂载、镜像是否可以下载、调度是否正常等。
    • 失败 (Failed):#Pod 中有容器启动失败而导致 pod 工作异常。
    • 未知 (Unknown):#由于某种原因无法获得 pod 的当前状态,通常是由于与 pod 所在的 node 节点通信错误。
    • 成功 (Succeeded):#Pod 中的所有容器都被成功终止即 pod 里所有的 containers 均已 terminated。
    • 第二阶段:
    • 失败的 (Unschedulable):#Pod 不能被调度,kube-scheduler 没有匹配到合适的 node 节点。
    • 调度 (PodScheduled):#pod 正处于调度中,在 kube-scheduler 刚开始调度的时候,还没有将 pod 分配到指定的 pid,在筛选出合适的节点后就会更新 etcd 数据,将 pod 分配到指定的 pod。
    • 初始化 (Initialized):#所有 pod 中的初始化容器已经完成了。
    • 镜像拉取失败 (ImagePullBackOff):#Pod 所在的 node 节点下载镜像失败。
    • 运行 (Running):#Pod 内部的容器已经被创建并且启动。
    • 就绪 (Ready):#表示 pod 中的容器已经可以提供访问服务。
  • 下图是 Pod 的生命周期示意图,从图中可以看到 Pod 状态的变化

mark

Pod 的创建过程

  • Pod 是 Kubernetes 的基础单元,了解其创建的过程,更有助于理解系统的运作。
    • ①用户通过 kubectl 或其他 API 客户端提交 Pod Spec 给 API Server。
    • ②API Server 尝试将 Pod 对象的相关信息存储到 etcd 中,等待写入操作完成,API Server 返回确认信息到客户端。
    • ③API Server 开始反映 etcd 中的状态变化。
    • ④所有的 Kubernetes 组件通过 "watch" 机制跟踪检查 API Server 上的相关信息变动。
    • ⑤kube-scheduler(调度器)通过其 "watcher" 检测到 API Server 创建了新的 Pod 对象但是没有绑定到任何工作节点。
    • ⑥kube-scheduler 为 Pod 对象挑选一个工作节点并将结果信息更新到 API Server。
    • ⑦调度结果新消息由 API Server 更新到 etcd,并且 API Server 也开始反馈该Pod对象的调度结果。
    • ⑧Pod 被调度到目标工作节点上的 kubelet 尝试在当前节点上调用 docker engine 进行启动容器,并将容器的状态结果返回到 API Server。
    • ⑨API Server 将 Pod 信息存储到 etcd 系统中。
    • ⑩在 etcd 确认写入操作完成,API Server 将确认信息发送到相关的 kubelet。

Pod 的状态

  • Pod 有一个 PodStatus 对象,其中包含一个 PodCondition 数组。PodCondition 数组的每个元素都有一个 type 字段和一个 status 字段。type 字段是字符串,可能的值有 PodScheduled、Ready、Initialized 和 Unschedulable。status 字段是一个字符串,可能的值有 True、False 和 Unknown。

mark

Pod 存活性探测

  • pod 探针官方文档: https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

  • 在 pod 生命周期中可以做的一些事情。主容器启动前可以完成初始化容器,初始化容器可以有多个,他们是串行执行的,执行完成后就推出了,在主程序刚刚启动的时候可以指定一个 post start 主程序启动开始后执行一些操作,在主程序结束前可以指定一个 pre stop 表示主程序结束前执行的一些操作。在程序启动后可以做两类检测 liveness probe(存活性探测) 和 readness probe(就绪性探测)。如下图:

mark

  • 探针是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 调用由容器实现的 Handler。其存活性探测的方法有以下三种:

    • ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
    • TCPSocketAction:对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的。
    • HTTPGetAction:对指定的端口和路径上的容器的 IP 地址执行 HTTP Get 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
  • 每次探测都将获得以下三种结果之一:

    • 成功:容器通过了诊断。
    • 失败:容器未通过诊断。
    • 未知:诊断失败,因此不会采取任何行动。

探针类型

  • Kubelet 可以选择是否执行在容器上运行的两种探针类型:
    • 存活探针 (livenessProbe): 指示容器是否正在运行。如果存活探测失败,则 kubelet 会杀死容器,并且容器将受到其重启策略的影响。如果容器不提供存活探针,则默认状态为 Success。livenessProbe 探针用于用户控制是否重建 pod。
    • 就绪探针 (readinessProbe): 指示容器是否准备好服务请求。如果就绪探测失败,端点控制器将从与 Pod 匹配的所有 Service 的端点中删除该 Pod 的 IP 地址。初始延迟之前的就绪状态默认为 Failure。如果容器不提供就绪探针,则默认状态为 Success。readinessProbe 探针用于控制 pod 是否添加至 service。

探针配置

  • 探针有很多配置字段,可以使用这些字段精确的控制存活和就绪检测的行为。
initialDelaySeconds: 120
#初始化延迟时间,告诉 kubelet 在执行第一次探测前应该等待多少秒,默认是0秒,最小值是0

periodSeconds: 60
#探测周期间隔时间,指定了 kubelet 应该每多少秒秒执行一次存活探测,默认是 10 秒。最小值是 1

timeoutSeconds: 5
#单次探测超时时间,探测的超时后等待多少秒,默认值是 1 秒,最小值是 1。

successThreshold: 1
#从失败转为成功的重试次数,探测器在失败后,被视为成功的最小连续成功数,默认值是1,存活探测的这个值必须是 1,最小值是 1。

failureThreshold: 3
#从成功转为失败的重试次数,当 Pod 启动了并且探测到失败,Kubernetes 的重试次数,存活探测情况下的放弃就意味着重新启动容器,就绪探测情况下的放弃 Pod 会被打上未就绪的标签,默认值是 3,最小值是 1。
  • HTTP 探测器可以在 httpGet 上配置额外的字段:
host:
#连接使用的主机名,默认是 Pod 的 IP,也可以在HTTP头中设置 “Host” 来代替。

scheme: http
#用于设置连接主机的方式(HTTP 还是 HTTPS),默认是 HTTP。

path: /monitor/index.html
#访问 HTTP 服务的路径。

httpHeaders:
#请求中自定义的 HTTP 头,HTTP 头字段允许重复。

port: 80
#访问容器的端口号或者端口名,如果数字必须在 1 ~ 65535 之间。
  • 设置 ExexAction 探针举例:基于命令
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
  labels:
    app: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: k8s.harbor.com/base-images/redis:v1
        ports:
        - containerPort: 6379
        livenessProbe: #重启 Pod
        #readinessProbe: #从 service 中添加/摘除 Pod
          exec:
            command:
            #- /apps/redis/bin/redis-cli
            - /usr/local/bin/redis-cli
            - quit
          initialDelaySeconds: 5
          periodSeconds: 3
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 3
---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: magedu-redis-service-label
  name: magedu-redis-service
  namespace: default
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    protocol: TCP 
    targetPort: 6379
    nodePort: 30006
  selector:
    app: redis
  • 设置 HTTP 探针举例:基于 URL
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: k8s.harbor.com/base-images/nginx:v1
        ports:
        - containerPort: 80
        livenessProbe: #重建 Pod
        #readinessProbe: #从 service 中添加/摘除 Pod
          httpGet:
            #path: /monitor/monitor.html
            path: /index.html
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 3
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 3
---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: magedu-nginx-service-label
  name: magedu-nginx-service
  namespace: default
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    protocol: TCP 
    targetPort: 80
    nodePort: 30004 
  selector:
    app: nginx
  • 设置 TCP 探针举例:基于端口
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: k8s.harbor.com/base-images/nginx:v1
        ports:
        - containerPort: 80
        livenessProbe: #重建 Pod
        #readinessProbe: #从 service 中添加/摘除 Pod
          tcpSocket:
            port: 80
            #port: 8080
          initialDelaySeconds: 5
          periodSeconds: 3
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 3
---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: magedu-nginx-service-label
  name: magedu-nginx-service
  namespace: default
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    protocol: TCP 
    targetPort: 80
    nodePort: 30004
  selector:
    app: nginx

livenessProbe 和 readinessProbe 对比

  • 配置参数一样
  • livenessProbe #连续探测失败会重启、重建 pod,readinessProbe 不会执行重启或者重建Pod操作
  • livenessProbe #连续检测指定次数失败后会将容器置于 (Crash Loop BackOff) 切不可用,readinessProbe不会
  • readinessProbe #连续探测失败会从 service 的 endpointd 中删除该 Pod,livenessProbe 不具备此功能,但是会将容器挂起 livenessProbe
  • livenessProbe用户控制是否重启pod,readinessProbe 用于控制pod是否添加至service
  • 建议:
    两个探针都配置

Pod 的重启策略

  • PodSpec 中有一个 restartPolicy 字段,可能的值为 Always、OnFailure 和 Never。默认为 Always。 restartPolicy 适用于 Pod 中的所有容器。restartPolicy 仅指通过同一节点上的 kubelet 重新启动容器。失败的容器由 kubelet 以五分钟为上限的指数退避延迟(10秒,20秒,40秒...)重新启动,并在成功执行十分钟后重置。pod 一旦绑定到一个节点,Pod 将永远不会重新绑定到另一个节点。

  • k8s 在 Pod 出现异常的时候会自动将 Pod 重启以恢复 Pod 中的服务。

restartPolicy:
    Always:当容器异常时,k8s 自动重启该容器,ReplicationController/Replicaset/Deployment。
    OnFailure:当容器失败时 (容器停止运行且退出码不为0),k8s 自动重启该容器。
    Never:不论容器运行状态如何都不会重启该容器, Job 或 CronJob。

Pod 的生命

  • 一般来说,Pod 不会消失,直到人为销毁他们。这可能是一个人或控制器。这个规则的唯一例外是成功或失败的 phase 超过一段时间(由 master 确定)的 Pod 将过期并被自动销毁。

  • 有三种可用的控制器:

    • 使用 Job 运行预期会终止的 Pod,例如批量计算。Job 仅适用于重启策略为 OnFailure 或 Never 的 Pod。
    • 对预期不会终止的 Pod 使用 ReplicationController、ReplicaSet 和 Deployment ,例如 Web 服务器。 ReplicationController 仅适用于具有 restartPolicy 为 Always 的 Pod。
    • 提供特定于机器的系统服务,使用 DaemonSet 为每台机器运行一个 Pod 。
  • 所有这三种类型的控制器都包含一个 PodTemplate。建议创建适当的控制器,让它们来创建 Pod,而不是直接自己创建 Pod。这是因为单独的 Pod 在机器故障的情况下没有办法自动复原,而控制器却可以。

  • 如果节点死亡或与集群的其余部分断开连接,则 Kubernetes 将应用一个策略将丢失节点上的所有 Pod 的 phase 设置为 Failed。

livenessProbe 解析

root@node-1:~# kubectl explain pod.spec.containers.livenessProbe

KIND:     Pod
VERSION:  v1

RESOURCE: livenessProbe <Object>

exec  #command 的方式探测例如 ps 一个进程

failureThreshold #探测几次失败才算失败,默认是连续三次

periodSeconds #每次的多长时间探测一次,默认10s

timeoutSeconds #探测超市的秒数,默认1s

initialDelaySeconds  #初始化延迟探测,第一次探测的时候,因为主程序未必启动完成

tcpSocket #检测端口的探测

httpGet #http 请求探测
  • 举个例子:定义一个 liveness 的 pod 资源类型,基础镜像为 busybox,在 busybox 这个容器启动后会执行创建 /tmp/test 的文件啊,并删除,然后等待 3600 秒。随后定义了存活性探测,方式是以 exec 的方式执行命令判断 /tmp/test 是否存在,存在即表示存活,不存在则表示容器已经挂了。
root@node-1:~# vim liveness.yaml

apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec-pod
  namespace: default
  labels:
    name: myapp
spec:
  containers:
  - name: livess-exec
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","touch /tmp/test; sleep 30; rm -f /tmp/test; sleep 3600"]
    livenessProbe:
      exec:
        command: ["test","-e","/tmp/test"]
      initialDelaySeconds: 1
      periodSeconds: 3

root@node-1:~# kubectl apply -f lineness.yaml

资源需求和资源限制

  • 在 Docker 的范畴内,我们知道可以对运行的容器进行请求或消耗的资源进行限制。而在 Kubernetes 中,也有同样的机制,容器或Pod可以进行申请和消耗的资源就是 CPU 和内存。CPU 属于可压缩型资源,即资源的额度可以按照需求进行收缩。而内存属于不可压缩型资源,对内存的收缩可能会导致无法预知的问题。

  • 资源的隔离目前是属于容器级别,CPU 和内存资源的配置需要 Pod 中的容器 spec 字段下进行定义。其具体字段,可以使用 "requests" 进行定义请求的确保资源可用量。也就是说容器的运行可能用不到这样的资源量,但是必须确保有这么多的资源供给。而 "limits" 是用于限制资源可用的最大值,属于硬限制。

  • 在 Kubernetes 中,1 个单位的 CPU 相当于虚拟机的 1 颗虚拟 CPU(vCPU)或者是物理机上一个超线程的 CPU,它支持分数计量方式,一个核心(1core)相当于1000个微核心(millicores),因此 500m 相当于是 0.5 个核心,即二分之一个核心。内存的计量方式也是一样的,默认的单位是字节,也可以使用 E、P、T、G、M 和 K 作为单位后缀,或者是 Ei、Pi、Ti、Gi、Mi、Ki 等形式单位后缀

资源需求举例

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        memory: "128Mi"
        cpu: "200m"
  • 上面的配置清单中,nginx 请求的 CPU 资源大小为 200m,这意味着一个 CPU 核心足以满足 nginx 以最快的方式运行,其中对内存的期望可用大小为 128Mi,实际运行时不一定会用到这么多的资源。考虑到内存的资源类型,在超出指定大小运行时存在会被 OOM killer 杀死的可能性,于是该请求值属于理想中使用的内存上限。

资源限制举例

  • 容器的资源需求只是能够确保容器运行时所需要的最少资源量,但是并不会限制其可用的资源上限。当应用程序存在 Bug 时,也有可能会导致系统资源被长期占用的情况,这就需要另外一个 limits 属性对容器进行定义资源使用的最大可用量。CPU 是属于可压缩资源,可以进行自由地调节。而内存属于硬限制性资源,当进程申请分配超过 limit 属性定义的内存大小时,该 Pod 将会被 OOM killer 杀死。如下:
root@node-1:~# vim memleak-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: memleak-pod
  labels:
    app: memleak
spec:
  containers:
  - name: simmemleak
    image: saadali/simmemleak
    resources:
      requests:
        memory: "64Mi"
        cpu: "1"
      limits:
        memory: "64Mi"
        cpu: "1"

root@node-1:~# kubectl apply -f memleak-pod.yaml 
pod/memleak-pod created
root@node-1:~# kubectl get pods -l app=memleak
NAME          READY     STATUS      RESTARTS   AGE
memleak-pod   0/1       OOMKilled   2          12s
root@node-1:~# kubectl get pods -l app=memleak
NAME          READY     STATUS             RESTARTS   AGE
memleak-pod   0/1       CrashLoopBackOff   2          28s
  • Pod 资源默认的重启策略为 Always,在 memleak 因为内存限制而终止会立即重启,此时该 Pod 会被 OOM killer 杀死,在多次重复因为内存资源耗尽重启会触发 Kunernetes 系统的重启延迟,每次重启的时间会不断拉长,后面看到的 Pod 的状态通常为 "CrashLoopBackOff"。

  • 这里还需要明确的是,在一个 Kubernetes 集群上,运行的 Pod 众多,那么当节点都无法满足多个 Pod 对象的资源使用时,是按照什么样的顺序去终止这些Pod对象呢??

  • Kubernetes 是无法自行去判断的,需要借助于 Pod 对象的优先级进行判定终止 Pod 的优先问题。根据 Pod 对象的 requests 和 limits 属性,Kubernetes 将 Pod 对象分为三个服务质量类别:

    • Guaranteed:每个容器都为 CPU 和内存资源设置了相同的 requests 和 limits 属性的 Pod 都会自动归属于该类别,属于最高优先级。
    • Burstable:至少有一个容器设置了 CPU 或内存资源的 requests 属性,但不满足 Guaranteed 类别要求的资源归于该类别,属于中等优先级。
    • BestEffort:未对任何容器设置 requests 属性和 limits 属性的 Pod 资源,自动归于该类别,属于最低级别。
  • 所以顾名思义,最低级别,死得越快!

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注