K8S资源之Pod

一、Pod简介

   Pod是K8S最小(最基本的)资源调度单位,一个Pod可以由一个及以上的容器组成,这些容器共享存储、网络,一般一个Pod的中容器都是业务具有关联的,比如起一个sidecar容器收集业务容器的日志。

   资源创建示例如下:

apiVersion: v1
kind: Pod
metadata:
  name: hello
spec:
  containers:
  - name: hello 
    image: busybox
    imagePullPolicy: IfNotPresent    #拉取镜像策略,always(默认):不管有没有镜像都会去仓库拉取镜像,IfNotPresent:没有镜像才回去拉去镜像,nerver:只使用本地镜像
    command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']

二、Pod原理

   Pod是通过Linux中的NameSpace,Cgroup技术实现容器隔离。简单理解就是类似于共享名字空间和文件系统卷的一组Docker容器。

 2.1 网络共享


   Pod共享Network NameSpace。在kubelet 服务中有个启动参数 --pod-infra-container-image=harbor.dy.com/baseimages/pause-amd64:3.1。这个容器叫做 Infra容器(我更喜欢叫跟容器),而且这个容器在 Pod 中永远都是第一个被创建的容器,这样是不是其他容器都加入到这个 Infra容器就可以了,这样就完全实现了 Pod 中的所有容器都和 Infra 容器共享同一个 Network Namespace 了。

root@k8s-master:~# /opt/kubernetes/bin/kubelet --help |grep infra
      --pod-infra-container-image string                                                                          The image whose network/ipc namespaces containers in each pod will use. This docker-specific flag only works when container-runtime is set to docker. (default "k8s.gcr.io/pause:3.2")

 2.2 存储共享


  一个 Pod 可以设置一组共享的存储卷。 Pod 中的所有容器都可以访问该共享卷,从而允许这些容器共享数据。 卷还允许 Pod 中的持久数据保留下来,即使其中的容器需要重新启动。例子如下:

apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: test1
    image: busybox
    command: ['sh', '-c', 'i=0;while true;do echo "$i log" > /mnt/test.log;i=$((i+1));sleep 1;done']
    volumeMounts:
    - mountPath: /mnt/
      name: test-volume

  - name: test2
    image: busybox
    command: ['/bin/sh','-c', 'tail -f /mnt/test.log']
    volumeMounts:
    - mountPath: /mnt/
      name: test-volume

  volumes:
  - name: test-volume
    hostPath:
      path: /var/log/

三、Pod 生命周期


   Pod 遵循一个预定义的生命周期,起始于 Pending 阶段,如果至少 其中有一个主要容器正常启动,则进入Running,之后取决于 Pod 中是否有容器以 失败状态结束而进入Succeeded 或者Failed阶段。Pod在其生命周期中只会被调度一次。 一旦 Pod 被调度(分派)到某个节点,Pod 会一直在该节点运行,直到 Pod 停止或者 被终止。

root@k8s-master:~# kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
java-demo-68764445d9-ksmrh   1/1     Running   2          9d
java-demo-68764445d9-q6v2n   1/1     Running   2          9d
java-demo-68764445d9-r6jpf   1/1     Running   2          9d
test                         2/2     Running   0          49m

3.1 Pod阶段


   首先先了解下 Pod 的状态值,我们可以通过 kubectl explain pod.status 命令来了解关于 Pod 状态的一些信息,Pod 的状态定义在 PodStatus 对象中,其中有一个 phase 字段,下面是 phase 的可能取值

状态描述
Pending(悬决)Pod 已被 Kubernetes 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间;
Running(运行中)Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态;
Succeeded(成功)Pod 中的所有容器都已成功终止,并且不会再重启;
Failed(失败)Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止;
Unknown(未知)因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。

3.2 Pod状态


   Pod 有一个 PodStatus 对象,其中包含一个 PodConditions 数组。Pod 可能通过也可能未通过其中的一些状况测试。

kubectl get pod test -o yaml 


PodScheduled:Pod 已经被调度到某节点;

ContainersReady:Pod 中所有容器都已就绪;

Initialized:所有的 Init 容器 都已成功启动;

Ready:Pod 可以为请求提供服务,并且应该被添加到对应服务的负载均衡池中。

Type: Condition 类型,包括以下方面:

字段名称描述
status表明该状况是否适用,可能的取值有 "True", "False" 或 "Unknown";
lastProbeTime上次探测 Pod 状况时的时间戳;
lastTransitionTimePod 上次从一种状态转换到另一种状态时的时间戳;
reason机器可读的、驼峰编码(UpperCamelCase)的文字,表述上次状况变化的原因;
message人类可读的消息,给出上次状态转换的详细信息。

