Kubernetes部署之終極指南 - semaphoreci

banq發表於2019-07-18

我們學習和使用的第一個Kubernetes命令之一是  kubectl run。有Docker經驗的人傾向於將它與之比較  docker run,並認為:“ 啊,這就是我如何簡單地執行容器!“
讓我們看看執行一個非常基本的kubectl run 命令後會發生什麼  :

$ kubectl run web --image=nginx
deployment.apps/web created


好了!然後我們檢查在我們的叢集上建立了什麼,並且.....

$ kubectl get all
NAME                       READY     STATUS    RESTARTS   AGE
pod/web-65899c769f-dhtdx   1/1       Running   0          11s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   46s

NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   1         1         1            1           11s

NAME                             DESIRED   CURRENT   READY     AGE
replicaset.apps/web-65899c769f   1         1         1         11s
$

我們沒有獲得一個容器,而是擁有一個不知名的野獸動物園:
  • 一個  deployment   (在該示例中稱為  web ),
  • 一個  replicaset  (web-65899c769f),
  • 一個  pod  (web-65899c769f-dhtdx)。

我只想要一個容器!為什麼我會得到三個不同的物件?
簡而言之,這些Kubernetes物件確保您可以在不停機的情況下逐步部署,回滾和擴充套件應用程式。這種情況乍一看,我們想知道“有什麼意義?“,但是一旦我們全面瞭解,我們就會理解每個元件的作用和目的。事實上,很多人最終都認為,如果我們負責設計系統,他們會想出一些非常相似的東西!持續整合使您對程式碼充滿信心。為了將這種信心擴充套件到釋出過程,您的部署操作需要配備安全帶。

容器和POD
在Kubernetes,最小的部署單位不是容器,而是一個POD。一個POD僅僅是一組容器(它可以是 一個容器的一組例項),執行在同一臺機器上,並分享一些東西。例如,POD內的容器可以基於localhost相互通訊  。從網路角度來看,這些容器中的所有程式都是本地的。
但我們永遠不能建立一個獨立的容器:我們最接近的是建立一個pod,其中包含一個容器。
當我們告訴Kubernetes時,“ 建立一些NGINX!“我們其實在說,” 我想要一個POD,其中應該有一個容器,使用  nginx 映象。“

# pod-nginx.yml
# Create it with:
#    kubectl apply -f pod-nginx.yml
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
    - image: nginx
      name: nginx
      ports:
        - containerPort: 80
          name: http



為什麼我們不只是一個POD就行了?為什麼還要設定和部署副本?

宣告與命令
Kubernetes是一個  宣告系統(與命令系統相反)。這意味著我們不能給它排序指定。我們不能說,“執行這個容器。”我們所能做的就是描述我們想要的東西,並等待Kubernetes採取行動來協調我們擁有的東西,以及我們想要擁有的東西。
換句話說,我們可以說,“ 我想要一個40英尺長的黃色門的藍色容器“, Kubernetes會為我們找到這樣一個容器。如果它不存在,它將構建它; 如果已經有一個,但它是綠色的紅色門,它將為我們繪製它成我們要求的顏色; 果已經有一個大小和顏色合適的容器,Kubernetes什麼都不做,因為  我們  已經匹配  了我們想要的東西。
在軟體容器術語中,我們可以說,“ 我想要一個名為pod命名為web,其中應該有一個容器,它將執行  nginx 映象。
如果該pod尚未存在,Kubernetes將建立它。如果該pod已經存在且符合我們的規範,Kubernetes不需要做任何事情。
我們如何擴充套件我們的  web 應用程式,以便它在多個容器或pod中執行?

副本集使縮放pod變得容易
如果我們所擁有的只是一個pod,並且我們想要更多相同的pod,我們所能做的就是回到Kubernetes,然後問它,“ 我想要一個名為的pod  web2,具有以下規格:...... ”並重新使用它規範和以前一樣。然後重複我們想要擁有pod的次數。
這是相當不方便的,因為現在我們的工作是跟蹤所有這些pod,並確保它們全部同步並使用相同的規範。
為了簡化操作,Kubernetes為我們提供了更高階別的構造,即  副本集。看起來非常像一個pod的規格,不同的是它帶有一個數字,表明有多少副本:即pod與我們想要的特定的規範。
所以我們告訴Kubernetes,“ 我想要一個名為的副本集  web,它應該有3個pod,都符合以下規範:.. ”

# pod-replicas.yml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: web-replicas
  labels:
    app: web
    tier: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        app: web
        tier: frontend
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80


Kubernetes將相應地確保有3個匹配的pod。如果我們從頭開始,將建立3個pod。如果我們已經有3個pod,那麼什麼都沒做,因為  我們  已經匹配  了我們想要的東西。

