使用Kubernetes進行零停機滾動更新

軟件世界比以往任何時候都更快。為了保持競爭力,需要盡快推出新的軟件版本,而不會中斷活躍用戶訪問,影響用戶體驗。越來越多企業(yè)已將其應(yīng)用遷移到Kubernetes,而Kubernetes的構(gòu)建基于生產(chǎn)準備。但是,為了通過Kubernetes實現(xiàn)真正的零停機時間,我們需要采取更多步驟,而不會破壞或丟失任何一個用戶的請求

關(guān)于如何使用Kubernetes實現(xiàn)零停機時間的系列文章

滾動更新

默認情況下,Kubernetes部署應(yīng)用使用滾動更新策略對pod進行更新。此策略旨在通過在執(zhí)行更新時在任何時間點保證至少一定數(shù)量pod實例正常運行來防止應(yīng)用程序停機。只有在新部署版本的新pod啟動并準備好處理流量后,才會關(guān)閉舊pod。
工程師可以進一步指定Kubernetes在更新期間如何處理多個副本的確切方式。根據(jù)我們可能要配置的工作負載和可用計算資源,我們希望在任何時候不出現(xiàn)過多或不足的實例的現(xiàn)象。例如,給定三個所需的副本,我們應(yīng)該立即創(chuàng)建三個新的pod并等待它們?nèi)繂?,我們?yīng)該終止除了一個之外的所有舊pod,還是逐個進行轉(zhuǎn)換?
以下deployment的部分yaml文件顯示了具有默認RollingUpdate升級策略的應(yīng)用程序的Kubernetes部署定義,以及maxSurge在更新期間最多一個過度配置的pod數(shù)量和maxUnavailable最大不可用的pod。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myapp-without-hook
spec:
  replicas: 3
  strategy:
    rollingUpdate:
       maxSurge: 1
       maxUnavailable: 0
...

此部署配置將按以下方式執(zhí)行版本更新過程:它將一次啟動一個新版本的pod,等待pod啟動并準備就緒,觸發(fā)其中一個舊pod的終止,然后繼續(xù)下一個新的pod,直到所有副本都已完成更新。

為了告訴Kubernetes我們的pod正在運行并準備好處理流量,我們需要配置存活探針與就緒探針。

更新過程

root@k8s-master-1:~/k8s_manifests/zero-downtime-tutorial# kubectl get pod
myapp-without-hook-77448c5b9f-648h5            1/1     Running             0          76s
myapp-without-hook-77448c5b9f-b9rxm            1/1     Running             0          76s
myapp-without-hook-77448c5b9f-gbrvc            1/1     Running             0          76s
myapp-without-hook-789fd7c548-m5bpw            0/1     ContainerCreating   0          2s
myapp-without-hook-77448c5b9f-648h5            1/1     Terminating         0          79s
...
myapp-without-hook-77448c5b9f-b9rxm            1/1     Running             0          83s
myapp-without-hook-77448c5b9f-gbrvc            1/1     Terminating         0          83s
myapp-without-hook-789fd7c548-79h9d            1/1     Running             0          5s
myapp-without-hook-789fd7c548-chbwh            0/1     ContainerCreating   0          1s
myapp-without-hook-789fd7c548-m5bpw            1/1     Running             0          9s
...
myapp-without-hook-77448c5b9f-b9rxm            1/1     Running             0          85s
myapp-without-hook-77448c5b9f-gbrvc            0/1     Terminating         0          85s
myapp-without-hook-789fd7c548-79h9d            1/1     Running             0          7s
myapp-without-hook-789fd7c548-chbwh            0/1     ContainerCreating   0          3s
myapp-without-hook-789fd7c548-m5bpw            1/1     Running             0          11s
...
myapp-without-hook-77448c5b9f-b9rxm            0/1     Terminating   0          91s
myapp-without-hook-77448c5b9f-gbrvc            0/1     Terminating   0          91s
myapp-without-hook-789fd7c548-79h9d            1/1     Running       0          13s
myapp-without-hook-789fd7c548-chbwh            1/1     Running       0          9s
myapp-without-hook-789fd7c548-m5bpw            1/1     Running       0          17s

負載壓測可用性差距

如果我們執(zhí)行從舊版本到新版本的滾動更新,并按照pod處于活動狀態(tài)并準備就緒的輸出,則首先行為似乎是有效的。但是,正如我們所看到的,從舊版本到新版本的轉(zhuǎn)換并不總是非常順利,也就是說,應(yīng)用程序可能會丟失一些客戶端的請求。