3.3容器状态


  一旦调度器将 Pod 分派给某个节点,kubelet 就通过 容器运行时 开始为 Pod 创建容器。 容器的状态有三种:Waiting(等待)Running(运行中)Terminated(已终止)

要检查 Pod 中容器的状态,你可以使用

kubectl describe pod test(容器名称 ) 

其输出中包含 Pod 中每个容器的状态。

状态描述
Waiting如果容器并不处在 Running 或 Terminated 状态之一,它就处在 Waiting 状态。 处于 Waiting 状态的容器仍在运行它完成启动所需要的操作:例如,从某个容器镜像 仓库拉取容器镜像,或者向容器应用 Secret 数据等等。 当你使用 kubectl 来查询包含 Waiting 状态的容器的 Pod 时,你也会看到一个 Reason 字段,其中给出了容器处于等待状态的原因。
RunningRunning 状态表明容器正在执行状态并且没有问题发生。 如果配置了 postStart 回调,那么该回调已经执行且已完成。 如果你使用 kubectl 来查询包含 Running 状态的容器的 Pod 时,你也会看到 关于容器进入 Running 状态的信息
Terminated处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。 如果你使用 kubectl 来查询包含 Terminated 状态的容器的 Pod 时,你会看到 容器进入此状态的原因、退出代码以及容器执行期间的起止时间。如果容器配置了 preStop 回调,则该回调会在容器进入 Terminated 状态之前执行。

3.5 重启策略

   Pod 的 spec 中包含一个 restartPolicy 字段,其可能取值包括 Always、OnFailure 和 Never。默认值是 Always。

root@k8s-master:~# kubectl explain pod.spec.restartPolicy
KIND:     Pod
VERSION:  v1

FIELD:    restartPolicy <string>

DESCRIPTION:
     Restart policy for all containers within the pod. One of Always, OnFailure,
     Never. Default to Always. More info:
     https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy


  restartPolicy 适用于 Pod 中的所有容器。restartPolicy 仅针对同一节点上 kubelet 的容器重启动作。当 Pod 中的容器退出时,kubelet 会按指数回退 方式计算重启的延迟(10s、20s、40s、…),其最长延迟为 5 分钟。 一旦某容器执行了 10 分钟并且没有出现问题,kubelet 对该容器的重启回退计时器执行 重置操作。

3.6 Init

  Init 容器是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。

  如果为一个 Pod 指定了多个 Init 容器,这些容器会按顺序逐个运行。 每个 Init 容器必须运行成功,下一个才能够运行。当所有的 Init 容器运行完成时, Kubernetes 才会为 Pod 初始化应用容器并像平常一样运行。

apiVersion: v1
kind: Pod
metadata:
 name: init-demo
spec:
 containers:
 - name: nginx
   image: nginx
   ports:
   - containerPort: 80
   volumeMounts:
   - name: workdir
     mountPath: /usr/share/nginx/html
 # These containers are run during pod initialization
 initContainers:
 - name: install
   image: busybox
   command:
   - wget
   - "-O"
   - "/work-dir/index.html"
   - http://info.cern.ch
   volumeMounts:
   - name: workdir
     mountPath: "/work-dir"
 dnsPolicy: Default
 volumes:
 - name: workdir
   emptyDir: {}

root@k8s-master:~# kubectl apply -f init.yaml
pod/init-demo created
root@k8s-master:~# kubectl get pod init-demo
NAME        READY   STATUS     RESTARTS   AGE
init-demo   0/1     Init:0/1   0          14s

  Init 容器将共享卷挂载到了 /work-dir 目录,应用容器将共享卷挂载到了 /usr/share/nginx/html 目录。 Init 容器执行完wget命令就终止了。

