10. 可观测性:应用健康

本文由 CNCF + Alibaba 云原生技术公开课 整理而来

需求来源

当把应用迁移到 Kubernetes 之后,要如何去保障应用的健康与稳定呢?其实很简单,可以从两个方面来进行增强:

1. 首先是提高应用的可观测性

2. 其次是提高应用的可恢复能力

从可观测性方面看,可以在三个角度进行增强:

1. 首先是应用的健康状态,可以实时地进行观测

2. 其次是可以获取应用的资源使用情况

3. 最后是可以拿到应用的实时日志,进行问题的诊断与分析

当出现问题之后,首先要做的事情是降低影响的范围,进行问题的调试与诊断。最后当出现问题时,理想的状况是:可以通过和 Kubernetes 集成的自愈机制进行完整的恢复。


Liveness 与 Readiness

  • 应用健康状态-LivenessReadiness

Liveness Probe 也称为存活探针,用来判断一个 Pod 是否处于存活状态。当一个 Pod 处于非存活状态时,会由上层的判断机制来判断这个 Pod 是否需要被重新拉起。如果上层配置的重启策略是 restart always 时,那此时这个 Pod 会直接被重新拉起。

Readiness Probe 也称为就绪探针,用来判断一个 Pod 是否处于就绪状态。一个 Pod 仅处于存活状态还不够,它可能无法提供正常的服务,因此还需要对 Pod 进行就绪检测。只有当 Pod 处于就绪状态的时候,它才能够对外提供相应的服务,也就是接入层的流量才能转发到相应的 Pod。当一个 Pod 不处于就绪状态时,接入层会把相应的流量从这个 Pod 上面进行摘除。

  • 应用健康状态-使用方式:

Liveness ProbeReadiness Probe 都支持以下 3 种不同的指针探测方式:

1. httpGet:它是通过发送 http Get 请求来进行判断的。当返回码是 200 ~ 399 之间的状态码时,说明这个应用是健康的

2. Exec:它是通过执行容器中的一个命令来判断当前服务是否正常的。当这条执行的命令的返回状态($?)为 0 时,说明这个容器是健康的

3. tcpSocket:它是通过探测容器的 IP 和 Port 来进行 TCP 健康检查的。如果这个 TCP 连接能够正常被建立,说明这个容器是健康的

探针探测方式的探测结果也主要分为 3 种:

1. Success:当状态是 Success 时,表示容器通过了健康检查,也就是 Liveness Probe 或 Readiness Probe 是正常的一个状态

2. Failure:当状态是 Failure 时,表示容器没有通过健康检查。此时如果是 Liveness Probe 探测失败,可能会去重启 Pod;
            此时如果是 Readiness Probe 探测失败,则 Service 会将没有通过检查的 Pod 踢除,不会再将流量转发到这些 Pod 上进行处理

3. Unknown:当状态是 Unknown 时,表示当前执行的机制没有完整的执行,可能是因为执行超时或类似脚本没有及时返回结果,
            此时 Liveness Probe 或 Readiness Probe 不会做任何操作,会等待下一次的机制来进行检验

在 Kubelet 中有 一个 ProbeManager 组件,这个组件里面会包含 Liveness ProbeReadiness Probe,这两个 Probe 会将相应的 Liveness 诊断和 Readiness 诊断作用在 Pod 之上,来实现一个具体的判断。

  • 应用健康状态-Pod Probe Spec

liveness-exec Pod yaml 文件示例:

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

文件解析:

.spec.contaniners.livenessProbe     声明 Pod 的 Liveness Probe

.spec.contaniners.livenessProbe.exec    声明 Liveness Probe 采用 Exec 方式执行命令(cat /tmp/healthy)探测容器是否存活

.spec.contaniners.livenessProbe.initialDelaySeconds     声明 Liveness Probe 在容器启动后多长时间开始第一次探测,以秒为单位

.spec.contaniners.livenessProbe.periodSeconds       声明 Liveness Probe 探测的时间间隔,以秒为单位

liveness-http Pod yaml 文件示例:

apiVersion: v1
kind: Pod
metadata:
  name: liveness-http
  labels:
    test: liveness
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

文件解析:

.spec.contaniners.livenessProbe     声明 Pod 的 Liveness Probe

.spec.contaniners.livenessProbe.httpGet    声明 Liveness Probe 采用 httpGet 方式 http Get 请求探测容器是否存活

