Kubernetes 学習ノート - アプリケーション開発のベスト プラクティス (1) 20230311

1. Kubernetes のすべてのリソース

以下は、典型的なアプリケーションで使用されるさまざまな Kubernetes コンポーネントです。

一般的なアプリケーション マニフェストには、1 つ以上のDeployment オブジェクトStatefulSetオブジェクトが含まれています。これらのオブジェクトには 1 つ以上のコンテナのポッド テンプレートが含まれており、各コンテナには liveness プローブと、コンテナによって提供されるサービス (存在する場合) の readiness プローブがあります。サービスを提供するポッドは、1 つ以上のサービスを通じて自身を公開します。これらのサービスにクラスターからアクセスする必要がある場合は、これらのサービスを LoadBalancer または NodePort タイプのサービスとして構成するか、Ingress リソースを通じてサービスを開きます。

ポッドテンプレート (ポッドの作成元となる構成ファイル) は通常、2 種類のシークレットを参照します。1 つはプライベート ミラー リポジトリからイメージをプルするときに使用され、もう 1 つはポッドで実行されているプロセスによって直接使用されます。プライバシー資格情報自体は、アプリケーション開発者ではなく運用チームによって構成されるため、通常はアプリケーション マニフェストの一部ではありません。通常、シークレット資格情報は ServiceAccount に割り当てられ、その後、個別のポッドに割り当てられます。

アプリケーションには 1 つ以上のConfigMapオブジェクトも含まれており、これを使用して環境変数を初期化したり、ポッドに configMap ボリュームとしてマウントしたりできます。一部のポッドは、emptyDir ボリュームや gitRepo ボリュームなどの追加ボリュームを使用します。永続ストレージを必要とするポッドには、persistentVolumeClaimボリュームが必要です。PersistentVolumeClaim もアプリケーション マニフェストの一部であり、PersistentVolumeClaim によって参照される StorageClass はシステム管理者によって事前に作成されます。

場合によっては、アプリケーションはタスク (ジョブ) または cron ジョブ (cronJobs) も使用する必要があります。DaemonSet は通常、アプリケーション展開の一部ではありませんが、通常、すべてまたは一部のノードでシステム サービスを実行するためにシステム管理者によって作成されます。水平ポッド オートスケーラーは、開発者がアプリケーション マニフェストに含めることも、運用および保守チームが後でシステムに追加することもできます。クラスター管理者は、各ポッドおよびすべてのポッド (全体として) のコンピューティング リソースの使用量を制御するために、LimitRange オブジェクトと ResourceQuota オブジェクトも作成します。

アプリケーションがデプロイされると、さまざまな kubernetes コントローラーが他のオブジェクトを自動的に作成します。これらには、エンドポイント コントローラー (Endpoint コントローラー) によって作成されるサービス エンドポイント (Endpoint) オブジェクト、デプロイメント コントローラー (Deployment コントローラー) によって作成される ReplicaSet オブジェクト、ReplicaSet (またはジョブ、CronJob、StatefulSet、DaemonSet) によって作成される実際のポッド オブジェクトが含まれます。 )。

通常、リソースは 1 つ以上のタグによって編成されます。これはポッドだけでなく、他のリソースにも当てはまります。ほとんどのリソースには、タグに加えて、リソースを説明する注釈、リソースの変更を担当する個人またはチームの連絡先情報のリスト、または管理者が使用する他のツールの追加メタデータも含まれています。

ポッドはすべてのリソースの中心であり、間違いなく kubernetes で最も重要なリソースです。結局のところ、各アプリケーションはポッド内で実行されます。

2. ポッドのライフサイクルを理解する

ポッドは単一のアプリケーションを実行する仮想マシンと比較できますが、依然として大きな違いがあります。一例として、kubernetes はポッドを別のノードにスケジュールする必要があるため、ポッド内で実行されているアプリケーションがいつでも強制終了される可能性があります。収縮をリクエストします。

  1. ポッド上のアプリケーションが強制終了または再スケジュールされる可能性があります

仮想マシンで実行されているアプリケーションが、あるマシンから別のマシンに移行されることはほとんどありません。移行オペレーターは、アプリケーションを移行するときに、アプリケーションを再構成し、アプリケーションが新しい場所で正しく動作することを手動で確認できます。

kubernetes を使用すると、手動介入なしでアプリケーションをより頻繁に自動的に移行できます。つまり、移行後にアプリケーションが正常に実行されることを保証するようにアプリケーションを構成する人がいないことを意味します。

ローカル IP とホスト名の変更が予想されます