副本集與擴充套件和高可用性特別相關
對於縮放,因為我們可以更新現有的副本集以修改所需的副本數。因此,Kubernetes將建立或刪除pod,以便最終變成你所需的數字。
為了實現高可用性,Kubernetes將持續監控叢集上發生的情況,並確保無論發生什麼情況,我們仍然擁有所需的數量。如果一個名為web的pod節點發生故障,  Kubernetes會建立另一個pod來替換它。如果是節點沒有關閉,但暫時無法訪問或無響應,當它恢復過來時,我們可能有了一個多餘的pod,Kubernetes會終止一個pod,以確保我們仍然擁有所需的數量。

當我們更改pod的定義時會發生什麼?
更改pod的定義並不罕見。例如,我們經常希望用更新的版本替換我們正在使用的容器映象。
請記住:副本集的任務是“ 確保有符合此規範的N個pod。”如果我們更改該定義會發生什麼?突然間,可能沒有匹配你的新規範定義的pod了。
到目前為止,我們知道宣告系統應該如何工作:Kubernetes應該立即建立符合我們新規範的N pod。舊的豆莢只會留下來,直到我們手動清理它們。
如果這些pod可以在CI / CD管道中乾淨地自動移除,那將很好,新pod的創造可能以更加漸進的方式發生。

部署驅動副本集
這正是部署deployment的作用  。乍一看,部署的規範看起來非常像副本集的規範:它具有pod規範和許多副本。(以及稍後我們將討論的一些其他引數。)

# deployment-nginx.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80


但是,部署不會直接建立或刪除pod。他們將這項工作委託給一個或多個副本集。當我們建立部署時,它會使用我們提供的確切pod規範建立副本集。當我們更新部署並調整副本數時,它會將該更新傳遞到副本集。

配置更改時
當我們需要更新pod規範本身時,事情變得有趣。例如,我們可能想要更改要使用的映象(因為我們釋出了新版本)或應用程式的引數(透過命令列引數,環境變數或配置檔案)。
當我們更新pod規範時,部署 將使用更新的pod規範建立  新的副本集。該副本集的初始大小為零。然後,該副本集的大小逐漸增加,同時減小另一副本集的大小。
我們可以想象我們前面有一個混音板,我們將在新的副本裝置上淡入(調高音量),同時我們淡出(調低音量)舊的。在整個過程中,請求將傳送到舊版和新版副本集的pod,而不會對使用者造成任何停機。
這是大方向,有許多小細節使這個過程更加強大。

破碎的部署和就緒狀態探測
如果我們推出一個破損的版本,它可能會導致整個應用程式崩潰(一次一個pod!),因為Kubernetes將逐步用新的(損壞的)版本替換舊的吊艙。
除非我們使用  就緒狀態探測/準備探針。準備情況探測是我們新增到容器規範的測試。這是一個二進位制測試,只能說“工作”或“不工作”,並將定期執行。(預設情況下,每10秒鐘。)
Kubernetes支援三種實現就緒探針的方法:

  1. 在容器內執行命令;
  2. 針對容器發出HTTP(S)請求; 要麼
  3. 開啟容器的TCP套接字。

Kubernetes使用該測試的結果來了解容器和它所屬的容器是否已準備好接收流量。當我們推出新版本時,Kubernetes將等待新的pod標記為“準備好”,然後再轉到下一個版本。
如果一個pod永遠不會達到就緒狀態,因為準備就緒探測失敗了,Kubernetes永遠不會繼續前進。部署停止,我們的應用程式繼續使用舊版本執行,直到我們解決問題。
如果  沒有  就緒探測器,則只要可以啟動容器,就認為該容器已就緒。因此,如果要利用該功能,請確保定義準備情況探測!

回滾以便從錯誤的部署中快速恢復
在任何時候,在滾動更新期間甚至更晚,我們都可以告訴Kubernetes:“ 嘿,我改變了主意; 請返回到該部署的先前版本。“它將立即切換”舊“和”新“副本集的角色。從那時起,它將增加舊副本集的大小(直到部署的名義大小),同時減小另一個副本的大小。
一般而言,這不限於兩個“舊”和“新”副本集。在引擎蓋下,有一個被認為是“最新”的副本集,我們可以將其視為“目標”副本集。那就是我們要努力的目標; 這是Kubernetes將逐步擴大規模的那個。同時,可以有任意數量的其他副本集,對應於舊版本。
例如,我們可能會在10個副本上執行應用程式的第1版。然後我們開始推出版本2.在某些時候,我們可能有7個pod執行版本1,3個pod執行版本2。然後我們可能決定釋出版本3而不等待版本2完全部署(因為它修復了我們之前沒有注意到的問題)。雖然正在部署版本3,但我們可能會決定返回版本1。Kubernetes將僅相應地調整副本集的大小(對應於應用程式的版本1,2和3)。

