浪小花 發表於 2020-1-21 10:34:00

kubernetes之容器健康状态检测

<h1 id="简介">简介</h1>
<p>此文讲述如何配置容器的<code>liveness</code>、<code>readiness</code>、<code>startup</code>探针。</p>
<p><code>kubelet</code> 使用<code>liveness</code>探测器来知道什么时候要重启容器。例如,<code>liveness</code>探测器可以捕捉到死锁(应用程序在运行,但是无法继续执行后面的步骤)。这样的情况下重启容器有助于让应用程序在有问题的情况下更可用。</p>
<p><code>kubelet</code> 使用<code>readiness</code>探测器可以知道容器什么时候准备好了并可以开始接受请求流量, 当一个 Pod 内的所有容器都准备好了,才能把这个 Pod 看作就绪了。这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。在 Pod 还没有准备好的时候,会从 Service 的负载均衡器中被剔除的。</p>
<p>kubelet 使用<code>startup</code>探测器可以知道应用程序容器什么时候启动了。如果配置了这类探测器,就可以控制容器在启动成功后再进行<code>liveness</code>和<code>readiness</code>检查,确保这些存活、就绪探测器不会影响应用程序的启动。这可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。</p>
<blockquote>
<p><strong>备注:此文档参考官方文档,并加以自己的理解。如有误导性的内容,请批评指正。</strong></p>
</blockquote>
<h2 id="定义一个-liveness-探针">定义一个 liveness 探针</h2>
<p>许多长时间运行的应用程序最终会过渡到断开的状态,除非重新启动,否则无法恢复。Kubernetes 提供了<code>liveness</code>探测器来发现并补救这种情况。</p>
<p>创建一个 Pod,其中运行一个基于 <code>k8s.gcr.io/busybox</code> 镜像的容器。配置文件如下。文件名:<code>exec-liveness.yaml</code></p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
labels:
    test: liveness
name: liveness-exec
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
</code></pre>
<p>在配置文件中,可以看到Pod中只有一个容器。<code>periodSeconds</code>字段指定了kubelet 应该每 5 秒执行一次存活检测。<code>initialDelaySeconds</code> 字段告诉 <code>kubelet</code> 在执行第一次探针前应该等待 5 秒。kubelet 在容器中执行命令<code>cat /tmp/healthy</code>来进行检测。如果命令执行成功并且返回值为0,kubelet会认为这个容器是健康存活的。如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。执行命令如下</p>
<pre><code>/bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600"
</code></pre>
<p>这个容器生命的前 30 秒,<code>/tmp/healthy</code>文件是存在的。执行命令<code>cat /tmp/healthy</code>会返回成功码。30秒后,执行命令<code>cat /tmp/healthy</code>就回返回失败码。</p>
<p>创建Pod</p>
<pre><code># kubectl apply -f /root/k8s-example/probe/exec-liveness.yaml
</code></pre>
<p>在 30 秒内,查看 Pod 的事件</p>
<pre><code>kubectl describe pod liveness-exec
</code></pre>
<p>输出结果显示还没有存活探测器失败</p>
<pre><code>Events:
Type    Reason   Age      From               Message
----    ------   ----       ----               -------
NormalScheduled&lt;unknown&gt;default-scheduler    Successfully assigned default/liveness-exec to k8s-node04
NormalPulled   22s      kubelet, k8s-node04Container image "k8s.gcr.io/busybox" already present on machine
NormalCreated    22s      kubelet, k8s-node04Created container liveness
NormalStarted    22s      kubelet, k8s-node04Started container liveness
</code></pre>
<p>30 秒之后,再来看 Pod 的事件:</p>
<pre><code>kubectl describe pod liveness-exec
</code></pre>
<p>在输出结果的最下面,有信息显示存活探测器失败了,这个容器被杀死并且被重建了。</p>
<pre><code>Events:
Type   Reason   Age               From               Message
----   ------   ----            ----               -------
Normal   Scheduled&lt;unknown&gt;         default-scheduler    Successfully assigned default/liveness-exec to k8s-node04
Normal   Pulled   47s               kubelet, k8s-node04Container image "k8s.gcr.io/busybox" already present on machine
Normal   Created    47s               kubelet, k8s-node04Created container liveness
Normal   Started    47s               kubelet, k8s-node04Started container liveness
WarningUnhealthy5s (x3 over 15s)kubelet, k8s-node04Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
Normal   Killing    5s                kubelet, k8s-node04Container liveness failed liveness probe, will be restarted
</code></pre>
<p>再等另外 30 秒,检查看这个容器被重启了:</p>
<pre><code>kubectl get pod liveness-exec
</code></pre>
<pre><code>NAME            READY   STATUS    RESTARTS   AGE
liveness-exec   1/1   Running   2          3m10s

