Kubernetesのアプリケーションの近代化

前書き

最新のステートレスアプリケーションは、Dockerなどのソフトウェアコンテナーで実行されるように構築および設計され、Kubernetesなどのコンテナークラスターによって管理されます。 これらは、Cloud NativeおよびTwelve Factorの原則とパターンを使用して開発され、手動による介入を最小限に抑え、移植性と冗長性を最大限に高めます。 仮想マシンまたはベアメタルベースのアプリケーションをコンテナーに移行し(「コンテナー化」と呼ばれる)、クラスター内に展開すると、多くの場合、これらのアプリの構築、パッケージ化、配信の方法が大幅に変更されます。

この概念ガイドでは、Architecting Applications for Kubernetesに基づいて、Kubernetesクラスターでアプリケーションを実行および管理することを最終目標として、アプリケーションを最新化するための高レベルの手順について説明します。 Kubernetesでデータベースなどのステートフルアプリケーションを実行できますが、このガイドでは、永続データを外部データストアにオフロードして、ステートレスアプリケーションの移行と近代化に焦点を当てています。 Kubernetesは、ステートレスアプリケーションを効率的に管理およびスケーリングするための高度な機能を提供します。Kubernetesでスケーラブルで観測可能なポータブルアプリケーションを実行するために必要なアプリケーションとインフラストラクチャの変更について検討します。

移行のためのアプリケーションの準備

アプリケーションをコンテナ化するか、Kubernetesポッドとデプロイメントの構成ファイルを作成する前に、アプリケーションレベルの変更を実装して、Kubernetesでのアプリの移植性と可観測性を最大化する必要があります。 Kubernetesは高度に自動化された環境であり、失敗したアプリケーションコンテナーを自動的に展開および再起動できるため、適切なアプリケーションロジックを構築して、コンテナーオーケストレーターと通信し、必要に応じてアプリを自動的にスケーリングできるようにすることが重要です。

構成データの抽出

実装する最初のアプリケーションレベルの変更の1つは、アプリケーションコードからアプリケーション構成を抽出することです。 構成は、サービスエンドポイント、データベースアドレス、資格情報、さまざまなパラメーターやオプションなど、展開や環境によって異なる情報で構成されます。 たとえば、stagingproductionなどの2つの環境があり、それぞれに個別のデータベースが含まれている場合、アプリケーションでは、データベースエンドポイントと資格情報をコードで明示的に宣言せず、個別に保存する必要があります実行環境の変数、ローカルファイル、または外部のKey-Valueストアのいずれかとしての場所。ここから、値がアプリに読み込まれます。

これらのパラメーターをコードにハードコーディングすると、この構成データは多くの場合機密情報で構成され、バージョン管理システムにチェックインするため、セキュリティ上のリスクが生じます。 また、アプリケーションの複数のバージョンを維持する必要があるため、それぞれが同じコアアプリケーションロジックで構成されていますが、構成がわずかに異なるため、複雑さが増します。 アプリケーションとその構成データが大きくなると、構成をアプリコードにハードコーディングするのがすぐに難しくなります。

アプリケーションコードから構成値を抽出し、代わりに実行中の環境またはローカルファイルからそれらを取り込むことにより、アプリは、付随する構成データを提供する限り、あらゆる環境に展開できる汎用のポータブルパッケージになります。 DockerのようなコンテナーソフトウェアとKubernetesのようなクラスターソフトウェアは、このパラダイムに基づいて設計されており、構成データを管理し、それをアプリケーションコンテナーに注入する機能を組み込みます。 これらの機能については、ContainerizingおよびKubernetesセクションで詳しく説明します。

これは、単純なPythonFlaskアプリのコードから2つの構成値DB_HOSTDB_USERを外部化する方法を示す簡単な例です。 アプリの実行環境でenv変数として使用できるようにします。この変数からアプリが読み取ります。

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

表示されるウェブページには、最初の例と同じテキストが含まれている必要がありますが、アプリの構成は、アプリケーションコードとは無関係に変更できるようになりました。 同様の方法を使用して、ローカルファイルから設定パラメーターを読み込むことができます。

次のセクションでは、アプリケーションの状態をコンテナ外に移動することについて説明します。

