問題の概要 ある日、k8s クラスターの計画的な更新中に、新しいノード上のほぼすべての POD (1,000 個中約 500 個) が起動できないことが判明し、数分があっという間に数時間に変わりました。根本原因を積極的に調査しましたが、3 時間経っても PODS は ステータスのままでした。 ContainerCreating 幸いなことに、これは本番環境ではなく、メンテナンス ウィンドウは週末に予定されていました。私たちはプレッシャーを感じることなく、問題を調査する時間がありました。 根本原因の調査はどこから始めればよいでしょうか? 私たちが見つけた解決策についてもっと知りたいですか? シートベルトを締めて、お楽しみください! 問題の詳細 問題は、クラスター内の各ノードで同時にプルして起動する必要のある Docker イメージが多数あることでした。これは、単一のノードで複数の Docker イメージを同時にプルすると、ディスク使用率が高くなり、コールド スタート時間が長くなる可能性があるためです。 CD プロセスでは、イメージをプルするのに最大 3 時間かかることがあります。ただし、今回は EKS アップグレード (インライン、クラスター内のすべてのノードを置き換えるとき) 中の PODS の量が多すぎたため、完全に停止しました。 当社のアプリはすべて k8s ( ベース) で稼働しています。開発環境のコストを節約するために、スポット インスタンスを使用しています。 EKS ノードには イメージを使用します。 AmazonLinux2 開発環境には、Kubernetes クラスターに継続的にデプロイされる多数の (FB) があります。各 FB には独自のアプリケーション セットがあり、各アプリケーションには独自の依存関係のセット (イメージ内) があります。 機能ブランチ 私たちのプロジェクトには約 200 個のアプリがあり、この数は増え続けています。各アプリは、サイズが約 2 GB の 7 つの基本 Docker イメージの 1 つを使用します。アーカイブされたイメージ ( 内) の最大合計サイズは約 3 GB です。 ECR すべてのイメージは Amazon Elastic Container Registry (ECR) に保存されます。 ノードにはデフォルトの gp3 EBS ボリューム タイプを使用します。 直面した問題 新しいイメージで新しいポッドを起動すると、特に 1 つのノードで複数のイメージが同時にプルされる場合、1 時間以上かかることがあります。 コールド スタート時間の延長: 頻繁に発生したり、 状態のままになったりして、イメージのプルに問題があることを示します。 ErrImagePull エラー: ErrImagePull ContainerCreating イメージ取得プロセス中、ディスク使用率は 100% 近くを維持します。これは主に、解凍 (例: 「unpigz」) に必要な集中的なディスク I/O が原因です。 ディスク使用率が高い: 一部のシステム DaemonSet ( や など) は、ディスクの負荷により「準備ができていない」状態に移行し、ノードの準備状況に影響を与えました。 システム DaemonSet の問題: aws-node ebs-csi-node スポット インスタンスを使用しているため、イメージのキャッシュにローカル ディスクを使用できません。 ノードにイメージ キャッシュがありません。 この結果、特に異なる FB には異なるベース イメージのセットがあるため、機能ブランチでのデプロイメントが停止するケースが多くなります。 すぐに調査した結果、主な問題は プロセスによるノードのディスク負荷であることがわかりました。このプロセスは、Docker イメージの解凍を担当します。gp3 EBS ボリューム タイプのデフォルト設定は、このケースには適していないため変更しませんでした。 unpigz クラスターを回復するための修正プログラム 最初のステップとして、ノード上の POD の数を減らすことにしました。 新しいノードを「Cordon」状態に移動します 詰まったPODSをすべて取り除き、ディスク圧力を軽減します。 PODを1つずつ実行してノードをウォームアップします その後、ウォームアップしたノードを通常の状態(「unCordon」)に移行します。 スタック状態のすべてのノードを削除しました すべてのPODSはDockerイメージキャッシュを使用して正常に起動しました 独自のCI/CD設計 ソリューションの主なアイデアは、すべてのアプリのルート イメージとして使用される Docker イメージの最大の部分 (JS 依存関係レイヤー) によって、CD プロセスが開始する前にノードをウォームアップすることです。アプリの種類に関連する JS 依存関係を持つルート イメージは少なくとも 7 種類あります。それでは、元の CI/CD 設計を分析してみましょう。 当社の CI/CD パイプラインには 3 つの柱があります。 独自の CI/CD パイプライン: it ステップでは、環境/変数を準備し、再構築するイメージのセットを定義します。 Init ステップでは、イメージをビルドしてECRにプッシュします。 Build 手順: イメージを k8s にデプロイします (デプロイメントの更新など) Deploy オリジナルの CICD 設計の詳細: 機能ブランチ (FB) は ブランチから分岐しました。CI プロセスでは、FB で変更されたイメージのセットを常に分析し、再構築します。 ブランチは常に安定しており、定義どおり、ベース イメージの最新バージョンが常に存在するはずです。 main main JS 依存関係の Docker イメージを環境ごとに個別に構築し、ECR にプッシュして、Dockerfile のルート (ベース) イメージとして再利用します。JS 依存関係の Docker イメージは 5 ~ 10 種類程度あります。 FB は、別の名前空間の k8s クラスターにデプロイされますが、FB の共通ノードにデプロイされます。FB には最大 200 個のアプリを含めることができ、イメージ サイズは最大 3 GB です。 クラスター自動スケーリング システムがあり、負荷または保留中の PODS に基づいて、対応する nodeSelector と toleration を使用してクラスター内のノードをスケーリングします。 ノードにはスポットインスタンスを使用します。 ウォームアッププロセスの実施 ウォームアッププロセスには要件があります。 必須: : 問題に対処して解決します。 問題解決 ContainerCreating : 事前加熱されたベースイメージ (JS 依存関係) を利用することで起動時間が大幅に短縮されます。 パフォーマンスの向上 改善されてよかった点: : ノード タイプとその寿命 (例: 高 SLA または存続期間の延長) を簡単に変更できます。 柔軟性 : 使用状況とパフォーマンスに関する明確な指標を提供します。 透明性 : 関連する機能ブランチが削除された直後に VNG を削除することでコストを節約します。 コスト効率 : このアプローチにより、他の環境が影響を受けないことが保証されます。 分離 解決 要件と制約を分析した後、ベース JS キャッシュ イメージを使用してノードを事前加熱するウォームアップ プロセスを実装することにしました。このプロセスは CD プロセスの開始前にトリガーされ、ノードが FB の展開の準備が整っていることが保証され、キャッシュをヒットする可能性が最大限に高まります。 この改善は、3 つの大きなステップに分割されます。 (仮想ノードグループ)を作成する 各FBごとに ノードセット 新しいノードの を追加します。 cloud-init スクリプトにベースイメージ CD プロセスが開始する前に、必要な Docker イメージをノードにダウンロードするために、 セクションで を追加します。 initContainers DaemonSet を実行する事前デプロイ手順 更新された CI/CD パイプラインは次のようになります。 更新された CI/CD パイプライン: ステップ 1.1.(新しい手順) : FB を初めて起動する場合は、ノード インスタンスの新しい個人セット (用語では仮想ノード グループまたは VNG) を作成し、メイン ブランチからすべての JS ベース イメージ (5~10 イメージ) をダウンロードします。FB をメイン ブランチからフォークしたので、これを行うのは妥当です。重要な点は、これがブロッキング操作ではないことです。 初期 初期デプロイ ステップ ビルド 手順 ECR から特定の FB タグが付いた最新の JS ベース イメージをダウンロードします。 3.1.(新しいステップ) : ディスク負荷を軽減する必要があるため、これはブロッキング操作です。関連する各ノードのベースイメージを 1 つずつダウンロードします。 ちなみに、「 ステップに感謝します。メイン ブランチからの基本 Docker イメージがすでにあるので、最初の起動時にキャッシュをヒットする大きなチャンスが得られます。 デプロイ前の 重要なポイント init deploy」 **展開する **このステップでは変更はありません。ただし、前のステップのおかげで、必要なノードにはすでにすべての重い Docker イメージ レイヤーが存在します。 初期デプロイ手順 CI パイプラインから API 呼び出し (サードパーティの自動スケーリング システムへ) を介して 。 、各 FB の新しいノード セットを作成します 解決された問題: : 各 FB には独自のノード セットがあり、環境が他の FB の影響を受けないことを保証します。 分離 : ノードタイプとその有効期間を簡単に変更できます。 柔軟性 : FB が削除されるとすぐにノードを削除できます。 コスト効率 : ノードの使用状況とパフォーマンスを簡単に追跡できます (各ノードには FB に関連するタグがあります)。 透明性 : スポット インスタンスは、すでに事前定義されたベース イメージを使用して起動します。つまり、スポット ノードが起動すると、ノード上には (メイン ブランチからの) ベース イメージがすでに存在します。 スポット インスタンスの効果的な使用方法 スクリプトを使用して 。 cloud-init 、メイン ブランチからすべての JS ベース イメージを新しいノードにダウンロードします イメージがバックグラウンドでダウンロードされている間も、CD プロセスは問題なく新しいイメージの構築を続行できます。さらに、このグループの次のノード (自動スケーリング システムによって作成されるノード) は、開始前にイメージをダウンロードする指示がすでに含まれた更新された データを使用して作成されます。 cloud-init 解決された問題: : メイン ブランチからベース イメージのダウンロードを追加して スクリプトを更新したため、ディスク プレッシャーはなくなりました。これにより、FB の最初の起動時にキャッシュにアクセスできるようになりました。 問題の解決 cloud-init : スポット インスタンスは更新された データで起動します。つまり、スポット ノードが起動すると、ノード上にベース イメージ (メイン ブランチから) がすでに存在することになります。 スポット インスタンスの効果的な使用法 cloud-init : CD プロセスは問題なく新しいイメージの構築を継続できます。 パフォーマンスの向上 このアクションにより、CI/CD パイプラインに約 17 秒 (API 呼び出し) が追加されました。 このアクションは、FB を初めて起動するときのみ意味があります。次回は、前回のデプロイメントで配信したベース イメージが既に存在するノードにアプリをデプロイします。 展開前の手順 FB イメージはメイン ブランチ イメージとは異なるため、この手順が必要です。CD プロセスを開始する前に、FB ベース イメージをノードにダウンロードする必要があります。これにより、複数の重いイメージが同時にプルされたときに発生する可能性のある、コールド スタート時間の延長とディスク使用率の上昇を軽減できます。 事前デプロイ手順の目的 : 最も重い Docker イメージを順番にダウンロードします。init-deploy ステップの後、ノード上にベース イメージがすでに存在するため、キャッシュにヒットする可能性が高くなります。 ディスク負荷の防止 : ノードが必須の Docker イメージで事前に加熱されていることを確認し、POD の起動時間を短縮 (ほぼ即時) します。 デプロイメント効率の向上 : / エラーが発生する可能性を最小限に抑え、システム デーモン セットが「準備完了」状態を維持するようにします。 安定性の向上 ErrImagePull ContainerCreating このステップでは、CD プロセスに 10 ~ 15 分を追加します。 デプロイ前の手順の詳細: CD では、 セクションを使用して DaemonSet を作成します。 initContainers セクションはメイン コンテナーが起動する前に実行され、メイン コンテナーが起動する前に必要なイメージがダウンロードされるようにします。 initContainers CD では、daemonSet のステータスを継続的にチェックしています。daemonSet が「準備完了」状態であれば、デプロイメントを続行します。そうでない場合は、daemonSet が準備完了になるまで待機します。 比較 予熱プロセスにおける元の手順と更新された手順の比較。 ステップ 初期デプロイ手順 展開前の手順 展開する 合計時間 差分 予熱なし 0 0 11分21秒 11分21秒 0 予熱あり 8秒 58秒 25秒 1分31秒 -9分50秒 主な変更点は、「デプロイ」時間(最初の適用コマンドからポッドの実行状態まで)が 11 分 21 秒から 25 秒に変更されたことです。合計時間は 11 分 21 秒から 1 分 31 秒に変更されました。 重要な点として、メイン ブランチからのベース イメージがない場合、「デプロイ」時間は元の時間と同じか、少し長くなります。いずれにせよ、ディスク プレッシャーとコールド スタート時間の問題は解決しました。 結論 主な問題はウォームアップ プロセスによって解決されました。その利点として、POD のコールド スタート時間が大幅に短縮されました。 ノードにベース イメージがすでに存在するため、ディスク プレッシャーは解消されました。システム daemonSets は「準備完了」かつ「正常」な状態にあり (ディスク プレッシャーがないため)、この問題に関連する エラーは発生していません。 ContainerCreating ErrImagePull 考えられる解決策とリンク の代わりにノードに インスタンスを使用する 非本番環境では予算の範囲外となるため、この方法は使用できません。 スポットインスタンス オンデマンド この機能は非本番環境の予算の範囲外であるため、この方法は使用できません。さらに、AWS にはリージョンごとにアカウントの IOPS があります。 IOPSが向上したAmazon EBS gp3(またはそれ以上)のボリュームタイプを使用する 制限 実際には、本番環境やその他の環境への影響が大きすぎるため、この方法では移行できませんが、これは私たちの問題に対する良い解決策でもあります。 Bottlerocket データボリュームを使用して Amazon EKS のコンテナ起動時間を短縮 Kubernetes Cluster Autoscaler のトラブルシューティング: 600 個のポッドをスケールアップするのに 1 時間かかります ( ) の素晴らしい技術チームに、彼らが直面するあらゆる問題に対してたゆまぬ努力と本当に創造的なアプローチをしていることに感謝したいと思います。特に、チームの素晴らしい仕事の責任者である素晴らしいリーダー、Ronny Sharaby に感謝したいと思います。皆さんの創造性が Justt 製品にどのような影響を与えるかを示す素晴らしい例を、もっともっと見ることを楽しみにしています。 追伸: Justt https://www.linkedin.com/company/justt-ai