Kubernetes——资源调度与Pod探针

目录

前言

一、资源调度策略

1.默认调度器(Default Scheduler)

2.自定义调度器(Custom Scheduler)

3.亲和性与反亲和性(Affinity and Anti-Affinity)

4.污点与容忍(Taints and Tolerations)

5.资源配额和限制(Resource Quotas and Limits)

6.优先级和抢占(Priority and Preemption)

7.多调度器(Multiple Schedulers)

二、资源调度机制实现原理

1.调度器组件(Scheduler Component)

2.调度队列(Scheduling Queue)

3.调度循环(Scheduling Cycle)

4.过滤阶段(Filtering Phase)

5.打分阶段(Scoring Phase)

6.节点绑定(Node Binding)

7.Pod条件(Pod Conditions)

8.抢占(Preemption)

9.扩展性(Extensibility)

10.事件和日志(Events and Logging)

11.回退机制(Fallback Mechanism)

三、资源限制

1.CPU资源单位

2.内存资源单位

3.资源限制示例

四、Pod探针——健康检查

1.Pod探针的三种规则

2.Probe支持的三种检查方法

3.探针原理

3.1Exec方式

3.2http-Get方式

3.3TcpSocket方式

3.4就绪检测1

3.5就绪检测2

3.6启动和退出动作探测

4.探针参数

5.探针结果

五、探针总结

1.ReadinessProbe

2.LivenessProbe

3.StartupProbe

六、实践

七、总结

1.Pod容器的资源限制

2.Pod探针

2.1探针机制

2.2探针方法

3.Pod状态

4.Container生命周期


前言

Kubernetes(K8s)的资源调度策略是高度可配置和可扩展的,以适应不同类型的工作负载和集群配置。

一、资源调度策略

1.默认调度器(Default Scheduler)

预选阶段(Predicates):这一阶段会过滤掉无法满足Pod需求的节点。例如,如果节点的CPU或内存资源不足,或者不满足Pod的节点选择器(Node Selector)要求,那么该节点会被排除。

优选阶段(Priorities):在预选阶段后,调度器会对剩余的节点进行评分,评分标准可能包括节点的资源空闲程度、Pod与节点的亲和性、节点上的Pod密度等。

绑定阶段(Binding):选择得分最高的节点,并将Pod绑定到该节点上。

2.自定义调度器(Custom Scheduler)

用户可以编写自己的调度器,以实现特定的调度策略。自定义调度器可以与默认调度器并行运行,或者通过配置替换默认调度器。

3.亲和性与反亲和性(Affinity and Anti-Affinity)

节点亲和性(Node Affinity):允许Pod指定对节点的偏好,例如,可以指定Pod只调度到具有特定标签的节点上。

Pod亲和性与反亲和性(Pod Affinity and Anti-Affinity):这些规则允许Pod根据其他Pod的运行位置来选择调度位置,或者避免与某些Pod运行在同一个节点上。

4.污点与容忍(Taints and Tolerations)

污点(Taints):节点可以设置污点,以防止Pod调度到该节点上。例如,如果一个节点需要维护,可以给它添加一个污点。

容忍(Tolerations):Pod可以定义容忍,以允许它们被调度到有相应污点的节点上。

5.资源配额和限制(Resource Quotas and Limits)

资源配额(Resource Quotas):用于管理计算资源在命名空间级别的使用,可以限制Pod可以使用的资源总量。

资源限制(Resource Limits):Pod可以设置资源请求(requests)和限制(limits),以确保它们在运行时得到足够的资源,并且不会超过某个最大值。

6.优先级和抢占(Priority and Preemption)

优先级(Priority):Pod可以设置优先级,用于在资源不足时决定哪个Pod可以被抢占。

抢占(Preemption):当高优先级的Pod无法调度时,调度器可能会终止低优先级的Pod以释放资源。

7.多调度器(Multiple Schedulers)

在大型集群中,可能需要多个调度器来处理不同类型的工作负载。例如,一个调度器可能专门用于在线服务,而另一个可能用于批处理作业。

总之,Kubernetes的资源调度是集群管理中的关键组成部分,它们确保了Pod可以有效地利用集群资源,同时满足特定的业务需求和约束。通过提供丰富的调度选项,Kubernetes能够适应各种复杂的应用场景。

二、资源调度机制实现原理

Kubernetes的资源调度内部实现机制涉及到多个组件和步骤。

1.调度器组件(Scheduler Component)

Kubernetes调度器是一个独立的组件,它负责将Pods分配到集群中的节点上。调度器不断 watch API server,一旦有新的Pod被创建并且尚未被调度,调度器就会尝试为它找到一个合适的节点。