アプリケーション状態のオフロード

クラウドネイティブアプリケーションはコンテナで実行され、KubernetesやDocker Swarmなどのクラスターソフトウェアによって動的に調整されます。 特定のアプリまたはサービスは複数のレプリカ間で負荷分散でき、個々のアプリコンテナは、クライアントへのサービスの中断を最小限に抑えるか、まったく中断せずに失敗する必要があります。 この水平で冗長なスケーリングを可能にするには、アプリケーションをステートレスな方法で設計する必要があります。 つまり、永続的なクライアントおよびアプリケーションデータをローカルに保存せずにクライアントリクエストに応答し、実行中のアプリコンテナーが破棄または再起動された場合でも、重要なデータは失われません。

たとえば、アドレス帳アプリケーションを実行していて、アプリがアドレス帳の連絡先を追加、削除、変更する場合、アドレス帳のデータストアは外部データベースまたは他のデータストアであり、コンテナメモリに保持されるデータは本質的に短期的であり、情報の重大な損失なしに使い捨て可能です。 セッションのようなユーザーのアクセスを超えて保持されるデータも、Redisなどの外部データストアに移動する必要があります。 可能な限り、アプリの状態をマネージデータベースやキャッシュなどのサービスにオフロードする必要があります。

永続的なデータストア(複製されたMySQLデータベースなど)を必要とするステートフルアプリケーションの場合、Kubernetesは永続的なブロックストレージボリュームをコンテナーとポッドにアタッチする機能を組み込みます。 再起動後にポッドが状態を維持し、同じ永続ボリュームにアクセスできるようにするには、StatefulSetワークロードを使用する必要があります。 StatefulSetsは、データベースおよびその他の長時間実行されるデータストアをKubernetesに展開するのに最適です。

ステートレスコンテナにより、最大限の移植性と利用可能なクラウドリソースの完全な使用が可能になり、Kubernetesスケジューラーは、リソースが利用可能な場合はいつでもアプリを迅速にスケールアップおよびダウンし、ポッドを起動できます。 StatefulSetワークロードによって提供される安定性と順序の保証が必要ない場合は、Deploymentワークロードを使用して、アプリケーションとアプリケーションを管理およびスケーリングする必要があります。

ステートレスなクラウドネイティブマイクロサービスの設計とアーキテクチャの詳細については、Kubernetes White Paperを参照してください。

ヘルスチェックを実装する

Kubernetesモデルでは、クラスターコントロールプレーンを使用して、破損したアプリケーションまたはサービスを修復できます。 これを行うには、アプリケーションポッドのヘルスをチェックし、異常なコンテナまたは応答しないコンテナを再起動または再スケジュールします。 デフォルトでは、アプリケーションコンテナーが実行されている場合、Kubernetesはポッドを「正常」と見なします。多くの場合、これは実行中のアプリケーションの健全性の信頼できるインジケーターです。 ただし、アプリケーションがデッドロックされ、意味のある作業を実行していない場合、アプリプロセスとコンテナーは無期限に実行され続け、デフォルトではKubernetesはストールしたコンテナーを存続させます。

アプリケーションの正常性をKubernetesコントロールプレーンに適切に伝えるには、アプリケーションが実行中でトラフィックを受信する準備が整ったことを示すカスタムアプリケーション正常性チェックを実装する必要があります。 最初のタイプのヘルスチェックはreadiness probeと呼ばれ、アプリケーションがトラフィックを受信する準備ができたことをKubernetesに通知します。 2番目のタイプのチェックはliveness probeと呼ばれ、アプリケーションが正常で実行されていることを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に2秒ごとに/healthをプローブするように指示します。

活性および準備プローブの詳細については、Kubernetes documentationを参照してください。

ロギングおよびモニタリング用の機器コード

Kubernetesなどの環境でコンテナ化されたアプリケーションを実行する場合、アプリケーションのパフォーマンスを監視およびデバッグするために、テレメトリおよびログデータを公開することが重要です。 応答時間やエラー率などのパフォーマンスメトリックを公開する機能を組み込むと、アプリケーションを監視し、アプリケーションが正常でない場合にアラートを出すのに役立ちます。