.spec.contaniners.livenessProbe.initialDelaySeconds     声明 Liveness Probe 在 Pod 启动后多长时间开始第一次探测,以秒为单位

.spec.contaniners.livenessProbe.periodSeconds       声明 Liveness Probe 探测的时间间隔,以秒为单位

goproxy Pod yaml 文件示例:

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

文件解析:

.spec.contaniners.livenessProbe     声明 Pod 的 Liveness Probe

.spec.contaniners.livenessProbe.exec    声明 Liveness Probe 采用 tcpSocket 方式建立 TCP 连接探测容器是否存活

.spec.contaniners.livenessProbe.initialDelaySeconds     声明 Liveness Probe 在容器启动后多长时间开始第一次探测,以秒为单位

.spec.contaniners.livenessProbe.periodSeconds       声明 Liveness Probe 探测的频率,以秒为单位

.spec.contaniners.readinessProbe    声明 Pod 的 Readiness Probe

.spec.contaniners.readinessProbe.exec    声明 Readiness Probe 采用 tcpSocket 方式建立 TCP 连接探测容器是否就绪

.spec.contaniners.readinessProbe.initialDelaySeconds     声明 Readiness Probe 在容器启动后多长时间开始第一次探测,以秒为单位

.spec.contaniners.readinessProbe.periodSeconds       声明 Readiness Probe 探测的频率,以秒为单位

Liveness ProbeReadiness Probe 的更多相关参数:

initialDelaySeconds,表示 Pod 启动多久后进行第一次探测

periodSeconds,表示探测的时间间隔,默认是 10s

timeoutSeconds,表示探测的超时时间,当超时时间之内没有探测成功,那么 Pod 会被认为是失败状态

successThreshold,表示当 Pod 从探测失败到再一次判断探测成功,所需要的阈值次数。默认是 1 次,即原本 Pod 是失败的,也只需要 1 次探测成功,就可以认为 Pod 回到正常状态

failureThreshold,表示探测失败的重试次数。默认是 3 次,即当 Pod 从一个健康的状态连续探测失败 3 次,那么就可以认为该 Pod 处于失败状态
  • 应用健康状态-LivenessReadiness 总结:
Pod Liveness Probe(存活探针) Readiness Probe(就绪探针)
介绍 判断容器是否存活 判断容器是否就绪
检测失败 杀掉 Pod 切断上层流量到 Pod
适用场景 支持重新拉起的应用 启动后无法立即对外服务的应用

注意事项:

不管是 Liveness Probe 还是 Readiness Probe,选择合适的探测方式可以防止被误操作:

1. 调大判断的超时阈值,防止在容器压力较高的情况下出现偶发超时

2. 调整判断的次数阈值,3 次的默认值在短周期下不一定是最佳实践

3. exec 如果执行的 shell 脚本判断,在容器中可能调用时间会比较长

4. 使用 tcpSocket 方式如果遇到 TLS 场景,需要业务层判断是否有影响

问题诊断

  • 应用故障排查-了解状态机制:

kubernetes 整体的设计是面向状态的,通过 yaml 文件来定义一个期望状态,真正在 yaml 的执行过程中会有各种各样的 Controller 来负责整体状态之间的转换。

对于一个 Pod 来说,刚开始它处在一个 Pending 状态,那接下来可能会转换到 Running,也可能转换到 Unknown,甚至可以转换到 Failed。当 Running 执行了一段时间之后,它可以转换到 Successded 或者 Failed;然后当出现在 Unknown 这个状态时,可能由于一些状态的恢复,它会重新恢复到 RunningSuccessdedFailed

Kubernetes 整体的一个状态就是基于这种类似像状态机的一个机制进行转换的,而不同状态之间的转化都会在相应的 Kubernetes 对象上留下类似像 Status 或者像 Conditions 的一些字段来进行表示。

  • 应用故障排查-常见应用异常:

Pod 停留在 Pending

第一个就是 Pending 状态,Pending 表示调度器没有进入介入。此时可以通过 kubectl describe pod 来查看相应的事件,如果由于资源不足或端口占用,
或者是由于 Node Selecotr 造成 Pod 无法调度时,可以在事件里看到相应的结果,这个结果会显示导致 Pending 的具体原因。

Pod 停留在 Waiting