</code></pre>
<p>再查看Pod资源详情:</p>
<pre><code>kubectl describe pod liveness-exec
</code></pre>
<p>输出结果如下,容器重启成功。</p>
<pre><code>Events:
Type   Reason   Age               From               Message
----   ------   ----                ----               -------
Normal   Scheduled&lt;unknown&gt;         default-scheduler    Successfully assigned default/liveness-exec to k8s-node04
WarningUnhealthy35s (x6 over 2m)    kubelet, k8s-node04Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
Normal   Killing    35s (x2 over 110s)kubelet, k8s-node04Container liveness failed liveness probe, will be restarted
Normal   Pulled   5s (x3 over 2m32s)kubelet, k8s-node04Container image "k8s.gcr.io/busybox" already present on machine
Normal   Created    5s (x3 over 2m32s)kubelet, k8s-node04Created container liveness
Normal   Started    5s (x3 over 2m32s)kubelet, k8s-node04Started container liveness
</code></pre>
<h2 id="定义一个存活态-http-请求接口">定义一个存活态 HTTP 请求接口</h2>
<p>另外一种类型的<code>liveness</code>探测方式是使用 HTTP GET 请求。下面是一个 Pod 的配置文件,其中运行一个基于 <code>k8s.gcr.io/liveness</code> 镜像的容器。</p>
<p>创建Pod。文件名:``</p>
<pre><code>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: X-Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3
</code></pre>
<p>配置文件中,Pod中只有一个容器。<code>periodSeconds</code>字段指定了kubelet每隔 3 秒执行一次检测。<code>initialDelaySeconds</code>字段告诉kubelet在执行第一次探测前应该等待 3 秒。kubelet 会向容器内运行的服务(服务会监听 8080 端口)发送一个 <code>HTTP GET</code> 请求来执行探测。如果服务上<code>/healthz</code>路径下的处理程序返回成功码。则kubelet认为容器是健康存活的。如果处理程序返回失败码,则kubelet会杀死这个容器并且重新启动它。</p>
<p>任何大于或等于 200 并且小于 400 的返回码标示成功,其它返回码都标示失败。</p>
<p>可以在这里看到服务的源码server.go。</p>
<p>容器存活的最开始 10 秒中,<code>/healthz</code> 处理程序返回一个 200 的状态码。之后处理程序返回 500 的状态码。</p>
<pre><code>http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
    duration := time.Now().Sub(started)
    if duration.Seconds() &gt; 10 {
      w.WriteHeader(500)
      w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
    } else {
      w.WriteHeader(200)
      w.Write([]byte("ok"))
    }
})
</code></pre>
<p>kubelet 在容器启动之后 3 秒开始执行健康检测。所以前几次健康检查都是成功的。但是 10 秒之后,健康检查会失败,并且 kubelet 会杀死容器再重新启动容器。</p>
<pre><code># kubectl apply -f /root/k8s-example/probe/http-liveness.yaml
</code></pre>
<p>10 秒之后,通过看 Pod 事件来检测存活探测器已经失败了并且容器被重新启动了。</p>
<pre><code>Events:
Type   Reason   Age            From               Message
----   ------   ----             ----               -------
Normal   Scheduled&lt;unknown&gt;      default-scheduler    Successfully assigned default/liveness-http to k8s-node01
Normal   Pulled   17s            kubelet, k8s-node01Container image "k8s.gcr.io/liveness" already present on machine
Normal   Created    17s            kubelet, k8s-node01Created container liveness
Normal   Started    16s            kubelet, k8s-node01Started container liveness
WarningUnhealthy1s (x2 over 4s)kubelet, k8s-node01Liveness probe failed: HTTP probe failed with statuscode: 500
</code></pre>
<h2 id="定义-tcp-的存活探测">定义 TCP 的存活探测</h2>
<p>第三种类型的<code>liveness</code>探测是使用 TCP 套接字。通过配置,kubelet 会尝试在指定端口和容器建立套接字链接。如果能建立链接,这个容器就被看作是健康的,如果不能则这个容器就被看作是有问题的。</p>
<p>创建一个Pod。文件名:<code>tcp-liveness-readiness.yaml</code></p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
name: goproxy
labels:
    app: goproxy