2.调度队列(Scheduling Queue)

调度器维护了一个待调度Pod的队列。这个队列中的Pod按照一定的优先级排序,确保高优先级的Pod先被调度。

3.调度循环(Scheduling Cycle)

调度器为每个待调度的Pod执行一个调度循环,这个循环包括两个主要步骤:过滤(Filtering)和打分(Scoring)。

4.过滤阶段(Filtering Phase)

在这个阶段,调度器会运行一系列预选规则(Predicates)来过滤出不能运行该Pod的节点。这些规则包括但不限于:节点是否有足够的资源、节点是否满足Pod的亲和性要求、节点是否有Pod所容忍的污点等。

5.打分阶段(Scoring Phase)

过滤阶段完成后,调度器会对剩余的节点进行打分。打分基于一系列优先级函数(Priorities),这些函数考虑了各种因素,如节点的资源空闲程度、Pod与节点的亲和性、节点上的Pod密度等。

6.节点绑定(Node Binding)

一旦调度器选出了最佳节点,它就会将该Pod绑定到该节点上。这个过程是通过更新Pod的API对象来完成的,将Pod的`spec.nodeName`字段设置为选中的节点名。

7.Pod条件(Pod Conditions)

调度器会检查Pod的状态和条件,以确保Pod可以正常运行。例如,如果Pod依赖于一个ConfigMap或Secret,调度器会确保这些资源已经准备好。

8.抢占(Preemption)

如果一个高优先级的Pod无法调度,调度器可能会启动抢占逻辑,尝试删除一些低优先级的Pod来为高优先级的Pod腾出空间。

9.扩展性(Extensibility)

Kubernetes调度器允许通过插件(Plugins)来扩展其功能。这些插件可以在过滤阶段和打分阶段被调用,以实现自定义的调度逻辑。

10.事件和日志(Events and Logging)

调度器会生成事件和日志,这些信息对于调试和理解调度决策非常有用。

11.回退机制(Fallback Mechanism)

如果调度器在给定的时间内无法找到一个合适的节点,Pod可能会被放入一个待定状态,等待重新调度。

总之,上述Kubernetes调度器的内部实现机制是高度优化的,以确保能够快速、有效地将Pods调度到集群中的节点上。通过不断地迭代和改进,Kubernetes调度器能够适应不断变化的集群状态和复杂的用户需求。

三、资源限制

当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存大小,以及其他类型的资源。

当为 Pod 中的容器指定了 request 资源时,调度器就使用该信息来决定将 Pod 调度到哪个节点上。当还为容器指定了 limit 资源时,kubelet 就会确保运行的容器不会使用超出所设的 limit 资源量。kubelet 还会为容器预留所设的 request 资源量, 供该容器使用。

如果 Pod 运行所在的节点具有足够的可用资源,容器可以使用超出所设置的 request 资源量。不过,容器不可以使用超出所设置的 limit 资源量。

如果给容器设置了内存的 limit 值,但未设置内存的 request 值,Kubernetes 会自动为其设置与内存 limit 相匹配的 request 值。 类似的,如果给容器设置了 CPU 的 limit 值但未设置 CPU 的 request 值,则 Kubernetes 自动为其设置 CPU 的 request 值 并使之与 CPU 的 limit 值匹配。

官网示例:
https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

Pod 和 容器 的资源请求和限制:
spec.containers[].resources.requests.cpu		//定义创建容器时预分配的CPU资源
spec.containers[].resources.requests.memory		//定义创建容器时预分配的内存资源
spec.containers[].resources.limits.cpu			//定义 cpu 的资源上限 
spec.containers[].resources.limits.memory		//定义内存的资源上限

1.CPU资源单位

CPU 资源的 request 和 limit 以 cpu 为单位。Kubernetes 中的一个 cpu 相当于1个 vCPU(1个超线程)。
Kubernetes 也支持带小数 CPU 的请求。spec.containers[].resources.requests.cpu 为 0.5 的容器能够获得一个 cpu 的  、一半 CPU 资源(类似于Cgroup对CPU资源的时间分片)。表达式 0.1 等价于表达式 100m(毫核),表示每 1000 毫秒内容器可以使用的 CPU 时间总量为 0.1*1000 毫秒。
Kubernetes 不允许设置精度小于 1m 的 CPU 资源。 

2.内存资源单位