ポッドが強制終了されて別の場所で実行されると (新しいポッドが古いポッドを置き換え、古いポッドは移行されませんでした)、新しい IP アドレスだけでなく、新しい名前とホスト名も持ちます。ほとんどのステートレス アプリケーションは、悪影響を与えることなくこのシナリオを処理できます。ただし、ステートフル サービスは通常は機能しません。ステートフル アプリケーションは StatefulSet を通じて実行でき、StatefulSet は、アプリケーションが新しいノードにスケジュールされて開始された後、この変更に対処できることを保証します。したがって、アプリケーション開発者は、クラスター内で相互の関係を確立するためにメンバー IP アドレスに依存しないでください。さらに、ホスト名を使用して関係を構築する場合は、StatefulSet を使用する必要があります。

ディスクに書き込まれたデータは消えることが予想されます

アプリケーションがデータをディスクに書き込む場合、アプリケーションの書き込みパスに永続ストレージを接続しない限り、アプリケーションが新しいポッドで起動されると、そのデータが失われる可能性があります。ポッドが再スケジュールされると、データ損失が保証されます。ただし、スケジュールがない場合でも、ディスクに書き込まれたファイルは失われます。単一ポッドの存続期間中であっても、ポッド内のアプリケーションによってディスクに書き込まれたファイルは失われます。

起動プロセスが比較的簡単で、多くの計算操作を必要とするアプリケーションがあるとします。その後のアプリケーションの起動を高速化するために、開発者は通常、起動プロセス中に一部の計算結果をディスクにキャッシュします。Kubernetes のアプリケーションはデフォルトでコンテナ内で実行されるため、これらのファイルはコンテナのファイル システムに書き込まれます。この時点でコンテナを再起動すると、これらのファイルは失われます。新しいコンテナが開始されると、新しい書き込み可能なレイヤーが使用されるためです。

プロセスのクラッシュ、インベントリ プローブが失敗を返した、ノードのメモリ不足や OOMKiller によるプロセスの強制終了など、さまざまな理由で単一のコンテナが再起動される可能性があります。上記の状況が発生すると、ポッドは同じままですが、コンテナーは新品になります。Kubelet はコンテナーを複数回実行しませんが、コンテナーを再作成します。

ストレージボリュームを使用してコンテナ間でデータを永続化する

ポッドのコンテナーが再起動されるとき、データが失われないようにするには、ポッドレベルのボリュームが必要です。ボリュームの存在と破棄はポッドのライフサイクルと一致するため、新しいコンテナーは、以前のコンテナーによってボリュームに書き込まれたデータを再利用できます。

ストレージ ボリュームを使用してコンテナ間で保存することが良い場合もありますが、常にそうとは限りません。新しく作成したプロセスがデータの破損により再びクラッシュした場合はどうすればよいでしょうか? これにより、継続的なループ クラッシュが発生します (ポッドは CrashLoopBackOff ステータスを要求します)。ストレージ ボリュームが使用されていない場合、新しいコンテナは最初から開始されるため、クラッシュすることはほとんどありません。ストレージ ボリュームを使用してコンテナ間でデータを保存することは、諸刃の剣です。

2. 停止したポッドまたは部分的に停止したポッドを再スケジュールする

ポッドのコンテナがクラッシュし続ける場合、kubelet はコンテナを再起動し続けます。各再起動の間隔は、5 分に達するまで指数関数的に増加します。この 5 分間の間に、コンテナー プロセスが実行されていなかったため、ポッドは実質的に停止しました。

公平を期すために、複数のコンテナーを含むポッドの場合、それらの一部は正常に実行されている可能性があるため、ポッドは部分的にのみ停止していますが、ポッドにコンテナーが 1 つしか含まれていない場合、ポッドは完全に停止しており、役に立ちません。その中で実行中のプロセスはありません。

これらのポッドが ReplicaSet または同様のコントローラーの一部であるにもかかわらず、なぜ自動的に削除または再スケジュールされないのか不思議に思う人もいるかもしれません。他のノードでは発生しないノード関連の問題が原因でコンテナがクラッシュした可能性があるため、ポッドを削除して、他のノードで正常に実行されているポッドを再起動できることが期待される場合があります。残念ながら、これは当てはまりません。ReplicaSet 自体は、ポッドが停止状態にあるかどうかを気にせず、ポッドの数が予想されるレプリカの数と一致するかどうかだけを気にします。

3. 固定順序でポッドを開始する

ポッドで実行されるアプリケーションと手動で実行されるアプリケーションのもう 1 つの違いは、アプリケーションを手動でデプロイするときにオペレーターがアプリケーション間の依存関係を認識しているため、アプリケーションを順番に起動できることです。

