如何優雅重啟 Kubernetes 的 Pod

閱讀:530 2023-10-26 00:44:17

方案 1

因為我們不同環境的 Pod 數不少,不可能手動一個個重啟;之前也做過類似的操作:

復制
kubectl delete --all pods --namespace=dev
  • 1.
 

這樣可以一鍵將 dev 這個命名空間下的 Pod 刪掉,kubernetes 之后會自動將這些 Pod 重啟,保證和應用的可用性。

但這有個大問題是對 kubernetes 的調度壓力較大,一般一個 namespace 下少說也是幾百個 Pod,全部需要重新調度啟動對 kubernetes 的負載會很高,稍有不慎就會有嚴重的后果。

所以當時我的第一版方案是遍歷所有的 deployment,刪除一個 Pod 后休眠 5 分鐘再刪下一個,偽代碼如下:

復制
deployments, err := clientSet.AppsV1().Deployments(ns).List(ctx, metav1.ListOptions{})  
if err != nil {  
    return err  
}
for _, deployment := range deployments.Items {
 podList, err := clientSet.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{  
     LabelSelector: fmt.Sprintf("app=%s", deployment.Name),  
 })
 err = clientSet.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{})  
 if err != nil {  
     return err  
 }  
 log.Printf("    Pod %s rebuild success.\n", pod.Name)
 time.Sleep(time.Minute * 5) 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
 

存在的問題

這個方案確實是簡單粗暴,但在測試的時候就發現了問題。

當某些業務只有一個 Pod 的時候,直接刪掉之后這個業務就掛了,沒有多余的副本可以提供服務了。

這肯定是不能接受的。

甚至還有刪除之后沒有重啟成功的:

  • 長期沒有重啟導致鏡像緩存沒有了,甚至鏡像已經被刪除了,這種根本就沒法啟動成功。
  • 也有一些 Pod 有 Init-Container 會在啟動的時候做一些事情,如果失敗了也是沒法啟動成功的。 總之就是有多種情況導致一個 Pod 無法正常啟動,這在線上就會直接導致生產問題,所以方案一肯定是不能用的。

方案二

為此我就準備了方案二:

image.png

  • 先將副本數+1,這是會新增一個 Pod,也會使用最新的 sidecar 鏡像。
  • 等待新建的 Pod 重啟成功。
  • 重啟成功后刪除原有的 Pod。
  • 再將副本數還原為之前的數量。

這樣可以將原有的 Pod 平滑的重啟,同時如果新的 Pod 啟動失敗也不會繼續重啟其他 Deployment 的 Pod,老的 Pod 也是一直保留的,對服務本身沒有任何影響。

存在的問題

看起來是沒有什么問題的,就是實現起來比較麻煩,流程很繁瑣,這里我貼了部分核心代碼:

復制
func RebuildDeploymentV2(ctx context.Context, clientSet kubernetes.Interface, ns string) error {
 deployments, err := clientSet.AppsV1().Deployments(ns).List(ctx, metav1.ListOptions{})
 if err != nil {
  return err
 }

 for _, deployment := range deployments.Items {

  // Print each Deployment
  log.Printf("Ready deployment: %s\n", deployment.Name)

  originPodList, err := clientSet.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{
   LabelSelector: fmt.Sprintf("app=%s", deployment.Name),
  })
  if err != nil {
   return err
  }

  // Check if there are any Pods
  if len(originPodList.Items) == 0 {
   log.Printf(" No pod in %s\n", deployment.Name)
   continue
  }

  // Skip Pods that have already been upgraded
  updateSkip := false
  for _, container := range pod.Spec.Containers {
   if container.Name == "istio-proxy" && container.Image == "proxyv2:1.x.x" {
    log.Printf("  Pod: %s Container: %s has already upgrade, skip\n", pod.Name, container.Name)
    updateSkip = true
   }
  }
  if updateSkip {
   continue
  }

  // Scale the Deployment, create a new pod.
  scale, err := clientSet.AppsV1().Deployments(ns).GetScale(ctx, deployment.Name, metav1.GetOptions{})
  if err != nil {
   return err
  }
  scale.Spec.Replicas = scale.Spec.Replicas + 1
  _, err = clientSet.AppsV1().Deployments(ns).UpdateScale(ctx, deployment.Name, scale, metav1.UpdateOptions{})
  if err != nil {
   return err
  }

  // Wait for pods to be scaled
  for {
   podList, err := clientSet.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{
    LabelSelector: fmt.Sprintf("app=%s", deployment.Name),
   })
   if err != nil {
    log.Fatal(err)
   }
   if len(podList.Items) != int(scale.Spec.Replicas) {
    time.Sleep(time.Second * 10)
   } else {
    break
   }
  }

  // Wait for pods to be running
  for {
   podList, err := clientSet.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{
    LabelSelector: fmt.Sprintf("app=%s", deployment.Name),
   })
   if err != nil {
    log.Fatal(err)
   }
   isPending := false
   for _, item := range podList.Items {
    if item.Status.Phase != v1.PodRunning {
     log.Printf("Deployment: %s Pod: %s Not Running Status: %s\n", deployment.Name, item.Name, item.Status.Phase)
     isPending = true
    }
   }
   if isPending == true {
    time.Sleep(time.Second * 10)
   } else {
    break
   }
  }

  // Remove origin pod
  for _, pod := range originPodList.Items {
   err = clientSet.CoreV1().Pods(ns).Delete(context.Background(), pod.Name, metav1.DeleteOptions{})
   if err != nil {
    return err
   }
   log.Printf(" Remove origin %s success.\n", pod.Name)
  }

  // Recover scale
  newScale, err := clientSet.AppsV1().Deployments(ns).GetScale(ctx, deployment.Name, metav1.GetOptions{})
  if err != nil {
   return err
  }
  newScale.Spec.Replicas = newScale.Spec.Replicas - 1
  newScale.ResourceVersion = ""
  newScale.UID = ""
  _, err = clientSet.AppsV1().Deployments(ns).UpdateScale(ctx, deployment.Name, newScale, metav1.UpdateOptions{})
  if err != nil {
   return err
  }
  log.Printf(" Depoloyment %s rebuild success.\n", deployment.Name)
  log.Println()

 }

 return nil
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
 

