自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何優(yōu)雅重啟 Kubernetes 的 Pod

云計(jì)算 云原生
最近在升級(jí)服務(wù)網(wǎng)格 Istio,升級(jí)后有個(gè)必要的流程就是需要重啟數(shù)據(jù)面的所有的 Pod,也就是業(yè)務(wù)的 Pod,這樣才能將這些 Pod 的 sidecar 更新為新版本。

方案 1

因?yàn)槲覀儾煌h(huán)境的 Pod 數(shù)不少,不可能手動(dòng)一個(gè)個(gè)重啟;之前也做過(guò)類似的操作:

kubectl delete --all pods --namespace=dev

這樣可以一鍵將 dev 這個(gè)命名空間下的 Pod 刪掉,kubernetes 之后會(huì)自動(dòng)將這些 Pod 重啟,保證和應(yīng)用的可用性。

但這有個(gè)大問(wèn)題是對(duì) kubernetes 的調(diào)度壓力較大,一般一個(gè) namespace 下少說(shuō)也是幾百個(gè) Pod,全部需要重新調(diào)度啟動(dòng)對(duì) kubernetes 的負(fù)載會(huì)很高,稍有不慎就會(huì)有嚴(yán)重的后果。

所以當(dāng)時(shí)我的第一版方案是遍歷所有的 deployment,刪除一個(gè) Pod 后休眠 5 分鐘再刪下一個(gè),偽代碼如下:

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) 
}

存在的問(wèn)題

這個(gè)方案確實(shí)是簡(jiǎn)單粗暴,但在測(cè)試的時(shí)候就發(fā)現(xiàn)了問(wèn)題。

當(dāng)某些業(yè)務(wù)只有一個(gè) Pod 的時(shí)候,直接刪掉之后這個(gè)業(yè)務(wù)就掛了,沒(méi)有多余的副本可以提供服務(wù)了。

這肯定是不能接受的。

甚至還有刪除之后沒(méi)有重啟成功的:

  • 長(zhǎng)期沒(méi)有重啟導(dǎo)致鏡像緩存沒(méi)有了,甚至鏡像已經(jīng)被刪除了,這種根本就沒(méi)法啟動(dòng)成功。
  • 也有一些 Pod 有 Init-Container 會(huì)在啟動(dòng)的時(shí)候做一些事情,如果失敗了也是沒(méi)法啟動(dòng)成功的。 總之就是有多種情況導(dǎo)致一個(gè) Pod 無(wú)法正常啟動(dòng),這在線上就會(huì)直接導(dǎo)致生產(chǎn)問(wèn)題,所以方案一肯定是不能用的。

方案二

為此我就準(zhǔn)備了方案二:

image.png

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

這樣可以將原有的 Pod 平滑的重啟,同時(shí)如果新的 Pod 啟動(dòng)失敗也不會(huì)繼續(xù)重啟其他 Deployment 的 Pod,老的 Pod 也是一直保留的,對(duì)服務(wù)本身沒(méi)有任何影響。

存在的問(wèn)題

看起來(lái)是沒(méi)有什么問(wèn)題的,就是實(shí)現(xiàn)起來(lái)比較麻煩,流程很繁瑣,這里我貼了部分核心代碼:

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
}

看的出來(lái)代碼是比較多的。

最終方案

有沒(méi)有更簡(jiǎn)單的方法呢,當(dāng)我把上述的方案和領(lǐng)導(dǎo)溝通后他人都傻了,這也太復(fù)雜了:kubectl 不是有一個(gè)直接滾動(dòng)重啟的命令嗎。

? 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

kubectl rollout restart deployment/abc 使用這個(gè)命令可以將 abc 這個(gè) deployment 進(jìn)行滾動(dòng)更新,這個(gè)更新操作發(fā)生在 kubernetes 的服務(wù)端,執(zhí)行的步驟和方案二差不多,只是 kubernetes 實(shí)現(xiàn)的比我的更加嚴(yán)謹(jǐn)。

后來(lái)我在查看 Istio 的官方升級(jí)指南中也是提到了這個(gè)命令:

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

整合 kubectl

既然有現(xiàn)成的了,那就將這個(gè)命令整合到我的腳本里即可,再遍歷 namespace 下的 deployment 的時(shí)候循環(huán)調(diào)用就可以了。

但這個(gè) rollout 命令在 kubernetes  client-go  SDK 中是沒(méi)有這個(gè) API 的。

所以我只有參考 kubectl 的源碼,將這部分功能復(fù)制過(guò)來(lái);不過(guò)好在可以直接依賴 kubect 到我的項(xiàng)目里。

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  
)

源碼里使用到的 RestartOptions 結(jié)構(gòu)體是公共訪問(wèn)的,所以我就參考它源碼魔改了一下:

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)  
    }  
}

最終在幾次 debug 后終于可以運(yùn)行了,只需要將這部分邏輯移動(dòng)到循環(huán)里,加上 sleep 便可以有規(guī)律的重啟 Pod 了。

參考鏈接:

責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2022-07-04 09:13:54

KubernetespodLinux

2022-05-10 10:09:12

KubernetesPod網(wǎng)絡(luò)抓包

2024-06-19 09:33:05

2020-11-30 12:15:26

KubernetesPodLinux

2023-02-09 16:47:34

KubernetesPod優(yōu)先級(jí)

2020-04-10 08:00:08

Kubernetes補(bǔ)丁pod

2021-07-21 09:50:35

Linux腳本命令

2021-12-21 15:17:53

Kubernetes緩存Linux

2022-01-21 09:45:42

Mozilla SOKubernetesLinux

2024-04-15 05:00:00

kubernete網(wǎng)絡(luò)容器

2022-09-22 12:11:38

PodKubernetes

2021-12-03 11:06:01

VeleroKubernetesLinux

2021-06-04 10:52:51

kubernetes場(chǎng)景容器

2025-04-25 08:55:00

Pod運(yùn)維

2019-11-20 09:15:53

KubernetesPod

2024-03-29 12:11:46

2021-06-25 15:53:25

Kubernetes程序技巧

2023-11-02 20:05:17

KubernetesPod管理

2021-12-29 17:24:16

Kubernetes集群事件

2021-10-26 10:28:41

開(kāi)發(fā)架構(gòu)Kubernetes
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)