ポッドの起動方法

kubernetes を使用して複数のポッド アプリケーションを実行する場合、kubernetes には最初にいくつかのポッドを実行し、それらのポッドが正常に実行されるまで待機してから他のポッドを実行する組み込みメソッドがありません。もちろん、最初に 1 つのアプリケーションの構成を公開し、ポッドの開始後に 2 番目のアプリケーションの構成を公開することもできます。ただし、システム全体は通常、複数のポッド、サービス、またはその他のオブジェクトの定義を含む単一の yaml ファイルまたは json ファイルで定義されます。

kubernetes API サーバーは、yaml ファイルと json ファイルで定義された順序でオブジェクトを処理します。ただし、それは etcd に書き込まれるときにそれらが正しい順序であることを意味するだけです。ポッドがこの順序で開始されるという保証はありません。

ただし、前提条件が満たされるまでメイン コンテナが起動しないようにすることもできます。これは、ポッドに含まれる init と呼ばれるコンテナーによって実現されます。

initコンテナの紹介

init コンテナーを使用してポッドを初期化できます

ポッドには任意の数の初期コンテナーを含めることができます。init コンテナは順番に実行されます。そして、最後の init コンテナが実行されたときにのみ、メイン コンテナが起動されます。ただし、init コンテナーを使用して、ポッドのメイン コンテナーの開始を遅らせることもできます。たとえば、特定の条件が満たされるまで、init コンテナはメイン コンテナが依存するサービスが開始されるまで待機し、サービスを提供できます。サービスが開始され、サービスを提供できるようになると、init コンテナの実行が終了します。その後、メインコンテナを起動できるようになります。こうすることで、メイン コンテナが、依存するサービスの準備が整う前に、たまたまそれを使用することがなくなります。

init コンテナをポッドに追加する

init コンテナは、メイン コンテナと同様にポッド仕様ファイルで定義できますが、フィールド spec.initContainers を使用します。

fortun-client.yaml

仕様:
initContainers:
-name:init
image:busybox
コマンド:
-sh
-c
-'while true;do echo "フォーチュン サービスが起動するのを待っています ...";
wget http://fortune -q -T 1 -o /dev/null > /dev/null 2>/dev/null &&break;sleep 1;done; echo "サービスが起動しています! メイン コンテナを開始しています。"'

このポッドをデプロイすると、ポッドの初期コンテナのみが起動します。これは、コマンド kubectl get po で表示してポッドのステータスを表示できます。

$kubectl ゲット ポー

kubectl ログを通じて init コンテナのログを表示できます。

$kubectl ログ Fortune-client -c init

ポッド内の複数の依存関係を処理するためのベスト プラクティス

Readiness プローブの使用を検討してください。依存関係のいずれかが存在しない場合にアプリケーションが動作しない場合は、アプリケーションの準備ができていないことを Kubernetes が認識できるように、Readiness プローブを通じてそのことを通知する必要があります。この理由は、Readiness Probe が Huizu town アプリケーションがサービス エンドポイントになるという信号を受信するためだけでなく、ローリング アップグレードの際に展開コントローラーがアプリケーションの Readiness Probe を使用するため、間違ったバージョンが回避されるためでもあります。

4. ライフサイクルフックを追加する

ポッドでは 2 種類のライフサイクル フックを定義することもできます

  • ポストスタートフック

  • プレストップフック

これらのライフサイクル フックは、ポッド全体に適用される init コンテナとは異なり、コンテナごとに指定されます。これらのフックは、その名前が示すように、コンテナーが開始された後、コンテナーが停止される前に実行されます。

ライフサイクル フックはインベントリ プローブと準備プローブに似ており、両方の機能を使用できます。

  • コンテナ内でコマンドを実行する

  • HTTP GET リクエストを URL に送信します

開始後のライフサイクルフックを使用する

開始後フックは、コンテナーのメイン プロセスの開始直後に実行されます。アプリケーションの起動時に追加の作業を行うために使用できます。開発を行う場合、これらの操作をアプリケーション コードに追加できますが、他の人が開発したアプリケーションを実行する場合、ほとんどの人はソース コードを変更したくありません。開始後フックを使用すると、コードを変更せずにいくつかの追加関数を実行できます。お申し込み、ご注文。これらのコマンドには、アプリケーションが停止したことを外部リスナーに通知することや、アプリケーションがスムーズに実行できるようにアプリケーションを初期化することが含まれる場合があります。

このフックはメインプロセスと並行して実行されます。フックの名前は誤解を招く可能性があります。フックはメイン プロセスが完全に開始されるまで待機せずに実行されるためです。