サービスの監視に使用できるツールの1つは、Cloud Native Computing Foundation(CNCF)によってホストされるオープンソースのシステム監視およびアラートツールキットであるPrometheusです。 Prometheusは、イベントとその継続時間をカウントするためにさまざまなメトリックタイプでコードを計測するためのいくつかのクライアントライブラリを提供します。 たとえば、Flask Pythonフレームワークを使用している場合は、PrometheusPython clientを使用してデコレータをリクエスト処理関数に追加し、リクエストの処理に費やされた時間を追跡できます。 これらのメトリックは、/metricsなどのHTTPエンドポイントでPrometheusによってスクレイピングできます。

アプリのインスツルメンテーションを設計するときに使用する便利な方法は、REDメソッドです。 次の3つの主要なリクエストメトリックで構成されます。

  • レート:アプリケーションが受信したリクエストの数

  • エラー:アプリケーションによって発行されたエラーの数

  • 期間:アプリケーションが応答を提供するのにかかる時間

この最小限のメトリックセットは、アプリケーションのパフォーマンスが低下したときにアラートを発するのに十分なデータを提供します。 上記のヘルスチェックとともにこのインスツルメンテーションを実装すると、障害のあるアプリケーションを迅速に検出して回復できます。

アプリケーションを監視するときに測定する信号の詳細については、Googleサイト信頼性エンジニアリングの本のMonitoring Distributed Systemsを参照してください。

テレメトリデータを公開するための機能について考えて設計することに加えて、アプリケーションが分散クラスターベースの環境にログインする方法を計画する必要もあります。 ローカルログファイルおよびログディレクトリへのハードコードされた構成参照を削除し、代わりにstdoutおよびstderrに直接ログすることが理想的です。 ログは連続したイベントストリーム、または時間順に並べられたイベントのシーケンスとして扱う必要があります。 この出力ストリームは、アプリケーションを包むコンテナによってキャプチャされ、そこからEFK(Elasticsearch、Fluentd、Kibana)スタックなどのログ層に転送できます。 Kubernetesは、ロギングアーキテクチャの設計に多くの柔軟性を提供します。これについては、以下で詳しく説明します。

管理ロジックをAPIに組み込む

Kubernetesなどのクラスター環境でアプリケーションをコンテナー化して実行すると、アプリケーションを実行しているコンテナーへのシェルアクセスができなくなる場合があります。 適切なヘルスチェック、ログ、監視を実装している場合は、本番の問題をすばやく警告し、デバッグできますが、コンテナの再起動と再デプロイ以外のアクションを実行することは困難です。 キューのフラッシュやキャッシュのクリアなどの運用およびメンテナンスの迅速な修正を行うには、適切なAPIエンドポイントを実装して、実行中のコンテナーでコンテナーまたはexecを再起動し、一連のコマンドを実行することなくこれらの操作を実行できるようにする必要があります。 コンテナは不変オブジェクトとして扱われるべきであり、本番環境では手動管理は避けるべきです。 キャッシュのクリアなど、1回限りの管理タスクを実行する必要がある場合は、APIを介してこの機能を公開する必要があります。

概要

これらのセクションでは、アプリケーションをコンテナ化してKubernetesに移動する前に実装するアプリケーションレベルの変更について説明しました。 クラウドネイティブアプリの構築に関するより詳細なウォークスルーについては、Architecting Applications for Kubernetesを参照してください。

アプリのコンテナを作成する際に留意すべきいくつかの考慮事項について説明します。

アプリケーションのコンテナ化

クラウドベースの環境で移植性と可観測性を最大化するアプリロジックを実装したので、次はコンテナ内にアプリをパッケージします。 このガイドでは、Dockerコンテナーを使用しますが、実稼働のニーズに最適なコンテナー実装を使用する必要があります。

依存関係を明示的に宣言する

アプリケーションのDockerfileを作成する前に、最初のステップの1つは、アプリケーションを正しく実行するために必要なソフトウェアとオペレーティングシステムの依存関係を把握することです。 Dockerfilesを使用すると、イメージにインストールされているすべてのソフトウェアを明示的にバージョン管理できます。親イメージ、ソフトウェアライブラリ、プログラミング言語のバージョンを明示的に宣言することで、この機能を利用する必要があります。

