k8s 如何让你的应用活的更久

众所周知,k8s 可以托管你的服务 / 应用,当出现各种原因导致你的应用挂掉之后,k8s 会自动重启你的服务,那么 k8s 是如何做到的呢?它又是通过什么功能实现这个效果的呢?这就是本文将要介绍的内容: 副本控制器存活探针

注:因为本文讨论的是如何让应用活的更久,所以文中提到的 pod 类型均为持久型,不包含其他如Job之类的应用。

副本控制器

首先解释一下副本控制器是什么,副本控制器是 k8s 中用于控制 pod 数量的一个工具,只要 告诉它你期望中的 pod 有多少,他就会努力去把 pod 控制在这个数量。这个副本控制器具体有两种,分别是:ReplicationController(简称rc)和ReplicaSet(简称rs)。在具体了解他们的区别之前,我们先来看一下副本控制器为了达到目标(把 pod 数量维持在你给它的期望值)都做了那些事:

  • 找到目标 pod。
  • 检查其数量,如果不符合则自动增减。

很简单不是么,只做了两步,这和把大象塞进冰箱没什么实质性的区别。好,先来看第一步:如何找到目标 pod。

通过标签查找指定目标

副本控制器是通过 pod 的标签来进行查找的,在创建副本控制器时,需要给他指定标签选择器。在生成之后,它就会通过自己携带的选择器来查找 pod 并将其纳入自己的管辖之下。比如,你可以通过下述文件创建一个ReplicationController,我们给其命名为kubia-controller

ReplicationController.yaml

apiVersion: v1
# 说明类型为 rc
kind: ReplicationController
metadata:
  name: kubia-controller
spec:
  # 这里指定了期望的副本数量
  replicas: 3
  # 这里指定了目标 pod 的选择器
  selector:
    # 目标 pod: app 标签的值为 kubia
    app: kubia
  # pod 模板
  template:
    metadata:
      # 指定 pod 的标签
      labels:
        app: kubia
    spec:
      # 指定 pod 容器的内容
      containers:
      - name: kubia
        image: luksa/kubia
        ports:
        - containerPort: 8080

什么是标签?

标签是 k8s 中用于分类 pod 提供的一个附加属性(当然所有资源都可以添加标签),一个标签包含标签名及标签值,如app=kubiaapp就是标签名,kubia是标签值,一个 pod 可以贴上多个标签,k8s 中的绝大多数资源都是通过标签来筛选/控制 pod 的。

可以通过如下命令来查看标签:

  • 显示 pod 标签:kubectl get pod --show-labels
  • 将指定标签单独列出一列:kubectl get pod -L <标签名>
  • 筛选指定标签的 pod:kubectl get pod -l <标签名>

然后使用kubectl create -f ReplicationController.yaml就可以创建出一个可用的副本控制器了。这时候再使用kubectl get pod,就会发现副本控制器kubia-controller自己创建了三个 pod:

root@master1:~# kubectl get pod                    
NAME          READY   STATUS    RESTARTS   AGE
kubia-2bb8c   1/1     Running   0          46s
kubia-6jv9r   1/1     Running   0          46s
kubia-rqnbx   1/1     Running   0          46s

kubia-controller在创建之后发现自己要管理的 pod 一个都没有(ReplicationController.yaml里我们指定了要检查标签app的值为kubia的 pod),于是kubia-controller根据ReplicationController.yaml里我们告诉它的 pod 模板创建了三个新的 pod。

这时候如果我们使用kubectl delete pod kubia-2bb8c杀死其中的一个 pod。再次查看之后就会发现kubia-controller又创建出了一个全新的 pod:

root@master1:~# kubectl delete pod kubia-2bb8c
pod "kubia-2bb8c" deleted
root@master1:~# kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
kubia-6bdq7   1/1     Running   0          80s
kubia-6jv9r   1/1     Running   0          13m
kubia-rqnbx   1/1     Running   0          13m

这里需要强调的是,副本控制器是通过 pod 的标签来进行控制的。如果修改了某个 pod 的标签,那么就可以使其脱离指定rc的控制。

例如,我们刚才生成的kubia-controller控制器是控制标签app的值为kubia的 pod。如下,我们修改一个已存在 pod 的标签。然后就会发现kubia-controller又新建了一个 pod,这说明标签被我们修改的 pod 脱离了该副本控制器的管理:

root@master1:~# kubectl label pod kubia-6bdq7 app=kubia-v1 --overwrite 
pod/kubia-6bdq7 labeled
root@master1:~# kubectl get pod -L app
NAME          READY   STATUS    RESTARTS   AGE   APP
kubia-6bdq7   1/1     Running   0          12m   kubia-v1
kubia-6jv9r   1/1     Running   0          24m   kubia
kubia-rqnbx   1/1     Running   0          24m   kubia
kubia-tsz58   1/1     Running   0          17s   kubia

这时再把这个 pod 的标签改回来,就可以看到kubia-controller因为副本数量大于 4,不符合期望数量而删除了一个 pod。

了解完了ReplicationController之后再来看一下ReplicaSet(简称rs)。其实这两个的区别就是,rs支持更加完善的标签选择器,例如,rc的标签选择器允许包含某个标签的 pod。而rs支持如下条件:

  • 匹配缺少某个标签的 pod
  • 包含特定标签名的 pod
  • 匹配多组标签
  • 只匹配标签名而不关心标签值
  • 等等等等...

好了,下面来看一下如何生成一个rs,首先生成如下yaml文件:

replicaset.yaml

# 需要指定对应的 apiVersion
apiVersion: apps/v1beta2
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    # 只有这里和 rc 的写法不同
    matchLabels:
      app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia
        ports:
        - containerPort: 8080

然后使用kubectl create -f replicaset.yaml来创建出一个rs资源,如果你认真观察了,你就会发现这个配置与rc的配置几乎完全一样。事实上,k8s 在实际的使用中几乎已经用rs完全代替了rc

存活探针

了解完了副本控制器后,我们再来看一下存活探针,这个存活探针是用在什么地方的呢?我们知道副本控制器可以把 pod 的数量保持在期望的数量。再详细一点就是:副本控制器可以把运行状态在Running的 pod 数量维持在期望的数量

那么问题来了,如果 pod 里运行的 docker 容器已经假死了,但是 k8s 不知道他已经挂掉了怎么办?例如:一个 java 程序因为内存溢出异常停止服务了,但是JVM依旧在运行。这时 k8s 依旧会把该 pod 的状态标记为Running

这时候,如果有一个小工具,把他插在 pod 上,不停的进行测试,然后对外输出 pod 里运行的服务是否状态良好就好了。是的,这个小工具就是存活探针。

存活探针都有什么手段来进行测试?

存活探针的监测方式包括以下三种:

  • HTTP GET请求:向指定端口发送get请求,若响应的状态码正常(2xx, 3xx),则证明容器存活
  • TCP 套接字:尝试与指定窗口建立tcp连接,建立成功则容器正常
  • Exec 执行命令:在容器中指定任意命令,若命令的退出状态码为0,则容器正常。

接下来因为篇幅 作者能力 的原因,只简单讲述一下最常用的http get存活探针。首先来介绍一下如何创建一个http存活探针:

liveness-probe.yaml

apiVersion: v1
kind: Pod
metadata:
  name: kubia-liveness
spec:
  containers:
  - image: luksa/kubia-unhealthy
    name: kubia
    # 这下面就是探针!
    livenessProbe:
      httpGet:
        path: /
        port: 8080

然后使用kubectl create -f libeness-probe.yaml即可创建一个拥有http探针的 pod。可以看到,想要创建探针只需要给containers数组的元素指定livenessProbe字段即可,然后使用httpGet创建一个 http 探针。httpGet需要如下两个属性:请求的访问路径path以及访问端口port

可以看到不需要指定其 ip 地址,因为 pod 的 ip 并不是固定的。探针会动态的获取其 ip 地址进行测试。这里使用的luksa/kubia-unhealthy是一个存在”异常“的 web 应用。向端口8080请求超过 5 次后服务会停止正常服务。

启动之后获取 pod 看一下:

root@master1:~# kubectl get pod
NAME             READY   STATUS    RESTARTS   AGE
kubia-liveness   1/1     Running   0          83s

没问题呀,看起来一切正常,我们执行kubectl logs -f kubia-liveness打开它的日志看一下:

root@master1:~# kubectl logs -f kubia-liveness
Kubia server starting...
Received request from ::ffff:10.244.3.1
Received request from ::ffff:10.244.3.1
Received request from ::ffff:10.244.3.1
...

你会看到服务正在缓慢的被请求,这个请求就是我们定义的http探针发出的。当我们耐心的等待到 8 次之后,日志突然被关闭了,怎么回事?这时我们再kubectl get pod看一下,可以看到他的RESTARTS值从0变成了1,原来是因为容器重启了呀:

root@master1:~# kubectl get pod
NAME             READY   STATUS    RESTARTS   AGE
kubia-liveness   1/1     Running   1          4m51s