看的出來代碼是比較多的。

最終方案

有沒有更簡單的方法呢,當我把上述的方案和領導溝通后他人都傻了,這也太復雜了:kubectl 不是有一個直接滾動重啟的命令嗎。

復制
? k rollout -h
Manage the rollout of one or many resources.

Available Commands:
  history       View rollout history
  pause         Mark the provided resource as paused
  restart       Restart a resource
  resume        Resume a paused resource
  status        Show the status of the rollout
  undo          Undo a previous rollout
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
 

kubectl rollout restart deployment/abc 使用這個命令可以將 abc 這個 deployment 進行滾動更新,這個更新操作發生在 kubernetes 的服務端,執行的步驟和方案二差不多,只是 kubernetes 實現的比我的更加嚴謹。

后來我在查看 Istio 的官方升級指南中也是提到了這個命令:

所以還是得好好看官方文檔。

整合 kubectl

既然有現成的了,那就將這個命令整合到我的腳本里即可,再遍歷 namespace 下的 deployment 的時候循環調用就可以了。

但這個 rollout 命令在 kubernetes 的 client-go 的 SDK 中是沒有這個 API 的。

所以我只有參考 kubectl 的源碼,將這部分功能復制過來;不過好在可以直接依賴 kubect 到我的項目里。

復制
require (  
    k8s.io/api v0.28.2  
    k8s.io/apimachinery v0.28.2  
    k8s.io/cli-runtime v0.28.2  
    k8s.io/client-go v0.28.2  
    k8s.io/klog/v2 v2.100.1  
    k8s.io/kubectl v0.28.2  
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
 

源碼里使用到的 RestartOptions 結構體是公共訪問的,所以我就參考它源碼魔改了一下:

復制
func TestRollOutRestart(t *testing.T) {  
    kubeConfigFlags := defaultConfigFlags()  
    streams, _, _, _ := genericiooptions.NewTestIOStreams()  
    ns := "dev"  
    kubeConfigFlags.Namespace = &ns  
    matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)  
    f := cmdutil.NewFactory(matchVersionKubeConfigFlags)  
    deploymentName := "deployment/abc"  
    r := &rollout.RestartOptions{  
       PrintFlags: genericclioptions.NewPrintFlags("restarted").WithTypeSetter(scheme.Scheme),  
       Resources:  []string{deploymentName},  
       IOStreams:  streams,  
    }  
    err := r.Complete(f, nil, []string{deploymentName})  
    if err != nil {  
       log.Fatal(err)  
    }  
    err = r.RunRestart()  
    if err != nil {  
       log.Fatal(err)  
    }  
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
 

最終在幾次 debug 后終于可以運行了,只需要將這部分邏輯移動到循環里,加上 sleep 便可以有規律的重啟 Pod 了。

參考鏈接:

  • https://istio.io/latest/docs/setup/upgrade/canary/#data-plane。
  • https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/rollout/rollout_restart.go。
相關文章
{{ v.title }}
{{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
你可能感興趣
推薦閱讀 更多>
推薦商標

{{ v.name }}

{{ v.cls }}類

立即購買 聯系客服
主站蜘蛛池模板: 成人免费无码视频在线网站 | 免费无码又爽又刺激高潮的视频| 无码里番纯肉h在线网站| 人妻无码第一区二区三区| 欧洲黑大粗无码免费| a级毛片无码免费真人久久| AV无码精品一区二区三区| 国产成人无码免费视频97| 无码福利一区二区三区| 精品久久久无码人妻字幂 | 亚洲AV无码不卡在线观看下载| 国产av无码专区亚洲av果冻传媒| 中文字幕人成无码人妻| 无码区日韩特区永久免费系列| 熟妇人妻中文av无码| 成人无码视频97免费| 色欲香天天综合网无码| 98久久人妻无码精品系列蜜桃| 18精品久久久无码午夜福利| 亚洲爆乳精品无码一区二区| 无码人妻丰满熟妇啪啪网站| 中文字幕AV中文字无码亚| 亚洲AV无码乱码在线观看| 激情射精爆插热吻无码视频| 亚洲av无码一区二区三区观看| 亚洲中文字幕无码一区二区三区 | 亚洲精品无码av片| 精品久久久无码人妻中文字幕| 67194成l人在线观看线路无码| 人妻丰满熟妇AV无码区乱| 亚洲AV无码一区二区三区在线| 无码孕妇孕交在线观看| 麻豆精品无码国产在线果冻 | 精品久久久久久久无码久中文字幕 | (无码视频)在线观看| 久久久无码精品人妻一区| 免费无码又爽又黄又刺激网站| 精品无码国产污污污免费网站国产 | 无码人妻久久一区二区三区免费丨 | 亚洲成AV人片天堂网无码| 无码福利一区二区三区|