第二个状态就是 Pod 可能会停留在 Waiting 的状态,Pod 的 States 处在 Waiting 的时候,通常表示这个 Pod 的镜像没有正常拉取。原因可能是由于这个镜像是私有镜像,
但是没有配置 Pod Secret;还可能由于这个镜像地址是不存在的,造成这个镜像拉取不下来;最后这个镜像可能是一个公网的镜像,但由于网络原因造成镜像的拉取失败。

Pod 不断被拉取并且可以看到 Crashing

第三种是 Pod 不断被拉起,而且可以看到类似像 Backoff,通常表示 Pod 已经被调度完成了,但是启动失败,此时通常要关注的应该是这个应用本身的一个状态,
并不是说配置是否正确、权限是否正确,此时需要查看的应该是 Pod 的具体日志。

Pod 处在 Runing 但是没有正常工作

第四种 Pod 处在 Running 状态,但是没有正常对外服务。此时比较常见的一个点就可能是由于一些非常细碎的配置,类似像有一些字段可能拼写错误,造成了 yaml 下发下去了,
但是有一段没有正常地生效,从而使得这个 Pod 处在 Running 的状态没有对外服务,那此时可以通过 apply-validate-f pod.yaml 的方式来进行判断当前 yaml 是否是正常的,
如果 yaml 没有问题,那么接下来可能要诊断配置的端口是否是正常的,以及 Liveness Probe 或 Readiness Probe 是否已经配置正确。

Service 无法正常的工作

最后一种就是 Service 无法正常工作的时候,该怎么去判断呢?那比较常见的 Service 出现问题的时候,是在使用上面出现了问题。因为 Service 和底层的 Pod 之间的关联关系是
通过 Selector 的方式来匹配的,也就是说 Pod 上面配置了一些 Label,然后 Service 通过 match label 的方式和这个 Pod 进行相互关联。如果这个 Label 配置的有问题,
可能会造成这个 Service 无法找到后面的 Endpoint,进而造成相应的 Service 没有办法对外提供服务,那如果 Service 出现异常的时候,首先要看的是这个 Service 后面是不是
有一个真正的 Endpoint,其次来看这个 Endpoint 是否可以对外提供正常的服务。

应用远程调试

  • 应用远程调试-Pod 远程调试:

首先把一个应用部署到集群里面的时候,发现问题的时候,需要进行快速验证修改的时候,可以通过远程调试的方式进行修改、验证。

进入一个正在运行的 Pod

kubectl exec -it pod-name /bin/bash

进入一个正在运行且包含多个容器的 Pod

kubectl exec -it pod-name -c container-name /bin/bash
  • 应用远程调试-Service 远程调试:

Service 的远程调试分为两部分:

反向链路:将一个服务暴露到远程的一个集群之内,让远程集群内的一些应用来调用本地的这个服务

正向链路:让本地的这个服务能够调用远程的服务

在反向链路上有一个开源组件 Telepresence,它可以将本地的应用代理到远程集群中的一个 Service 上。首先将 Telepresence 的一个 Proxy 应用部署到远程的 Kubernetes 集群里面,然后使用命令 Telepresence-swap-deployment $DEPLOYMENT_NAME 将本地一个应用代理到远程集群中的一个 Service 之上,这样就可以将应用在远程集群里面进行本地调试。

在正向链路上,如果本地应用需要调用远程集群的服务时候,可以通过 port-forward 的方式将远程的应用调用到本地的端口上。使用命令 kubectl port-forward svc/app -n app-namespace,之后就可以通过访问本地端口来访问远程的服务。

  • 开源调试工具-kubectl-debug

kubectl-debugkubectl 的一个插件,这个工具依赖于 Linux namespace 进行隔离,它可以 datash 一个 Linux namespace 到一个额外的 container,然后在这个 container 里面执行任何的 debug 动作,这和直接 debug 这个 Linux namespace 是一致的。

如果已经安装 kubectl-debug,此时就可以通过 kubectl debug pod-name 直接去诊断远程的一个 Pod。首先它会去拉取镜像,镜像中会默认带一些诊断工具,当镜像启用时,它会把 debug container 进行启动,同时会把 debug container 与要诊断的 container 的 namespace 进行挂靠,即 debug container 与要诊断的 container 在同一 namespace 下,因此 debug container 中可以实时查看相关的参数。


猜你喜欢

转载自blog.csdn.net/miss1181248983/article/details/111596859
10.
今日推荐