フックは非同期で実行されますが、コンテナーには 2 つの方法で影響を与えます。フックが実行されるまで、コンテナーは ContainerCreating のため待機状態になるため、ポッドの状態は実行中ではなく保留中になります。フックが失敗するか、ゼロ以外のステータス コードを返した場合、メイン コンテナは強制終了されます。

開始後のフックを含むポッド マニフェスト: post-start-hook.yaml

apiVersion:v1
種類:pod
メタデータ:
名前:pod-with-poststart-hook
仕様:
コンテナー:
-image:luksa/kubia
名前:kubia
ライフサイクル:
postStart:
exec:
コマンド:
-sh
- -c
-"echo 'フックは失敗します終了コード 15'; スリープ 5; 終了 15"

上記の例のように、コマンド echo、sleep、exit はコンテナ作成時にコンテナのメインプロセスと一緒に実行されます。通常、このようなコマンドは実行されませんが、シェル スクリプトまたはコンテナ イメージに保存されているバイナリ実行可能ファイルを通じて実行されます。

stop を使用する前にライフサイクルをフックさせます

コンテナが終了する直前に、停止前フックで実行されます。コンテナを終了する必要がある場合、kubelet は停止前フックが設定されている場合に停止前フックを実行し、フック プログラムの実行後にのみ SIGTERM シグナルをコンテナ プロセスに送信します (プロセスが正常に終了しない場合)。 、殺されます)

停止前フックを使用すると、コンテナーが SIGTERM シグナルの受信後に正常にシャットダウンしない場合に、コンテナーが正常にシャットダウンするようにトリガーできます。これらのフックは、コンテナが終了する前に任意の操作を実行することもでき、アプリケーションがこれらの操作を内部で実装する必要はありません。

ポッド マニフェストでの停止前フックの構成は、開始後フック メソッドの追加に似ています。次は、HTTP GET リクエストを実行する停止前フックです。

pre-stop-hoop-httpget.yaml コード スニペット

ライフサイクル:
preStop:
httpGet:
ポート:8080
パス:シャットダウン

上で定義したように、kubelet がコンテナーの停止を開始すると、停止前フックはhttp://pod_IP :8080/shutdown へのHTTP GET リクエストをただちに実行します。スキームのホスト (HTTP または HTTPS) を設定することもできます。

注: デフォルトでは、host の値はポッドの IP アドレスです。localhost はポッドではなくノードを意味するため、リクエストがローカルホストに送信されないように注意してください。

開始後フックとは異なり、フックの実行が成功したかどうかに関係なくコンテナは終了します。HTTP によって返されるエラー ステータス コードも、コマンド ベースのフックによって返されるゼロ以外の終了コードも、コンテナーの終了を妨げることはありません。停止前フックの実行が失敗した場合は、ポッドのイベントに FailedPreStopHook アラームが表示されます。

ライフサイクルフックはポッドではなくコンテナ用です

ライフサイクル フックはポッドではなくコンテナ用です。ポッドの終了時に実行する必要がある操作の実行には、停止前フックを使用しないでください。その理由は、停止前フックはコンテナーが終了する前にのみ調査を行うためです (おそらく liveness プローブの失敗が原因です)。このプロセスは、ポッドのシャットダウン時だけでなく、ポッドのライフサイクル中に複数回発生します。

5. ポッドのシャットダウンについて理解する

ポッドのシャットダウンは、API サーバーによるポッドのオブジェクトの削除によってトリガーされます。HTTP DELETE リクエストを受信した後、API サーバーはポッド オブジェクトを削除しませんが、ポッドの deletionTimestamp 値を設定します。deletionTimestamp を持つポッドが停止を開始します。

kubelet は、ポッドを終了する必要があることを認識すると、ポッド内の各コンテナーの終了を開始します。kubelet は、各コンテナーに一定の時間を与えて正常に停止します。この時間は終了猶予期間と呼ばれ、各ポッドは個別に構成できます。終了プロセスが開始されるとタイマーが動き始め、次のイベントが順番に実行されます。

1) 停止前フックを実行し (構成されている場合)、それが完了するまで待ちます。

2) コンテナのメインプロセスに SIGTERM シグナルを送信します。

3) コンテナが正常にシャットダウンされるまで待つか、終了猶予期間が期限切れになるまで待ちます。

4) メインコンテナプロセスが正常にシャットダウンしない場合は、sigkill シグナルを使用してプロセスを強制終了します。

終了猶予期間を指定する

ポッド仕様の spec.terminationGracePeriodPeriods フィールドを通じて設定します。デフォルトでは、値は 30 です。これは、コンテナが強制終了される前に、30 秒以内にコンテナ自体を正常に終了することを意味します。