spec:
containers:
- name: goproxy
    image: k8s.gcr.io/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
      port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
      port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20
</code></pre>
<p>TCP 检测的配置和 HTTP 检测非常相似。下面这个例子同时使用就绪和存活探测器。kubelet 会在容器启动 5 秒后发送第一个就绪探测。这会尝试连接 goproxy 容器的 8080 端口。如果探测成功,这个 Pod 会被标记为就绪状态,kubelet 将继续每隔 10 秒运行一次检测。</p>
<p>除了<code>readiness</code>探测,这个配置包括了一个<code>liveness</code>探测。kubelet 会在容器启动 15 秒后进行第一次<code>liveness</code>探测。就像<code>readiness</code>探测一样,会尝试连接 goproxy 容器的 8080 端口。如果存活探测失败,这个容器会被重新启动。</p>
<pre><code># kubectl apply -f /root/k8s-example/probe/tcp-liveness-readiness.yaml
</code></pre>
<p>15 秒之后,通过看 Pod 事件来检测存活探测器:</p>
<pre><code># kubectl describe pod goproxy
</code></pre>
<h3 id="使用命名端口">使用命名端口</h3>
<p>对于 HTTP 或者 TCP 存活检测可以使用命名的容器端口。</p>
<pre><code>ports:
- name: liveness-port
containerPort: 8080
hostPort: 8080

livenessProbe:
httpGet:
    path: /healthz
    port: liveness-port
</code></pre>
<h2 id="使用startup探测器保护慢启动容器">使用startup探测器保护慢启动容器</h2>
<p>有时候,会有一些现有的应用程序在启动时需要较多的初始化时间。要不影响对引起探测死锁的快速响应,这种情况下,设置<code>liveness</code>探测参数是要技巧的。技巧就是使用一个命令来设置<code>startup</code>探测,针对HTTP 或者 TCP 检测,可以通过设置 <code>failureThreshold * periodSeconds</code> 参数来保证有足够长的时间应对糟糕情况下的启动时间。</p>
<p>所以,前面的例子就变成了:</p>
<pre><code>ports:
- name: liveness-port
containerPort: 8080
hostPort: 8080

livenessProbe:
httpGet:
    path: /healthz
    port: liveness-port
failureThreshold: 1
periodSeconds: 10

startupProbe:
httpGet:
    path: /healthz
    port: liveness-port
