解決策1さまざまな環境に多数の Pod があるため、手動で 1 つずつ再起動することは不可能です。これまでにも同様の操作を実行しました: kubectl delete --all pods --namespace=dev この方法では、dev 名前空間内の Pod をワンクリックで削除でき、Kubernetes はこれらの Pod を自動的に再起動してアプリケーションの可用性を確保します。 しかし、大きな問題は、Kubernetes のスケジュールに大きな負担がかかることです。通常、名前空間の下には少なくとも数百の Pod が存在します。これらすべてを再スケジュールして起動する必要がある場合、 Kubernetesの負荷が非常に高くなり、注意しないと深刻な結果を招くことになります。 したがって、私の最初のバージョンのソリューションは、すべてのデプロイメントを反復処理し、ポッドを削除し、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) } 問題点この解決策は確かに単純かつ粗雑ですが、テスト中に問題が発見されました。 一部のサービスに Pod が 1 つしかない場合、サービスを提供する追加のコピーがないため、直接削除するとサービスは失敗します。 これは絶対に受け入れられません。 削除後に再起動に失敗したものもあります。 - 長期間再起動しないと、イメージ キャッシュが失われたり、イメージが削除されたりして、正常に起動できなくなります。
- 一部の Pod には起動時に何かを実行する Init-Container があり、それが失敗すると正常に起動できなくなります。つまり、Pod が正常に起動できず、オンライン運用上の問題に直接つながる状況は多くあるため、ソリューション 1 は絶対に実行可能ではありません。
解決策2このため、私はプラン2を用意しました。 画像.png - まずレプリカの数を 1 増やします。これにより、新しい Pod が追加され、最新のサイドカー イメージが使用されます。
- 新しく作成された 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.xx" { 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 } コードがさらに増えていることがわかります。 最終解決策もっと簡単な方法はありますか?上記の解決策をリーダーに伝えると、彼は唖然としました。これは複雑すぎます。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 kubectl rollout restart deployment/abcこのコマンドは、デプロイメントabcに対してローリング アップデートを実行するために使用できます。この更新操作は Kubernetes サーバー上で実行されます。実行手順はソリューション 2 と似ていますが、Kubernetes の実装が私のものよりも厳密である点が異なります。 その後、Istio の公式アップグレード ガイドで次のコマンドも見かけました。 したがって、公式ドキュメントを注意深く読む必要があります。
kubectlを統合する準備が整ったので、このコマンドをスクリプトに統合し、名前空間の下のデプロイメントを走査するときにループで呼び出すことができます。 ただし、 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 ) ソース コードで使用されている 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) } } 最後に、いくつかのデバッグ手順を経た後、実行できます。このロジックの部分をループに移動し、スリープを追加してPod を定期的に再起動します。 参考リンク: - https://istio.io/latest/docs/setup/upgrade/canary/#data-plane。
- https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/rollout/rollout_restart.go。
|