latestタグとバージョン管理されていないパッケージは、シフトしてアプリケーションを壊す可能性があるため、できるだけ避けてください。 プライベートレジストリまたはパブリックレジストリのプライベートミラーを作成して、イメージのバージョン管理をより細かく制御し、アップストリームの変更が意図せずにイメージビルドを壊さないようにすることができます。

プライベートイメージレジストリの設定の詳細については、Dockerの公式ドキュメントのDeploy a Registry Serverと以下のRegistriesセクションを参照してください。

画像サイズを小さく保つ

コンテナイメージを展開およびプルする場合、大きなイメージは処理速度を大幅に低下させ、帯域幅コストを増大させる可能性があります。 最小限のツールとアプリケーションファイルのセットをイメージにパッケージ化すると、いくつかの利点があります。

  • 画像サイズを縮小する

  • イメージのビルドを高速化

  • コンテナの開始遅延を減らす

  • 画像転送時間を短縮

  • 攻撃対象を減らすことでセキュリティを向上

画像を作成するときに考慮できるいくつかの手順:

  • ubuntuのようなフル機能のOSの代わりに、alpineのような最小限のベースOSイメージを使用するか、scratchからビルドします

  • ソフトウェアのインストール後に不要なファイルとアーティファクトをクリーンアップする

  • 個別の「ビルド」コンテナと「ランタイム」コンテナを使用して、本番アプリケーションのコンテナを小さく保つ

  • 大きなディレクトリにコピーするときに、不要なビルドアーティファクトとファイルを無視する

多くの実例を含むDockerコンテナーの最適化に関する完全なガイドについては、Building Optimized Containers for 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 and Secrets sectionを参照してください。

画像をレジストリに公開する

アプリケーションイメージを作成したら、Kubernetesで使用できるようにするには、コンテナーイメージレジストリにアップロードする必要があります。 Docker Hubのようなパブリックレジストリは、https://hub.docker.com//node/[Node.js] and https://hub.docker.com/ / nginx / [nginx]のような人気のあるオープンソースプロジェクトの最新のDockerイメージをホストします。 プライベートレジストリを使用すると、内部アプリケーションイメージを公開できるため、開発者やインフラストラクチャで利用できますが、より広い世界では利用できません。

既存のインフラストラクチャを使用してプライベートレジストリを展開できます(例: クラウドオブジェクトストレージの上に)、またはオプションでQuay.ioや有料のDockerHubプランなどのいくつかのDockerレジストリ製品の1つを使用します。 これらのレジストリは、GitHubなどのホストされたバージョン管理サービスと統合できるため、Dockerfileが更新およびプッシュされると、レジストリサービスは自動的に新しいDockerfileを取得し、コンテナーイメージを構築し、更新されたイメージをサービスで使用できるようにします。

コンテナイメージの構築とテスト、およびそれらのタグ付けと公開をさらに制御するために、継続的統合(CI)パイプラインを実装できます。

ビルドパイプラインを実装する

手動でイメージを構築、テスト、公開、および運用環境に展開すると、エラーが発生しやすくなり、拡張性が低下します。 ビルドを管理し、最新のコード変更を含むコンテナをイメージレジストリに継続的に発行するには、ビルドパイプラインを使用する必要があります。

ほとんどのビルドパイプラインは、次のコア機能を実行します。

  • ソースコードリポジトリの変更を確認します

  • 変更されたコードでスモークおよびユニットテストを実行する

  • 変更されたコードを含むコンテナイメージを構築する

  • ビルドされたコンテナイメージを使用してさらに統合テストを実行する

  • テストに合格したら、タグを付けて画像をレジストリに公開します

  • (オプション、継続的な展開セットアップで)Kubernetes Deploymentsを更新し、ステージング/運用クラスターにイメージをロールアウトします

GitHubなどの一般的なバージョン管理サービスやDocker Hubなどのイメージレジストリとの統合が組み込まれた有料の継続的統合製品が多数あります。 これらの製品の代わりに、上記のすべての機能を実行するように構成できる無料のオープンソースビルド自動化サーバーであるJenkinsがあります。 Jenkins継続的インテグレーションパイプラインを設定する方法については、How To Set Up Continuous Integration Pipelines in Jenkins on Ubuntu 16.04を参照してください。