内存的 request 和 limit 以字节为单位。可以以整数表示,或者以10为底数的指数的单位(E、P、T、G、M、K)来表示, 或者以2为底数的指数的单位(Ei、Pi、Ti、Gi、Mi、Ki)来表示。
如:1KB=10^3=1000,1MB=10^6=1000000=1000KB,1GB=10^9=1000000000=1000MB
1KiB=2^10=1024,1MiB=2^20=1048576=1024KiB

在买硬盘的时候,操作系统报的数量要比产品标出或商家号称的小一些,主要原因是标出的是以 MB、GB为单位的,1GB 就是1,000,000,000Byte,而操作系统是以2进制为处理单位的,因此检查硬盘容量时是以MiB、GiB为单位,1GiB=2^30=1,073,741,824,相比较而言,1GiB要比1GB多出1,073,741,824-1,000,000,000=73,741,824Byte,所以检测实际结果要比标出的少一些。

3.资源限制示例

官方网站:https://kubernetes.io/zh-cn/docs/concepts/configuration/manage-resources-containers/

vim demo1.yaml

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: web
    image: nginx
    env:
    - name: WEB_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: db
    image: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "abc123"
    resources:
      requests:
        memory: "64Mi"
        cpu: "0.25"
      limits:
        memory: "128Mi"
        cpu: "500m"


kubectl apply -f demo1.yaml

kubectl get pod

OOMKilled:内存资源不足

kubectl logs frontend -c db

查找到该Pod容器资源是Mysql无法正常Running,显示的是内存资源不足,无法正常启用,所以我们要对该问题进行解决。

kubectl delete -f demo1.yaml

vim demo1.yaml 

kubectl apply -f demo1.yaml 

kubectl get pod

kubectl describe pod frontend

kubectl describe nodes node

四、Pod探针——健康检查

探针是由kubelet对容器执行的定期诊断检查。

Kubernetes 可以对业务进行故障自愈,即针对运行异常的 Pod 进行重启。那么 K8S 是如何认定 Pod 是否异常呢?

Kubelet 组件根据 Pod 中容器退出状态码判定 Pod 是否异常,然后重启 Pod,进而达到故障自愈的效果。但是有些复杂场景,这种判定 Pod 异常的机制就无法满足了。

例如,Pod 中容器进程依然存在,但是容器死锁了,那么服务肯定是异常了,但是这时候利用上述异常检测机制就无法认定 Pod 异常了,从而无法重启 Pod。

这时候就需要利用 K8S 中的探针检测机制

1.Pod探针的三种规则

  • LivenessProbe :判断容器是否正在运行。如果探测失败,则kubelet会杀死容器,并且容器将根据 restartPolicy 来设置 Pod 状态。 如果容器不提供存活探针,则默认状态为Success。
  • ReadinessProbe :判断容器是否准备好接受请求。如果探测失败,端点控制器将从与 Pod 匹配的所有 service 址endpoints 中剔除删除该Pod的IP地。 初始延迟之前的就绪状态默认为Failure。如果容器不提供就绪探针,则默认状态为Success。
  • StartupProbe(这个1.17版本增加的):判断容器内的应用程序是否已启动,主要针对于不能确定具体启动时间的应用。如果配置了 startupProbe 探测,在则在 startupProbe 状态为 Success 之前,其他所有探针都处于无效状态,直到它成功后其他探针才起作用。 如果 startupProbe 失败,kubelet 将杀死容器,容器将根据 restartPolicy 来重启。如果容器没有配置 startupProbe, 则默认状态为 Success。

注:以上规则可以同时定义。在readinessProbe检测成功之前,Pod的running状态是不会变成ready状态的。

StartupProbe只在容器启动时进行探测,只探测一次,之后不进行探测。

2.Probe支持的三种检查方法

  • Exec :在容器内执行指定命令。如果命令退出时返回码为0则认为诊断成功。
  • TcpSocket :对指定端口上的容器的IP地址进行TCP检查(三次握手)。如果端口打开,则诊断被认为是成功的。
  • HttpGet :对指定的端口和路径上的容器的IP地址执行HTTPGet请求。如果响应的状态码大于等于200且小于400,则诊断被认为是成功的

3.探针原理

K8S 中探针的原理,实际上就是利用业务服务自身提供的健康检查接口,Kubelet 根据策略去探测该接口。

探针定义在Pod、Spec、Containers字段中,如下我们举一个官网的例子

3.1Exec方式

#官方示例exec方式

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/busybox
    args:  
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 60
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      failureThreshold: 1 
      initialDelaySeconds: 5
      periodSeconds: 5
                         