為了測試,正在請求的連接是否丟失,特別是那些針對正在停止使用的實例的請求,我們可以使用連接到我們的應(yīng)用程序的負載測試工具。我們感興趣的要點是是否所有HTTP請求都得到了正確處理,包括HTTP保持活動連接。為此,我們使用負載壓測工具,例如Apache BenchFortio。

我們使用多個線程(即多個連接)以并發(fā)方式通過HTTP連接到我們正在運行的應(yīng)用程序。這里我們不關(guān)注延遲或吞吐量,而是對響應(yīng)狀態(tài)和潛在的連接故障感興趣。

 ? root@ubuntu  ~  fortio load -a -c 8 -qps 500 -t 60s "http://$NodeIP:$NodePort/"

這里是用NodePort的服務(wù)發(fā)布形式,發(fā)布我們的示例程序$NodeIP $NodePort

root@k8s-master-1:~/k8s_manifests/zero-downtime-tutorial# kubectl get svc
myapp-without-hook            NodePort    10.68.21.130    <none>        80:31467/TCP        3s

在Fortio的示例中,每秒500個請求和8個線程并發(fā)保持活動連接的調(diào)用如下所示

 ? ? root@ubuntu  ~  fortio load -a -c 8 -qps 500 -t 60s "http://192.168.2.12:31467/"
Fortio 1.3.1 running at 500 queries per second, 1->1 procs, for 1m0s: http://192.168.2.12:31467/
13:26:32 I httprunner.go:82> Starting http test for http://192.168.2.12:31467/ with 8 threads at 500.0 qps
Starting at 500 qps with 8 thread(s) [gomax 1] for 1m0s : 3750 calls each (total 30000)
...
Sockets used: 23 (for perfect keepalive, would be 8)
Code  -1 : 4 (0.0 %)
Code 200 : 29996 (100.0 %)
Response Header Sizes : count 30000 avg 235.96853 +/- 2.725 min 0 max 236 sum 7079056
Response Body/Total Sizes : count 30000 avg 330.25453 +/- 3.931 min 0 max 331 sum 9907636
All done 30000 calls (plus 8 warmup) 2.489 ms avg, 500.0 qps

輸出表明并非所有請求都可以成功處理code 200。以上的壓測結(jié)果顯示還是有4個請求code -1處理失敗。

我們可以運行多個測試場景,通過不同的方式連接到應(yīng)用程序,例如通過Kubernetes ingress,或通過服務(wù)直接從集群內(nèi)部連接。我們將看到滾動更新期間的行為可能會有所不同,具體取決于我們的測試設(shè)置如何連接。與通過入口連接相比,從群集內(nèi)部連接到服務(wù)的客戶端可能不會遇到任何數(shù)量的失敗連接。

更新過程發(fā)生了什么

現(xiàn)在的問題是,當Kubernetes在滾動更新期間重新路由流量時,從舊的實例版本到新的pod實例版本會發(fā)生什么。我們來看看Kubernetes如何管理工作負載連接。

如果我們的客戶端,即零停機測試,F(xiàn)ortio直接通過NodeIP:NodePort形式從集群外部連接到服務(wù),它通常使用通過集群DNS解析的服務(wù)VIP,最終到達Pod實例,具體的實現(xiàn)通過在每個Kubernetes節(jié)點上運行的kube-proxy實現(xiàn)的,并更新iptables轉(zhuǎn)發(fā)規(guī)則到pod的IP地址。

NodePort形式

image.png

Ingress的方式

image.png

無論我們?nèi)绾芜B接到我們的應(yīng)用程序,Kubernetes都旨在最大限度地減少滾動更新過程中的服務(wù)中斷。

新的pod處于活動狀態(tài)并正常處理客戶端請求,Kubernetes將使舊的pod停止服務(wù),從而更新pod的狀態(tài)為Terminating,將其從端點對象中刪除,然后發(fā)送一個SIGTERM。在SIGTERM導致容器優(yōu)雅正常退出,并且不接受任何新客戶端連接。將pod從endpoints列表中剔除后,負載均衡器會將流量路由到剩余的(新)流量。這就是我們部署中可用性差距的原因; 在終端信號之前,當負載均衡器注意到更改并且可以更新其配置時,已通過SIGTERM信號停用Pod。這種重新配置是異步發(fā)生的,因此不能保證正確的排序,將導致很少的不幸請求被路由到終止pod 從而出現(xiàn)少量的請求丟失現(xiàn)象。

走向零停機時間

如何增強我們的應(yīng)用程序以實現(xiàn)(真正的)零停機時間遷移?

