3. すべてのクライアント要求が適切に処理されることを確認します。
ポッドの開始時にすべての接続が適切に処理されることを確認する方法
1. ポッド起動時のクライアント接続の切断を回避する
ポッドが開始すると、ラベル セレクターがポッドのラベルと一致するサービス エンドポイントとしてすべてのサービスが公開されます。ポッドは、準備ができているという信号を kubernetes に送信する必要があります。ポッドの準備が完了すると、サービス エンドポイントになることができます。それ以外の場合は、クライアント接続リクエストを受け入れることができません。
ポッド仕様で Readiness プローブが指定されていない場合、ポッドは常に準備完了とみなされます。最初の kube-proxy がそのノード上の iptables ルールを更新し、最初のクライアント ポッドがサービスへの接続を開始すると、デフォルトで準備が整っているとみなされているポッドは、ほぼ即座にリクエストの受け入れを開始します。この時点でアプリケーションが接続を受け入れる準備ができていない場合、クライアントは「接続が拒否されました」などのエラー メッセージを受け取ります。
必要なのは、メッセージが受信リクエストを処理する準備ができている場合にのみ、readiness プローブが成功を返すようにすることだけです。実際の最初のステップは、HTTP GET リクエストの準備プローブをアプリケーションのルート URL に追加することです。
2. ポッドが閉じられると、クライアント接続が切断されます。
ポッドが削除され、ポッドのコンテナが終了する場合、SIGTERM シグナルを受信したときにポッドのコンテナはどのようにして正常にシャットダウンされますか。すべてのクライアント要求が適切に処理されることを確認するにはどうすればよいでしょうか?
ポッドの削除時に発生する一連のイベントを理解する
1) API サーバーはポッドの削除リクエストを受信すると、まず etcd のステータスを変更し、削除イベントをオブザーバーに通知します。オブザーバーのうちの 2 つは、kubelet とエンドポイント コントローラーです。
kubelet は、Pod アプリケーションが終了されたという通知を受け取ると、停止前フックを実行し、SIGTERM シグナルを送信し、一定時間待機した後、コンテナーが自己終了しない場合はコンテナーを強制的に強制終了します。
アプリケーションが SIGTERM シグナルに応じてクライアント要求の受け入れを停止することを理解している場合、アプリケーションに接続しようとすると、接続拒否エラーが発生します。ポッドが削除されてからこのリクエストが発生するまでの時間は比較的短いです。これは API サーバーと kubelet 間の直接通信であるためです。
2) ポッドが iptables ルールから削除される前に、エンドポイント コントローラーがポッドが削除されようとしているという通知を受け取ると、ポッドが配置されているすべてのサービスからポッドのサービス エンドポイントを削除します。彼は、API サーバーに REST リクエストを送信して、Endpoit API オブジェクトを変更します。次に、API サーバーは、このエンドポイント オブジェクトに注意を払うようにすべてのクライアントに通知します。これらのウォッチャーの一部は、ワーカー ノード上で実行される kube-proxy サービスです。各 kube-proxy サービスは、独自のノードの iptables ルールを更新して、新しい接続がこれらの停止したポッドに転送されないようにします。
上記の 2 つの一連の時間は並行して発生します。おそらく、ポッド内のアプリケーション プロセスをシャットダウンするのにかかる時間は、iptables ルールの更新を完了するのに必要な時間よりわずかに短く、 iptables ルールの更新には比較的時間がかかります。これらのイベントはまずエンドポイント コントローラーに到達する必要があり、次にエンドポイント コントローラーが API サーバーに新しいリクエストを送信し、次に API サーバーが kube プロキシを制御する必要があり、最後に kube プロキシが iptables ルールを変更します。iptables ルールがすべてのノードに更新される前に、SIGTERM シグナルが送信される可能性が高くなります。
最終結果として、ポッドに kill シグナルを送信した後も、ポッドは引き続きクライアント リクエストを受信できるようになります。アプリケーションがサーバーソケットを閉じてリクエストの受信を停止することを理解すると、クライアントは「接続が拒否されました」タイプのエラーを受け取ります。
問題を解く
Readiness Probe をポッドに追加すると、問題が解決します。ポッドが SIGTERM シグナルを受信したときに Readiness プローブが失敗し始めるだけであれば、ポッドはサービスのエンドポイントから削除されます。ただし、この削除アクションは、readiness Probe が一定期間失敗し続けた場合にのみ発生します (readiness Probe の仕様で構成できます)。この削除アクションは、最初に kube-proxy に到達する必要があり、その後 iptables ルールがこのポッドを取り外しました。
できる唯一の合理的な方法は、すべての kube-proxy が作業を完了するまで十分な時間待つことです。では、どれくらいの時間が十分なのでしょうか? ほとんどのシナリオでは数秒で十分ですが、毎回十分であるという保証はありません。API サーバーまたはエンドポイント コントローラーが過負荷になると、通知が kube-proxy に届くまでに時間がかかります。この問題に対する完璧な解決策はないことを理解することが重要ですが、5 秒または 10 秒の遅延を追加することで、ユーザー エクスペリエンスを大幅に向上させることもできます。遅延時間を長くすることもできますが、長すぎないように注意してください。これにより、コンテナーが正常にシャットダウンできなくなり、ポッドが長時間削除されてもリストに表示されたままになり、ポッドを削除するユーザーにとっては問題です。
まとめ:
アプリケーションを正常に閉じるには、次の手順が必要です。
- 数秒待ってから、新しい接続の受け入れを停止します
- 要求されていない長い接続をすべて閉じます。
- すべてのリクエストが完了するまで待ちます
- アプリケーションを完全に閉じます
4. アプリケーションを kubernetes で簡単に実行および管理できるようにする
1. 管理しやすいコンテナイメージを構築する
アプリケーションをイメージにパッケージ化する場合、アプリケーションのバイナリ ファイルとその依存ライブラリを含めることも、完全なオペレーティング システムとアプリケーションを一緒にパッケージ化することもできます。イメージ内のオペレーティング システム内のすべてのファイルが必要ですか? いいえ、ほとんどのファイルは使用されることはなく、画像が必要以上に大きくなるだけです。ポッドを初めてノードにスケジュールするときは、長い時間がかかります。最小限のビルド イメージはデバッグが非常に困難です。コンテナ内で ping、dig、curl、またはその他の同様のコマンドなどのツールを実行する必要がある場合、コンテナにこれらのツールの最小限のセットが含まれていることがいかに重要であるかがわかるでしょう。
どのツールがイメージに含まれ、どのツールが含まれないかは、ユーザー自身のニーズによって異なります。
2. イメージに適切なラベルを付け、ImagePullPolicypod を正しく使用する
マニフェストで最新のものを使用しないことをお勧めします。そうしないと、指定されたバージョンにフォールバックできなくなります。
特定のバージョンを指定できるラベルを使用する必要があります。変更可能なラベルを使用している場合は、ポッド仕様で imagepullpolicy を常に設定する必要があります。ただし、実稼働環境でこの方法を使用する場合は、追加の指示に注意する必要があります。イメージのプル ポリシーが常にに設定されている場合、コンテナ操作は、デプロイする必要がある新しいポッドを検出したときにイメージ レジストリに接続します。ノードはイメージが変更されているかどうかを確認する必要があるため、これによりポッドの起動速度が遅くなります。さらに悪いことに、この戦略では、ミラー レジストリに到達できない場合、新しいポッドが起動できなくなります。
3. 単次元ラベルの代わりに多次元ラベルを使用する
ラベルには次のものを含めることができます。
- リソースが属するアプリケーション (またはマイクロサービス) の名前
- アプリケーションレベル (フロントエンド、バックエンドなど)
- ランタイム環境 (開発、テスト、ステージング、本番環境など)
- バージョンナンバー
- リリースタイプ
- リリースタイプ (安定、カナリア、青緑色展開の緑色または青色など)
- テナント (名前空間を使用する代わりに各テナントで異なるポッドを実行する場合)
- シャーディング (シャーディングを備えたシステム)
タグ管理を使用すると、リソースを個別に管理するのではなくグループで管理できるため、リソースがどこに属しているかを簡単に理解できるようになります。
4. 注釈を使用して各リソースを説明する
注釈を使用して、リソースに追加情報を追加できます。リソースには、少なくともリソースを説明する注釈とリソースの所有者を説明する注釈が含まれている必要があります。
マイクロサービス フレームワークでは、ポッドには、そのポッドが依存する他のサービスの名前を説明するアノテーションが含まれている必要があります。これにより、ポッド間の依存関係を簡単に表示できるようになります。その他の注釈には、ビルド情報やバージョン情報に加え、他のツールや GUI で使用されるメタ情報 (アイコン名など) を含めることができます。
5. プロセス終了に関する詳細情報を提供する
診断を容易にするために、ポッド ステータスにコンテナ終了の理由を表示すると、コンテナ内のプロセスがコンテナのシステム内の指定されたファイルに終了メッセージを書き込むことができます。このファイルの内容は、コンテナーの終了後に kubelet によって読み取られ、kubectl description pod に表示されます。このプロセスが終了メッセージを書き込む必要があるファイルへのデフォルトのパスは /dev/termination-log です。このパスは、ポッド仕様のコンテナ定義部分で terminationMessagePath フィールドを設定することによってカスタマイズすることもできます。
注: コンテナーがどのファイルにもメッセージを書き込まない場合、terminationMessagePolicy フィールドは FallbackToLogsOnError にのみ設定できます。この場合、コンテナのログの最後の数行は終了メッセージとして扱われます (コンテナが正常に終了しなかった場合のみ)
6. アプリケーションログの処理
アプリケーションはファイルではなく標準出力割り込みにログを書き込み、kubectl log コマンドを使用してアプリケーション ログを簡単に表示できます。
ヒント: コンテナーがクラッシュし、新しいコンテナーに置き換えられると、新しいコンテナーのログが表示されます。以前のコンテナのログを確認したい場合は、kubectl logs コマンドを使用するときに、オプション --provious を追加します。
アプリケーションが標準出力ターミナルではなくファイルにログを書き込む場合、ログを表示する別の方法があります。
$kubectl exec <ポッド> cat <ログファイル>
このコマンドはコンテナー内で cat コマンドを実行し、ログ ストリームを kubectl に返し、kubectl はそれらをターミナルに表示します。
ログやその他のファイルをコンテナとの間でコピーする
ファイルをローカル マシンに転送します。
$kubectl cp foo-pod:/var/log/foo.log foo.log
ローカル マシンからポッドにファイルをコピーするには、2 番目の引数としてポッド名を指定できます。
$kubectl cp localfile foo-pod:/etc/remotefile
集中ログを使用する
kubectl 自体は一元化されたログを提供せず、通常はクラスター内の通常のポッドとして実行される他のコンポーネントを介して、すべてのコンテナー ログの一元化されたストレージと分析をサポートする必要があります。
集中ログ ソリューションのデプロイは非常に簡単で、いくつかの YAML/JSON マニフェスト ファイルをデプロイするだけで済みます。
Google の kubernetes エンジンでは、これはさらに簡単で、クラスターのセットアップ時に [Strackdriver Logging を有効にする] オプションを選択するだけです。
おそらく、ElasticSearch、Logstash、および Kibanna で構成される ELK スタックについて聞いたことがあるでしょう。これは、EFK スタックのわずかに変更されたバリアントで、Logstash が FluentD に置き換えられています。
EFK を集中ログとして使用する場合、各 kubernetes クラスター ノードは FluentD エージェント (ポッドとして DaemonSet を使用してデプロイ) を実行します。このエージェントは、コンテナーからログを収集し、ログにポッド関連情報をマークして、それらを送信する役割を果たします。 ElasticSearch はそれらを永久に保存します。ElasticSearch はクラスター内のポッドとしてもデプロイされます。これらのログは、ElasticSearch データを視覚化するツールである kubana を介して Web ブラウザーで表示および分析できます。これは、多くの場合ポッドとして実行され、サービスとして公開されます。EFK の 3 つのコンポーネントを次の図に示します。
複数行のログ入力を処理する
FluentD エージェントは、ログ ファイルの各行を ElasticSearch データ ストレージのエントリとして保存します。Java の例外スタックなど、ログ出力が複数行にまたがる場合、集中ログ システムに別のエントリとして保存されます。
この問題を解決するには、アプリケーション ログの出力コンテンツをプレーン テキストではなく JSON 形式で作成できます。このようにして、複数行のログ出力をエントリとして保存できます。kibana のエントリとして表示することもできますが、この方法ではログを表示する kubectl log コマンドの使いやすさが低下します。
解決策は、標準出力ターミナルに出力されるログはユーザースケールのログのままですが、FluentD が処理するためにログ ファイルに書き込まれるログは JSON 形式になることです。これには、ノード レベルでの FluentD エージェントの適切な構成、または軽量のログ コンテナーを各ポッドに追加する必要があります。