健康状态检测接口

kubelet仅能在控制循环中根据容器主进程的运行状态来判断其健康与否,主进程以非0状态码退出代表处于不健康状态,其它均为正常状态。然而,有些异常情境中,仍处于运行状态的进程内部的业务处理机制可能已经处于僵死状态或陷入死循环等,无法正常处理业务请求,对于这种状态的判断便要依赖应用自身专用于健康状态检测的接口。

存活状态(liveness)检测用于定期检测容器是否正常运行,就绪状态(readiness)检测用于定期检测容器是否可以接收流量,它们能够通过减少运维问题和提高服务质量来使服务更健壮和更具有弹性。kubernetes在pod内部的容器资源上提供了livenessProbe和readinessProbe两个字段,分别让用户自定义容器应用的存活状态和就绪状态检测,对于合格的云原生应用,它们可调用容器应用自身定义的响应API完成,而对于不具该类API的传统应用程序,用户也可精心设置一个更能反应其相应状态的系统命令或服务请求完成该功能。

  • 存活状态检测:用于判定容器是否处于运行状态;若此类检测未通过,kubelet将杀死容器并根据其restartPolicy决定是否将其重启;未定义存活性检测的容器的默认状态为Success。
  • 就绪状态检测:用于判断容器是否准备就绪并可对外提供服务;为通过该检测时,端点控制器会将其IP地址从所有匹配到此Pod对象的Service对象的端点列表移除;检测通过之后,会再次将其IP添加至端点列表中;未定义就绪状态检测的容器的默认状态为Success。
  • 容器探测时Pod对象生命周期中的一项重要日常任务,它由kubelet周期性执行。kubelet可在活动容器上分别执行由用户定义的启动状态检查(startupProbe)、存活状态检测(livenessProbe)和就绪状态检测(readinessProbe),定义在容器上的存活状态和就绪状态操作称为检测探针,它要通过容器的句柄(handler)进行定义。kubernetes定义了用于容器探测的3种句柄。

  • ExecAction:通过在容器中执行一个命令并根据其返回的状态码进行的诊断操作称为Exec探测,状态码为0表示成功,否则为不健康状态。
  • TCPSocketAction: 通过与容器的某TCP端口尝试建立连接进行诊断,端口能够完成打开即为正常状态,否则为不健康状态。
  • HTTPGetAction:通过向容器IP地址的某指定端口的指定path发起http GET请求进行诊断,响应码为2xx或3xx即为成功,否则为失败。
  • kubernetes自v1.16版本起还支持启动状态(startup)检测。将传统模式开发的大型应用程序迁移至容器编排平台运行时,可能需要相当长的时间进行启动后的初始化,但其初始化过程是否正确完成检测机制和探测参数都可能有别于存活状态检测,例如需要更长的间隔周期和更高的错误阈值等。该类检测的结果处理机制与存活状态检查相同,检测失败时kubelet将杀死ring器并根据其restartPolicy决定是否将其重启,而未定义时的默认状态为success。需要注意的是,一旦定义了启动检测探针,则必须等启动检测成功完成之后,存活探针和就绪探针才可启动。

    容器存活状态检测

    存活探针配置格式

    Pod配置格式中,spec.containers.livenessProbe字段用于定义此类检测,配置格式如下所示。但一个容器之上仅能定义一种类型的探针,即exec、httpGet和tcpSocket三者互斥,它们不可在一个容器同时使用。

    spec:
      containers:
      - name: ...
        image: ...
        imagePullPolicy: IfNotPresent
          livenessProbe:
            exec <Object>                    # 命令式探针
            httpGet <Object>                 # http GET类型探针
            TCPSocket <Object>               # tcp Socket类型探针
            initialDelaySeconds <integer>    # 发起初次探测请求的延后时长
            periodSeconds <integer>          # 请求周期
            timeoutSeconds <integer>         # 超时时长
            sucessThreshold <integer>        # 成功阈值
            failureThreshold <integer>       # 失败阈值
    

    探针之外的其它字段用于定义探测操作的行为方式,用户没有明确定义这些属性字段时,它们会使用各自的默认值,各字段的详细说明如下。

  • initialDelaySeconds <integer>:首次发出存活探针探测请求的延迟时长,即容器启动多久之后开始第一次探测操作,显示为delay属性;默认为0秒,即容器启动后便立刻进行探测;该参数值应该大于容器的最大初始时长,以避免程序永远无法启动。
  • timeoutSeconds <integer> : 存活探针的超时时长,显示为timeout属性,默认值为1秒,最小值为1秒;应该为此参数设置一个合理值,以避免因应用负载较大时响应延迟导致Pod重启。  
  • periodSeconds <integer>:存活探针的频度,显示为period属性,默认值为10秒,最小值为1秒;需要注意的是,过高的频率会给pod对象带来较大的额外开销,而过低的频率又会使得对错误反应不及时。
  • sucessThreshold <integer>:处于失败状态时,探测操作至少连续多少次的成功才被认为通过检测,显示为#success属性,仅可取值为1.   
  • failureThreshold <integer>:处于成功状态时,探测操作至少连续多少次的失败才被认为检测不通过,显示为#failure属性,默认值为3,最小值为1;尽量设置宽容一些的失败计数,能有效避免一些场景中的服务级联失败。
  • 使用kubectl describe命令查看配置了存活性探针探测的Pod对象的详细信息时,其相关容器中会输出探测方式及其额外的配置属性delay、timeout、period、success和failure及其各自的相关属性。

    exec探针 

           exec类型的探针通过在目标容器中执行用户自定义的命令来判断容器的健康状态,命令状态返回值为0表示成功通过检测,其余值均为失败状态。spec.containers.livenessProbe.exec字段只有一个可用属性command,用于指定要执行的命令。

    apiVersion: v1
    kind: Pod
    metadata:
      name: ...
      namespace: default
    spec:
      containers:
      - name: ...
        image: ...
        imagePullPolicy: IfNotPresent
        livenessProbe:
          exec:
            command: ['/bin/sh', '-c', '[ "$(curl -s 127.0.0.1/livez)" == "OK" ]']
          initialDelaySeconds: 5
          periodSeconds: 5

    该配置清单定义了Pod对象exec探针,它通过在容器本地执行测试命令来比较curl -s 127.0.0.1/livez的返回值是否为Ok以判断容器的存活状态。命令成功执行则表示容器正常进行,否则3次检测失败之后则将其判定为检测失败。首次检测在容器启动5秒之后进行,请求间隔也是5秒。

    容器退出状态码137事实上有由两部分数字之和生成: 128+signum,其中signum是导致进程终止的信号的数字表示,9表示SIGKILL,这意味着进程是被强行终止的。

    特别说明的是,exec指定的命令运行在容器中,会消耗容器的可用计算资源配额,例外考虑到探测操作的效率等因素,探测操作的命令应该尽可能简单和清理。

    HTTP探针

    HTTP探针是基于HTTP协议的探测(HTTPGetAction),通过向目标容器发起一个GET请求,并根据其响应码进行结果判定,2xx或3xx类的响应码表示检测通过。HTTP探针可用配置字段有如下几个:

  • host <string>:请求的主机地址,默认为Pod IP;也可以在httpHeaders使用“host:”来定义。
  • port <string>:请求端口,必选字段。
  • httpHeaders <[]Object>:自定义的请求报文头部。
  • path <string>:请求的HTTP资源路径,即URL path。
  • scheme:建立连接使用的协议,仅可为HTTP或HTTPS。默认为HTTP。
  • apiVersion: v1
    kind: Pod
    metadata:
      name: ...
      namespace: default
    spec:
      containers:
      - name: ...
        image: ...
        imagePullPolicy: IfNotPresent
        livenessProbe:
          httpGet:
            path: '/livez'
            port: 80
            scheme: HTTP
          initialDelaySeconds: 5
          periodSeconds: 5

    一般来说,HTTP探针应该针对专用的URL路径进行,例如前面实例中特别为其准备的/livez,此URL路径对应的web资源也应该以轻量化的方式在内部对应于程序的各关键组件进行全面检测,以确保它们可正常向客户端提供完整的服务。

    需要注意的是,这种检测方式仅对分层架构中的前一层有效,例如,它能检测应用程序工作正常与否的状态,但重启操作却无法解决其后端服务导致的故障。此时,容器可能会被反复重启,知道后端服务恢复正常。其它两种检测方式也存在类似的问题。

    TCP探针

    TCP探针是基于TCP协议进行存活性探测(TCPSocketAction),通过向容器的特定端口发起TCP请求并尝试建立连接并进行结果判定,连接建立成功即为通过检测。相比较来说,它比基于HTTP协议的探测要更高效、更节约资源,但精准度较低,毕竟连接建立成功未必意味着资源可用。

    spec.containers.livenessProbe。tcpSocket字段用于定义此类检测,它主要有以下两个可用字段:

  • host <string>:请求的主机地址,默认为Pod IP。
  • port <string>:请求端口,必选字段。
  • apiVersion: v1
    kind: Pod
    metadata:
      name: ...
      namespace: default
    spec:
      containers:
      - name: ...
        image: ...
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 80
        livenessProbe:
          tcpSocket:
            port: http
          periodSeconds: 5
          initialDelaySeconds: 5

    按照配置,将该清单中的Pod对象创建在集群之上,20秒后即会进行首次的tcpSocket检测。当80端请求失败经过至少1个检测周期后,Pod会重启。

    Pod重启策略

    pod对象的应用容器因程序崩溃、启动状态检测失败、存活状态检查失败或容器申请超出限制的资源等原因都可能导致其被终止,此时是否应该重启则取决于pod上的restartPolicy(重启策略)字段的定义,该字段支持以下取值。

  • Always:无论因何原因、以何种方式终止,kubelet都将重启该pod,此为默认设定。
  • OnFailure:仅在pod对象以非0方式退出时才将其重启。
  • Never:不在重启该pod。
  • 需要注意的是,restartPolicy适用于pod对象中的所有容器,而且它仅用于控制在同一节点重启pod对象的相关容器。首次需要重启的容器,其重启操作会立即执行,而再次重启操作将由kubelet延迟一段时间后进行,反复的重启操作的延迟时长依次为10秒、20秒、40秒、80秒、160秒和300秒,300秒是最大延迟时长。

    事实上,一旦绑定到一个节点,pod对象将永远不会被重新绑定到另一个节点,它要么重启,要么被终止,知道节点故障、被删除或被驱逐。

    容器就绪状态检测

           pod对象重启后,应用程序通常需要一段时间完成其初始化过程,例如加载配置或数据、缓存初始化等,甚至有些程序需要运行某类预热过程等,因此通常应该避免在pod对象启动后立即让其处理客户端请求,而是需要等待容器初始化工作执行完成后并转为就绪状态,尤其是存在其它提供相同服务的pod对象的场景更是如此。

           与存活探针不同的是,就绪状态检测是用来判断容器应用就绪与否的周期性(默认周期为10秒)操作,它用于检测容器是否已经初始化完成并可服务客户端请求。与存活探针触发的操作不同,检测失败时,就绪探针不会杀死或重启容器来确保其健康状态,而仅仅是通知其尚未就绪,并触发依赖其就绪状态的其它操作(例如从service对象中移除此pod对象),以确保不会有客户端请求接入此pod对象。

          就绪探针也支持exec、HTTP GET和TCP Socket这3中探测方式,且它们各自的定义机制与存活探针相同,因而,将容器定义中的livenessProbe字段名替换为readinessProbe,并略做适应性修改即可定义出就绪性检测的配置,甚至有些场景的就绪探针与存活探针可以完全相同。

    apiVersion: v1
    kind: Pod
    metadata:
      name: ...
      namespace: default
    spec:
      containers:
      - name: ...
        image: ...
        imagePullPolicy: IfNotPresent
        readinessProbe:
          httpGet:
            path: '/readyz'
            port: 80
            scheme: HTTP
          initialDelaySeconds: 15
          timeoutSecconds: 2
          periodSeconds: 5
          failureThreshold: 3
        restartPolicy: Always

    该实例通过/readyz暴露了专用于就绪状态检测的接口,它在程序启动15秒后能够以200状态码响应、以OK响应结果。

    未定义就绪性检测的pod对象在进入Running状态后将立即就绪,这在容器需要时间进行初始化的场景中可能会导致客户请求失败,因此生产实践中,必须为关键性pod资源中容器定义就绪探针。

    容器生命周期

           许多编程语言框架都实现了生命周期管理的概念,其主要用于指定平台如何与它创建的组件在启动之后或停止之前进行交互。实现这类功能很重要,毕竟有时我们可能需要在pod上执行一些操作,例如测试同一个或多个依赖向的连接性,以及在销毁pod之前进行一些清理活动等。容器应用生命周期管理是指它可以从平台接收管理事件并执行相应的操作,以便于平台能够更好地管理容器的生命周期机制,因而也称为容器生命周期管理API。

           容器需要处理来自平台的最重要时间是SIGTERM信号,任何需要干净关闭进程的应用程序都需要捕捉该信号进行必要处理,例如释放文件锁、关闭数据库连接和网络连接等,而后尽快终止进程,以避免宽限期过后强制关闭信息SIGKILL的接入。SIGKILL信号是由底层操作系统接收的,而非应用进程,一旦检测带该信号,内核将停止为相应进程提供内核资源,并终止进程正在使用的CPU线程,类似于直接切断了进程的电源。

           但是,容器应用很可能是功能复杂的分布式应用程序的一个组件,仅依赖信号终止进程很可能不足以完成所有的必要操作。因此,容器还需要支持postStart和preStop事件,前者常用于为程序启动前进行预热,后者则一般在干净地关闭应用之前释放占用的资源。

           生命周期钩子函数lifecycle hook是编程语言中常用的生命周期管理组件,它实现了程序运行周期中的关键时间的可见性,并赋予用户为此采取某种行动的能力,类似地,容器生命周期钩子使它能够感知自身生命周期管理中的事件,并在相应时刻到来时运行由用户指定的处理程序代码。kubernetes同样为容器提供了postStart和preStop两种生命周期钩子。

  • postStart:在容器创建完成后立即运行的钩子句柄(handler),该钩子定义的事件执行完成后容器才能真正完成启动过程。
  • preStop:在容器终止操作执行之前立即运行的钩子句柄,它以同步方式调用,因此在其完成之前会阻塞删除容器的操作;这意味着该钩子定义的事件成功执行并退出,容器终止操作才能真正完成。
  •      钩子句柄的实现方式类似于容器探针句柄的类型,同样有exec、httpGet和tcpSocket这3种,它们各自的配置格式和工作逻辑也完全相同,exec在容器中执行用户定义的一个或多个命令,httpGet在容器中向指定的本地URL发起HTTP请求,而tcpSocket则视图与指定的端口建立TCP连接。

         postStart和preStop句柄定义在容器的lifecycle字段中,其内部一次仅支持嵌套使用一种句柄类型。

    apiVersion: v1
    kind: Pod
    metadata:
      name: ...
      namespace: default
    spec:
      containers:
      - name: ...
        image: ...
        imagePullPolicy: IfNotPresent
        securityContext:
          capabilities:
            - NET_ADMIN
        livenessProbe:
          httpGet:
            path: '/livez'
            port: 80
            scheme: HTTP
          initialDelaySeconds: 5
        lifecycle:
          postStart:
            exec:
              command: ['/bin/sh','-c','iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-ports 80']
          preStop:
            exec:
              command: ['/bin/sh','-c','while killall python3; do sleep 1; done']
      restartPolicy: Always

    实例容器通过postStart执行IPtables命令设置端口重定向规则,将发往该pod IP的8080端口的所有请求重定向至80端口,从而让容器应用能够同时从8080端口接收请求。容器有借助preStop执行killall命令,它假设该命令能够更优雅的终止基于python3运行的容器应用。