构建事件监控组件——云原生测试实战(5)

发表于:2024-2-19 09:28

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:孙高飞    来源:51Testing软件测试网原创

  6.3  构建事件监控组件
  虽然通过List-Watch中的watch接口可以实时获取到集群中对象的事件,但要从众多事件中过滤出服务异常事件以及从中获取到想要的信息是要花费一番工夫的。接下来将介绍一些其他功能来辅助事件监控组件的开发。
  6.3.1  Pod与容器的状态
  在K8s中判断一个Pod是否处于健康状态并不简单,使用kubectl get pods命令来查询Pod状态,显示结果如图6-1所示。
图6-1  查询Pod状态
  在图6-1中可以看到有两个字段(READY和STATUS)可以标识Pod的状态,相信不少读者在这里会对究竟以哪个字段为准来判断Pod当前的状态感到困惑。事实上,kubectl所展现出来的状态并不是K8s原始的状态,而是经过了一层封装后用一种用户比较容易理解的方式呈现出来的状态,虽然这种方式也容易让用户感到困惑,但相比原始状态已经非常好了。初学者一般会认为,如果STATUS字段显示为Running,那么Pod便是健康的。不可否认这个字段很有迷惑性,事实上,STATUS字段为Running只表示当前Pod处于运行阶段,并不能说明Pod中运行的容器是健康的,用户会发现如果Pod中的容器崩溃,STATUS字段依然显示为Running。也就是说,STATUS字段只表示Pod本身所处的调度阶段,只要Pod调度成功了它就显示为Running,至于Pod中的容器状态,STATUS字段是不负责的。所以,用户应通过READY字段来查看Pod中的容器是否处于健康状态。READY字段会用1/1这样的方式来表达容器状态,表示当前处于健康状态的容器数量和容器的总数。这里需要注意的是,如果为容器配置了探针,那么判断容器是否健康的依据就是探针是否探活成功,如果没有配置探针,那么容器只要处于运行状态就会被认为是健康的。一般来说,测试人员主要使用READY字段来判断该Pod是否可用。
  通过上述讲解,相信大家已经发现kubectl把Pod所处的调度阶段和容器状态分成了两个字段来表达,用户需要根据不同场景选取不同的字段。
  6.3.2  Pod的Condition和Phase
  虽然kubectl已经可以表达出Pod和容器的状态了,但它毕竟面向的是普通用户,在监控组件中是没有办法使用kubectl来完成状态判断的,我们需要利用K8s客户端原生的能力进行开发。不过kubectl的状态其实也只是针对K8s内部状态做了一层封装而已,其本质还是没有变化的。所以,在K8s内部通过PodCondition和PodPhase两个字段(即Condition和Phase)来表达Pod和容器的状态。代码清单6-7中分别列出了它们的定义。
  代码清单6-7  PodCondition和PodPhase:
  const (
      // ContainersReady表示Pod内所有容器已经处于Ready状态
      ContainersReady PodConditionType = "ContainersReady"
      // PodInitialized表示所有的初始化容器已经成功启动
      PodInitialized PodConditionType = "Initialized"
      // PodReady表示Pod已经准备好接收来自Service(K8s的负载均衡服务)的请求
      PodReady PodConditionType = "Ready"
      // PodScheduled表示Pod已经被K8s调度器处理,处于已经调度的状态
      PodScheduled PodConditionType = "PodScheduled"
  )

  // PodCondition包含Pod中容器当前状态的所有细节
  type PodCondition struct {
      // Type表示状态的类型
      Type PodConditionType `json:"type" protobuf:"bytes,1,opt,name = type,casttype 
                                                                         = PodConditionType"`
      // Status表示当前状态的值,这个值可以是True、False或者Unknown
      Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name = status,
                               casttype = ConditionStatus"`
      // LastProbeTime表示探针最后一次执行的时间
      LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:
                                          "bytes,3,opt,name = lastProbeTime"`
      // LastTransitionTime表示状态最后一次发生变化的时间
      LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:
                                                "bytes,4,opt,name = lastTransitionTime"`
      // Reason表示状态最后发生变化的原因
      Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name = reason"`
      // Message表示状态发生变化时的细节描述
      Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name = message"`
  }

  // PodPhase记录一个Pod的调度阶段
  type PodPhase string
  const (
      // PodPending表示Pod已经被K8s接受,但其中的容器还没有启动,这里需要花一些时间等待Pod与
             某个节点进行绑定或者拉取镜像
      PodPending PodPhase = "Pending"
      // PodRunning表示Pod已经绑定到一个节点并且其中所有容器都已经启动。这里需要至少一个容器处于
             运行状态,其他容器即便没有处于运行状态也是处于重新启动的过程中
      PodRunning PodPhase = "Running"
      // PodSucceeded表示在Pod中的所有容器已经运行成功并退出,这意味着容器的退出码为0
      PodSucceeded PodPhase = "Succeeded"
      // PodFailed表示在Pod中的所有容器已经退出运行,并且至少有一个容器的退出码是非0的
      PodFailed PodPhase = "Failed"
      // PodUnknown表示由于某些不确定的原因无法获取Pod当前的状态
      PodUnknown PodPhase = "Unknown"
  )
  PodCondition表示Pod中所有容器的健康状态,PodPhase表示Pod当前所处的调度阶段,这跟kubectl显示的STATUS和READY非常像。事实上kubectl展示的状态正源自PodCondition和PodPhase,只不过kubectl将自己封装后展示给用户。这里详细讲解一下PodCondition的使用方式,因为需要使用PodCondition来判断服务是否处于健康状态。
  在K8s中,每个对象都有多种类型的Condition,表明该对象处于什么状态,例如对PodCondition来说有PodScheduled、PodInitialized、ContainersReady和PodReady这4种类型。
  这里需要注意的是,ContainersReady与PodReady之间的区别,前者仅表示所有容器都处于运行状态,而后者则说明Pod中所有的容器不仅已经处于运行状态,并且探针都已经探测成功,该Pod已经被加入负载均衡器(K8s中为Service对象)来对外提供服务。这也是我们应该使用PodReady作为判断健康状态的依据的原因。Condition作为一个数组被保存在Pod的Status属性中,用户需要遍历该数组并找到PodReady所属的Condition来判断Pod是否健康,如代码清单6-8所示。
  代码清单6-8  Condition的调用方式:
  func checkIsPodsReadyByLabel(clientset *kubernetes.Clientset, labels, namespace 
                                    string) (bool, error){
      podsList, err := clientset.CoreV1().Pods(namespace).List(v1.ListOptions{
          LabelSelector: labels,
      })

      if err != nil {
          return false, errors.Wrap(err, "failed to get pods when check pods status")
      }

      var flag  = true
      for _, p := range podsList.Items {
          for _, condition := range p.Status.Conditions{
              if condition.Type == corev1.PodReady{
                  if condition.Status != corev1.ConditionTrue{
                      flag = false
                      break
                  }
              }
          }
      }
      return flag, nil
  }
  Condition是K8s中重要的状态管理机制,在不少开源的监控项目中会通过向K8s对象添加额外的Condition来标识当前对象的状态。例如,NPD(node-problem-detector,节点问题检测器)项目在检测到节点异常后就会为Node对象标识不同的Condition来告知用户当前节点的异常状态。
  6.3.3  获取异常容器
  目前我们已经可以在Pod发生异常时感知到相应的事件,接下来需要从事件中提取有用信息来帮助开发人员和测试人员进行分析。由于一个Pod中可以包含多个容器,因此为了找出哪一个容器出现了异常,需要遍历Pod中所有容器的状态来进行检查,如代码清单6-9所示。
  代码清单6-9  获取异常容器:
  ...
  for _, container := range pod.Status.ContainerStatuses {
      if container.State.Terminated != nil || container.State.Waiting != nil{
  ...
  从代码清单6-9中,可以看到Pod的Status属性除了包含Phase和Condition,还包含一个名为ContainerStatuses的数组,其中保存了该Pod所有容器的状态对象(即ContainerState)。而该状态对象也包含3个属性,如代码清单6-10所示。
  代码清单6-10  容器的3种状态:
  // ContainerState保存了容器所有可能的状态
  type ContainerState struct {
      // 保存处于等待状态容器的所有信息
      Waiting *ContainerStateWaiting `json:"waiting,omitempty" protobuf:"bytes,1,opt,
                                                name = waiting"`
      // 保存处于运行状态容器的所有信息
      Running *ContainerStateRunning `json:"running,omitempty" protobuf:"bytes,2,opt,
                                                name = running"`
      // 保存处于退出状态容器的所有信息
      Terminated *ContainerStateTerminated `json:"terminated,omitempty" protobuf:
                                                      "bytes,3,opt,name = terminated"`
  }
  在ContainerState中分别用Waiting、Running和Terminated这3个属性来表示容器现在是否处于等待状态、运行状态和退出状态,所以用户需要使用代码清单6-9所示的代码来判断容器是否处于非运行状态。这里需要注意的是,仅根据容器处于退出状态来判断异常是不够的,因为有些时候可能镜像仓库出现问题也会导致容器一直拉取不到镜像从而长时间保持等待状态,所以对于处于等待状态的容器,可以通过它的创建时间计算它处于这一状态的时长来判断是否出现了异常,如代码清单6-11所示。
  代码清单6-11  根据处于等待状态的时长判断容器是否异常:
  if time.Now().Before(pod.ObjectMeta.CreationTimestamp.Add(time.Minute * 10)) {
      log.Warnf("container terminated in 10 minutes after pod creation, maybe need 
                       an init time. pod:%s namespace:%s, skip", pod.Name, pod.Namespace)
  }
查看《云原生测试实战》全部连载章节
版权声明:51Testing软件测试网获得作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号