コンテナのログと監視を実装する

コンテナを使用する場合、実行中および停止中のすべてのコンテナのログを管理および保存するために使用するロギングインフラストラクチャについて検討することが重要です。 ロギングに使用できるコンテナレベルのパターンは複数あり、Kubernetesレベルのパターンも複数あります。

Kubernetesでは、デフォルトでコンテナはjson-file Dockerlogging driverを使用します。これにより、stdoutストリームとstderrストリームがキャプチャされ、コンテナが実行されているノード上のJSONファイルに書き込まれます。 stderrおよびstdoutに直接ログを記録するだけでは、アプリケーションコンテナーに十分でない場合があります。また、アプリコンテナーをKubernetesポッドのロギングsidecarコンテナーとペアリングすることをお勧めします。 このサイドカーコンテナは、ファイルシステム、ローカルソケット、またはsystemdジャーナルからログを取得できるため、単にstderrおよびstdoutストリームを使用するよりも少し柔軟性が高くなります。 このコンテナは処理を行ってから、強化されたログをstdout / stderrに、または直接ロギングバックエンドにストリーミングできます。 Kubernetesのログパターンの詳細については、このチュートリアルのKubernetesのログとモニタリングのsectionを参照してください。

コンテナレベルでのアプリケーションのログ方法は、その複雑さに依存します。 シンプルな単一目的のマイクロサービスの場合、stdout / stderrに直接ログを記録し、Kubernetesにこれらのストリームを取得させることをお勧めします。これは、kubectl logsコマンドを利用してKubernetesがデプロイしたコンテナからログストリームにアクセスできるためです。

ロギングと同様に、コンテナおよびクラスターベースの環境での監視について考える必要があります。 Dockerは、ホスト上でコンテナーを実行するためのCPUやメモリー使用量などの標準メトリックを取得するための便利なdocker statsコマンドを提供し、Remote REST APIを介してさらに多くのメトリックを公開します。 さらに、オープンソースツールcAdvisor(デフォルトでKubernetesノードにインストールされます)は、履歴メトリック収集、メトリックデータのエクスポート、データを並べ替えるための便利なWebUIなどのより高度な機能を提供します。

ただし、マルチノード、マルチコンテナの本番環境では、PrometheusGrafanaなどのより複雑なメトリックスタックが、コンテナのパフォーマンスデータの整理と監視に役立つ場合があります。

概要

これらのセクションでは、コンテナを構築し、CI / CDパイプラインとイメージレジストリを設定するためのベストプラクティスと、コンテナの視認性を高めるための考慮事項について簡単に説明しました。

次のセクションでは、クラスター化されたコンテナ化されたアプリの実行とスケーリングを可能にするKubernetesの機能について説明します。

Kubernetesにデプロイする

この時点で、アプリをコンテナ化し、ロジックを実装して、クラウドネイティブ環境での移植性と可観測性を最大化しました。 次に、Kubernetesクラスターでアプリを管理およびスケーリングするためのシンプルなインターフェイスを提供するKubernetesの機能について説明します。

展開およびポッド構成ファイルの書き込み

アプリケーションをコンテナ化してレジストリに公開したら、Podワークロードを使用してKubernetesクラスターにデプロイできます。 Kubernetesクラスターで展開可能な最小単位は、コンテナーではなくポッドです。 通常、ポッドは、アプリケーションコンテナー(コンテナー化されたFlask Webアプリなど)、またはアプリコンテナーと、監視やログなどのヘルパー機能を実行する「サイドカー」コンテナーで構成されます。 ポッド内のコンテナーは、ストレージリソース、ネットワーク名前空間、およびポートスペースを共有します。 それらはlocalhostを使用して相互に通信でき、マウントされたボリュームを使用してデータを共有できます。 さらに、ポッドワークロードを使用すると、メインアプリコンテナの実行を開始する前に、セットアップスクリプトまたはユーティリティを実行するInit Containersを定義できます。