改善: コンテナ プロセスがこの期間内にクリーンアップを完了できるように、終了猶予期間を十分に長く設定する必要があります。

ポッドを削除する場合、ポッドの仕様で指定されている終了猶予期間は、次の方法で上書きすることもできます。

$kubectl delete po mypod --grace-period=5

上記のコマンドは、kuectl がポッドを自動的にシャットダウンするまで 5 秒待機します。すべてのポッド コンテナが停止すると、kubelet が API サーバーに通知し、最終的にポッド リソースが削除されます。確認を待たずに、API サーバーにポッド リソースを即座に削除するよう強制することができます。これを実現するには、猶予期間を 0 に設定し、--force オプションを追加します。

$kubectl delete po mypod --grace-period=0 --force

上記のオプションを使用するときは、特に StatefulSet ポッドの場合に注意する必要があります。StatefulSet コントローラーは、同じポッドの 2 つのインスタンスを同時に実行しないように細心の注意を払います (2 つのポッドのシリアル番号、名前、マウントが同じです)。同じ PersistentVolume)。ポッドを強制的に削除すると、コントローラーは、削除されたポッド内のコンテナーのシャットダウンが完了するのを待たずに、代替ポッドを作成します。つまり、同じポッドの 2 つのインスタンスが同時に実行され、ステートフル クラスター サービスが異常動作する可能性があります。ステートフル ポッドを強制的に削除するのは、ポッドが実行されなくなった場合、またはクラスターの他のメンバーと通信できないことが確認された場合のみです (これは、ポッドをホストしているノードのネットワーク接続障害によって確認でき、再接続できません)。

アプリケーションでコンテナーのシャットダウン操作を適切に処理する

アプリケーションは、シャットダウン プロセスを開始することで SIGTERM シグナルに応答し、プロセスが完了すると終了する必要があります。SIGTERM シグナルの処理に加えて、停止前にフックしてシャットダウン通知を受信することも可能である必要があります。どちらの場合も、アプリケーションが正常に終了するまでに必要な時間は決まっています。

しかし、アプリケーションが正常に終了するまでにどれくらいの時間がかかるかを予測できない場合はどうすればよいでしょうか? アプリケーションが分散データ ストレージである場合、ポッド インスタンスの 1 つが削除され、圧縮中にシャットダウンされます。シャットダウン プロセス中に、データが失われないように、ポッドはそのデータを他の存続するポッドに移行する必要があります。の場合、このポッドは終了信号を受信したときにデータの移行を開始する必要があります (SIGTERM 信号または停止前フックを介したものであるかどうか)。

答えは「いいえ」です。このアプローチは次の理由からお勧めできません。

  • コンテナの終了は、必ずしもポッド全体の終了を意味するわけではありません

  • このシャットダウン プロセスが強制終了される前に実行できるという保証はありません。

重要なシャットダウン プロセスをシャットダウン プロセスに重点を置いたポッドに置き換えます。

1 つの解決策は、削除されたポッドから生き残ったポッドにデータを移行することだけがジョブである新しいポッドを実行する新しいジョブ リソースをアプリケーションに作成させることです (終了信号を受信したときに)。ただし、アプリケーションが毎回 jod オブジェクトを正常に作成できるかどうかは保証できません。アプリケーションがジョブを作成しようとしたときにノードに障害が発生した場合はどうなるでしょうか。

この問題に対する合理的な解決策は、孤立したデータを継続的にチェックするために継続的に実行される専用のポッドを用意することです。ポッドは孤立したデータを見つけると、それらのデータを存続しているポッドに移行できます。もちろん、継続的に実行するポッドである必要はなく、CronJob リソースを使用してこのポッドを定期的に実行することもできます。

StatefulSet にも問題があり、たとえば、StatefulSet を縮小すると Persistent VolumeClaim が孤立状態になり、Persistent VolumeClaim に保存されているデータが取り残されてしまいます。もちろん、後続の拡張プロセスでは、PersistentVolume が新しいポッド インスタンスにアタッチされますが、この拡張操作がまったく実行されない場合 (または、長い時間が経過した後に実行される場合) はどうなるでしょうか? したがって、StatefulSet を使用する場合、データ移行用のポッドを実行することをお勧めします。アプリケーションのアップグレード プロセス中のデータ移行を回避するために、データ移行に特別に使用されるポッドは、データ移行の前に待機時間を設定して、ステートフル ポッドが実行されるように設定できます。始める時間があるので立ち上がってください。

おすすめ

転載: blog.csdn.net/wwxsoft/article/details/129462532