#可以看到 Pod 中只有一个容器。kubelet 在执行第一次探测前需要等待 5 秒,kubelet 会每 5 秒执行一次存活探测。kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 当到达第 31 秒时,这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。
vim demo2.yaml

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/busybox
    args:  
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 60
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      failureThreshold: 2
      initialDelaySeconds: 5
      periodSeconds: 5

kubectl apply -f demo2.yaml

kubectl get pod

kubectl get pod -w


#initialDelaySeconds:指定 kubelet 在执行第一次探测前应该等待5秒,即第一次探测是在容器启动后的第6秒才开始执行。默认是 0 秒,最小值是 0。
#periodSeconds:指定了 kubelet 应该每 5 秒执行一次存活探测。默认是 10 秒。最小值是 1。
#failureThreshold: 当探测失败时,Kubernetes 将在放弃之前重试的次数。 存活探测情况下的放弃就意味着重新启动容器。就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。
#timeoutSeconds:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。(在 Kubernetes 1.20 版本之前,exec 探针会忽略 timeoutSeconds 探针会无限期地 持续运行,甚至可能超过所配置的限期,直到返回结果为止。)

#在这个配置文件中,可以看到 Pod 中只有一个 Container。 periodSeconds 字段指定了 Kubelet 应该每 5 秒执行一次存活探测。 initialDelaySeconds 字段告诉 Kubelet 在执行第一次探测前应该等待 5 秒。Kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。如果命令执行成功并且返回值为 0,Kubelet 就会认为这个容器是健康存活的。如果这个命令返回非 0 值,Kubelet 会根据 pod restartPolicy 决定是否杀死这个容器并重新启动它。

kubectl describe pod liveness-exec 

这里查看当前的状态是正常的

这里会探测Pod容器资源是否存活

上面例子使用 EXEC 执行命令的方式来探测服务,同样还支持 HTTP、TCP、GRPC 协议这三种探测的方式,使用方式和上面例子类似,具体可参考 kubernetes 官网

使用探针来检查容器有四种不同的方法。每个探针都必须准确定义为这四种机制中的一种

  • Exec 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
  • Grpc 使用 gRPC 执行一个远程过程调用。目标应该实现  gRPC 健康检查。如果响应的状态是 "SERVING",则认为诊断成功。
  • HttpGet 对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
  • TcpSocket 对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。

 注意: 和其他机制不同,exec 探针的实现涉及每次执行时创建/复制多个进程。因此,在集群中具有较高 pod 密度、较低的 initialDelaySeconds 和 periodSeconds 时长的时候, 配置任何使用 exec 机制的探针可能会增加节点的 CPU 负载。这种场景下,请考虑使用其他探针机制以避免额外的开销。

3.2http-Get方式

#官方示例httpGet方式

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/liveness
    args:
    - /server
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

#在这个配置文件中,可以看到 Pod 也只有一个容器。initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 3 秒。periodSeconds 字段指定了 kubelet 每隔 3 秒执行一次存活探测。kubelet 会向容器内运行的服务(服务会监听 8080 端口)发送一个 HTTP GET 请求来执行探测。如果服务器上 /healthz 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。
vim demo3.yaml

apiVersion: v1
kind: Pod
metadata:
  name: liveness-httpget
  namespace: default
spec:
  containers:
  - name: liveness-httpget-container
    image: soscscs/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    livenessProbe:
      httpGet:
        port: http
        path: /index.html
      initialDelaySeconds: 1
      periodSeconds: 3
      timeoutSeconds: 10

#在这个配置文件中,可以看到 Pod 也只有一个容器。initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 3 秒。periodSeconds 字段指定了 kubelet 每隔 3 秒执行一次存活探测。kubelet 会向容器内运行的服务(服务会监听 80 端口)发送一个 HTTP GET 请求来执行探测。如果服务器上 /index.html 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。
#任何大于或等于 200 并且小于 400 的返回代码标示成功,其它返回代码都标示失败。


kubectl apply -f demo3.yaml 

kubectl get pod

kubectl describe pod liveness-httpget

我们看到http-get目前是没有问题的,Pod探测服务是正常启用的。下面我们模拟破坏当前Pod

kubectl exec -it liveness-httpget -- rm -rf /usr/share/nginx/html/index.html

kubectl get pod

我们可以看到删除一次,就会重启一次Pod资源

3.3TcpSocket方式

#官方示例tcpSocket方式

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: k8s.gcr.io/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