MaxSurge和MaxUnavailable
Kubernetes並沒有一次完全更新我們的部署。早些時候,我們說部署有“一些額外的引數”:這些引數包括  MaxSurge  和  MaxUnavailable,它們表示更新應該進行的速度。
在推出新版本時,我們可以想象兩種策略。我們可以非常保守我們的應用程式可用性,並決定在  關閉舊的pod 之前啟動新的pod  。只有在新的pod啟動,執行和準備好後,我們才能終止舊的。
這意味著我們的群集上有一些備用容量。但是,可能會出現這樣的情況:我們無法承擔任何額外的pod,因為我們的群集已滿,並且我們更願意在開始新的pod之前關閉舊的pod。

MaxSurge  表示我們願意在滾動更新期間執行多少個額外的pod,而  MaxUnavailable  表示在滾動更新期間我們可以丟失多少個pod。這兩個引數都特定於部署(換句話說,每個部署可以具有不同的值)。兩個引數都可以表示為pod的絕對數量,或者表示為部署大小的百分比; 兩個引數都可以為零(但不能同時)。
讓我們看一下MaxSurge和MaxUnavailable的一些典型值,以及它們的含義。
將MaxUnavailable設定為0意味著“ 在新伺服器啟動並準備好為流量提供服務之前,不要關閉任何舊伺服器 ”。
將MaxSurge設定為100%意味著“ 立即啟動所有新pod ”,這意味著我們的群集上有足夠的備用容量,並且我們希望儘可能快地啟動。
兩個引數的預設值均為25%,這意味著在更新大小為100的部署時,會立即建立25個新pod,而關閉25箇舊pod。每次新的pod出現(並標記為準備好)時,可以關閉另一箇舊pod。每次舊pod完成其關閉(並且其資源已被釋放)時,可以建立另一個新pod。

演示
很容易看到這些引數在起作用。我們不需要編寫自定義YAML,定義準備探針或類似的東西。
我們所要做的就是告訴部署使用無效影像; 例如,不存在的影像。容器永遠不會出現,Kubernetes永遠不會將它們標記為“準備就緒”。
如果你有一個Kubernetes叢集(像minikube這樣的單節點叢集或者Docker Desktop很好),你可以在不同的終端執行以下命令來觀察將要發生的事情:

  • kubectl get pods -w
  • kubectl get replicasets -w
  • kubectl get deployments -w
  • kubectl get events -w

然後,使用以下命令建立,擴充套件和更新部署:

kubectl run deployment web --image=nginx
kubectl scale deployment web --replicas=10
kubectl set image deployment web nginx=that-image-does-not-exist


我們看到部署停滯不前,但仍有80%的應用程式容量可用。
如果我們執行  kubectl rollout undo deployment web,Kubernetes將返回初始版本(執行  nginx映象)。

瞭解選擇器和標籤
事實證明,當我們之前說過時,“ 副本集的工作是確保確切存在與正確規範匹配的N個pod ”,這並不是正在發生的事情。實際上,副本集不會檢視pod的規格,而只會檢視其標籤。
換句話說,如果pod 正在執行不要緊 ,比如nginx 或  redis 或什麼; 重要的是他們有正確的標籤。(在上面的示例中,標籤看起來像  run=web 和  pod-template-hash=xxxyyyzzz。)
副本集包含一個  選擇器,它是一個邏輯表示式,用於“選擇”(就像SELECT SQL中的查詢一樣  )許多pod。副本集確儲存在正確數量的pod,必要時建立或刪除pod; 但它不會改變現有的pod。
萬一你想知道:是否絕對有可能手動建立帶有這些標籤的pod,但執行不同的影像(或使用不同的設定),並欺騙我們的副本集。起初,這聽起來像是一個很大的潛在問題。但實際上,我們不太可能不小心選擇“正確”(或“錯誤”,取決於perspective)標籤,因為它們涉及pod規範的雜湊函式,它幾乎是隨機的。

作為負載平衡器的服務
選擇器也被  服務使用,它們充當Kubernetes流量,內部和外部的負載平衡器。我們可以 使用以下命令為  部署建立服務web:

kubectl expose deployment web --port=80


該服務將擁有自己的內部IP地址(由名稱表示  ClusterIP),並且埠80上與此IP地址的連線將在此部署的所有pod中進行負載平衡。
事實上,這些連線將在與服務選擇器匹配的所有pod中進行負載平衡。在那種情況下,該選擇器將是  run=web。
編輯部署並觸發滾動更新時,會建立新的副本集。此副本集將建立pod,其標籤將包括(以及其他)  run=web。因此,這些pod將自動接收連線。
這意味著在部署期間,部署不會重新配置或通知負載均衡器已啟動和停止容器。它透過 與負載均衡器關聯的服務的選擇器自動發生  。
(如果您想知道探針和健康檢查如何發揮作用: 只有當一個容器的所有容器都透過了準備檢查時,它才會被新增為服務的有效  端點。換句話說,只有當一個容器實際準備就緒後,它才會開始接收流量它。)