我们还可以通过kubectl describe pod kubia-liveness来查看他的详细重启信息,以下是 pod 的事件信息,你可以在输出的末尾找到它:

Events:
  Type     Reason     Age                    From               Message
  ----     ------     ----                   ----               -------
  Normal   Scheduled  9m36s                  default-scheduler  Successfully assigned default/kubia-liveness to worker2
  Normal   Created    4m53s (x3 over 8m34s)  kubelet, worker2   Created container kubia
  Normal   Started    4m52s (x3 over 8m33s)  kubelet, worker2   Started container kubia
  Warning  Unhealthy  3m35s (x9 over 7m35s)  kubelet, worker2   Liveness probe failed: HTTP probe failed with statuscode: 500
  Normal   Killing    3m34s (x3 over 7m15s)  kubelet, worker2   Container kubia failed liveness probe, will be restarted
  Normal   Pulling    3m4s (x4 over 9m35s)   kubelet, worker2   Pulling image "luksa/kubia-unhealthy"
  Normal   Pulled     3m2s (x4 over 8m43s)   kubelet, worker2   Successfully pulled image "luksa/kubia-unhealthy"

可以看到第四条事件被标记为了Warning,而原因是因为探针检查到了 pod 的响应状态为 500服务器错误。然后从第 5 ~ 7 条事件可以看到 pod 被杀死并重新启动。

如果你足够无聊,在 pod 重启数十次之后你可能会发现他的状态变成了 CrashLoopBackOff,这是容器多次异常退出导致的。不过不用担心,k8s 依旧会尽力重启它,经过几十秒时候你就可以看到容器的状态重新变成Running了。

存活探针的附加属性

现在我们更近一步,了解存活探针的具体配置。执行kubectl get pod kubia-liveness -o yaml命令来打开该容器的完整yaml配置。然后我们定位到livenessProbe字段,如下,可以看到完整的配置里多出了几个属性,需要我们关注的就是下面添加了注释的四条属性:

livenessProbe:
  httpGet:
    path: /
    port: 8080
    scheme: HTTP
  # 失败多少次将重启 pod
  failureThreshold: 3
  # 测试间隔
  periodSeconds: 10
  # 失败后成功多少次将解除失败状态
  successThreshold: 1
  # 超时时长
  timeoutSeconds: 1

首先回想一下,luksa/kubia-unhealthy容器会响应五次请求,然后返回500服务器异常,那为什么我们看到日志里刷新出了 8 次请求后容器才重启呢?这个就是failureThreshold属性的功劳。

存活探针在发现了请求失败时首先会将 pod 标记为异常状态。然后继续发送请求,如果依旧失败的请求次数超过了failureThreshold的次数,那么容器将会被删除重建,而在被标记为异常状态之后如果请求成功了,并且成功的总次数超过了successThreshold的次数。那么异常状态将会被清除。至于timeoutSeconds属性则代表了请求的超时时间,而periodSeconds则是间隔多久进行下一次测试,它们两个的单位都是秒。

上述四个属性是属于livenessProbe对象的,而非httpGet对象,在创建时不要搞错缩进了。

探针导致的问题

探针并不是尽善尽美的,如果使用不当的话很容易引起无限重启的问题。最容易发生的是以下三种:

  • 探针请求超时时间太短:从配置中可以看到,默认的超时时长是1,如果服务的正常处理时间就超过了 1 秒的话,那么探针将一直认为服务异常了,从而导致容器一直重启。
  • 初始延迟太短:其实还有一个属性是上文没有提到的,就是initialDelaySeconds。这个属性描述了在首次检测之前需要等待多少秒,如果这个时间指定的太短的话,就会导致容器还没有完全启动检测就已经开始了。自然会导致问题的产生。
  • 检测的接口需要验证:在创建http探针的时候一定要指定没有身份验证的接口,探针不会区分身份验证失败和服务器错误的状态码有什么区别。

总结

k8s 通过副本控制器和存活探针来保证服务健康可靠。副本控制器分为ReplicationControllerReplicaSet两种,但是在实际使用中rs已经全面取代了rc。副本控制器通过规定好的标签来将容器纳入自己的管理,并根据用户指定的期望数量来维持实际存在的容器数量。但是副本控制器只能保证Running的副本数量不会减改变,而不能得知处于Running状态的容器是否健康。为了实现这个目标,我们使用存活探针来获取容器内部的服务是否健康。通过自定义的探针,k8s 就可以持续的获取容器的内部状态,进而完成更加可靠的健康管理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容