#这个例子同时使用 readinessProbe 和 livenessProbe 探测。kubelet 会在容器启动 5 秒后发送第一个 readinessProbe 探测。这会尝试连接 goproxy 容器的 8080 端口。如果探测成功,kubelet 将继续每隔 10 秒运行一次检测。除了 readinessProbe 探测,这个配置包括了一个 livenessProbe 探测。kubelet 会在容器启动 15 秒后进行第一次 livenessProbe 探测。就像 readinessProbe 探测一样,会尝试连接 goproxy 容器的 8080 端口。如果 livenessProbe 探测失败,这个容器会被重新启动。
vim demo4.yaml

apiVersion: v1
kind: Pod
metadata:
  name: cxk-tcp-live
spec:
  containers:
  - name: nginx
    image: soscscs/myapp:v1
    livenessProbe:
      initialDelaySeconds: 5
      timeoutSeconds: 1
      tcpSocket:
        port: 8080
      periodSeconds: 10
      failureThreshold: 2


kubectl create -f demo4.yaml

kubectl exec -it probe-tcp  -- netstat -natp

我们可以看到Pod服务资源没问题,但是Pod探针探测的8080端口有问题,所以会重启 

3.4就绪检测1

vim demo5.yaml

apiVersion: v1
kind: Pod
metadata:
  name: readiness-httpget
  namespace: default
spec:
  containers:
  - name: readiness-httpget-container
    image: soscscs/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    readinessProbe:
      httpGet:
        port: 80
        path: /index1.html
      initialDelaySeconds: 1
      periodSeconds: 3
    livenessProbe:
      httpGet:
        port: http
        path: /index.html
      initialDelaySeconds: 1
      periodSeconds: 3
      timeoutSeconds: 10

kubectl apply -f demo5.yaml

kubectl get pod

kubectl describe pod readiness-httpget 

探测404报错,这里提示是缺少一个页面,我们需要进入容器资源内创建一个页面

kubectl exec -it readiness-httpget sh

cd /usr/share/nginx/html/

echo "this is test" > index1.html 

 此时Pod资源探测结果/index1.html已经有了,所以该资源正常运行Running

我们再删除刚才新建的页面

readiness探测失败,无法进入READY状态

3.5就绪检测2

vim demo6.yaml

apiVersion: v1
kind: Pod
metadata:
  name: myapp1
  labels:
     app: myapp
spec:
  containers:
  - name: myapp
    image: soscscs/myapp:v1
    ports:
    - name: http
      containerPort: 80
    readinessProbe:
      httpGet:
        port: 80
        path: /index.html
      initialDelaySeconds: 5
      periodSeconds: 5
      timeoutSeconds: 10 
---
apiVersion: v1
kind: Pod
metadata:
  name: myapp2
  labels:
     app: myapp
spec:
  containers:
  - name: myapp
    image: soscscs/myapp:v1
    ports:
    - name: http
      containerPort: 80
    readinessProbe:
      httpGet:
        port: 80
        path: /index.html
      initialDelaySeconds: 5
      periodSeconds: 5
      timeoutSeconds: 10 
---
apiVersion: v1
kind: Pod
metadata:
  name: myapp3
  labels:
     app: myapp
spec:
  containers:
  - name: myapp
    image: soscscs/myapp:v1
    ports:
    - name: http
      containerPort: 80
    readinessProbe:
      httpGet:
        port: 80
        path: /index.html
      initialDelaySeconds: 5
      periodSeconds: 5
      timeoutSeconds: 10 
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  type: ClusterIP
  ports:
  - name: http
    port: 80
    targetPort: 80

kubectl create -f demo6.yaml

kubectl get pods,svc,endpoints -o wide

下面我们模拟删除页面文件,测试就绪探针探测 

kubectl exec -it pod/myapp1 -- rm -rf /usr/share/nginx/html/index.html

kubectl get pods,svc,endpoints -o wide

这里我们看到由于删掉了一个Pod资源内的index.html,所以myapp的Pod就将没有index.html的资源剔除地址池中(Pod有问题就剔除该地址池)

3.6启动和退出动作探测

vim demo7.yaml

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: soscscs/myapp:v1
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler >> /var/log/nginx/message"]      
      preStop:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the poststop handler >> /var/log/nginx/message"]
    volumeMounts:
    - name: message-log
      mountPath: /var/log/nginx/
      readOnly: false
  initContainers:
  - name: init-myservice
    image: soscscs/myapp:v1
    command: ["/bin/sh", "-c", "echo 'Hello initContainers'   >> /var/log/nginx/message"]
    volumeMounts:
    - name: message-log
      mountPath: /var/log/nginx/
      readOnly: false
  volumes:
  - name: message-log
    hostPath:
      path: /data/volumes/nginx/log/
      type: DirectoryOrCreate

kubectl apply -f demo7.yaml