高階Kubernetes部署策略
有時,我們在推出新版本時需要更多控制權。您可能聽說過的兩種流行技術是  藍/綠部署  和  金絲雀部署。
1.使用Kubernetes進行藍/綠部署

Kubernetes部署之終極指南 - semaphoreci
在藍/綠部署中,我們希望立即切換從舊版本到新版本的所有流量,而不是像之前解釋的那樣逐步進行。我們可能有幾個原因,包括:

  • 我們不希望混合使用新舊請求,我們希望從一個版本到另一個版本的中斷儘可能乾淨;
  • 我們一起更新多個元件(例如,Web前端和API後端),我們不希望新版本的Web前端與舊版本的API後端通訊,反之亦然;
  • 如果出現問題,我們希望能夠儘快恢復,甚至無需等待舊的容器重啟。

我們可以透過建立多個部署(在Kubernetes意義上)實現藍/綠部署,然後透過更改 我們服務的選擇器從一個部署切換到另一個部署  。
這聽起來比聽起來容易!
以下命令將建立兩個部署,  blue 並  green分別使用  nginx 和  httpd容器映象:

kubectl create deployment blue --image=nginx
kubectl create deployment green --image=httpd


然後,我們建立一個名為的服務web,該服務  最初不會在任何地方傳送流量:

kubectl create service clusterip web --tcp=80


現在我們可以執行:
kubectl edit service web

執行web來更新服務的選擇器  ,web將從 Kubernetes API 檢索服務定義  ,在文字編輯器中開啟定義檔案,尋找到:

selector:
  app: web

用  blue 或者  green替換web,儲存並退出。 kubectl 將我們更新的定義推回到Kubernetes API,並且瞧!服務  web 現在將流量傳送到相應的部署。
(您可以透過檢索該服務的IP地址kubectl get svc web 並使用該連線到該IP地址  來驗證自己  curl。)

我們透過文字編輯器進行的修改也可以完全從命令列完成,使用(例如)kubectl patch 如下:

[b]kubectl patch service web -p '{"spec": {"selector": {"app": "green"}}}'[/b]


藍/綠部署的優勢在於流量切換幾乎是即時的,我們可以透過再次更新服務定義來快速回滾到以前的版本。

2. 使用Kubernetes進行金絲雀部署
金絲雀部署  暗示了用於煤礦的金絲雀,以檢測危險濃度的有毒氣體,如一氧化碳。礦工們會在籠子裡放一隻金絲雀。金絲雀對有毒氣體的敏感性高於人類。如果金絲雀昏倒,那意味著礦工已經到達危險區域時,應該在他們昏倒之前回頭退出來   。
這如何對映到軟體部署?
有時,我們不能或不會影響所有有缺陷版本的使用者,即使是短暫的時間。相反,我們會對新版本進行部分推廣。例如,我們部署了幾個執行新版本的副本; 或者我們將1%的使用者傳送到該新版本。
然後,我們比較當前版本和剛剛部署的金絲雀之間的指標。如果指標相似,我們可以繼續。如果延遲,錯誤率或其他任何錯誤,我們會回滾。
由於Kubernetes的標籤和選擇器的本機機制,這種技術可以相當簡單地進行設定。
值得注意的是,在前面的示例中,我們更改了服務的選擇器,但也可以更改pod的標籤。
例如,服務的選擇器是否設定為查詢帶有標籤的  status=enabledpod,我們可以將這樣的標籤應用於特定的pod:

kubectl label pod fronted-aabbccdd-xyz status=enabled


我們也可以集體申請標籤   ,例如:

kubectl label pods -l app=blue,version=v1.5 status=enabled


我們可以輕鬆刪除它們:

kubectl label pods -l app=blue,version=v1.4 status-


結論
我們看到了一些可以用來更自信地部署的技術。其中一些技術只是減少了部署本身造成的停機時間,這意味著我們可以更頻繁地部署,而不必擔心影響我們的使用者。
其中一些技術為我們提供了安全帶,防止壞版本取消我們的服務。而其他一些讓我們更加安心,比如在嘗試特別困難的序列之前點選影片遊戲中的“儲存”按鈕,知道如果出現問題,我們總能回到原來的位置。
Kubernetes使開發人員和運營團隊可以利用這些技術,從而實現更安全的部署。如果與部署相關的風險較低,則意味著我們可以更頻繁地,逐步地部署,並在我們實施時更容易地看到我們的更改結果; 例如,而不是每週或每月部署一次。
最終結果是更高的開發速度,更低的修復和新功能的上市時間,以及更好的應用程式可用性。這首先是實施集裝箱和持續交付的重點。
 

相關文章