通常、ポッドは、特定の目的の状態を宣言するYAMLファイルによって定義されたコントローラーであるデプロイメントを使用してロールアウトされます。 たとえば、アプリケーションの状態は、Flask Webアプリコンテナの3つのレプリカを実行しており、ポート8080を公開しています。 作成されたコントロールプレーンは、必要に応じてコンテナをノードにスケジュールすることにより、クラスターの実際の状態を徐々に展開で宣言された目的の状態に一致させます。 クラスターで実行されているアプリケーションレプリカの数を、たとえば3から5までスケーリングするには、デプロイメント構成ファイルのreplicasフィールドを更新してから、新しい構成ファイルをkubectl applyします。 これらの構成ファイルを使用すると、既存のソース管理サービスと統合を使用して、スケーリングおよび展開操作をすべて追跡およびバージョン管理できます。

FlaskアプリのKubernetes Deployment設定ファイルのサンプルは次のとおりです。

flask_deployment.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ポッドとデプロイの詳細については、Kubernetesの公式ドキュメントのPodsセクションとDeploymentsセクションを参照してください。

ポッドストレージを構成する

Kubernetesは、ボリューム、永続ボリューム(PV)、および永続ボリュームクレーム(PVC)を使用してPodストレージを管理します。 ボリュームは、Podストレージを管理するために使用されるKubernetesの抽象概念であり、ほとんどのクラウドプロバイダーブロックストレージ製品と、実行中のPodをホストするノード上のローカルストレージをサポートします。 サポートされているボリュームタイプの完全なリストを確認するには、Kubernetesdocumentationを参照してください。