kubectl get pods -o wide

#在 node02 节点上查看
cd /data/volumes/nginx/log/

cat message 
Hello initContainers
Hello from the postStart handler
#由上可知,init Container先执行,然后当一个主容器启动后,Kubernetes 将立即发送 postStart 事件。

#在原Master删除 pod 后,再在 node02 节点上查看
kubectl delete -f demo7.yaml

cat message 
Hello initContainers
Hello from the postStart handler
Hello from the poststop handler
#由上可知,当在容器被终结之前, Kubernetes 将发送一个 preStop 事件。

综上所述,探针的时候先启动Init初始化,然后开启探针,探测存活和就绪准备工作,删除资源的话,最后才是停止探针(生命周期结束)

4.探针参数

上面例子发现探针配置中有几个配置参数,可以使用这些字段精确地控制启动、存活和就绪检测的行为

  • initialDelaySeconds:容器启动后要等待多少秒后才启动启动、存活和就绪探针。如果定义了启动探针,则存活探针和就绪探针的延迟将在启动探针已成功之后才开始计算。如果 periodSeconds 的值大于 initialDelaySeconds,则 initialDelaySeconds 将被忽略。默认是 0 秒,最小值是 0。

  • periodSeconds:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。

  • timeoutSeconds:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。

  • successThreshold:探针在失败后,被视为成功的最小连续成功数。默认值是 1。存活和启动探测的这个值必须是 1。最小值是 1。

  • failureThreshold:探针连续失败了 failureThreshold 次之后, Kubernetes 认为总体上检查已失败:容器状态未就绪、不健康、不活跃。对于启动探针或存活探针而言,如果至少有 failureThreshold 个探针已失败, Kubernetes 会将容器视为不健康并为这个特定的容器触发重启操作。Kubelet 遵循该容器的 terminationGracePeriodSeconds 设置。对于失败的就绪探针,Kubelet 继续运行检查失败的容器,并继续运行更多探针;因为检查失败,Kubelet 将 Pod 的 Ready 状况设置为 false

  • terminationGracePeriodSeconds:为 Kubelet 配置从为失败的容器触发终止操作到强制容器运行时停止该容器之前等待的宽限时长。默认值是继承 Pod 级别的 terminationGracePeriodSeconds 值(如果不设置则为 30 秒),最小值为 1。更多细节请参见探针级别 terminationGracePeriodSeconds

5.探针结果

三种类型的探针每次探测都将获得以下三种结果之一:

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

五、探针总结

1.ReadinessProbe

就绪探针,探测容器启动后,是否就绪,是否能够提供服务,只有 pod 所有容器都探测成功后,pod 状态变成 Ready。只要有一个容器的 readinessProbe 失败,那么整个 pod 都会处于 NotReady 状态。

控制器将此 Pod 的 Endpoint 从对应的 service 的 Endpoint 列表中移除,从此不再将任何请求调度此 Pod 上,直到下次探测成功。

通过使用 ReadinessProbe,Kubernetes 能够等待应用程序完全启动,然后才允许服务将流量发送到新副本。

2.LivenessProbe

存活探针,检查容器是否运行正常,如死锁、无法响应等,探测失败后 Kubelet 根据 restartPolicy 来重启容器。

当一个 pod 内有多个容器,且 restartpolicy 为默认值( Always )。其中某个容器探针失败后,Kubelet 重启该容器,并不会重启其他容器,且 pod 的状态值会变为 NotReady

如果一个容器不包含 livenessProbe 探针,则 Kubelet 认为容器的 livenessProbe 探针的返回值永远成功。

3.StartupProbe

启动探针,判断容器是否已启动。如果提供了启动探测探针,则禁用所有其他探测探针( readinessProbe,livenessProbe ),直到它成功为止。如果启动探测失败,Kubelet 将杀死容器,容器将服从其重启策略。如果容器没有提供启动探测,则默认状态为成功。

那什么时候需要使用到启动探针呢?

例如如下有个含有 livenessProbe 的 pod

livenessProbe:
  httpGet:
    path: /healthz
    prot: 80
  failureThreshold: 1
  initialDelay:10
  periodSeconds: 10

#该探针的意思是容器启动 10s 后开始探测,每 10s 检查一次,允许失败的次数是 1 次。如果失败次数超过 1 则表示探针失败。

