4.1 保持pod健康

作者: 众神开挂 | 来源:发表于2021-07-14 09:06 被阅读0次

    4.1 保持pod健康

    使用Kubernetes的一个主要好处是,可以给Kubernetes一个容器列表来由其保持容器在集群中的运行。可以通过让Kubernetes创建pod资源,为其选择一个工作节点并在该节点上运行该pod的容器来完成此操作。但是,如果其中一个容器终止,或一个pod的所有容器都终止,怎么办?

    只要将pod调度到某个节点,该节点上的Kubelet就会运行pod的容器,从此只要该pod存在,就会保持运行。如果容器的主进程崩溃,Kubelet将重启容器。如果应用程序中有一个导致它每隔一段时间就会崩溃的bug,Kubernetes会自动重启应用程序,所以即使应用程序本身没有做任何特殊的事,在Kubernetes中运行也能自动获得自我修复的能力。

    即使进程没有崩溃,有时应用程序也会停止正常工作。例如,具有内存泄漏的Java应用程序将开始抛出OutOfMemoryErrors,但JVM进程会一直运行。如果有一种方法,能让应用程序向Kubernetes发出信号,告诉Kubernetes它运行异常并让Kubernetes重新启动,那就很棒了。

    我们已经说过,一个崩溃的容器会自动重启,所以也许你会想到,可以在应用中捕获这类错误,并在错误发生时退出该进程。当然可以这样做,但这仍然不能解决所有的问题。

    例如,你的应用因为无限循环或死锁而停止响应。为确保应用程序在这种情况下可以重新启动,必须从外部检查应用程序的运行状况,而不是依赖于应用的内部检测。

    4.1.1 介绍存活探针

    Kubernetes可以通过存活探针(liveness probe)检查容器是否还在运行。可以为pod中的每个容器单独指定存活探针。如果探测失败,Kubernetes将定期执行探针并重新启动容器。

    注意 我们将在下一章中学习到Kubernetes还支持就绪探针(readiness probe),一定不要混淆两者。它们适用于两种不同的场景。

    Kubernetes有以下三种探测容器的机制:

    • HTTP GET探针对容器的IP地址(你指定的端口和路径)执行HTTP GET请求。如果探测器收到响应,并且响应状态码不代表错误(换句话说,如果HTTP响应状态码是2xx或3xx),则认为探测成功。如果服务器返回错误响应状态码或者根本没有响应,那么探测就被认为是失败的,容器将被重新启动。
    • TCP套接字探针尝试与容器指定端口建立TCP连接。如果连接成功建立,则探测成功。否则,容器重新启动。
    • Exec探针在容器内执行任意命令,并检查命令的退出状态码。如果状态码是0,则探测成功。所有其他状态码都被认为失败。

    上述三种探针参考官网教程 https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

    下面只拷贝http探针

    4.1.2 创建和使用基于HTTP的存活探针

    我们来看看如何为你的Node.js应用添加一个存活探针。因为它是一个Web应用程序,所以添加一个存活探针来检查其Web服务器是否提供请求是有意义的。但是因为这个Node.js应用程序太简单了,所以不得不人为地让它失败。

    你将创建一个包含HTTP GET存活探针的新pod,下面的代码清单显示了pod的yaml。

    将存活探针添加到 pods/probe/http-liveness.yaml

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

    任何大于或等于 200 并且小于 400 的返回代码标示成功,其它返回代码都标示失败。

    可以在这里看服务的源码 server.go

    容器存活的最开始 10 秒中,/healthz 处理程序返回一个 200 的状态码。之后处理程序返回 500 的状态码。

    http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
        duration := time.Now().Sub(started)
        if duration.Seconds() > 10 {
            w.WriteHeader(500)
            w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
        } else {
            w.WriteHeader(200)
            w.Write([]byte("ok"))
        }
    })
    

    kubelet 在容器启动之后 3 秒开始执行健康检测。所以前几次健康检查都是成功的。 但是 10 秒之后,健康检查会失败,并且 kubelet 会杀死容器再重新启动容器。

    创建一个 Pod 来测试 HTTP 的存活检测:

    $ kubectl apply -f http-liveness.yaml
    

    10 秒之后,通过看 Pod 事件来检测存活探测器已经失败了并且容器被重新启动了。

    $ kubectl describe pod liveness-http
    ----------------
    Events:
      Type     Reason     Age                From               Message
      ----     ------     ----               ----               -------
      Normal   Scheduled  63s                default-scheduler  Successfully assigned custom2/liveness-http to minikube
      Normal   Pulled     50s                kubelet            Successfully pulled image "k8s.gcr.io/liveness" in 7.632370383s
      Normal   Pulled     23s                kubelet            Successfully pulled image "k8s.gcr.io/liveness" in 4.033967024s
      Normal   Created    22s (x2 over 48s)  kubelet            Created container liveness
      Normal   Started    21s (x2 over 47s)  kubelet            Started container liveness
      Warning  Unhealthy  5s (x6 over 35s)   kubelet            Liveness probe failed: HTTP probe failed with statuscode: 500
      Normal   Killing    5s (x2 over 29s)   kubelet            Container liveness failed liveness probe, will be restarted
      Normal   Pulling    3s (x3 over 57s)   kubelet            Pulling image "k8s.gcr.io/liveness"
    

    在 1.13(包括 1.13版本)之前的版本中,如果在 Pod 运行的节点上设置了环境变量 http_proxy(或者 HTTP_PROXY),HTTP 的存活探测会使用这个代理。 在 1.13 之后的版本中,设置本地的 HTTP 代理环境变量不会影响 HTTP 的存活探测。

    4.1.4 配置存活探针的附加属性

    你可能已经注意到,kubectl describe 还显示关于存活探针的附加信息:

    http-get http://:8080/healthz delay=3s timeout=1s period=3s #success=1 #failure=3
    

    除了明确指定的存活探针选项,还可以看到其他属性,例如delay(延迟)、timeout(超时)、period(周期)等。delay=0s部分显示在容器启动后立即开始探测。timeout仅设置为1秒,因此容器必须在1秒内进行响应,不然这次探测记作失败。每10秒探测一次容器(period=10s),并在探测连续三次失败(#failure=3)后重启容器。

    定义探针时可以自定义这些附加参数。例如,要设置初始延迟,请将initialDelaySeconds属性添加到存活探针的配置中,如下面的代码清单所示。

        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            httpHeaders:
            - name: Custom-Header
              value: Awesome
          initialDelaySeconds: 3 #设置初始延迟
          periodSeconds: 3
    

    如果没有设置初始延迟,探针将在启动时立即开始探测容器,这通常会导致探测失败,因为应用程序还没准备好开始接收请求。如果失败次数超过阈值,在应用程序能正确响应请求之前,容器就会重启。

    提示 务必记得设置一个初始延迟来说明应用程序的启动时间。

    很多场合都会看到这种情况,用户很困惑为什么他们的容器正在重启。但是如果使用 kubectl describe ,他们会看到容器以退出码137或143结束,并告诉他们该pod是被迫终止的。此外,pod事件的列表将显示容器因liveness探测失败而被终止。如果你在pod启动时看到这种情况,那是因为未能适当设置initialDelaySeconds。

    注意 退出代码137表示进程被外部信号终止,退出代码为128+9(SIGKILL)。同样,退出代码143对应于128+15(SIGTERM)。

    4.1.5 创建有效的存活探针

    对于在生产中运行的pod,一定要定义一个存活探针。没有探针的话,Kubernetes无法知道你的应用是否还活着。只要进程还在运行,Kubernetes会认为容器是健康的。

    存活探针应该检查什么

    简易的存活探针仅仅检查了服务器是否响应。虽然这看起来可能过于简单,但即使是这样的存活探针也可以创造奇迹,因为如果容器内运行的web服务器停止响应HTTP请求,它将重启容器。与没有存活探针相比,这是一项重大改进,而且在大多数情况下可能已足够。

    但为了更好地进行存活检查,需要将探针配置为请求特定的URL路径(例如/health),并让应用从内部对内部运行的所有重要组件执行状态检查,以确保它们都没有终止或停止响应。

    提示 请确保/health HTTP端点不需要认证,否则探测会一直失败,导致你的容器无限重启。

    一定要检查应用程序的内部,而没有任何外部因素的影响。例如,当服务器无法连接到后端数据库时,前端Web服务器的存活探针不应该返回失败。如果问题的底层原因在数据库中,重启Web服务器容器不会解决问题。由于存活探测将再次失败,你将反复重启容器直到数据库恢复。

    保持探针轻量

    存活探针不应消耗太多的计算资源,并且运行不应该花太长时间。默认情况下,探测器执行的频率相对较高,必须在一秒之内执行完毕。一个过重的探针会大大减慢你的容器运行。在本书的后面,还将学习如何限制容器可用的CPU时间。探针的CPU时间计入容器的CPU时间配额,因此使用重量级的存活探针将减少主应用程序进程可用的CPU时间。

    提示 如果你在容器中运行Java应用程序,请确保使用HTTP GET存活探针,而不是启动全新JVM以获取存活信息的Exec探针。任何基于JVM或类似的应用程序也是如此,它们的启动过程需要大量的计算资源。

    无须在探针中实现重试循环

    你已经看到,探针的失败阈值是可配置的,并且通常在容器被终止之前探针必须失败多次。但即使你将失败阈值设置为1,Kubernetes为了确认一次探测的失败,会尝试若干次。因此在探针中自己实现重试循环是浪费精力。

    存活探针小结

    你现在知道Kubernetes会在你的容器崩溃或其存活探针失败时,通过重启容器来保持运行。这项任务由承载pod的节点上的Kubelet执行 —— 在主服务器上运行的Kubernetes Control Plane组件不会参与此过程。

    但如果节点本身崩溃,那么Control Plane必须为所有随节点停止运行的pod创建替代品。它不会为你直接创建的pod执行此操作。这些pod只被Kubelet管理,但由于Kubelet本身运行在节点上,所以如果节点异常终止,它将无法执行任何操作。

    为了确保你的应用程序在另一个节点上重新启动,需要使用ReplicationController或类似机制管理pod,我们将在本章其余部分讨论该机制。

    相关文章

      网友评论

        本文标题:4.1 保持pod健康

        本文链接:https://www.haomeiwen.com/subject/uboepltx.html