• ASP.NET Core on K8S深入学习(6)Health Check


    本篇已加入《.NET Core on K8S学习实践系列文章索引》,可以点击查看更多容器化技术相关系列文章。

    一、关于K8S中的Health Check

      所谓Health Check,就是健康检查,即防微杜渐。K8S是一个编排引擎可以帮助我们快捷地部署容器集群,如果部署上错误的容器导致服务崩溃,通常情况下我们都会通过一些高可用机制进行故障转移。但是,前提条件是有健康检查。

      K8S自然帮我们考虑到了这个问题,健康检查是K8S的重要特性之一,默认有健康检查机制,此外还可以主动设置一些自定义的健康检查。

      默认情况下,每个容器启动时都会执行一个进程,由Dockerfile中的CMD或ENTRYPOINT指定。如果进程退出时的返回码不为0,则认为容器发生了故障,K8S会根据重启策略(restartPolicy)重启容器。

      例如下面这个例子,它模拟了容器发生故障的场景,注意下面配置文件中的args选项的定义:

    apiVersion: v1
    kind: Pod
    metadata:
      name: edc-healthcheck-demo
      labels:
        test: healthcheck
    spec:
      restartPolicy: OnFailure
      containers:
        - name: healthcheck
          image: busybox
          imagePullPolicy: IfNotPresent
          args:
          - /bin/sh
          - -c
          - sleep 10; exit 1

      其中 sleep 10; exit 1代表启动10秒之后就非正常退出(返回码不为0),然后通过kubectl创建Pod:

    kubectl apply -f health-check.yaml

      过一段时间后查看Pod的状态,如下图所示:

      

       可以看到,该容器已经重启了2次。也可以看出,restartPolicy简单直接暴力有效,不由感叹重启大法好!

      但是,也要正视一个问题:必须等到进程退出后的返回值是非零才会触发重启策略,不能直接监测容器是否是健康

      那么,K8S中有没有更好的机制能够实现智能一点的健康检查呢?答案就是使用Liveness与Readinesss。

    二、Liveness探测

    2.1 Liveness初体验

    一句话Liveness:如果检测有问题(如果健康检查失败),重启pod!至于怎么检测,你说了算(自定义判断容器是否健康的条件)!  

      Liveness提供了一些重要的参数:

    initialDelaySeconds:容器启动后第一次执行探测是需要等待多少秒,看运行的服务而定。
    periodSeconds:执行探测的频率,默认是10秒,最小1秒。
    timeoutSeconds:探测超时时间,默认1秒,最小1秒。
    successThreshold:探测失败后,最少连续探测成功多少次才被认定为成功,默认是1,对于liveness必须是1,最小值是1。
    failureThreshold:探测成功后,最少连续探测失败多少次才被认定为失败。默认是3。最小值是1.

      下面实践一个小例子创建一个Pod:

    #command自己定义,例子为 /tmp/healthy 不存在则认为pod有问题,大家根据实际业务来自定义。
    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        test: liveness
      name: liveness-demo
    spec:
      containers:
      - name: liveness
        image: busybox
        args:
        - /bin/sh
        - -c
        - touch /tmp/healthy; sleep 30; rm -rf/tmp/healthy; sleep 10
        livenessProbe:
          exec:
            command:
            - cat
            - /tmp/healthy
          initialDelaySeconds: 10
          periodSeconds: 5

      这里启动pod后会创建文件夹 /tmp/healthy,30秒后删除,在我们的设置中,如果 /tmp/healthy 存在,则认为容器处于正常状态,否则认为发生故障。

      需要注意的就是livenessProbe部分的定义了:

      (1)探测方法:通过cat命令查看/tmp/healthy是否存在;如果返回值为0,则探测成功;否则,探测失败;

      (2)initialDelaySeconds: 10 => 容器启动10秒之后开始执行liveness探测;

      (3)periodSeconds: 5 => 每5秒执行一次liveness探测;如果连续执行3次探测都失败,那么就会杀掉并重启容器;

      下面快速地验证一下:

      (1)kubectl创建demo

    kubectl apply -f liveness-demo.yaml

      (2)查看pod日志

    kubectl describe pod liveness-demo

      结果如下图所示:

      

       30秒之后,/tmp/healthy 被删除了,liveness探测失败,又过了几十秒,重复探测均失败后,开启了重启容器。

      

    2.2 Liveness探针

      上面的例子使用的是Liveness的exec探针,此外K8S还有几种其他类型的探针:

    • exec:在容器中执行一个命令,如果命令退出码返回0则表示探测成功,否则表示失败
    • tcpSocket:对指定的容IP及端口执行一个TCP检查,如果端口是开放的则表示探测成功,否则表示失败
    • httpGet:对指定的容器IP、端口及路径执行一个HTTP Get请求,如果返回的状态码在 [200,400)之间则表示探测成功,否则表示失败

      针对tcpSocket的例子:这里会检测80端口是否可以正常访问;

    #检测80端口是否联通
    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        test: readiness
      name: readiness-tcp
    spec:
      containers: 
      - name: readiness
        image: nginx
        readinessProbe:
          failureThreshold: 3
          tcpSocket:
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 10

      针对httpGet的例子:这里会检测index.html文件是否可以正常访问;

    #访问80端口的index.html文件是否存在
    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        test: readiness
      name: readiness-httpget
    spec:
      containers:
      - name: readiness
        image: nginx
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /index.html
            port: 80
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 10

    三、Readiness探测

    3.1 Readiness初体验

    一句话Readiness:如果检查失败,K8S会将该Pod从服务代理的分发后端去除,不再让其接客(分发请求给该Pod)。如果检测成功,那么K8S就会将容器加入到分发后端,重新对外接客(对外提供服务)。  

      下面继续以上面Liveness的例子来实践一下:

    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        test: readiness
      name: readiness-demo
    spec:
      containers:
      - name: readiness
        image: busybox
        args:
        - /bin/sh
        - -c
        - touch /tmp/healthy; sleep 30; rm -rf/tmp/healthy; sleep 10
        readinessProbe:
          exec:
            command:
            - cat
            - /tmp/healthy
          initialDelaySeconds: 10
          periodSeconds: 5

      readinessProbe的配置语法与livenessProbe完全一致,但执行后的效果却不一样,见下图所示:

      

      可以看出:

      (1)刚被创建时,其READY状态为不可用;

      (2)15秒(initialDelaySeconds + periodSeconds = 10 + 5 = 15)之后,第一次进行Readiness探测成功,其READY状态变为可用。

      (3)30秒之后,/tmp/healthy被删除,连续3次Readiness探测均失败后,其READY状态又变为了不可用。

      此外,我们也可以通过 kubectl describe pod readiness-demo 查看到更想起的日志信息。

    3.2 与Liveness的对比

      Liveness与Readiness都是K8S的Health Check机制,Liveness探测是重启容器,而Readiness探测则是将容器设置为不可用,不让其再接受Service转发的请求。

      Liveness与Readiness是独立执行的,二者无依赖,可以单独使用也可以同时使用。

    四、Health Check在K8S中的应用

    4.1 在Scale Up中的应用

      对于多副本应用,当执行Scale Up操作时,新的副本会作为后端服务加入到Service的负载均衡列表中。但是,很多时候应用的启动都需要一定的时间做准备(比如加载缓存、连接数据库等等),这时我们可以通过Readiness探测判断容器是否真正就绪,从而避免将请求发送到还未真正就绪的后端服务。

      下面一个示例YAML配置文件定义了Readiness探测,重点关注readinessProbe部分:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: edc-webapi-deployment
      namespace: aspnetcore
    spec:
      replicas: 2
      selector:
        matchLabels:
          name: edc-webapi
      template:
        metadata:
          labels:
            name: edc-webapi
        spec:
          containers:
          - name: edc-webapi-container
            image: edisonsaonian/k8s-demo:1.2
            ports:
            - containerPort: 80
            imagePullPolicy: IfNotPresent
            readinessProbe:
              httpGet:
                scheme: HTTP
                path: /api/health
                port: 80
              initialDelaySeconds: 10
              periodSeconds: 5
    
    ---
    
    apiVersion: v1
    kind: Service
    metadata:
      name: edc-webapi-service
      namespace: aspnetcore
    spec:
      type: NodePort
      ports:
        - nodePort: 31000 
          port: 8080
          targetPort: 80
      selector:
        name: edc-webapi

      对于readinessProbe部分:

      (1)schema指定了协议,这里是HTTP协议,也可以是HTTPS协议;

      (2)path指定访问路径,这里是我们自定义的一个Controller中的接口:简单地返回一个状态码为200的响应;

        [Produces("application/json")]
        [Route("api/Health")]
        public class HealthController : Controller
        {
            [HttpGet]
            public IActionResult Get() => Ok("ok");
        }

      (3)port指定端口,这里是容器的端口80;

      (4)initialDelaySeconds和periodSeconds指定了容器启动10秒之后开始探测,然后每隔5秒执行探测,如果发生3次以上探测失败,则该容器会从Service的负载均衡中移除,直到下次探测成功后才会重新加入。

    4.2 在Rolling Update中的应用

      假设现在有一个正常运行的多副本应用,我们要对其进行滚动更新即Rolling Update,K8S会逐步用新Pod替换旧Pod,结果就有可能发生这样的一个场景:当所有旧副本被替换之后,而新的Pod由于人为配置错误一直无法启动,因此整个应用将无法处理请求,无法对外提供服务,后果很严重!

      因此,Readiness探测还提供了用于避免滚动更新中出现这种情况的一些解决办法,比如maxSurge和maxUnavailable两个参数,用来控制副本替换的数量。

      继续以上面的YAML配置文件为例,重点关注strategy部分:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: edc-webapi-deployment
      namespace: aspnetcore
    spec:
      strategy:
        rollingupdate:
          maxSurge: 25%
          maxUnavailable: 25%
      replicas: 10
      selector:
        matchLabels:
          name: edc-webapi
      template:
        metadata:
          labels:
            name: edc-webapi
        spec:
          containers:
          - name: edc-webapi-container
            image: edisonsaonian/k8s-demo:1.2
            ports:
            - containerPort: 80
            imagePullPolicy: IfNotPresent
            readinessProbe:
              httpGet:
                scheme: HTTP
                path: /api/health
                port: 80
              initialDelaySeconds: 10
              periodSeconds: 5
    
    ---
    
    apiVersion: v1
    kind: Service
    metadata:
      name: edc-webapi-service
      namespace: aspnetcore
    spec:
      type: NodePort
      ports:
        - nodePort: 31000 
          port: 8080
          targetPort: 80
      selector:
        name: edc-webapi

      (1)maxSurge : 25% => 控制滚动更新过程中副本总数超过预期(这里预期是10个副本 replicas: 10)的上限,可以是数值也可以是百分比,然后向上取整。这里写的百分比,默认值是25%;

      如果预期副本数为10,那么副本总数的最大值为RoundUp(10 + 10 * 25%)=13个。

      (2)maxUnavailable : 25% => 控制滚动更新过程中不可用的副本(这里预期是10个副本 replicas: 10)占预期的最大比例,可以是数值也可以是百分比,然后向下取整,同样地默认值也是25%;

      如果预期副本总数为10,那么可用的副本数至少要为10-roundDown(10 * 25%)=10-2=8个。

      综上看来,maxSurge的值越大,初始创建的新副本数量就越多;maxUnavaliable值越大,初始销毁的旧副本数量就越多;

    五、小结

      本文探索了K8S中的默认健康检查机制以及Liveness和Readiness两种各有特点的探测机制,并通过一些小例子进行了说明。不过由于笔者也是初学,对于这一块没有过多实践经验,因此也是讲的比较粗浅,也希望以后能够有更多的实际经验分享与各位。

    参考资料

    (1)CloudMan,《每天5分钟玩转Kubernetes

    (2)李振良,《一天入门Kubernets教程

    (3)马哥(马永亮),《Kubernetes快速入门

    (4)华仔,《[译]Kubernetes最佳实践:使用Readiness和Liveness探测做Health Check

    (5)benjanmin杨,《K8S中的Health Check

    (6)条子在洗澡,《K8S健康性检查-探测

  • 相关阅读:
    画板
    多线程
    Runtime
    今日头条UI搭建
    支付宝UI界面搭建
    控制器的创建
    UIWindow简单介绍
    UIApplication
    UIPickerView的使用
    代理、通知、KVO
  • 原文地址:https://www.cnblogs.com/edisonchou/p/aspnet_core_on_k8s_deepstudy_part6.html
Copyright © 2020-2023  润新知