首先,實現(xiàn)這一目標的先決條件是我們的容器正確處理SIGTERM信號,即該進程將在Unix上正常退出??纯碐oogle 構(gòu)建容器最佳實踐如何實現(xiàn)這一目標。

下一步是包括準備探針,檢查我們的應(yīng)用程序是否已準備好處理流量。理想情況下,探針已經(jīng)檢查了需要預(yù)熱的功能狀態(tài),例如高速緩存或servlet初始化。

準備情況探測是我們平滑滾動更新的起點。為了解決pod終端當前沒有阻塞的問題并等到負載均衡器重新配置,我們將包含一個preStop生命周期鉤子。在容器終止之前調(diào)用此掛鉤。

容器生命周期鉤子

生命周期鉤子是同步的,因此必須在最終終止信號被發(fā)送到容器之前完成。在我們的例子中,我們使用這個鉤子來簡單地等待,然后SIGTERM才會終止應(yīng)用程序進程。同時,Kubernetes將從端點對象中刪除pod,因此pod將從我們的負載平衡器中排除。我們的生命周期鉤子等待時間可確保在應(yīng)用程序進程停止之前重新配置負載平衡器。

為了實現(xiàn)此行為,我們在myapp的deployment部署中定義了一個preStop鉤子,依賴于您選擇的技術(shù)如何實現(xiàn)就緒和存活探針以及生命周期鉤子行為;

kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: myapp
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        #image: 314315960/zero-downtime-tutorial:green
        image: 314315960/zero-downtime-tutorial:blue
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /healthy.html
            port: 80
          periodSeconds: 1
          successThreshold: 1
          failureThreshold: 2
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "rm /usr/share/nginx/html/healthy.html && sleep 10"]

readinessProbe 探針檢查該pod是否準備就緒
proStop 鉤子必須在刪除容器的調(diào)用之前完成,這里選擇刪除/healthy.html探針及程序睡10秒的以提供同步寬限期。只有在完成這一系列操作,pod才會繼續(xù)正常退出。

root@k8s-master-1:~/k8s_manifests/zero-downtime-tutorial# kubectl get deploy,svc,pod
NAME                                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/myapp                         3/3     3            3           6m38s

NAME                                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
myapp                         NodePort    10.68.57.14     <none>        80:26532/TCP        7m32s

service/myapp                         NodePort    10.68.57.14     <none>        80:26532/TCP        6m31s
NAME                                               READY   STATUS    RESTARTS   AGE
pod/myapp-56646bbf86-8dw9f                         1/1     Running   0          6m38s
pod/myapp-56646bbf86-mbzgj                         1/1     Running   0          6m38s
pod/myapp-56646bbf86-mpwk2                         1/1     Running   0          6m38s

#更換image: 314315960/zero-downtime-tutorial:green
root@k8s-master-1:~/k8s_manifests/zero-downtime-tutorial# kubectl edit deployment myapp
deployment.extensions/myapp edited

Fortio 壓測

 ? root@ubuntu ? ~ ? fortio load -a -c 8 -qps 500 -t 60s "http://192.168.2.12:26532/"
Fortio 1.3.1 running at 500 queries per second, 1->1 procs, for 1m0s: http://192.168.2.12:26532/
14:11:27 I httprunner.go:82> Starting http test for http://192.168.2.12:26532/ with 8 threads at 500.0 qps
Starting at 500 qps with 8 thread(s) [gomax 1] for 1m0s : 3750 calls each (total 30000)
...
Sockets used: 17 (for perfect keepalive, would be 8)
Code 200 : 30000 (100.0 %)
Response Header Sizes : count 30000 avg 236 +/- 0 min 236 max 236 sum 7080000
Response Body/Total Sizes : count 30000 avg 330.2126 +/- 0.9771 min 329 max 331 sum 9906378
All done 30000 calls (plus 8 warmup) 2.332 ms avg, 499.9 qps


輸出表明所有30000個請求都可以成功處理code 200。

image.png

image.png

總結(jié)

Kubernetes在編寫具有生產(chǎn)準備的應(yīng)用程序方面做得非常出色。然而,為了在生產(chǎn)中運行我們的企業(yè)系統(tǒng),我們的工程師必須了解Kubernetes如何在引擎蓋下運行以及我們的應(yīng)用程序在啟動和關(guān)閉期間的行為過程。

以上用到的yaml和dockerfile,都已經(jīng)上傳到github

參考文檔:
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/
https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/
https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/
https://blog.sebastian-daschner.com/entries/zero-downtime-updates-kubernetes
https://github.com/chrismoos/zero-downtime-tutorial

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內(nèi)容