failureThreshold: 30
periodSeconds: 10
</code></pre>
<p>幸亏有<code>startup</code>探测,应用程序将会有最多 <code>5 分钟(30 * 10 = 300s)</code> 的时间来完成它的启动。 一旦<code>startup</code>探测成功一次,存活探测任务就会接管对容器的探测,对容器死锁可以快速响应。 如果<code>startup</code>探测一直没有成功,容器会在 300 秒后被杀死,并且根据 <code>restartPolicy</code> 来设置 Pod 状态。</p>
<h2 id="定义readliness探测器">定义<code>readliness</code>探测器</h2>
<p>有时候,应用程序会暂时性的不能提供通信服务。例如,应用程序在启动时可能需要加载很大的数据或配置文件,或是启动后要依赖等待外部服务。在这种情况下,既不想杀死应用程序,也不想给它发送请求。Kubernetes 提供了就绪探测器来发现并缓解这些情况。容器所在 Pod 上报还未就绪的信息,并且不接受通过 <code>Kubernetes Service</code> 的流量。</p>
<blockquote>
<p>注意:就绪探测器在容器的整个生命周期中保持运行状态。</p>
</blockquote>
<p>就绪探测器的配置和存活探测器的配置相似。唯一区别就是要使用 <code>readinessProbe</code> 字段,而不是 l<code>ivenessProbe</code> 字段。</p>
<pre><code>readinessProbe:
exec:
    command:
    - cat
    - /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
</code></pre>
<p>HTTP 和 TCP 的<code>readliness</code>探测器配置也和<code>liveness</code>探测器的配置一样的。</p>
<p><code>readliness</code>和<code>liveness</code>探测可以在同一个容器上并行使用。两者都用可以确保流量不会发给还没有准备好的容器,并且容器会在它们失败的时候被重新启动。</p>
<h2 id="配置探测器">配置探测器</h2>
<p>探测器有很多配置字段,可以使用这些字段精确的控制存活和就绪检测的行为:</p>
<ul>
<li>initialDelaySeconds:容器启动后要等待多少秒后存活和就绪探测器才被初始化,默认是 0 秒,最小值是 0。</li>
<li>periodSeconds:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。</li>
<li>timeoutSeconds:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。</li>
<li>successThreshold:探测器在失败后,被视为成功的最小连续成功数。默认值是 1。存活探测的这个值必须是 1。最小值是 1。</li>
<li>failureThreshold:当 Pod 启动了并且探测到失败,Kubernetes 的重试次数。存活探测情况下的放弃就意味着重新启动容器。就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。</li>
</ul>
<p>HTTP 探测器可以在 <code>httpGet</code> 上配置额外的字段:</p>
<ul>
<li>host:连接使用的主机名,默认是 Pod 的 IP。也可以在 HTTP 头中设置 “Host” 来代替。</li>
<li>scheme:用于设置连接主机的方式(HTTP 还是 HTTPS)。默认是 HTTP。</li>
<li>path:访问 HTTP 服务的路径。</li>
<li>httpHeaders:请求中自定义的 HTTP 头。HTTP 头字段允许重复。</li>
<li>port:访问容器的端口号或者端口名。如果数字必须在 1 ~ 65535 之间。</li>
</ul>
<p>对于 HTTP 探测,kubelet 发送一个 HTTP 请求到指定的路径和端口来执行检测。除非 <code>httpGet</code> 中的 <code>host</code> 字段设置了,否则 kubelet 默认是给 Pod 的 IP 地址发送探测。如果 <code>scheme</code> 字段设置为了 HTTPS,kubelet 会跳过证书验证发送 HTTPS 请求。大多数情况下,不需要设置<code>host</code>字段。这里有个需要设置 <code>host</code> 字段的场景,假设容器监听 <code>127.0.0.1</code>,并且 Pod 的 <code>hostNetwork</code> 字段设置为了 <code>true</code>。那么 <code>httpGet</code> 中的 <code>host</code> 字段应该设置为 <code>127.0.0.1</code>。可能更常见的情况是如果 Pod 依赖虚拟主机,你不应该设置 <code>host</code> 字段,而是应该在 <code>httpHeaders</code> 中设置 Host。</p>
<p>对于一次探测,kubelet 在节点上(不是在 Pod 里面)建立探测连接,这意味着你不能在 <code>host</code> 参数上配置 <code>service name</code>,因为 kubelet 不能解析 <code>service name</code>。</p><br><br>
来源:https://www.cnblogs.com/mcsiberiawolf/p/12220865.html
頁: [1]
查看完整版本: kubernetes之容器健康状态检测