1.存活探针

  使用Kubernetes的一个主要好处是,可以给Kubernetes—个容器列表来由其保持容器在集群中的运行。可以通过让Kubernetes创建pod资源,为其选择一个工作节点并在该节点上运行该pod的容器来完成此操作。但是,如果其中一个容器终止,或一个pod的所有容器都终止,怎么办?

  只要将pod调度到某个节点,该节点上的Kubelet就会运行pod的容器,从此只要该pod存在,就会保持运行。如果容器的主进程崩溃,Kubelet将重启容器。如果应用程序中有一个导致它每隔一段时间就会崩溃的bug, Kubernetes会自动重启应用程序,所以即使应用程序本身没有做任何特殊的事,在Kubernetes中运行也能自动获得自我修复的能力。

  即使进程没有崩溃,有时应用程序也会停止正常工作。例如,具有内存泄漏的 Java应用程序将开始抛出OutOfMemoryErrors,但JVM进程会一直运行。如果有一种方法,能让应用程序向Kubernetes发出信号,告诉Kubernetes它运行异常并让 Kubernetes重新启动,那就很棒了。

  我们已经说过,一个崩溃的容器会自动重启,所以也许你会想到,可以在应用中捕获这类错误,并在错误发生时退出该进程。当然可以这样做,但这仍然不能解决所有的问题。

  例如,你的应用因为无限循环或死锁而停止响应。为确保应用程序在这种情况下可以重新启动,必须从外部检查应用程序的运行状况,而不是依赖于应用的内部检测。

 

1.1 介绍存活探针

  Kubernetes可以通过存活探针(liveness probe)检查容器是否还在运行。可以为pod中的每个容器单独指定存活探针。如果探测失败,Kubernetes将定期执行探针并重新启动容器。

  Kubernetes有以下三种探测容器的机制:

  HTTPGET探针对容器的IP地址(你指定的端口和路径)执行HTTPGET请求。

如果探测器收到响应,并且响应状态码不代表错误(换句话说,如果HTTP响应状态码是2xx或3xx),则认为探测成功。如果服务器返回错误响应状态码或者根本没有响应,那么探测就被认为是失败的,容器将被重新启动。
TCP套接字探针尝试与容器指定端口建立TCP连接。如果连接成功建立,则探测成功。否则,容器重新启动。
Exec探针在容器内执行任意命令,并检查命令的退出状态码。如果状态码是0,则探测成功。所有其他状态码都被认为失败。

 

1.2 创建基于HTTP的存活探针

  来看看如何为一个Node.js应用添加一个存活探针。因为它是一个Web应用程序,所以添加一个存活探针来检查其Web服务器是否提供请求是有意义的。但是因为这个Node.js应用程序太简单了,所以不得不人为地让它失败。

  要正确演示存活探针,需要稍微修改应用程序。在第五个请求之后,给每个请求返回HTTP状态码500 (Internal Server Error)–你的应用程序将正确处理前

  五个客户端请求,之后每个请求都会返回错误。多亏了存活探针,应用在这个时候会重启,使其能够再次正确处理客户端请求。

  你将创建一个包含HTTPGET存活探针的新pod,下面的代码显示了pod的yaml。

#代码4.1 将存活探针添加到pod
apiVersion: v1
kind: Pod
metadata:
  name: kubia-liveness
spec:
  containers:
  - image: luksa/kubia-unhealthy    #这个镜像包含了坏掉的应用
    name: kubia
    livenessProbe:                             #一个HTTP GET存活探针
      httpGet:
        path: /                                      #请求
        port: 8080                                #探针连接的网络端口

  该pod的描述文件定义了一个httpGet存活探针,该探针告诉Kubernetes定期在端口 8080路径上执行HTTP GET请求,以确定该容器是否健康。这些请求在容器运行后立即开始。

  经过五次这样的请求(或实际的客户端请求)后,你的应用程序开始返回HTTP状态码500, Kubernetes会认为探测失败并重启容器。

 

1.3 使用存活探针

  要查看存活探针是如何工作的,请尝试立即创建该pod。大约一分半钟后,容器将重启。可以通过运行kubectl get看到:

$ kubectl get po kubia-liveness
NAME             READY     STATUS    RESTARTS   AGE
kubia-liveness   1/1       Running   1          2m

  RESTARTS列显示pod的容器已被重启一次(如果你再等一分半钟,它会再次重启,然后无限循环下去)。

  可以通过查看kubectl describe的内容来了解为什么必须重启容器,如下面的代码清单所示

$ kubectl describe po kubia-liveness
Name:           kubia-liveness
...
Containers:
  kubia:
    Container ID:       docker://480986f8
    Image:              luksa/kubia-unhealthy
    Image ID:           docker://sha256:2b208508
    Port:
    State:              Running                                         
      Started:          Sun, 14 May 2017 11:41:40 +0200                                 #容器正在运行          
    Last State:         Terminated                                                                    #先前的容器由于发生错误被终止,返回码是147
      Reason:           Error                                           
      Exit Code:        137                                             
      Started:          Mon, 01 Jan 0001 00:00:00 +0000                 
      Finished:         Sun, 14 May 2017 11:41:38 +0200                 
    Ready:              True
    Restart Count:      1                                                                                 #该容器已被重启一次
    Liveness:           http-get http://:8080/ delay=0s timeout=1s
                        period=10s #success=1 #failure=3
    ...