3.7 探针(Probe)

   Probe是由kubelet执行的,定期对容器进行检查。K8S中提供有三种探针:

  livenessProbe:指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器, 并且容器将根据其重启策略决定重新拉起容器。如果容器不提供存活探针, 则默认状态为 Success。


  readinessProbe:指示容器是否准备好为请求提供服务。如果就绪态探测失败, 端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址(换句话说控制哪个 Pod 作为 Service 的后端。 在 Pod 还没有准备好的时候,会从 Service 的负载均衡器中被剔除的。)。 初始延迟之前的就绪态的状态值默认为 Failure。 如果容器不提供就绪态探针,则默认状态为 Success。


  startupProbe: 指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被 禁用,直到此探针成功为止。如果启动探测失败,kubelet 将杀死容器,而容器依其 重启策略进行重启。 如果容器没有提供启动探测,则默认状态为 Success。启动探测器可以知道应用程序容器什么时候启动了。 如果配置了这类探测器,就可以控制容器在启动成功后再进行存活性和就绪检查, 确保这些存活、就绪探测器不会影响应用程序的启动。 这可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。startupProbe在1.16版本假如alpha版。


 &emsp探针可以采取以下三种方式实现:

  ExecAction: 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。

  TCPSocketAction: 对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。

  HTTPGetAction: 对容器的 IP 地址上指定端口和路径执行 HTTP Get 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。

使用存货探针来举例:

  许多长时间运行的应用程序最终进入死锁状态(服务运行,但是无法提供服务),除非重新启动,否则无法恢复。 Kubernetes 提供了存活探测器来发现并补救这种情况。

   exec例子:

apiVersion: v1
kind: Pod
metadata:
  name: liveness-test
spec:
  containers:
  - name: liveness-exec
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy;sleep 30;rm -f /tmp/health;sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5  #  kubelet 在执行第一次探测前应该等待 5 秒,可以考虑时间稍微久一点  
      periodSeconds: 5 # kubelet每5秒执行一次存活探测


  kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。 如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。

apiVersion: v1
kind: Pod
metadata:
  name: liveness-test
spec:
  containers:
  - name: liveness-http
    image: harbor.dy.com/java-demo/nginx:latest
    livenessProbe:
      httpGet:
        path: /health/healthy.html
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 5


   httpGet例子(1.18版本):

apiVersion: v1
kind: Pod
metadata:
  name: liveness-test
spec:
  containers:
  - name: liveness-http
    image: harbor.dy.com/java-demo/nginx:latest
    livenessProbe:
      httpGet:
        path: /health/healthy.html
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 5


   TCP例子(1.18版本):

apiVersion: v1
kind: Pod
metadata:
  name: liveness-test
spec:
  containers:
  - name: liveness-http
    image: harbor.dy.com/java-demo/nginx:latest
    livenessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 5

3.8 回调

  K8S提供了两种回调函数:

  PostStart:这个钩子在容器创建后立即执行。但是,并不能保证钩子将在容器 ENTRYPOINT 之前运行,因为没有参数传递给处理程序。主要用于资源部署、环境准备等。不过需要注意的是如果钩子花费太长时间以至于不能运行或者挂起,容器将不能达到 running 状态。

  PostStop:在容器因 API 请求或者管理事件(诸如存活态探针、启动探针失败、资源抢占、资源竞争等) 而被终止之前,此回调会被调用。 如果容器已经处于已终止或者已完成状态,则对 preStop 回调的调用将失败。 在用来停止容器的 TERM 信号被发出之前,回调必须执行结束。 Pod 的终止宽限周期在 PreStop 回调被执行之前即开始计数,所以无论 回调函数的执行结果如何,容器最终都会在 Pod 的终止宽限期内被终止。 没有参数会被传递给处理程序。


  k8s提供两种方式实现上面两种回调函数:

  Exec - 用于执行一段特定的命令,不过要注意的是该命令消耗的资源会被计入容器。

  HTTP - 对容器上的特定的端点执行 HTTP 请求。

  举个栗子:

apiVersion: v1
kind: Pod
metadata:
  name: hook-demo2
spec:
  containers:
  - name: hook-demo2
    image: nginx
    lifecycle:
      preStop:
        exec:
          command: ["/usr/sbin/nginx","-s","quit"]  # 优雅退出

---
apiVersion: v1
kind: Pod
metadata:
  name: hook-demo3
spec:
  volumes:
  - name: message
    hostPath:
      path: /tmp
  containers:
  - name: hook-demo2
    image: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - name: message
      mountPath: /usr/share/
    lifecycle:
      preStop:
        exec:
          command: ['/bin/sh', '-c', 'echo Hello from the preStop Handler > /usr/share/message']


  使用上述资源创建yaml文件创建完成后,我们可以直接删除 hook-demo2 这个 Pod,在容器删除之前会执行 preStop 里面的优雅关闭命令,这个用法在后面我们的滚动更新的时候用来保证我们的应用零宕机非常有用。第二个 Pod 我们声明了一个 hostPath 类型的 Volume,在容器里面声明挂载到了这个 Volume,所以当我们删除 Pod,退出容器之前,在容器里面输出的信息也会同样的保存到宿主机(一定要是 Pod 被调度到的目标节点)的 /tmp 目录下面。

  注意:回调的日志没有暴露给 Pod,所以只能通过 describe 命令来获取,如果有错误将可以看到 FailedPostStartHook 或 FailedPreStopHook 这样的 event。