如果这个服务启动时间在 10s 内则没有任何问题,因为探针 10s 后才开始探测。但是该服务在启动的时候需要一些预启动的操作,比如导数据之类,需要 60s 才能完全启动好。这时候上面的探针就会进入死循环,因为上面的探针10s 后就开始探测,这时候我们服务并没有起来,发现探测失败就会触发容器重启策略。当然可以把 initialDelay 调成 60s ,但是我们并不能保证这个服务每次起来都是 60s ,假如新的版本起来要70s,甚至更多的时间,我们就不好控制了。有的朋友可能还会想到把失败次数增加,比如下面配置

livenessProbe:
  httpGet:
    path: /healthz
    prot: 80
  failureThreshold: 5
  initialDelay:60
  periodSeconds: 10

这在启动的时候是可以解决我们目前的问题,但是如果这个服务挂了呢?如果 failureThreshold=1 则 10s 后就会报警通知服务挂了,如果设置了failureThreshold=5,那么就需要 5*10s=50s 的时间,在现在大家追求快速发现、快速定位、快速响应的时代是不被允许的。

在这时候我们把 startupProbe 和 livenessProbe 结合起来使用就可以很大程度上解决我们的问题。

livenessProbe:
  httpGet:
    path: /healthz
    prot: 80
  failureThreshold: 1
  initialDelay:10
  periodSeconds: 10

startupProbe:
  httpGet:
    path: /healthz
    prot: 80
  failureThreshold: 10
  initialDelay:10
  periodSeconds: 10

上面的配置是只有 startupProbe 探测成功后再交给 livenessProbe 。应用在 10s + 10s * 10s = 110s 内启动都是可以的,而且应用启动后运行过程中挂掉了 10s 就会发现问题。

所以说启动探针一般都是搭配着存活探针一起工作的,不会单独配置启动配置。

六、实践

熟练使用好上面三种探针,可以增强业务的可用性和稳定性。

如果服务自身启动时间略长,0s-20s 之间那么需要配置 readinessProbe

readinessProbe:
  httpGet:
    path: /healthz
    prot: 80
  failureThreshold: 1
  initialDelay:10
  periodSeconds: 10
  • 该配置作用是当容器启动 10s 后,开始第一次探针,且每隔 10s 探针一次。
  • 一次探针失败即表示失败,将该 pod 表示为 NotReady
  • 如果启动后探针成功后,pod 状态置为 Ready,该服务即可被请求。
  • 后续每隔 10s 请求一次,如果失败了,将 pod 状态置为 NotReady,Endpoint Controller 就会将该 endpoint 从 service 上剔除。

关于 ReadinessProbe 有一点很重要,它会在容器的整个生命周期中运行。这意味着 ReadinessProbe 不仅会在启动时运行,而且还会在 Pod 运行期间反复运行。这是为了处理应用程序暂时不可用的情况(比如加载大量数据、等待外部连接时)。在这种情况下,我们不一定要杀死应用程序,可以等待它恢复。ReadinessProbe 可用于检测这种情况,并在 Pod 再次通过 ReadinessProbe 检查后,将流量发送到这些 Pod。 

如果服务会出现假死现象,即服务进程在,但已经无法提供服务了。那么这时候就需要 livenessProbe

readinessProbe:
  httpGet:
    path: /healthz
    prot: 80
  failureThreshold: 3
  initialDelay:10
  periodSeconds: 10
  
livenessProbe:
  httpGet:
    path: /healthz
    prot: 80
  failureThreshold: 10
  initialDelay:15
  periodSeconds: 10

当同时使用 readinessProbe、livenessProbe,两者配置不能保持一样。

  • 如果两者 initialDelay 都为 10 ,即服务启动 10s 后,readinessProbe、livenessProbe 都开始探测,这样两者都探测失败后,该 pod 即重启也会 NotReady 是一个多此一举的过程。
  • 可以将 readinessProbe 的 initialDelay 设置的短一点,即先开始就绪探针,再开始存活探针。
  • 或者将 livenessProbe 的 failureThreshold 设置大一点。(例如,在 3 次尝试后标记为未就绪,在 10 次尝试后将 LivenessProbe 标记为失败)

如果服务启动时间很长,20s - 100s,就需要使用 startupProbe

readinessProbe:
  httpGet:
    path: /healthz
    prot: 80
  failureThreshold: 3
  initialDelay:10
  periodSeconds: 10
  
livenessProbe:
  httpGet:
    path: /healthz
    prot: 80
  failureThreshold: 10
  initialDelay:15
  periodSeconds: 10

startupProbe:
  httpGet:
    path: /healthz
    prot: 80
  failureThreshold: 10
  initialDelay:10
  periodSeconds: 10
  • 当该服务启动 10s 后开始启动探针,探测成功后,该探针结束,后面不会再探测了,然后到 readinessProbe、livenessProbe 工作;
  • startupProbe 探测失败后,重启该 pod,重新探测,直到服务启动成功。 