Events:
... Killing container with id docker://95246981:pod "kubia-liveness ..."
    container "kubia" is unhealthy, it will be killed and re-created.

  可以看到容器现在正在运行,但之前由于错误而终止。退出代码为137,这有特殊的含义——表示该进程由外部信号终止。数字137是两个数字的总和:128 + x,其中x是终止进程的信号编号。在这个例子中,x等于9,这是SIGKILL的信号编号,意味着这个进程被强行终止。

  在底部列出的事件显示了容器为什么终止一一Kubernetes发现容器不健康,所以终止并重新创建。

  注意:当容器被强行终止时,会创建一个全新的容器——而不是重启原来的容器。

 

1.4 配置存活探针的附加属性

  你可能己经注意到,kubectl describe还显示关于存活探针的附加信息:

Liveness http-get http://:8080/ delay=0s timeout=1s period=1Os #success=1  #failure=3

  除了明确指定的存活探针选项,还可以看到其他属性,例如delay (延迟)、timeout (超时)、period (周期)等。delay=0s部分显示在容器启动后立即开始探测。timeout仅设置为1秒,因此容器必须在1秒内进行响应,不然这次探测记作失败。每10秒探测一次容器(period=lOs),并在探测连续三次失败 (#failure=3)后重启容器。

  定义探针时可以自定义这些附加参数。例如,要设置初始延迟,请将 initialDelaySeconds属性添加到存活探针的配置中,如下面的代码清单所示。

#代码4.3 具有初始延迟的存活探针:kubia-liveness-probe-initial-delay.yaml
apiVersion: v1 kind: Pod metadata: name: kubia
-liveness spec: containers: - image: luksa/kubia-unhealthy name: kubia livenessProbe: httpGet: path: / port: 8080 initialDelaySeconds: 15 #kubernetes会在第一次探测前等待15秒,为了让有些启动长时间的程序有个缓冲 timeoutSeconds: 10 #超过10s或者返回码不在200~300内,就绪检查就失败 periodSeconds: 5 #每5秒检查一次

 

2.就绪探针

  如果pod没有准备好,如何处理服务请求呢?

  有些pod可能需要时间来加载配置或数据,或者可能需要执行预热过程以防止第一个用户请求时间太长影响了用户体验。在这种情况下,不希望该pod立即开始接收请求,尤其是在运行的实例可以正确快速地处理请求的情况下。不要将请求转发到正在启动的pod中,直到完全准备就绪。

2.1 介绍就绪探针

  了解了存活探针,以及它们如何通过确保异常容器自动重启来保持应用程序的正常运行。与存活探针类似,Kubernetes还允许为容器定义准备就绪探针。

  就绪探测器会定期调用,并确定特定的pod是否接收客户端请求。当容器的准备就绪探测返回成功时,表示容器已准备好接收请求。

  这个准备就绪的概念显然是每个容器特有的东西。Kubernetes只能检查在容器中运行的应用程序是否响应一个简单的GET/请求,或者它可以响应特定的URL路径(该URL导致应用程序执行一系列检查以确定它是否准备就绪)。考虑到应用程序的具体情况,这种确切的准备就绪的判定是应用程序开发人员的责任。

  就绪探针的类型

  像存活探针一样,就绪探针有三种类型:

Exec探针,执行进程的地方。容器的状态由进程的退出状态代码确定。
HTTP GET探针,向容器发送HTTP GET请求,通过响应HTTP状态代码,判断容器是否准备好。
TCP socket探针,它打开一个TCP连接到容器的指定端口。如果连接己建立,则认为容器己准备就绪。

  了解就绪探针的操作

  启动容器时,可以为Kubernetes配置一个等待时间,经过等待时间后才可以执行第一次准备就绪检查。之后,它会周期性地调用探针,并根据就绪探针的结果采取行动。如果某个pod报告它尚未准备就绪,则会从该服务中删除该pod。如果pod再次准备就绪,则重新添加pod。

  与存活探针不同,如果容器未通过准备检查,则不会被终止或重新启动。这是存活探针与就绪探针之间的重要区别。存活探针通过杀死异常的容器并用新的正常 容器替代它们来保持pod正常工作,而就绪探针确保只有准备好处理请求的pod才可以接收它们(请求)。这在容器启动时最为必要,当然在容器运行一段时间后也是有用的。

  如图5.11所示,如果一个容器的就绪探测失败,则将该容器从端点对象中移除。 连接到该服务的客户端不会被重定向到pod。这和pod与服务的标签选择器完全不匹配的效果相同。

kubernetes的存活探针和就绪探针-风君雪科技博客

  了解就绪探针的重要性

  设想一组pod(例如,运行应用程序服务器的pod)取决于另一个pod(例如,后端数据库)提供的服务。如果任何一个前端连接点出现连接问题并且无法再访问数据库,那么就绪探针可能会告知Kubernetes该pod没有准备好处理任何请求。如果其他pod实例没有遇到类似的连接问题,则它们可以正常处理请求。就绪探针确保客户端只与正常的pod交互,并且永远不会知道系统存在问题。

 

2.2 向pod 添加就绪探针

  接下来,将通过修改Replication Controller(deployment也一样)的pod模板来为现有的pod添加就绪探针。

  向pod template添加就绪探针

  可以通过kubectl edit命令来向己存在的ReplicationController中的pod模板添加探针。

$ kubectl edit rc kubia

  当在文本编辑器中打开ReplicationControlier的YAML时,在pod模板中查找容器规格,并将以下就绪探针定义添加到spec.template.spec.containers下的第一个容器。YAML看起来应该就像下面的代码清单。

#代码5.17 RC创建带有就绪探针的pod:kubia-rc-readinessprobe.yaml
apiVersion: v1 kind: ReplicationController metadata: name: kubia spec: replicas:
3 selector: app: kubia template: metadata: labels: app: kubia spec: containers: - name: kubia image: luksa/kubia ports: - name: http containerPort: 8080 readinessProbe: #pod中每个容器都会有一个就绪探针 exec: #使用exec的方式 command: - ls - /var/ready

  就绪探针将定期在容器内执行ls/var/ready命令。如果文件存在,则ls命令返回退出码0,否则返回非零的退出码。如果文件存在,则就绪探针将成功;否则,它会失败。

  定义这样一个奇怪的就绪探针的原因是,可以通过创建或删除有问题的文件来触发结果。该文件尚不存在,所以所有的pod现在应该报告没有准备好,是这样的吗?其实并不完全是,正如在前面章节中了解的那样,更改ReplicationController的pod模板对现有的pod没有影响。

  换句话说,现有的所有pod仍没有定义准备就绪探针。可以通过使用kubectl get pods列出pod并查看READY列。需要删除pod并让它们通过ReplicationController重新创建。新的pod将进行就绪检查会一直失败,并且不会将其作为服务的端点,直到在每个pod中创建/var/ready文件。

  观察并修改pod 就绪状态

  再次列出pod并检查它们是否准备好:

$ kubectl get po
NAME       READY STATUS RESTARTS AGE
kubia-2rlqb 0/1  Running 0       lm
kubia-3raxl 0/1  Running 0       lm
kubia-3yw4s 0/1  Running 0       lm

  READY列显示出没有一个容器准备好。现在通过创建/var/ready文件使其中一个文件的就绪探针返回成功,该文件的存在可以模拟就绪探针成功:

  使用kubectl exec命令在kubia-2rlqb的pod容器内执行touch命令。 如果文件尚不存在,touch命令会创建该文件。就绪探针命令现在应该返回退出码0, 这意味着探测成功,并且现在应该显示pod己准备就绪。现在去查看其状态:

$ kubectl get po kubia-2rlqb
NAME            READY STATUS   RESTARTS AGE
kubia-2rlqb      0/1   Running     0     2

  该pod还没有准备好。有什么不对或者这是预期的结果吗?用kubectl describe来获得更详细的关于pod的信息。输出应该包含以下内容:

Readiness: exec [ls /var/ready] delay=0s timeout=1s period=10s #success=1  #failure=3

  准备就绪探针会定期检查–默认情况下每10秒检查一次。由于尚未调用就绪探针,因此容器未准备好。但是最晚10秒钟内,该pod应该己经准备就绪,其IP应该列为service的endpoint(运行kubectl get endpoint kubia-loadbalancer来确认)。

  服务打向单独pod

  现在可以点击几次服务网址,查看每个请求都被重定向到这个pod:

$ curl http://130.211.53.173
You've hit kubia-2rlqb
$ curl http://130.211.53.173
You've hit kubia-2rlqb
$ curl http://130.211.53.173
You've hit kubia-2rlqb

  即使有三个pod正在运行,但只有一个pod报告己准备好,因此是唯一的pod接收请求。如果现在删除该文件,则将再次从该服务中删除该容器。

 

2.3 了解就绪探针的实际作用

  此模拟就绪探针仅用于演示就绪探针的功能。在实际应用中,应用程序是否可以(并且希望)接收客户端请求,决定了就绪探测应该返回成功或失败。

  应该通过删除pod或更改pod标签而不是手动更改探针来从服务中手动移除pod。

  提示:如果想要从某个服务中手动添加或删除pod,请将enabled=true作为标签添加到pod,以及服务的标签选择器中。当想要从服务中移除pod时,删除标签。

  务必定义就绪探针,就绪探针当然在容器运行一段时间后也是有用的。可以检测失败的容器并移除。

3.总结

  1.使用存活探针,让Kubernetes在容器不再健康的情况下立即重启它(应用程序定义了健康的条件)。

  2.如果定义了就绪探针,请确保它返回成功;否则该pod不会成为服务的一部分。

作者:小家电维修

相见有时,后会无期。