kubernetes實現spring cloud服務平滑升級的一種解決方案

又是三月天

因爲過年加上疫情的原因,2020年的2月沒有了對時間的概念

也因爲疫情的原因,今年回到了離別多年的家鄉

家鄉還是以前的樣子

只是許多以前的一起玩耍的小夥伴們大多都結了婚,出了嫁

曾經那些個青澀的我們就像羅大佑唱的歌裏說的那樣:

輕飄飄的時光就這麼溜走,轉頭回去看看時已匆匆數年




問題描述

回到本文正題,情況是這樣的:

由於公司的服務用的是springcloud框架並且服務全採用的kubernetes來進行部署的,但每次有新的服務進行升級時,新發上去的服務都會出現2分鐘左右的無法服務,在開發環境自己測試時有點影響就算了,但是如果是線上環境造成某一個服務短時間的不能訪問影響還是蠻大的。

在服務升級時,讓服務能平滑升級達到用戶無感的效果這是非常有必要的

正好今天的事不多,決定自己來研究一把,並且最後實現了想要達到服務平滑升級的效果,以此來記錄一下

環境:

  • kubernetes
  • spring cloud,註冊中心用的eureka

原因分析

先來分析原因:

之所以會出現上面那種服務需要2分鐘左右的時間才能正常被外部訪問的情況,是因爲每次新的服務發佈到k8s上去後,k8s會將新的服務啓動,然後再會讓原始服務down掉,滾動升級就是這麼高大上,看起來確實是天衣無縫。

而在spring cloud的服務中,用戶訪問的一般都是網關(gateway)或zuul,通過gateway或zuul進行一次中轉再去訪問內部的服務,但是通過網關訪問內部服務時需要一個過程。一般流程是這樣的:服務啓動好了後會先將自己(服務名->ip:端口)註冊到eureka中以便其他服務能訪問到它,然後其他服務會定時訪問註冊中心(eureka)以獲取到eureka中最新的服務註冊列表。

所以用戶通過網關訪問到其他服務的一個前提便是:所訪問的服務網關已經拿到它在eureka的註冊信息了。否則會報出錯,提示No instances available這樣的錯誤信息出來。

那麼通過kubenetes按照滾動刷新的方式來更新服務的話,就可能出現這樣的情況:

在T時刻,serverA_1(老服務)已經down了,serverA_2(新服務)已經啓動好,並已註冊到了eureka中,但是對於gateway中存在的仍是serverA_1(老服務)的註冊信息,那麼此時用戶去訪問serverA就會報錯的,因爲serverA_1所在的容器都已經stop了。

解決辦法

減小從eureka獲取註冊列表的間隔

gateway去拉取eureka中的註冊信息的間隔時間是由參數eureka.client.registry-fetch-interval-seconds來決定的,默認爲30秒

所以對於gateway這種類型的服務,我們可以考慮將從eureka拉取註冊列表的間隔設小一點,比如5秒。

老服務主動從註冊中心裏移除

同時,serverA_1(老服務)stop的時候應該主動通知eureka將自己從註冊列表中主動移除,k8s中的PreStop正好也有對應的實現方法。

https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#hook-details

PreStop

This hook is called immediately before a container is terminated due to an API request or management event such as liveness probe failure, preemption, resource contention and others. A call to the preStop hook fails if the container is already in terminated or completed state. It is blocking, meaning it is synchronous, so it must complete before the call to delete the container can be sent. No parameters are passed to the handler

如在k8s的服務中添加preStop,通過手動的方式通知eurekaServer讓本服務下線

          lifecycle:
            preStop:
              httpGet:
                port: 8881
                path: /spring/shutdown

依賴於retry,待新服務在所有節點中都已就緒再將老服務停止

上面兩種辦法都不能立即讓其他服務獲取到新服務的註冊信息

讓服務達到平滑升級另一種解決辦法:依賴於ribbon和spring retry的方式來讓服務實現平滑升級的辦法,通過配置readiness,讓新的服務啓動好了分鐘後再將舊服務停止

https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

藉助liveness或readiness便可以實現:

The periodSeconds field specifies that the kubelet should perform a liveness probe every 5 seconds. The initialDelaySeconds field tells the kubelet that it should wait 5 second before performing the first probe. To perform a probe, the kubelet executes the command cat /tmp/healthy in the Container. If the command succeeds, it returns 0, and the kubelet considers the Container to be alive and healthy. If the command returns a non-zero value, the kubelet kills the Container and restarts it.

根據文檔可知,在探針的結果返回0就代表已就緒,返回非0就代表還未就緒。

在服務的kubernetes的deployment配置文件中添加redainessProbe或livenessProbe即可

        readinessProbe:
          exec:
            command: 
            - echo 
            - '0'
          initialDelaySeconds: 180
          periodSeconds: 10

參數initialDelaySeconds參數代表首次探針的延遲時間,這裏的180就是指待pod啓動好了後,再等待180秒再進行存活性檢測

echo '0'一直返回0,代表服務已就緒

這樣配置的話就會達到新的服務啓動好了3分鐘後kubenetes纔會讓舊服務down掉,而三分鐘後基本上所有的服務都已經從eureka獲取到了新服務的註冊信息了,再藉助於ribbon的重試機制則可實現服務的平滑升級,當然,前提是得要開啓loadbalancer的retry

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章