3.9 Pod结束生命周期


   由于 Pod 所代表的是在集群中节点上运行的进程,当不再需要这些进程时允许其体面地 终止是很重要的。一般不应武断地使用 KILL 信号终止它们,导致这些进程没有机会 完成清理操作。


   通常情况下,容器运行时会发送一个 TERM 信号到每个容器中的主进程。 很多容器运行时都能够注意到容器镜像中 STOPSIGNAL 的值,并发送该信号而不是 TERM。 一旦超出了体面终止限期,容器运行时会向所有剩余进程发送 KILL 信号,之后 Pod 就会被从 API 服务器 上移除。如果 kubelet 或者容器运行时的管理服务在等待进程终止期间被重启, 集群会从头开始重试,赋予 Pod 完整的体面终止限期。


  当用户请求删除含有 Pod 的资源对象时(如 Deployment 等),K8S 为了让应用程序优雅关闭(即让应用程序完成正在处理的请求后,再关闭软件),K8S 提供两种信息通知:

    默认:K8S 通知 node 执行docker stop命令,docker 会先向容器中 PID 为 1 的进程发送系统信号SIGTERM,然后等待容器中的应用程序终止执行,如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送SIGKILL的系统信号强行 kill 掉进程

    使用 Pod 生命周期(利用PreStop回调函数),它在发送终止信号之前执行
默认所有的优雅退出时间都在30秒内。kubectl delete 命令支持 --grace-period=选项,这个选项允许用户用他们自己指定的值覆盖默认值。值’0’代表强制删除 pod。 在 kubectl 1.5 及以上的版本里,执行强制删除时必须同时指定 --force --grace-period=0。

  强制删除一个 pod 是从集群状态还有 etcd 里立刻删除这个 pod,只是当 Pod 被强制删除时, APIServer 不会等待来自 Pod 所在节点上的 kubelet 的确认信息:pod 已经被终止。在 API 里 pod 会被立刻删除,在节点上, pods 被设置成立刻终止后,在强行杀掉前还会有一个很小的宽限期。

3.10 Pod资源限制

  通过为集群中运行的容器配置资源请求和限制,可以有效利用集群节点上可用的资源。 通过限制Pod 的内存请求保持在较低水平,可以更好地安排 Pod 调度。

  计算机中的 CPU 的资源是按“时间片”的方式来进行分配的,系统里的每一个操作都需要 CPU 的处理,所以,哪个任务要是申请的 CPU 时间片越多,那么它得到的 CPU 资源就越多。

  PS:CGroup 里面对于 CPU 资源的单位换算:

1 CPU =  1000 millicpu(1 Core = 1000m)

0.5 CPU = 500 millicpu (0.5 Core = 500m)

这里的 m 就是毫、毫核的意思,Kubernetes 集群中的每一个节点可以通过操作系统的命令来确认本节点的 CPU 内核数量,然后将这个数量乘以1000,得到的就是节点总 CPU 总毫数。在 Pod 里面我们可以通过下面的四个参数来现在和请求 CPU 和内存资源:
spec.containers[].resources.limits.cpu:CPU 上限值,可以短暂超过,容器也不会被停止
spec.containers[].resources.requests.cpu:CPU请求值,Kubernetes 调度算法里的依据值,可以超过
需要注意的是,如果resources.requests.cpu设置的值大于集群里每个节点的最大 CPU 核心数,那么这个 Pod 将无法调度,因为没有节点能满足它。
spec.containers[].resources.limits.memory:最大内存限制
spec.containers[].resources.requests.memory:请求存限制

所以requests 是用于集群调度使用的资源,而 limits 才是真正的用于资源限制的配置,如果你需要保证的你应用优先级很高,也就是资源吃紧的情况下最后再杀掉你的 Pod,那么你就把你的 requests 和 limits 的值设置成一致

apiVersion: v1
kind: Pod
metadata:
  name: limit-demo
spec:
  containers:
  - name: limit-demo
    image: nginx
    resources:
      limits:
        memory: "200Mi" #内存200M
        cpu: "1"
      requests:
        memory: "100Mi"
        cpu: "0.5"

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