七、总结

1.Pod容器的资源限制

spec.containers.resources.requests.cpu/memory
#创建Pod容器时需要预留的资源量0.5/500m 内存 MI(以2为底的) M G(以10为底的)

spec.containers.resources.limit.cpu/memory
#Pod容器能够使用资源量的一个上限,举例 4Gi 内存的上限(不允许超过上限值) 1 cpu的上限

kubectl describe 名称
#查看Pod或查看Node资源使用情况

2.Pod探针

2.1探针机制

  • LivenessProbe:存活探针,即探测容器是否运行、存活
  • ReadinessProbe:就绪探针,探测容器是否就绪,是否能够正常提供服务了
  • StartupProbe:启动探针,探测容器是否启动。

2.2探针方法

  • Exec 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
  • Grpc 使用 gRPC 执行一个远程过程调用。目标应该实现  gRPC 健康检查。如果响应的状态是 "SERVING",则认为诊断成功。
  • HttpGet 对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
  • TcpSocket 对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。

3.Pod状态

  • Pending:pod已经被系统认可了,但是内部的container还没有创建出来。这里包含调度到node上的时间以及下载镜像的时间,会持续一小段时间。
  • Running:pod已经与node绑定了(调度成功),而且pod中所有的container已经创建出来,至少有一个容器在运行中,或者容器的进程正在启动或者重启状态。--这里需要注意pod虽然已经Running了,但是内部的container不一定完全可用。因此需要进一步检测container的状态。
  • Succeeded:这个状态很少出现,表明pod中的所有container已经成功的terminated了,而且不会再被拉起了。
  • Failed:pod中的所有容器都被terminated,至少一个container是非正常终止的。(退出的时候返回了一个非0的值或者是被系统直接终止)
  • Unknown:由于某些原因pod的状态获取不到,有可能是由于通信问题。 一般情况下pod最常见的就是前两种状态。而且当Running的时候,需要进一步关注container的状态

4.Container生命周期

  • Waiting:启动到运行中间的一个等待状态。
  • Running:运行状态。
  • Terminated:终止状态。 如果没有任何异常的情况下,container应该会从Waiting状态变为Running状态,这时容器可用。

但如果长时间处于Waiting状态,container会有一个字段reason表明它所处的状态和原因,如果这个原因很容易能标识这个容器再也无法启动起来时,例如ContainerCannotRun,整个服务启动就会迅速返回。(这里是一个失败状态返回的特性,不详细阐述)

Kubernetes 探针可以极大地提高服务的健壮性和弹性,并提供出色的最终用户体验。但是,如果您不仔细考虑如何使用这些探针,特别是如果您不考虑异常的系统动态(无论多么罕见),则有可能使服务的可用性变差,而不是变好。下面列举了探针使用的一些技巧和注意事项。

  • 对于提供 HTTP 协议(REST 服务等)的微服务, 始终定义一个 readinessProbe,用于检查的应用程序(Pod)是否已准备好接收流量。

  • 对于慢启动的应用,我们应该使用 startupProbe,来防止容器没有启动,就被 livenessProbe 杀死了。

  • 不要依赖外部依赖项(如数据存储)进行就绪/探活检查,因为这可能会导致级联故障

假如10 个 pod 的服务,数据库使用 Postgres,缓存使用 redis:当你的探针的路径依赖于工作的 redis 连接时,如果出现 redis 网络故障,则所有 10 个 Pod 都将“重启。这通常会产生影响比它应该的更糟。因为服务还能到 Postgres 拿去数据。 

只探测自己内部的端口,不要去探测外部 pod 的端口。探测器不应依赖于同一集群中其他 Pod 的状态,以防止级联故障。 

  • 需要明确知道使用 livenessProbe 的原因,否则不要为的 Pod 使用 livenessProbe
    • livenessProbe 可以帮助恢复假死的容器,但是当我们能控制我们的应用程序,出现意料之外的假死进程和死锁之类的故障,更好的选择是从应用内部故意崩溃以恢复到已知良好状态。
    • 失败的 livenessProbe 将导致容器重启,从而可能使与负载相关的错误的影响变得更糟:容器重启将导致停机(至少的应用程序的启动时间,例如 30s+),从而导致更多错误并为其他容器提供更多流量负载,导致更多失败的容器,等等
  • 如果同时使用 readinessProbe、livenessProbe,请不要为 readinessProbe、livenessProbe 设置相同的规范
  • 38
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值