序章
最新のステートレスアプリケーションは、Dockerなどのソフトウェアコンテナで実行するように構築および設計されており、Kubernetesなどのコンテナクラスタによって管理されます。 これらは、 CloudNativeおよびTwelveFactor の原則とパターンを使用して開発されており、手動による介入を最小限に抑え、移植性と冗長性を最大限に高めます。 仮想マシンまたはベアメタルベースのアプリケーションをコンテナに移行し(「コンテナ化」と呼ばれます)、それらをクラスタ内にデプロイすると、多くの場合、これらのアプリの構築、パッケージ化、および配信の方法が大幅に変更されます。
Kubernetes用アプリケーションのアーキテクチャに基づいて、この概念ガイドでは、アプリケーションをKubernetesクラスタで実行および管理することを最終目標として、アプリケーションを最新化するための高レベルの手順について説明します。 Kubernetesでデータベースなどのステートフルアプリケーションを実行できますが、このガイドでは、永続データを外部データストアにオフロードして、ステートレスアプリケーションの移行と最新化に焦点を当てています。 Kubernetesは、ステートレスアプリケーションを効率的に管理およびスケーリングするための高度な機能を提供します。ここでは、Kubernetesでスケーラブルで監視可能なポータブルアプリを実行するために必要なアプリケーションとインフラストラクチャの変更について説明します。
移行のためのアプリケーションの準備
アプリケーションをコンテナ化する前、またはKubernetes PodとDeploymentの設定ファイルを作成する前に、アプリケーションレベルの変更を実装して、Kubernetesでのアプリの移植性と可観測性を最大化する必要があります。 Kubernetesは高度に自動化された環境であり、障害が発生したアプリケーションコンテナを自動的にデプロイして再起動できるため、適切なアプリケーションロジックを組み込んでコンテナオーケストレータと通信し、必要に応じてアプリを自動的にスケーリングできるようにすることが重要です。
構成データの抽出
実装する最初のアプリケーションレベルの変更の1つは、アプリケーションコードからアプリケーション構成を抽出することです。 構成は、サービスエンドポイント、データベースアドレス、クレデンシャル、さまざまなパラメーターやオプションなど、展開や環境によって異なる情報で構成されます。 たとえば、staging
とproduction
の2つの環境があり、それぞれに個別のデータベースが含まれている場合、アプリケーションには、データベースエンドポイントと資格情報をコードで明示的に宣言してはならず、実行環境の変数、ローカルファイル、または外部のKey-Valueストアのいずれかとして、値がアプリに読み込まれる別の場所。
これらのパラメーターをコードにハードコーディングすると、セキュリティ上のリスクが発生します。これは、この構成データが機密情報で構成されていることが多く、その情報をバージョン管理システムにチェックインするためです。 また、アプリケーションの複数のバージョンを維持する必要があるため、複雑さが増します。各バージョンは同じコアアプリケーションロジックで構成されていますが、構成がわずかに異なります。 アプリケーションとその構成データが大きくなるにつれて、構成をアプリコードにハードコーディングするとすぐに扱いにくくなります。
アプリケーションコードから構成値を抽出し、実行環境またはローカルファイルからそれらを取り込むことで、アプリは、付属の構成データを提供することで、任意の環境にデプロイできる汎用のポータブルパッケージになります。 DockerのようなコンテナーソフトウェアとKubernetesのようなクラスターソフトウェアは、このパラダイムを中心に設計されており、構成データを管理してアプリケーションコンテナーに挿入するための機能が組み込まれています。 これらの機能については、ContainerizingおよびKubernetesセクションで詳しく説明します。
これは、Python Flaskアプリのコードから2つの構成値DB_HOST
とDB_USER
を外部化する方法を示す簡単な例です。 それらをアプリの実行環境でenvvarsとして利用できるようにし、そこからアプリがそれらを読み取ります。
hardcoded_config.py
from flask import Flask DB_HOST = 'mydb.mycloud.com' DB_USER = 'sammy' app = Flask(__name__) @app.route('/') def print_config(): output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER) return output
このアプリを実行し(方法については、 Flask Quickstart を参照してください)、そのWebエンドポイントにアクセスすると、これら2つの構成値を含むページが表示されます。
次に、アプリの実行環境に外部化された構成値を使用した同じ例を示します。
env_config.py
import os from flask import Flask DB_HOST = os.environ.get('APP_DB_HOST') DB_USER = os.environ.get('APP_DB_USER') app = Flask(__name__) @app.route('/') def print_config(): output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER) return output
アプリを実行する前に、ローカル環境で必要な構成変数を設定します。
export APP_DB_HOST=mydb.mycloud.com export APP_DB_USER=sammy flask run
表示されるWebページには最初の例と同じテキストが含まれているはずですが、アプリの構成はアプリケーションコードとは関係なく変更できるようになりました。 同様のアプローチを使用して、ローカルファイルから構成パラメーターを読み込むことができます。
次のセクションでは、アプリケーションの状態をコンテナの外に移動する方法について説明します。
アプリケーションの状態をオフロード
クラウドネイティブアプリケーションはコンテナーで実行され、KubernetesやDockerSwarmなどのクラスターソフトウェアによって動的に調整されます。 特定のアプリまたはサービスは、複数のレプリカ間で負荷分散でき、個々のアプリコンテナーは、クライアントのサービスの中断を最小限に抑えて、またはまったく中断することなく、障害を発生させることができるはずです。 この水平で冗長なスケーリングを有効にするには、アプリケーションをステートレスな方法で設計する必要があります。 つまり、永続的なクライアントとアプリケーションのデータをローカルに保存せずにクライアントのリクエストに応答し、実行中のアプリコンテナが破棄または再起動されても、重要なデータが失われることはありません。
たとえば、名簿アプリケーションを実行していて、アプリが名簿から連絡先を追加、削除、変更する場合、名簿データストアは外部データベースまたは他のデータストアである必要があり、コンテナメモリに保持されるデータは次のとおりです。本質的に短期的であり、情報を重大に失うことなく使い捨て可能です。 セッションなどのユーザー訪問を超えて保持されるデータも、Redisなどの外部データストアに移動する必要があります。 可能な限り、状態をアプリからマネージドデータベースやキャッシュなどのサービスにオフロードする必要があります。
永続的なデータストアを必要とするステートフルアプリケーション(複製されたMySQLデータベースなど)の場合、Kubernetesには、永続的なブロックストレージボリュームをコンテナーとポッドにアタッチするための機能が組み込まれています。 ポッドが状態を維持し、再起動後に同じ永続ボリュームにアクセスできるようにするには、StatefulSetワークロードを使用する必要があります。 StatefulSetsは、データベースやその他の長時間実行されるデータストアをKubernetesにデプロイするのに理想的です。
ステートレスコンテナは、最大限の移植性と利用可能なクラウドリソースのフル活用を可能にし、Kubernetesスケジューラがアプリをすばやくスケールアップおよびスケールダウンし、リソースが利用可能な場所であればどこでもポッドを起動できるようにします。 StatefulSetワークロードによって提供される安定性と順序の保証が必要ない場合は、Deploymentワークロードを使用してアプリケーションを管理およびスケーリングする必要があります。
ステートレスなクラウドネイティブマイクロサービスの設計とアーキテクチャの詳細については、Kubernetesホワイトペーパーをご覧ください。
ヘルスチェックを実装する
Kubernetesモデルでは、クラスターコントロールプレーンを使用して、壊れたアプリケーションやサービスを修復できます。 これは、アプリケーションポッドの状態をチェックし、異常なコンテナまたは応答しないコンテナを再起動または再スケジュールすることによって行われます。 デフォルトでは、アプリケーションコンテナが実行されている場合、Kubernetesはポッドを「正常」と見なします。 多くの場合、これは実行中のアプリケーションの状態を示す信頼できる指標です。 ただし、アプリケーションがデッドロックして意味のある作業を実行していない場合、アプリプロセスとコンテナは無期限に実行され続け、デフォルトでは、Kubernetesは停止したコンテナを存続させます。
アプリケーションの正常性をKubernetesコントロールプレーンに適切に伝達するには、アプリケーションが実行中であり、トラフィックを受信する準備ができていることを示すカスタムアプリケーションの正常性チェックを実装する必要があります。 最初のタイプのヘルスチェックは準備プローブと呼ばれ、アプリケーションがトラフィックを受信する準備ができたことをKubernetesに通知します。 2番目のタイプのチェックはライブネスプローブと呼ばれ、アプリケーションが正常で実行されていることをKubernetesに通知します。 Kubelet Nodeエージェントは、次の3つの異なる方法を使用して、実行中のポッドでこれらのプローブを実行できます。
- HTTP:Kubeletプローブはエンドポイント(
/health
など)に対してHTTP GET要求を実行し、応答ステータスが200〜399の場合に成功します - コンテナコマンド:Kubeletプローブは、実行中のコンテナ内でコマンドを実行します。 終了コードが0の場合、プローブは成功します。
- TCP:Kubeletプローブは、指定されたポートでコンテナーに接続しようとします。 TCP接続を確立できる場合、プローブは成功します。
実行中のアプリケーション、プログラミング言語、およびフレームワークに応じて、適切な方法を選択する必要があります。 レディネスプローブとライブネスプローブはどちらも同じプローブ方法を使用して同じチェックを実行できますが、レディネスプローブを含めると、プローブが成功し始めるまでポッドがトラフィックを受信しないようになります。
アプリケーションをコンテナ化してKubernetesで実行することを計画および検討するときは、特定のアプリケーションの「正常」と「準備完了」の意味を定義するための計画時間と、エンドポイントやチェックコマンドの実装とテストのための開発時間を割り当てる必要があります。
上記で参照したFlaskの例の最小ヘルスエンドポイントは次のとおりです。
env_config.py
. . . @app.route('/') def print_config(): output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER) return output @app.route('/health') def return_ok(): return 'Ok!', 200
このパスをチェックするKubernetesライブネスプローブは、次のようになります。
pod_spec.yaml
. . . livenessProbe: httpGet: path: /health port: 80 initialDelaySeconds: 5 periodSeconds: 2
initialDelaySeconds
フィールドは、Kubernetes(具体的にはノードKubelet)が5秒待機した後に/health
エンドポイントをプローブする必要があることを指定し、periodSeconds
はKubeletに/health
をプローブするように指示します2秒ごと。
活性と準備のプローブの詳細については、Kubernetesのドキュメントを参照してください。
ロギングとモニタリングのための機器コード
コンテナ化されたアプリケーションをKubernetesのような環境で実行する場合、アプリケーションのパフォーマンスを監視およびデバッグするために、テレメトリとログデータを公開することが重要です。 応答時間やエラー率などのパフォーマンスメトリックを公開する機能を組み込むと、アプリケーションを監視し、アプリケーションが異常な場合にアラートを出すのに役立ちます。
サービスの監視に使用できるツールの1つは、Cloud Native Computing Foundation(CNCF)によってホストされるオープンソースのシステム監視およびアラートツールキットであるPrometheusです。 Prometheusは、イベントとその期間をカウントするためのさまざまなメトリックタイプを使用してコードをインストルメント化するためのいくつかのクライアントライブラリを提供します。 たとえば、Flask Pythonフレームワークを使用している場合は、Prometheus Pythonクライアントを使用してデコレータをリクエスト処理関数に追加し、リクエストの処理に費やされた時間を追跡できます。 これらのメトリックは、/metrics
などのHTTPエンドポイントでPrometheusによってスクレイプできます。
アプリのインストルメンテーションを設計するときに使用するのに役立つ方法は、REDメソッドです。 これは、次の3つの主要な要求メトリックで構成されています。
- レート:アプリケーションが受信したリクエストの数
- エラー:アプリケーションによって発行されたエラーの数
- 期間:アプリケーションが応答を提供するのにかかる時間
この最小限のメトリックセットは、アプリケーションのパフォーマンスが低下したときにアラートを生成するのに十分なデータを提供するはずです。 上記のヘルスチェックとともにこのインストルメンテーションを実装すると、障害のあるアプリケーションをすばやく検出して回復できます。
アプリケーションを監視するときに測定する信号の詳細については、Googleサイト信頼性エンジニアリングの本の分散システムの監視を参照してください。
テレメトリデータを公開するための機能について考え、設計することに加えて、アプリケーションが分散クラスターベースの環境にログインする方法も決定する必要があります。 理想的には、ローカルログファイルとログディレクトリへのハードコードされた構成参照を削除し、代わりにstdout
とstderr
に直接ログインする必要があります。 ログは、連続したイベントストリーム、または時間順に並べられたイベントのシーケンスとして扱う必要があります。 この出力ストリームは、アプリケーションを包むコンテナーによってキャプチャされ、そこからEFK(Elasticsearch、Fluentd、Kibana)スタックなどのロギングレイヤーに転送できます。 Kubernetesは、ロギングアーキテクチャの設計に多くの柔軟性を提供します。これについては、以下で詳しく説明します。
管理ロジックをAPIに組み込む
アプリケーションがコンテナ化され、Kubernetesなどのクラスタ環境で稼働すると、アプリを実行しているコンテナへのシェルアクセスができなくなる可能性があります。 適切なヘルスチェック、ログ記録、および監視を実装している場合は、本番環境の問題をすばやく警告してデバッグできますが、コンテナーの再起動と再デプロイ以外のアクションを実行するのは難しい場合があります。 キューのフラッシュやキャッシュのクリアなどの運用とメンテナンスをすばやく修正するには、適切なAPIエンドポイントを実装して、実行中のコンテナでコンテナやdocker exec
を再起動せずにこれらの操作を実行できるようにする必要があります。 コンテナは不変オブジェクトとして扱う必要があり、本番環境では手動管理を避ける必要があります。 キャッシュのクリアなど、1回限りの管理タスクを実行する必要がある場合は、APIを介してこの機能を公開する必要があります。
概要
これらのセクションでは、アプリケーションをコンテナ化してKubernetesに移動する前に、実装する可能性のあるアプリケーションレベルの変更について説明しました。 クラウドネイティブアプリの構築に関するより詳細なウォークスルーについては、Kubernetes用アプリケーションのアーキテクチャを参照してください。
ここで、アプリのコンテナーを作成するときに留意すべきいくつかの考慮事項について説明します。
アプリケーションのコンテナ化
クラウドベースの環境で移植性と可観測性を最大化するためのアプリロジックを実装したので、次はアプリをコンテナー内にパッケージ化します。 このガイドでは、Dockerコンテナーを使用しますが、本番環境のニーズに最適なコンテナー実装を使用する必要があります。
依存関係を明示的に宣言する
アプリケーションのDockerfileを作成する前に、最初のステップの1つは、アプリケーションを正しく実行するために必要なソフトウェアとオペレーティングシステムの依存関係を確認することです。 Dockerfileを使用すると、イメージにインストールされているすべてのソフトウェアを明示的にバージョン管理できます。この機能を利用するには、親イメージ、ソフトウェアライブラリ、およびプログラミング言語のバージョンを明示的に宣言する必要があります。
latest
タグとバージョン管理されていないパッケージは、シフトしてアプリケーションを壊す可能性があるため、できるだけ避けてください。 プライベートレジストリまたはパブリックレジストリのプライベートミラーを作成して、イメージのバージョン管理をより詳細に制御し、アップストリームの変更によって意図せずにイメージビルドが破損するのを防ぐことができます。
プライベートイメージレジストリの設定の詳細については、Dockerの公式ドキュメントおよび以下のレジストリセクションからレジストリサーバーのデプロイを参照してください。
画像サイズを小さく保つ
コンテナイメージをデプロイおよびプルする場合、大きなイメージは処理速度を大幅に低下させ、帯域幅のコストを増加させる可能性があります。 最小限のツールとアプリケーションファイルのセットをイメージにパッケージ化すると、いくつかの利点があります。
- 画像サイズを小さくする
- イメージビルドを高速化
- コンテナの開始ラグを減らす
- 画像転送時間を短縮します
- 攻撃対象領域を減らすことでセキュリティを向上させる
イメージを作成するときに考慮できるいくつかの手順:
ubuntu
のようなフル機能のOSの代わりに、alpine
のような最小限のベースOSイメージを使用するか、scratch
からビルドします。- ソフトウェアのインストール後、不要なファイルやアーティファクトをクリーンアップします
- 個別の「ビルド」コンテナと「ランタイム」コンテナを使用して、本番アプリケーションコンテナを小さく保ちます
- 大きなディレクトリにコピーするときに、不要なビルドアーティファクトとファイルを無視します
多くの実例を含むDockerコンテナーの最適化に関する完全なガイドについては、Kubernetes用に最適化されたコンテナーの構築を参照してください。
構成を挿入
Dockerは、アプリの実行環境に構成データを挿入するためのいくつかの便利な機能を提供します。
これを行うための1つのオプションは、ENV
ステートメントを使用してDockerfileで環境変数とその値を指定することです。これにより、構成データがイメージに組み込まれます。
Dockerfile
... ENV MYSQL_USER=my_db_user ...
その後、アプリは実行環境からこれらの値を解析し、設定を適切に構成できます。
docker run
および-e
フラグを使用してコンテナーを開始するときに、環境変数をパラメーターとして渡すこともできます。
docker run -e MYSQL_USER='my_db_user' IMAGE[:TAG]
最後に、環境変数とその値のリストを含むenvファイルを使用できます。 これを行うには、ファイルを作成し、--env-file
パラメーターを使用して次のコマンドに渡します。
docker run --env-file var_list IMAGE[:TAG]
Kubernetesなどのクラスターマネージャーを使用してアプリケーションを実行するようにアプリケーションを最新化する場合は、イメージから構成をさらに外部化し、Kubernetesの組み込みのConfigMapとSecretsを使用して構成を管理する必要があります]オブジェクト。 これにより、構成をイメージマニフェストから分離できるため、アプリケーションとは別に構成を管理およびバージョン管理できます。 ConfigMapsとSecretsを使用して構成を外部化する方法については、以下のConfigMapsとSecretsセクションを参照してください。
レジストリに画像を公開する
アプリケーションイメージをビルドしたら、Kubernetesで利用できるようにするには、それらをコンテナイメージレジストリにアップロードする必要があります。 Docker Hub などのパブリックレジストリは、Node.jsやnginxなどの人気のあるオープンソースプロジェクトの最新のDockerイメージをホストします。 プライベートレジストリを使用すると、内部アプリケーションイメージを公開して、開発者やインフラストラクチャで利用できるようになりますが、より広い世界では利用できません。
既存のインフラストラクチャを使用してプライベートレジストリを展開できます(例: クラウドオブジェクトストレージの上に)、またはオプションでQuay.ioや有料のDockerHubプランなどのいくつかのDockerレジストリ製品の1つを使用します。 これらのレジストリは、GitHubなどのホストされたバージョン管理サービスと統合できるため、Dockerfileが更新およびプッシュされると、レジストリサービスは自動的に新しいDockerfileをプルし、コンテナイメージを構築し、更新されたイメージをサービスで利用できるようにします。
コンテナイメージの構築とテスト、およびそれらのタグ付けと公開をより細かく制御するために、継続的インテグレーション(CI)パイプラインを実装できます。
ビルドパイプラインを実装する
イメージを手動で構築、テスト、公開、本番環境にデプロイすると、エラーが発生しやすくなり、拡張性が低下します。 ビルドを管理し、最新のコード変更を含むコンテナーをイメージレジストリに継続的に公開するには、ビルドパイプラインを使用する必要があります。
ほとんどのビルドパイプラインは、次のコア機能を実行します。
- ソースコードリポジトリで変更を確認する
- 変更されたコードで煙と単体テストを実行する
- 変更されたコードを含むコンテナイメージをビルドする
- ビルドされたコンテナイメージを使用して、さらに統合テストを実行します
- テストに合格したら、画像にタグを付けてレジストリに公開します
- (オプション、継続的デプロイのセットアップで)Kubernetesデプロイメントを更新し、イメージをステージング/本番クラスターにロールアウトします
GitHubなどの一般的なバージョン管理サービスやDockerHubなどのイメージレジストリとの統合が組み込まれている、多くの有料の継続的インテグレーション製品があります。 これらの製品の代替品は、 Jenkins です。これは、上記のすべての機能を実行するように構成できる、無料のオープンソースビルド自動化サーバーです。 Jenkins継続的インテグレーションパイプラインを設定する方法については、 Ubuntu20.04でJenkinsに継続的インテグレーションパイプラインを設定する方法を参照してください。
コンテナのログ記録と監視を実装する
コンテナを操作するときは、実行中および停止中のすべてのコンテナのログを管理および保存するために使用するロギングインフラストラクチャについて考えることが重要です。 ロギングに使用できるコンテナレベルのパターンは複数あり、Kubernetesレベルのパターンも複数あります。
Kubernetesでは、デフォルトでコンテナはjson-file
Docker ログドライバを使用します。これにより、stdoutストリームとstderrストリームがキャプチャされ、コンテナが実行されているノードのJSONファイルに書き込まれます。 stderrおよびstdoutに直接ログを記録するだけでは、アプリケーションコンテナーに十分でない場合があります。また、アプリコンテナーをKubernetesポッドのロギングsidecarコンテナーとペアリングすることをお勧めします。 このサイドカーコンテナは、ファイルシステム、ローカルソケット、またはsystemdジャーナルからログを取得できるため、stderrおよびstdoutストリームを使用するよりも少し柔軟性があります。 このコンテナは、いくつかの処理を実行してから、強化されたログをstdout / stderrにストリーミングしたり、ロギングバックエンドに直接ストリーミングしたりすることもできます。 Kubernetesのロギングパターンの詳細については、このチュートリアルのKubernetesのロギングとモニタリングセクションを参照してください。
アプリケーションがコンテナレベルでどのようにログを記録するかは、その複雑さに依存します。 単一目的のマイクロサービスの場合、stdout / stderrに直接ログインし、Kubernetesにこれらのストリームを取得させることをお勧めします。これは、kubectl logs
コマンドを利用して、Kubernetesがデプロイされたコンテナからログストリームにアクセスできるためです。
ロギングと同様に、コンテナーおよびクラスターベースの環境での監視について考え始める必要があります。 Dockerは、ホスト上でコンテナーを実行するためのCPUやメモリ使用量などの標準メトリックを取得するための便利なdocker stats
コマンドを提供し、リモートRESTAPIを介してさらに多くのメトリックを公開します。 さらに、オープンソースツール cAdvisor (デフォルトでKubernetesノードにインストールされます)は、履歴メトリック収集、メトリックデータのエクスポート、データを並べ替えるための便利なWebUIなどのより高度な機能を提供します。
ただし、マルチノード、マルチコンテナーの本番環境では、PrometheusやGrafanaなどのより複雑なメトリックスタックが、コンテナーのパフォーマンスデータの整理と監視に役立つ場合があります。
概要
これらのセクションでは、コンテナを構築するためのいくつかのベストプラクティス、CI / CDパイプラインとイメージレジストリを設定するためのいくつかのベストプラクティス、およびコンテナへの可観測性を高めるためのいくつかの考慮事項について簡単に説明しました。
- Kubernetes用に最適化されたコンテナの最適化の詳細については、Kubernetes用に最適化されたコンテナの構築を参照してください。
- CI / CDの詳細については、継続的インテグレーション、デリバリー、およびデプロイメントの概要および CI/CDのベストプラクティスの概要を参照してください。
次のセクションでは、コンテナ化されたアプリをクラスタで実行およびスケーリングできるKubernetesの機能について説明します。
Kubernetesへのデプロイ
この時点で、アプリをコンテナー化し、クラウドネイティブ環境での移植性と可観測性を最大化するロジックを実装しました。 次に、Kubernetesクラスターでアプリを管理およびスケーリングするためのインターフェースを提供するKubernetes機能について説明します。
デプロイメントおよびポッド構成ファイルの書き込み
アプリケーションをコンテナ化してレジストリに公開すると、ポッドワークロードを使用してアプリケーションをKubernetesクラスタにデプロイできるようになります。 Kubernetesクラスターでデプロイ可能な最小のユニットは、コンテナーではなくポッドです。 ポッドは通常、アプリケーションコンテナー(コンテナー化されたFlask Webアプリなど)、またはアプリコンテナーと、監視やログ記録などのヘルパー機能を実行する「サイドカー」コンテナーで構成されます。 ポッド内のコンテナは、ストレージリソース、ネットワーク名前空間、およびポートスペースを共有します。 localhost
を使用して相互に通信し、マウントされたボリュームを使用してデータを共有できます。 さらに、ポッドワークロードを使用すると、メインのアプリコンテナーの実行を開始する前に、セットアップスクリプトまたはユーティリティを実行する InitContainersを定義できます。
ポッドは通常、デプロイメントを使用してロールアウトされます。デプロイメントは、特定の目的の状態を宣言するYAMLファイルによって定義されたコントローラーです。 たとえば、アプリケーションの状態は、Flask Webアプリコンテナの3つのレプリカを実行し、ポート8080
を公開している可能性があります。 作成されると、コントロールプレーンは、必要に応じてノードにコンテナーをスケジュールすることにより、クラスターの実際の状態を展開で宣言された目的の状態に徐々に一致させます。 クラスタで実行されているアプリケーションレプリカの数を3から5まで増やすには、展開構成ファイルのreplicas
フィールドを更新してから、新しい構成ファイルをkubectl apply
更新します。 これらの構成ファイルを使用すると、既存のソース管理サービスと統合を使用して、スケーリングと展開の操作をすべて追跡およびバージョン管理できます。
これがFlaskアプリのサンプルKubernetesDeployment構成ファイルです。
フラスコ展開.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: flask-app labels: app: flask-app spec: replicas: 3 selector: matchLabels: app: flask-app template: metadata: labels: app: flask-app spec: containers: - name: flask image: sammy/flask_app:1.0 ports: - containerPort: 8080
このデプロイメントは、ポート8080
を開いた状態でsammy/flask_app
イメージ(バージョン1.0
)を使用してflask
というコンテナーを実行する3つのポッドを起動します。 展開はflask-app
と呼ばれます。
Kubernetes Pods and Deploymentsの詳細については、Kubernetesの公式ドキュメントのPodsおよびDeploymentsセクションを参照してください。
ポッドストレージを構成する
Kubernetesは、ボリューム、永続ボリューム(PV)、永続ボリュームクレーム(PVC)を使用してポッドストレージを管理します。 ボリュームは、ポッドストレージを管理するために使用されるKubernetesの抽象化であり、ほとんどのクラウドプロバイダーブロックストレージオファリングと、実行中のポッドをホストするノード上のローカルストレージをサポートします。 サポートされているボリュームタイプの完全なリストを確認するには、Kubernetesドキュメントを参照してください。
たとえば、ポッドにデータを共有する必要のある2つのNGINXコンテナが含まれている場合(たとえば、nginx
と呼ばれる最初のコンテナはWebページを提供し、nginx-sync
と呼ばれる2番目のコンテナは外部からページをフェッチします) nginx
コンテナによって提供されるページの場所と更新)、ポッドの仕様は次のようになります(ここでは emptyDir ボリュームタイプを使用します):
pod_volume.yaml
apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx volumeMounts: - name: nginx-web mountPath: /usr/share/nginx/html - name: nginx-sync image: nginx-sync volumeMounts: - name: nginx-web mountPath: /web-data volumes: - name: nginx-web emptyDir: {}
コンテナごとにvolumeMount
を使用します。これは、nginx
コンテナの/usr/share/nginx/html
にあるWebページファイルを含むnginx-web
ボリュームをマウントすることを示します。 nginx-sync
コンテナの/web-data
にあります。 タイプemptyDir
のnginx-web
と呼ばれるvolume
も定義します。
同様に、volume
タイプをemptyDir
から関連するクラウドストレージボリュームタイプに変更することで、クラウドブロックストレージ製品を使用してポッドストレージを構成できます。
ボリュームのライフサイクルはポッドのライフサイクルに関連付けられていますが、コンテナのライフサイクルにはではありません。 ポッド内のコンテナが停止した場合、ボリュームは存続し、新しく起動されたコンテナは同じボリュームをマウントしてそのデータにアクセスできるようになります。 ポッドが再起動または停止すると、ボリュームも再起動します。ただし、ボリュームがクラウドブロックストレージで構成されている場合は、将来のポッドから引き続きアクセスできるデータでマウントが解除されます。
ポッドの再起動と更新の間でデータを保持するには、PersistentVolume(PV)オブジェクトとPersistentVolumeClaim(PVC)オブジェクトを使用する必要があります。
PersistentVolumesは、クラウドブロックストレージボリュームやNFSストレージなどの永続ストレージの一部を表す抽象化です。 これらは、開発者がストレージの一部を要求するPersistentVolumeClaimsとは別に作成されます。 ポッド構成では、開発者はPVCを使用して永続ストレージをリクエストします。これはKubernetesが利用可能なPVボリュームと一致します(クラウドブロックストレージを使用している場合、KubernetesはPersistentVolumeClaimsの作成時にPersistentVolumeを動的に作成できます)。
多くのデータベースの場合のように、アプリケーションでレプリカごとに1つの永続ボリュームが必要な場合は、デプロイメントを使用せずに、安定したネットワークID、安定した永続ストレージ、および順序の保証を必要とするアプリ用に設計されたStatefulSetコントローラーを使用する必要があります。 デプロイメントはステートレスアプリケーションに使用する必要があります。デプロイメント構成で使用するPersistentVolumeClaimを定義すると、そのPVCはデプロイメントのすべてのレプリカで共有されます。
StatefulSetコントローラーの詳細については、Kubernetesドキュメントを参照してください。 PersistentVolumesとPersistentVolumeクレームの詳細については、Kubernetesストレージドキュメントを参照してください。
Kubernetesを使用した構成データの挿入
Dockerと同様に、Kubernetesには、ポッド構成ファイルで環境変数を設定するためのenv
フィールドとenvFrom
フィールドがあります。 実行中のポッドのHOSTNAME
環境変数をmy_hostname
に設定するポッド構成ファイルのサンプルスニペットを次に示します。
sample_pod.yaml
... spec: containers: - name: nginx image: nginx:1.21.6 ports: - containerPort: 80 env: - name: HOSTNAME value: my_hostname ...
これにより、構成をDockerfilesからPodおよびDeployment構成ファイルに移動できます。 Dockerfileから構成をさらに外部化する主な利点は、アプリケーションコンテナ定義とは別に、これらのKubernetesワークロード構成を(たとえば、HOSTNAME
値をmy_hostname_2
に変更することで)変更できることです。 ポッド構成ファイルを変更すると、新しい環境を使用してポッドを再デプロイできますが、基になるコンテナーイメージ(Dockerfileを介して定義)を再構築してテストし、リポジトリにプッシュする必要はありません。 これらのポッドとデプロイメントの構成をDockerfilesとは別にバージョン管理することもできます。これにより、重大な変更をすばやく検出し、構成の問題をアプリケーションのバグからさらに分離できます。
Kubernetesは、構成データをさらに外部化および管理するための別の構成要素であるConfigMapsとSecretsを提供します。
ConfigMapsとシークレット
ConfigMapsを使用すると、構成データをオブジェクトとして保存し、ポッドおよびデプロイメント構成ファイルで参照できるため、構成データのハードコーディングを回避して、ポッドおよびデプロイメント全体で再利用できます。
上記のポッド構成を使用した例を次に示します。 最初にHOSTNAME
環境変数をConfigMapとして保存してから、ポッド構成で参照します。
kubectl create configmap hostname --from-literal=HOSTNAME=my_host_name
ポッド構成ファイルから参照するには、valueFrom
およびconfigMapKeyRef
構成を使用します。
sample_pod_configmap.yaml
... spec: containers: - name: nginx image: nginx:1.21.6 ports: - containerPort: 80 env: - name: HOSTNAME valueFrom: configMapKeyRef: name: hostname key: HOSTNAME ...
そのため、HOSTNAME
環境変数の値は、構成ファイルから完全に外部化されています。 次に、これらの変数を参照しているすべてのデプロイメントとポッドでこれらの変数を更新し、ポッドを再起動して変更を有効にします。
アプリケーションで構成ファイルを使用する場合、ConfigMapsを使用すると、これらのファイルをConfigMapオブジェクトとして(--from-file
フラグを使用して)保存し、構成ファイルとしてコンテナーにマウントできます。
シークレットはConfigMapsと同じ重要な機能を提供しますが、値はbase64でエンコードされているため、データベースのクレデンシャルなどの機密データに使用する必要があります。
ConfigMapsとSecretsの詳細については、Kubernetesドキュメントを参照してください。
サービスの作成
アプリケーションをKubernetesで起動して実行すると、すべてのポッドに(内部)IPアドレスが割り当てられ、コンテナーによって共有されます。 これらのポッドの1つが削除または停止した場合、新しく開始されたポッドには異なるIPアドレスが割り当てられます。
内部および/または外部クライアントに機能を公開する長時間実行サービスの場合、同じ機能(またはデプロイメント)を実行するポッドのセットに、コンテナー間でリクエストの負荷を分散する安定したIPアドレスを付与することをお勧めします。 これは、Kubernetesサービスを使用して実行できます。
Kubernetesサービスには、サービス構成ファイルのtype
フィールドで指定された4つのタイプがあります。
ClusterIP
:これはデフォルトのタイプであり、クラスター内のどこからでもアクセスできる安定した内部IPをサービスに付与します。NodePort
:これにより、静的ポート(デフォルトでは30000〜32767)の各ノードでサービスが公開されます。 リクエストがノードのIPアドレスとサービスのNodePort
でノードにヒットすると、リクエストは負荷分散され、サービスのアプリケーションコンテナにルーティングされます。LoadBalancer
:これにより、クラウドプロバイダーの負荷分散製品を使用してロードバランサーが作成され、外部リクエストのルーティング先となるサービスのNodePort
とClusterIP
が構成されます。ExternalName
:このサービスタイプを使用すると、KubernetesサービスをDNSレコードにマッピングできます。 KubernetesDNSを使用してポッドから外部サービスにアクセスするために使用できます。
クラスターで実行されているデプロイメントごとにタイプLoadBalancer
のサービスを作成すると、サービスごとに新しいクラウドロードバランサーが作成され、コストがかかる可能性があることに注意してください。 単一のロードバランサーを使用して複数のサービスへの外部リクエストのルーティングを管理するには、IngressControllerを使用できます。 Ingress Controllerはこの記事の範囲を超えていますが、それらの詳細については、Kubernetesドキュメントを参照してください。 人気のあるイングレスコントローラーはNGINXイングレスコントローラーです。
このガイドのポッドとデプロイメントセクションで使用されているFlaskの例のサービス構成ファイルは次のとおりです。
フラスコ_app_svc.yaml
apiVersion: v1 kind: Service metadata: name: flask-svc spec: ports: - port: 80 targetPort: 8080 selector: app: flask-app type: LoadBalancer
ここでは、このflask-svc
サービスを使用してflask-app
デプロイメントを公開することを選択します。 ロードバランサーポート80
から公開されたコンテナポート8080
にトラフィックをルーティングするクラウドロードバランサーを作成します。
Kubernetesサービスの詳細については、KubernetesドキュメントのServicesセクションを参照してください。
ロギングとモニタリング
kubectl logs
およびdocker logs
を使用して個々のコンテナーおよびポッドのログを解析することは、実行中のアプリケーションの数が増えるにつれて面倒になる可能性があります。 アプリケーションまたはクラスターの問題のデバッグを支援するには、集中ログを実装する必要があります。 大まかに言えば、これはポッドログファイルとストリームを処理し、メタデータでそれらを強化し、ログをElasticsearchのようなバックエンドに転送するすべてのワーカーノードで実行されるエージェントで構成されます。 そこから、 Kibana などの視覚化ツールを使用して、ログデータを視覚化、フィルタリング、および整理できます。
コンテナレベルのログセクションでは、コンテナ内のアプリケーションをstdout/stderrストリームにログに記録するというKubernetesの推奨アプローチについて説明しました。 また、アプリケーションからロギングする際の柔軟性を高めることができるサイドカーコンテナのロギングについても簡単に説明しました。 ローカルログデータをキャプチャしてロギングバックエンドに直接転送するロギングエージェントをポッドで直接実行することもできます。 それぞれのアプローチには長所と短所があり、リソース使用率のトレードオフがあります(たとえば、各ポッド内でロギングエージェントコンテナを実行すると、リソースを大量に消費し、ロギングバックエンドをすぐに圧倒する可能性があります)。 さまざまなロギングアーキテクチャとそのトレードオフの詳細については、Kubernetesドキュメントを参照してください。
標準のセットアップでは、各ノードはFilebeatやFluentdなどのロギングエージェントを実行し、Kubernetesによって作成されたコンテナログを取得します。 Kubernetesがノード上のコンテナのJSONログファイルを作成することを思い出してください(ほとんどのインストールでは、これらは/var/lib/docker/containers/
にあります)。 これらは、logrotateなどのツールを使用して回転させる必要があります。 ノードロギングエージェントは、 DaemonSet Controller として実行する必要があります。これは、すべてのノードがDaemonSetポッドのコピーを実行することを保証するKubernetesワークロードの一種です。 この場合、ポッドにはログエージェントとその構成が含まれ、ログデーモンセットポッドにマウントされたファイルとディレクトリからのログを処理します。
kubectl logs
を使用してコンテナの問題をデバッグする際のボトルネックと同様に、最終的には、kubectl top
とKubernetesダッシュボードを使用してクラスタのポッドリソースの使用状況を監視するよりも堅牢なオプションを検討する必要があります。 クラスターおよびアプリケーションレベルの監視は、 Prometheus 監視システムと時系列データベース、およびGrafanaメトリックダッシュボードを使用して設定できます。 Prometheusは、「プル」モデルを使用して動作します。このモデルは、HTTPエンドポイント(ノード上の/metrics/cadvisor
や/metrics
アプリケーションのRESTAPIエンドポイントなど)を定期的にスクレイプしてメトリックデータを取得し、処理して保存します。 このデータは、Grafanaダッシュボードを使用して分析および視覚化できます。 PrometheusとGrafanaは、他のデプロイやサービスと同じように、Kubernetesクラスターで起動できます。
復元力を高めるために、ロギングとモニタリングのインフラストラクチャを別のKubernetesクラスタで実行するか、外部のロギングとメトリクスサービスを使用することをお勧めします。
結論
アプリケーションをKubernetesクラスターで効率的に実行できるように移行および最新化するには、多くの場合、ソフトウェアとインフラストラクチャの変更の計画と設計が簡単ではありません。 これらの変更を実装すると、サービス所有者はアプリの新しいバージョンを継続的に展開し、必要に応じて、最小限の手動介入で簡単に拡張できます。 アプリからの設定の外部化、適切なロギングとメトリクスの公開の設定、ヘルスチェックの設定などの手順により、Kubernetesが設計されたクラウドネイティブパラダイムを十分に活用できます。 ポータブルコンテナを構築し、DeploymentsやServicesなどのKubernetesオブジェクトを使用してそれらを管理することで、利用可能なコンピューティングインフラストラクチャと開発リソースを完全に使用できます。