たとえば、ポッドにデータを共有する必要のある2つのNGINXコンテナが含まれている場合(たとえば、最初のnginxはWebページを提供し、2番目のnginx-syncは外部の場所からページをフェッチします。 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を使用します。これは、Webページファイルを含むnginx-webボリュームをnginxコンテナの/usr/share/nginx/htmlと%( nginx-syncコンテナ内のt4)s。 また、タイプemptyDirnginx-webと呼ばれるvolumeを定義します。

同様に、volumeタイプをemptyDirから関連するクラウドストレージボリュームタイプに変更することにより、クラウドブロックストレージ製品を使用してポッドストレージを構成できます。

ボリュームのライフサイクルはポッドのライフサイクルに関連付けられていますが、notはコンテナのライフサイクルに関連付けられています。 ポッド内のコンテナーが停止した場合、ボリュームは保持され、新しく起動されたコンテナーは同じボリュームをマウントしてそのデータにアクセスできます。 ポッドが再起動または停止すると、ボリュームも同様に動作しますが、ボリュームがクラウドブロックストレージで構成されている場合は、将来のポッドからアクセス可能なデータでマウント解除されます。

Podの再起動および更新後もデータを保持するには、PersistentVolume(PV)およびPersistentVolumeClaim(PVC)オブジェクトを使用する必要があります。

PersistentVolumeは、クラウドブロックストレージボリュームやNFSストレージなどの永続ストレージの断片を表す抽象概念です。 これらは、開発者がストレージの一部として要求するPersistentVolumeClaimsとは別に作成されます。 Pod構成では、開発者はPVCを使用して永続ストレージを要求します。これはKubernetesが利用可能なPVボリュームと一致します(クラウドブロックストレージを使用している場合、KubernetesはPersistentVolumeClaimsの作成時にPersistentVolumesを動的に作成できます)。

アプリケーションがレプリカごとに1つの永続ボリュームを必要とする場合(多くのデータベースの場合)、展開を使用するのではなく、安定したネットワーク識別子、安定した永続ストレージ、および順序保証を必要とするアプリ向けに設計されたStatefulSetコントローラーを使用する必要があります。 デプロイメントはステートレスアプリケーションに使用する必要があり、デプロイメント構成で使用するPersistentVolumeClaimを定義すると、そのPVCはすべてのデプロイメントのレプリカで共有されます。

StatefulSetコントローラーの詳細については、Kubernetesdocumentationを参照してください。 PersistentVolumesおよびPersistentVolumeクレームの詳細については、Kubernetesストレージdocumentationを参照してください。

Kubernetesを使用した構成データの注入

Dockerと同様に、Kubernetesには、ポッド構成ファイルで環境変数を設定するためのenvフィールドとenvFromフィールドがあります。 実行中のポッドのHOSTNAME環境変数をmy_hostnameに設定するポッド構成ファイルのサンプルスニペットを次に示します。

sample_pod.yaml

...
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
        env:
        - name: HOSTNAME
          value: my_hostname
...

これにより、構成をDockerfilesからPodおよびDeployment構成ファイルに移動できます。 Dockerfileから構成をさらに外部化する主な利点は、アプリケーションコンテナ定義とは別に、これらのKubernetesワークロード構成を(たとえば、HOSTNAME値をmy_hostname_2に変更することで)変更できることです。 Pod構成ファイルを変更したら、新しい環境を使用してPodを再デプロイできますが、基礎となるコンテナーイメージ(Dockerfileで定義)を再構築、テスト、リポジトリにプッシュする必要はありません。 これらのPodおよびDeployment構成をDockerfilesとは別にバージョン管理することもできます。これにより、重大な変更をすばやく検出し、構成の問題をアプリケーションのバグからさらに分離できます。

Kubernetesは、構成データをさらに外部化および管理するための別の構成要素、ConfigMapsおよびSecretsを提供します。

ConfigMapと秘密

ConfigMapsを使用すると、構成データをオブジェクトとして保存し、PodおよびDeployment構成ファイルで参照できるため、構成データのハードコーディングを避けて、PodおよびDeployment全体で再利用できます。

上記のポッド設定を使用した例を次に示します。 最初に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.7.9
        ports:
        - containerPort: 80
        env:
        - name: HOSTNAME
          valueFrom:
            configMapKeyRef:
              name: hostname
              key: HOSTNAME
...

したがって、HOSTNAME環境変数の値は、構成ファイルから完全に外部化されています。 次に、これらの変数を参照するすべての展開とポッドでこれらの変数を更新し、変更を有効にするためにポッドを再起動します。

アプリケーションが構成ファイルを使用する場合、ConfigMapsを使用すると、これらのファイルをConfigMapオブジェクトとして(--from-fileフラグを使用して)保存し、構成ファイルとしてコンテナーにマウントできます。

シークレットはConfigMapと同じ重要な機能を提供しますが、値はbase64エンコードされているため、データベース資格情報などの機密データに使用する必要があります。

ConfigMapsとSecretsの詳細については、Kubernetesdocumentationを参照してください。

サービスを作成する

Kubernetesでアプリケーションを起動して実行すると、すべてのポッドに(内部)IPアドレスが割り当てられ、そのコンテナーで共有されます。 これらのPodの1つが削除されるか、または死んだ場合、新しく開始されたPodには異なるIPアドレスが割り当てられます。

内部および/または外部クライアントに機能を公開する長期実行サービスの場合、同じ機能(または展開)を実行するポッドのセットに、コンテナー全体で要求の負荷を分散する安定したIPアドレスを付与できます。 Kubernetesサービスを使用してこれを行うことができます。

Kubernetesサービスには、サービス構成ファイルのtypeフィールドで指定された4つのタイプがあります。

  • ClusterIP:これはデフォルトのタイプであり、クラスター内のどこからでもアクセスできる安定した内部IPをサービスに付与します。

  • NodePort:これにより、静的ポート(デフォルトでは30000〜32767)の各ノードでサービスが公開されます。 リクエストがノードのIPアドレスとサービスのNodePortでノードにヒットすると、リクエストは負荷分散され、サービスのアプリケーションコンテナにルーティングされます。

  • LoadBalancer:これにより、クラウドプロバイダーの負荷分散製品を使用してロードバランサーが作成され、外部リクエストがルーティングされるサービスのNodePortClusterIPが構成されます。

  • ExternalName:このサービスタイプを使用すると、KubernetesサービスをDNSレコードにマッピングできます。 Kubernetes DNSを使用してポッドから外部サービスにアクセスするために使用できます。

クラスターで実行されているデプロイメントごとにタイプLoadBalancerのサービスを作成すると、サービスごとに新しいクラウドロードバランサーが作成され、コストがかかる可能性があることに注意してください。 単一のロードバランサーを使用して外部要求の複数のサービスへのルーティングを管理するには、イングレスコントローラーを使用できます。 Ingress Controllerはこの記事の範囲を超えていますが、それらの詳細については、Kubernetesdocumentationを参照してください。 人気のある単純なIngressControllerはNGINX Ingress Controllerです。

このガイドのポッドとデプロイメントsectionで使用されているFlaskの例の簡単なサービス構成ファイルは次のとおりです。

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 logsdocker logsを使用して個々のコンテナーとポッドのログを解析することは、実行中のアプリケーションの数が増えるにつれて面倒になる可能性があります。 アプリケーションまたはクラスターの問題をデバッグするには、集中ログを実装する必要があります。 大まかに言えば、これは、ポッドログファイルとストリームを処理し、メタデータでそれらを強化し、ログをElasticsearchなどのバックエンドに転送するすべてのワーカーノードで実行されるエージェントで構成されます。 そこから、Kibanaなどの視覚化ツールを使用して、ログデータを視覚化、フィルタリング、および整理できます。

コンテナレベルのログセクションでは、コンテナ内のアプリケーションがstdout / stderrストリームにログを記録するKubernetesの推奨アプローチについて説明しました。 また、アプリケーションからログを記録する際の柔軟性を高めることができるサイドカーコンテナのロギングについても簡単に説明しました。 また、ローカルのログデータをキャプチャしてログバックエンドに直接転送するログエージェントをポッドで直接実行することもできます。 それぞれのアプローチには長所と短所があり、リソース使用率のトレードオフがあります(たとえば、各Pod内でロギングエージェントコンテナーを実行すると、リソースが集中し、ロギングバックエンドがすぐに圧倒されます)。 さまざまなロギングアーキテクチャとそのトレードオフの詳細については、Kubernetesdocumentationを参照してください。

標準のセットアップでは、各ノードはFilebeatFluentdなどのロギングエージェントを実行し、Kubernetesによって作成されたコンテナログを取得します。 Kubernetesがノード上のコンテナのJSONログファイルを作成することを思い出してください(ほとんどのインストールでは、これらは/var/lib/docker/containers/にあります)。 これらは、logrotateなどのツールを使用して回転させる必要があります。 ノードロギングエージェントは、すべてのノードがDaemonSetポッドのコピーを実行することを保証するKubernetesワークロードの一種であるDaemonSet Controllerとして実行する必要があります。 この場合、Podにはロギングエージェントとその構成が含まれ、ロギングDaemonSet Podにマウントされたファイルおよびディレクトリからのログを処理します。

kubectl logsを使用してコンテナの問題をデバッグする際のボトルネックと同様に、最終的には、kubectl topとKubernetesダッシュボードを使用してクラスタのポッドリソースの使用状況を監視するよりも堅牢なオプションを検討する必要があります。 クラスターおよびアプリケーションレベルの監視は、Prometheus監視システムと時系列データベース、およびGrafanaメトリックダッシュボードを使用して設定できます。 Prometheusは、「プル」モデルを使用して機能します。このモデルは、HTTPエンドポイント(ノード上の/metrics/cadvisor/metricsアプリケーションのREST APIエンドポイントなど)を定期的にスクレイプしてメトリックデータを取得し、処理して保存します。 このデータは、Grafanaダッシュボードを使用して分析および視覚化できます。 PrometheusとGrafanaは、他の展開およびサービスと同様にKubernetesクラスターに起動できます。

復元力を高めるために、別のKubernetesクラスターで、または外部のログおよびメトリックサービスを使用して、ログおよび監視インフラストラクチャを実行することをお勧めします。

結論

Kubernetesクラスターで効率的に実行できるようにアプリケーションを移行および最新化するには、多くの場合、ソフトウェアおよびインフラストラクチャの変更の重要な計画と設計が必要になります。 実装後、これらの変更により、サービス所有者はアプリの新しいバージョンを継続的に展開し、最小限の手動操作で必要に応じて簡単に拡張できます。 アプリから設定を外部化する、適切なログとメトリックスの公開を設定する、ヘルスチェックを設定するなどのステップにより、Kubernetesが設計したCloud Nativeパラダイムを完全に活用できます。 ポータブルコンテナーを構築し、展開やサービスなどのKubernetesオブジェクトを使用して管理することにより、利用可能なコンピューティングインフラストラクチャと開発リソースを完全に使用できます。

Related