これは、このセクションの複数ページの印刷可能なビューです。 印刷するには、ここをクリックしてください.

このページの通常のビューに戻る.

チュートリアル

本セクションにはチュートリアルが含まれています。チュートリアルでは、単一のタスクよりも大きな目標を達成する方法を示します。通常、チュートリアルにはいくつかのセクションがあり、各セクションには一連のステップがあります。各チュートリアルを進める前に、後で参照できるように標準化された用語集ページをブックマークしておくことをお勧めします。

基本

設定

ステートレスアプリケーション

ステートフルアプリケーション

クラスター

サービス

次の項目

チュートリアルのページタイプについての情報は、Content Page Typesを参照してください。

1 - Hello Minikube

このチュートリアルでは、MinikubeとKatacodaを使用して、Kubernetes上でサンプルアプリケーションを動かす方法を紹介します。Katacodaはブラウザで無償のKubernetes環境を提供します。

目標

  • Minikubeへのサンプルアプリケーションのデプロイ
  • アプリケーションの実行
  • アプリケーションログの確認

始める前に

このチュートリアルはNGINXを利用してすべての要求をエコーバックするコンテナイメージを提供します。

Minikubeクラスターの作成

  1. Launch Terminal をクリックしてください

  1. ブラウザーでKubernetesダッシュボードを開いてください:

    minikube dashboard
    
  2. Katacoda環境のみ:ターミナルペーン上部の+ボタンをクリックしてから Select port to view on Host 1 をクリックしてください。

  3. Katacoda環境のみ:30000を入力し、Display Portをクリックしてください。

Deploymentの作成

KubernetesのPod は、コンテナの管理やネットワーキングの目的でまとめられた、1つ以上のコンテナのグループです。このチュートリアルのPodがもつコンテナは1つのみです。Kubernetesの Deployment はPodの状態を確認し、Podのコンテナが停止した場合には再起動します。DeploymentはPodの作成やスケールを管理するために推奨される方法(手段)です。

  1. kubectl create コマンドを使用してPodを管理するDeploymentを作成してください。Podは提供されたDockerイメージを元にコンテナを実行します。

    kubectl create deployment hello-node --image=k8s.gcr.io/echoserver:1.4
    
  2. Deploymentを確認します:

    kubectl get deployments
    

    出力は下記のようになります:

    NAME         READY   UP-TO-DATE   AVAILABLE   AGE
    hello-node   1/1     1            1           1m
    
  3. Podを確認します:

    kubectl get pods
    

    出力は下記のようになります:

    NAME                          READY     STATUS    RESTARTS   AGE
    hello-node-5f76cf6ccf-br9b5   1/1       Running   0          1m
    
  4. クラスターイベントを確認します:

    kubectl get events
    
  5. kubectl で設定を確認します:

    kubectl config view
    

Serviceの作成

通常、PodはKubernetesクラスター内部のIPアドレスからのみアクセスすることができます。hello-nodeコンテナをKubernetesの仮想ネットワークの外部からアクセスするためには、KubernetesのServiceとしてPodを公開する必要があります。

  1. kubectl expose コマンドを使用してPodをインターネットに公開します:

    kubectl expose deployment hello-node --type=LoadBalancer --port=8080
    

    --type=LoadBalancerフラグはServiceをクラスター外部に公開したいことを示しています。

  2. 作成したServiceを確認します:

    kubectl get services
    

    出力は下記のようになります:

    NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
    hello-node   LoadBalancer   10.108.144.78   <pending>     8080:30369/TCP   21s
    kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP          23m
    

    ロードバランサーをサポートするクラウドプロバイダーでは、Serviceにアクセスするための外部IPアドレスが提供されます。 Minikube では、LoadBalancerタイプはminikube serviceコマンドを使用した接続可能なServiceを作成します。

  3. 次のコマンドを実行します:

    minikube service hello-node
    
  4. Katacoda環境のみ:ターミナル画面上部の+ボタンをクリックして Select port to view on Host 1 をクリックしてください。

  5. Katacoda環境のみ:8080の反対側のService出力に、5桁のポート番号が表示されます。このポート番号はランダムに生成されるため、ここで使用するポート番号と異なる場合があります。ポート番号テキストボックスに番号を入力し、ポートの表示をクリックしてください。前の例の場合は、30369と入力します。

    アプリケーションとその応答が表示されるブラウザーウィンドウが開きます。

アドオンの有効化

Minikubeはビルトインのアドオンがあり、有効化、無効化、あるいはローカルのKubernetes環境に公開することができます。

  1. サポートされているアドオンをリストアップします:

    minikube addons list
    

    出力は下記のようになります:

    addon-manager: enabled
    dashboard: enabled
    default-storageclass: enabled
    efk: disabled
    freshpod: disabled
    gvisor: disabled
    helm-tiller: disabled
    ingress: disabled
    ingress-dns: disabled
    logviewer: disabled
    metrics-server: disabled
    nvidia-driver-installer: disabled
    nvidia-gpu-device-plugin: disabled
    registry: disabled
    registry-creds: disabled
    storage-provisioner: enabled
    storage-provisioner-gluster: disabled
    
  2. ここでは例としてmetrics-serverのアドオンを有効化します:

    minikube addons enable metrics-server
    

    出力は下記のようになります:

    metrics-server was successfully enabled
    
  3. 作成されたPodとサービスを確認します:

    kubectl get pod,svc -n kube-system
    

    出力:

    NAME                                        READY     STATUS    RESTARTS   AGE
    pod/coredns-5644d7b6d9-mh9ll                1/1       Running   0          34m
    pod/coredns-5644d7b6d9-pqd2t                1/1       Running   0          34m
    pod/metrics-server-67fb648c5                1/1       Running   0          26s
    pod/etcd-minikube                           1/1       Running   0          34m
    pod/influxdb-grafana-b29w8                  2/2       Running   0          26s
    pod/kube-addon-manager-minikube             1/1       Running   0          34m
    pod/kube-apiserver-minikube                 1/1       Running   0          34m
    pod/kube-controller-manager-minikube        1/1       Running   0          34m
    pod/kube-proxy-rnlps                        1/1       Running   0          34m
    pod/kube-scheduler-minikube                 1/1       Running   0          34m
    pod/storage-provisioner                     1/1       Running   0          34m
    
    NAME                           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
    service/metrics-server         ClusterIP   10.96.241.45    <none>        80/TCP              26s
    service/kube-dns               ClusterIP   10.96.0.10      <none>        53/UDP,53/TCP       34m
    service/monitoring-grafana     NodePort    10.99.24.54     <none>        80:30002/TCP        26s
    service/monitoring-influxdb    ClusterIP   10.111.169.94   <none>        8083/TCP,8086/TCP   26s
    
  4. metrics-serverを無効化します:

    minikube addons disable metrics-server
    

    出力は下記のようになります:

    metrics-server was successfully disabled
    

クリーンアップ

クラスターに作成したリソースをクリーンアップします:

kubectl delete service hello-node
kubectl delete deployment hello-node

(オプション)Minikubeの仮想マシン(VM)を停止します:

minikube stop

(オプション)MinikubeのVMを削除します:

minikube delete

次の項目

2 - Kubernetesの基本を学ぶ

Kubernetesの基本

このチュートリアルでは、Kubernetesクラスターオーケストレーションシステムの基本について学びます。各モジュールには、Kubernetesの主な機能と概念に関する背景情報と、インタラクティブなオンラインチュートリアルが含まれています。これらの対話型チュートリアルでは、簡単なクラスターとそのコンテナ化されたアプリケーションを自分で管理できます。

この対話型のチュートリアルでは、以下のことを学ぶことができます:

  • コンテナ化されたアプリケーションをクラスターにデプロイ
  • Deploymentのスケーリング
  • 新しいソフトウェアのバージョンでコンテナ化されたアプリケーションをアップデート
  • コンテナ化されたアプリケーションのデバッグ

このチュートリアルでは、Katacodaを使用して、Webブラウザ上の仮想ターミナルでMinikubeを実行します。Minikubeは、どこでも実行できるKubernetesの小規模なローカル環境です。ソフトウェアをインストールしたり、何かを設定したりする必要はありません。各対話型チュートリアルは、Webブラウザ自体の上で直接実行されます


Kubernetesはどんなことができるの?

モダンなWebサービスでは、ユーザはアプリケーションが24時間365日利用可能であることを期待しており、開発者はそれらのアプリケーションの新しいバージョンを1日に数回デプロイすることを期待しています。コンテナ化は、パッケージソフトウェアがこれらの目標を達成するのを助け、アプリケーションをダウンタイムなしで簡単かつ迅速にリリース、アップデートできるようにします。Kubernetesを使用すると、コンテナ化されたアプリケーションをいつでもどこでも好きなときに実行できるようになり、それらが機能するために必要なリソースとツールを見つけやすくなります。Kubernetesは、コンテナオーケストレーションにおけるGoogleのこれまでの経験と、コミュニティから得られた最善のアイデアを組み合わせて設計された、プロダクションレディなオープンソースプラットフォームです。

2.1 - クラスターの作成

2.1.1 - Minikubeを使ったクラスターの作成

目標

  • Kubernetesクラスターとは何かを学ぶ
  • Minikubeとは何かを学ぶ
  • Kubernetesクラスターを、オンラインのターミナルを使って動かす

Kubernetesクラスター

Kubernetesは、単一のユニットとして機能するように接続された、可用性の高いコンピュータのクラスターをまとめあげます。Kubernetesの抽象化により、コンテナ化されたアプリケーションを個々のマシンに特に結び付けることなくクラスターにデプロイできます。この新しいデプロイモデルを利用するには、アプリケーションを個々のホストから切り離す方法でアプリケーションをパッケージ化(つまり、コンテナ化)する必要があります。コンテナ化されたアプリケーションは、アプリケーションがホストに深く統合されたパッケージとして特定のマシンに直接インストールされていた従来のデプロイモデルよりも柔軟で、より迅速に利用可能です。Kubernetesはより効率的な方法で、クラスター全体のアプリケーションコンテナの配布とスケジューリングを自動化します。Kubernetesはオープンソースのプラットフォームであり、プロダクションレディです。

Kubernetesクラスターは以下の2種類のリソースで構成されています:

  • マスターがクラスターを管理する
  • ノードがアプリケーションを動かすワーカーとなる

まとめ:

  • Kubernetesクラスター
  • Minikube

Kubernetesは、コンピュータクラスター内およびコンピュータクラスター間でのアプリケーションコンテナの配置(スケジューリング)および実行を調整する、プロダクショングレードのオープンソースプラットフォームです。


クラスターダイアグラム


マスターはクラスターの管理を担当します。マスターは、アプリケーションのスケジューリング、望ましい状態の維持、アプリケーションのスケーリング、新しい更新のロールアウトなど、クラスター内のすべての動作をまとめあげます。

ノードは、Kubernetesクラスターのワーカーマシンとして機能するVMまたは物理マシンです。各ノードにはKubeletがあり、これはノードを管理し、Kubernetesマスターと通信するためのエージェントです。ノードにはDockerやrktなどのコンテナ操作を処理するためのツールもあるはずです。プロダクションのトラフィックを処理するKubernetesクラスターには、最低3つのノードが必要です。

マスターは実行中のアプリケーションをホストするために使用されるノードとクラスターを管理します。

Kubernetesにアプリケーションをデプロイするときは、マスターにアプリケーションコンテナを起動するように指示します。マスターはコンテナがクラスターのノードで実行されるようにスケジュールします。ノードは、マスターが公開しているKubernetes APIを使用してマスターと通信します。エンドユーザーは、Kubernetes APIを直接使用して対話することもできます。

Kubernetesクラスターは、物理マシンまたは仮想マシンのどちらにも配置できます。Kubernetes開発を始めるためにMinikubeを使うことができます。Minikubeは、ローカルマシン上にVMを作成し、1つのノードのみを含む単純なクラスターをデプロイする軽量なKubernetes実装です。Minikubeは、Linux、macOS、およびWindowsシステムで利用可能です。Minikube CLIは、起動、停止、ステータス、削除など、クラスターを操作するための基本的なブートストラップ操作を提供します。ただし、このチュートリアルでは、Minikubeがプリインストールされた状態で提供されているオンラインのターミナルを使用します。

Kubernetesが何であるかがわかったので、オンラインチュートリアルに行き、最初のクラスターを動かしましょう!


2.1.2 - 対話型チュートリアル - クラスターの作成

ターミナルを使うにはスクリーンが狭い場合は、PCまたはタブレットをお使いください

2.2 - アプリケーションのデプロイ

2.2.1 - kubectlを使ったDeploymentの作成

目標

  • アプリケーションのデプロイについて学ぶ
  • kubectlを使って、Kubernetes上にはじめてのアプリケーションをデプロイする

Kubernetes Deployments

実行中のKubernetesクラスターを入手すると、その上にコンテナ化アプリケーションをデプロイすることができます。そのためには、KubernetesのDeployment の設定を作成します。DeploymentはKubernetesにあなたのアプリケーションのインスタンスを作成し、更新する方法を指示します。Deploymentを作成すると、KubernetesマスターはDeployment内に含まれるアプリケーションインスタンスをクラスター内の個々のノードで実行するようにスケジュールします。

アプリケーションインスタンスが作成されると、Kubernetes Deploymentコントローラーは、それらのインスタンスを継続的に監視します。インスタンスをホストしているノードが停止、削除された場合、Deploymentコントローラーはそのインスタンスをクラスター内の別のノード上のインスタンスと置き換えます。これは、マシンの故障やメンテナンスに対処するためのセルフヒーリングの仕組みを提供しています。

オーケストレーションが入る前の世界では、インストールスクリプトを使用してアプリケーションを起動することはよくありましたが、マシン障害が発生した場合に復旧する事はできませんでした。アプリケーションのインスタンスを作成し、それらをノード間で実行し続けることで、Kubernetes Deploymentsはアプリケーションの管理に根本的に異なるアプローチを提供します。

まとめ:

  • Deployments
  • kubectl

Deploymentは、アプリケーションのインスタンスを作成および更新する責務があります。


Kubernetes上にはじめてのアプリケーションをデプロイする


Kubernetesのコマンドラインインターフェイスであるkubectlを使用して、Deploymentを作成、管理できます。kubectlはKubernetes APIを使用してクラスターと対話します。このモジュールでは、Kubernetesクラスターでアプリケーションを実行するDeploymentを作成するために必要な、最も一般的なkubectlコマンドについて学びます。

Deploymentを作成するときは、アプリケーションのコンテナイメージと実行するレプリカの数を指定する必要があります。Deploymentを更新することで、あとでその情報を変更できます。チュートリアルのモジュール56では、Deploymentをどのようにスケール、更新できるかについて説明します。

Kubernetesにデプロイするには、アプリケーションをサポートされているコンテナ形式のいずれかにパッケージ化する必要があります。

最初のDeploymentには、DockerコンテナにパッケージされたNode.jsアプリケーションを使用します。(まだNode.jsアプリケーションを作成してデプロイしていない場合、Hello Minikubeチュートリアルの通りにやってみましょう。)

Deploymentが何であるかがわかったので、オンラインチュートリアルに行き、最初のアプリケーションをデプロイしましょう!


2.2.2 - 対話型チュートリアル - アプリケーションのデプロイ

Podは、Kubernetesアプリケーションの基本的な実行単位です。各Podは、クラスターで実行されているワークロードの一部を表します。Podの詳細はこちらです


ターミナルを使うには、PCまたはタブレットをお使いください

2.3 - アプリケーションの探索

2.3.1 - Podとノードについて

目標

  • KubernetesのPodについて学ぶ
  • Kubernetesのノードについて学ぶ
  • デプロイされたアプリケーションのトラブルシューティング

Kubernetes Pod

モジュール2でDeploymentを作成したときに、KubernetesはアプリケーションインスタンスをホストするためのPodを作成しました。Podは、1つ以上のアプリケーションコンテナ(Dockerなど)のグループとそれらのコンテナの共有リソースを表すKubernetesの抽象概念です。 Podには以下のものが含まれます:

  • 共有ストレージ(ボリューム)
  • ネットワーキング(クラスターに固有のIPアドレス)
  • コンテナのイメージバージョンや使用するポートなどの、各コンテナをどう動かすかに関する情報

Podは、アプリケーション固有の「論理ホスト」をモデル化し、比較的密接に結合されたさまざまなアプリケーションコンテナを含むことができます。 たとえば、Podには、Node.jsアプリケーションを含むコンテナと、Node.js Webサーバによって公開されるデータを供給する別のコンテナの両方を含めることができます。Pod内のコンテナはIPアドレスとポートスペースを共有し、常に同じ場所に配置され、同じスケジュールに入れられ、同じノード上の共有コンテキストで実行されます。

Podは、Kubernetesプラットフォームの原子単位です。 Kubernetes上にDeploymentを作成すると、そのDeploymentはその中にコンテナを持つPodを作成します(コンテナを直接作成するのではなく)。 各Podは、スケジュールされているノードに関連付けられており、終了(再起動ポリシーに従って)または削除されるまでそこに残ります。 ノードに障害が発生した場合、同じPodがクラスター内の他の使用可能なノードにスケジュールされます。

まとめ:

  • Pod
  • ノード
  • kubectlの主要なコマンド

Podは1つ以上のアプリケーションコンテナ(Dockerなど)のグループであり、共有ストレージ(ボリューム)、IPアドレス、それらの実行方法に関する情報が含まれています。


Podの概要


ノード

Podは常にノード上で動作します。ノードはKubernetesではワーカーマシンであり、クラスターによって仮想、物理マシンのどちらであってもかまいません。各ノードはマスターによって管理されます。ノードは複数のPodを持つことができ、Kubernetesマスターはクラスター内のノード間でPodのスケジュールを自動的に処理します。マスターの自動スケジューリングは各ノードで利用可能なリソースを考慮に入れます。

すべてのKubernetesノードでは少なくとも以下のものが動作します。

  • Kubelet: Kubernetesマスターとノード間の通信を担当するプロセス。マシン上で実行されているPodとコンテナを管理します。
  • レジストリからコンテナイメージを取得し、コンテナを解凍し、アプリケーションを実行することを担当する、Dockerのようなコンテナランタイム。

コンテナ同士が密接に結合され、ディスクなどのリソースを共有する必要がある場合は、コンテナを1つのPodにまとめてスケジュールする必要があります。


ノードの概要


kubectlを使ったトラブルシューティング

モジュール2では、kubectlのコマンドラインインターフェースを使用しました。モジュール3でもこれを使用して、デプロイされたアプリケーションとその環境に関する情報を入手します。最も一般的な操作は、次のkubectlコマンドで実行できます。

  • kubectl get - リソースの一覧を表示
  • kubectl describe - 単一リソースに関する詳細情報を表示
  • kubectl logs - 単一Pod上の単一コンテナ内のログを表示
  • kubectl exec - 単一Pod上の単一コンテナ内でコマンドを実行

これらのコマンドを使用して、アプリケーションがいつデプロイされたか、それらの現在の状況、実行中の場所、および構成を確認することができます。

クラスターのコンポーネントとコマンドラインの詳細についてわかったので、次にデプロイしたアプリケーションを探索してみましょう。

ノードはKubernetesではワーカーマシンであり、クラスターに応じてVMまたは物理マシンになります。 複数のPodを1つのノードで実行できます。


2.3.2 - 対話型チュートリアル - デプロイしたアプリケーションの探索


ターミナルを使うには、PCまたはタブレットをお使いください

2.4 - アプリケーションの公開

2.4.1 - Serviceを使ったアプリケーションの公開

目標

  • KubernetesにおけるServiceについて理解する
  • ラベルとLabelSelectorオブジェクトがServiceにどう関係しているかを理解する
  • Serviceを使って、Kubernetesクラスターの外にアプリケーションを公開する

Kubernetes Serviceの概要

Kubernetes Podの寿命は永続的ではありません。実際、Podにはライフサイクルがあります。ワーカーのノードが停止すると、そのノードで実行されているPodも失われます。そうなると、ReplicaSetは、新しいPodを作成してアプリケーションを実行し続けるために、クラスターを動的に目的の状態に戻すことができます。別の例として、3つのレプリカを持つ画像処理バックエンドを考えます。それらのレプリカは交換可能です。フロントエンドシステムはバックエンドのレプリカを気にしたり、Podが失われて再作成されたとしても配慮すべきではありません。ただし、Kubernetesクラスター内の各Podは、同じノード上のPodであっても一意のIPアドレスを持っているため、アプリケーションが機能し続けるように、Pod間の変更を自動的に調整する方法が必要です。

KubernetesのServiceは、Podの論理セットと、それらにアクセスするためのポリシーを定義する抽象概念です。Serviceによって、依存Pod間の疎結合が可能になります。Serviceは、すべてのKubernetesオブジェクトのように、YAML(推奨)またはJSONを使って定義されます。Serviceが対象とするPodのセットは通常、LabelSelectorによって決定されます(なぜ仕様にセレクタを含めずにServiceが必要になるのかについては下記を参照してください)。

各Podには固有のIPアドレスがありますが、それらのIPは、Serviceなしではクラスターの外部に公開されません。Serviceによって、アプリケーションはトラフィックを受信できるようになります。ServiceSpecでtypeを指定することで、Serviceをさまざまな方法で公開することができます。

  • ClusterIP (既定値) - クラスター内の内部IPでServiceを公開します。この型では、Serviceはクラスター内からのみ到達可能になります。
  • NodePort - NATを使用して、クラスター内の選択された各ノードの同じポートにServiceを公開します。<NodeIP>:<NodePort>を使用してクラスターの外部からServiceにアクセスできるようにします。これはClusterIPのスーパーセットです。
  • LoadBalancer - 現在のクラウドに外部ロードバランサを作成し(サポートされている場合)、Serviceに固定の外部IPを割り当てます。これはNodePortのスーパーセットです。
  • ExternalName - 仕様のexternalNameで指定した名前のCNAMEレコードを返すことによって、任意の名前を使ってServiceを公開します。プロキシは使用されません。このタイプはv1.7以上のkube-dnsを必要とします。

さまざまな種類のServiceに関する詳細情報はUsing Source IP tutorialにあります。アプリケーションとServiceの接続も参照してください。

加えて、Serviceには、仕様にselectorを定義しないというユースケースがいくつかあります。selectorを指定せずに作成したServiceについて、対応するEndpointsオブジェクトは作成されません。これによって、ユーザーは手動でServiceを特定のエンドポイントにマッピングできます。セレクタがない可能性があるもう1つの可能性は、type:ExternalNameを厳密に使用していることです。

まとめ

  • Podを外部トラフィックに公開する
  • 複数のPod間でトラフィックを負荷分散する
  • ラベルを使う

Kubernetes Serviceは、Podの論理セットを定義し、それらのPodに対する外部トラフィックの公開、負荷分散、およびサービス検出を可能にする抽象化層です。


Serviceとラベル

Serviceは、一連のPodにトラフィックをルーティングします。Serviceは、アプリケーションに影響を与えることなく、KubernetesでPodが死んだり複製したりすることを可能にする抽象概念です。(アプリケーションのフロントエンドおよびバックエンドコンポーネントなどの)依存Pod間の検出とルーティングは、Kubernetes Serviceによって処理されます。

Serviceは、ラベルとセレクタを使用して一連のPodを照合します。これは、Kubernetes内のオブジェクトに対する論理操作を可能にするグループ化のプリミティブです。ラベルはオブジェクトに付けられたkey/valueのペアであり、さまざまな方法で使用できます。

  • 開発、テスト、および本番用のオブジェクトを指定する
  • バージョンタグを埋め込む
  • タグを使用してオブジェクトを分類する


ラベルは、作成時またはそれ以降にオブジェクトにアタッチでき、いつでも変更可能です。Serviceを使用してアプリケーションを公開し、いくつかのラベルを適用してみましょう。


2.4.2 - 対話型チュートリアル - アプリケーションの公開

ターミナルを使うには、PCまたはタブレットをお使いください

2.5 - アプリケーションのスケーリング

2.5.1 - アプリケーションの複数インスタンスを実行

目標

  • kubectlを使用してアプリケーションをスケールする

アプリケーションのスケーリング

前回のモジュールでは、Deploymentを作成し、それをService経由で公開しました。該当のDeploymentでは、アプリケーションを実行するためのPodを1つだけ作成しました。トラフィックが増加した場合、ユーザーの需要に対応するためにアプリケーションをスケールする必要があります。

スケーリングは、Deploymentのレプリカの数を変更することによって実現可能です。

まとめ

  • Deploymentのスケーリング

kubectl create deploymentコマンドの--replicasパラメーターを使用することで、最初から複数のインスタンスを含むDeploymentを作成できます。


スケーリングの概要


Deploymentをスケールアウトすると、新しいPodが作成され、使用可能なリソースを持つノードにスケジュールされます。スケールすると、Podの数が増えて新たな望ましい状態になります。KubernetesはPodのオートスケーリングもサポートしていますが、このチュートリアルでは範囲外です。スケーリングを0に設定することも可能で、指定された配置のすべてのPodを終了させます。

アプリケーションの複数インスタンスを実行するには、それらすべてにトラフィックを分散する方法が必要になります。Serviceには、公開されたDeploymentのすべてのPodにネットワークトラフィックを分散する統合ロードバランサがあります。Serviceは、エンドポイントを使用して実行中のPodを継続的に監視し、トラフィックが使用可能なPodにのみ送信されるようにします。

スケーリングは、Deploymentのレプリカの数を変更することによって実現可能です。


アプリケーションの複数のインスタンスを実行すると、ダウンタイムなしでローリングアップデートを実行できます。それについては、次のモジュールで学習します。それでは、オンラインのターミナルを使って、アプリケーションをデプロイしてみましょう。


2.5.2 - 対話型チュートリアル - アプリケーションのスケーリング

ターミナルを使うには、PCまたはタブレットをお使いください

2.6 - アプリケーションのアップデート

2.6.1 - ローリングアップデートの実行

目標

  • kubectlを使ってローリングアップデートを実行する

アプリケーションのアップデート

ユーザーはアプリケーションが常に利用可能であることを期待し、開発者はそれらの新しいバージョンを1日に数回デプロイすることが期待されます。Kubernetesでは、アプリケーションのアップデートをローリングアップデートで行います。ローリングアップデートでは、Podインスタンスを新しいインスタンスで段階的にアップデートすることで、ダウンタイムなしでDeploymentをアップデートできます。新しいPodは、利用可能なリソースを持つノードにスケジュールされます。

前回のモジュールでは、複数のインスタンスを実行するようにアプリケーションをデプロイしました。これは、アプリケーションの可用性に影響を与えずにアップデートを行うための要件です。デフォルトでは、アップデート中に使用できなくなる可能性があるPodの最大数と作成できる新しいPodの最大数は1です。どちらのオプションも、Podの数または全体数に対する割合(%)のいずれかに設定できます。Kubernetesでは、アップデートはバージョン管理されており、Deploymentにおけるアップデートは以前の(stable)バージョンに戻すことができます。

まとめ

  • アプリケーションのアップデート

ローリングアップデートでは、Podを新しいインスタンスで段階的にアップデートすることで、ダウンタイムなしDeploymentをアップデートできます。


ローリングアップデートの概要


アプリケーションのスケーリングと同様に、Deploymentがパブリックに公開されている場合、Serviceはアップデート中に利用可能なPodのみにトラフィックを負荷分散します。 利用可能なPodは、アプリケーションのユーザーが利用できるインスタンスです。

ローリングアップデートでは、次の操作が可能です。

  • コンテナイメージのアップデートを介した、ある環境から別の環境へのアプリケーションの昇格
  • 以前のバージョンへのロールバック
  • ダウンタイムなしでのアプリケーションのCI/CD

Deploymentがパブリックに公開されている場合、Serviceはアップデート中に利用可能なPodにのみトラフィックを負荷分散します。


次の対話型チュートリアルでは、アプリケーションを新しいバージョンにアップデートし、ロールバックも実行します。


2.6.2 - 対話型チュートリアル - アプリケーションのアップデート

ターミナルを使うには、PCまたはタブレットをお使いください

3 - 設定

3.1 - ConfigMapを使ったRedisの設定

本ページでは、ConfigMapを使ったコンテナの設定に基づき、ConfigMapを使ってRedisの設定を行う実践的な例を提供します。

目標

  • 以下の要素を含むkustomization.yamlファイルを作成する:
    • ConfigMapGenerator
    • ConfigMapを使ったPodリソースの設定
  • kubectl apply -k ./コマンドにてディレクトリ全体を適用する
  • 設定が正しく反映されていることを確認する

始める前に

Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

バージョンを確認するには次のコマンドを実行してください: kubectl version.

実践例: ConfigMapを使ったRedisの設定

以下の手順に従って、ConfigMapに保存されているデータを使用してRedisキャッシュを設定できます。

最初に、redis-configファイルからConfigMapを含むkustomization.yamlを作成します:

maxmemory 2mb
maxmemory-policy allkeys-lru
curl -OL https://k8s.io/examples/pods/config/redis-config

cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: example-redis-config
  files:
  - redis-config
EOF

Podリソースの設定をkustomization.yamlに入れます:

apiVersion: v1
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: kubernetes/redis:v1
    env:
    - name: MASTER
      value: "true"
    ports:
    - containerPort: 6379
    resources:
      limits:
        cpu: "0.1"
    volumeMounts:
    - mountPath: /redis-master-data
      name: data
    - mountPath: /redis-master
      name: config
  volumes:
    - name: data
      emptyDir: {}
    - name: config
      configMap:
        name: example-redis-config
        items:
        - key: redis-config
          path: redis.conf
curl -OL https://raw.githubusercontent.com/kubernetes/website/master/content/en/examples/pods/config/redis-pod.yaml

cat <<EOF >>./kustomization.yaml
resources:
- redis-pod.yaml
EOF

kustomizationディレクトリを反映して、ConfigMapオブジェクトとPodオブジェクトの両方を作成します:

kubectl apply -k .

作成されたオブジェクトを確認します

> kubectl get -k .
NAME                                        DATA   AGE
configmap/example-redis-config-dgh9dg555m   1      52s

NAME        READY   STATUS    RESTARTS   AGE
pod/redis   1/1     Running   0          52s

この例では、設定ファイルのボリュームは/redis-masterにマウントされています。 pathを使ってredis-configキーをredis.confという名前のファイルに追加します。 したがって、redisコンフィグのファイルパスは/redis-master/redis.confです。 ここが、コンテナイメージがredisマスターの設定ファイルを探す場所です。

kubectl execを使ってPodに入り、redis-cliツールを実行して設定が正しく適用されたことを確認してください:

kubectl exec -it redis -- redis-cli
127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"
2) "2097152"
127.0.0.1:6379> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-lru"

作成したPodを削除します:

kubectl delete pod redis

次の項目

4 - ステートレスアプリケーション

4.1 - クラスター内のアプリケーションにアクセスするために外部IPアドレスを公開する

このページでは、外部IPアドレスを公開するKubernetesのServiceオブジェクトを作成する方法を示します。

始める前に

  • kubectlをインストールしてください。

  • Kubernetesクラスターを作成する際に、Google Kubernetes EngineやAmazon Web Servicesのようなクラウドプロバイダーを使用します。このチュートリアルでは、クラウドプロバイダーを必要とする外部ロードバランサーを作成します。

  • Kubernetes APIサーバーと通信するために、kubectlを設定してください。手順については、各クラウドプロバイダーのドキュメントを参照してください。

目標

  • 5つのインスタンスで実際のアプリケーションを起動します。
  • 外部IPアドレスを公開するServiceオブジェクトを作成します。
  • 起動中のアプリケーションにアクセスするためにServiceオブジェクトを使用します。

5つのPodで起動しているアプリケーションへのServiceの作成

  1. クラスターにてHello Worldアプリケーションを実行してください。
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: load-balancer-example
  name: hello-world
spec:
  replicas: 5
  selector:
    matchLabels:
      app.kubernetes.io/name: load-balancer-example
  template:
    metadata:
      labels:
        app.kubernetes.io/name: load-balancer-example
    spec:
      containers:
      - image: gcr.io/google-samples/node-hello:1.0
        name: hello-world
        ports:
        - containerPort: 8080
kubectl apply -f https://k8s.io/examples/service/load-balancer-example.yaml

上記のコマンドにより、 Deploymentを作成し、ReplicaSetを関連づけます。ReplicaSetには5つのPodがあり、それぞれHello Worldアプリケーションが起動しています。

  1. Deploymentに関する情報を表示します:

     kubectl get deployments hello-world
     kubectl describe deployments hello-world
    
  2. ReplicaSetオブジェクトに関する情報を表示します:

     kubectl get replicasets
     kubectl describe replicasets
    
  3. Deploymentを公開するServiceオブジェクトを作成します。

     kubectl expose deployment hello-world --type=LoadBalancer --name=my-service
    
  4. Serviceに関する情報を表示します:

     kubectl get services my-service
    

    出力は次のようになります:

     NAME         TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)    AGE
     my-service   LoadBalancer   10.3.245.137   104.198.205.71   8080/TCP   54s
    
  5. Serviceに関する詳細な情報を表示します:

     kubectl describe services my-service
    

    出力は次のようになります:

     Name:           my-service
     Namespace:      default
     Labels:         app.kubernetes.io/name=load-balancer-example
     Annotations:    <none>
     Selector:       app.kubernetes.io/name=load-balancer-example
     Type:           LoadBalancer
     IP:             10.3.245.137
     LoadBalancer Ingress:   104.198.205.71
     Port:           <unset> 8080/TCP
     NodePort:       <unset> 32377/TCP
     Endpoints:      10.0.0.6:8080,10.0.1.6:8080,10.0.1.7:8080 + 2 more...
     Session Affinity:   None
     Events:         <none>
    

    Serviceによって公開された外部IPアドレス(LoadBalancer Ingress)を記録しておいてください。 この例では、外部IPアドレスは104.198.205.71です。 また、PortおよびNodePortの値も控えてください。 この例では、Portは8080、NodePortは32377です。

  6. 先ほどの出力にて、Serviceにはいくつかのエンドポイントがあることを確認できます: 10.0.0.6:8080、 10.0.1.6:8080、10.0.1.7:8080、その他2つです。 これらはHello Worldアプリケーションが動作しているPodの内部IPアドレスです。 これらのPodのアドレスを確認するには、次のコマンドを実行します:

     kubectl get pods --output=wide
    

    出力は次のようになります:

     NAME                         ...  IP         NODE
     hello-world-2895499144-1jaz9 ...  10.0.1.6   gke-cluster-1-default-pool-e0b8d269-1afc
     hello-world-2895499144-2e5uh ...  10.0.1.8   gke-cluster-1-default-pool-e0b8d269-1afc
     hello-world-2895499144-9m4h1 ...  10.0.0.6   gke-cluster-1-default-pool-e0b8d269-5v7a
     hello-world-2895499144-o4z13 ...  10.0.1.7   gke-cluster-1-default-pool-e0b8d269-1afc
     hello-world-2895499144-segjf ...  10.0.2.5   gke-cluster-1-default-pool-e0b8d269-cpuc
    
  7. Hello Worldアプリケーションにアクセスするために、外部IPアドレス(LoadBalancer Ingress)を使用します:

     curl http://<external-ip>:<port>
    

    ここで、<external-ip>はServiceの外部IPアドレス(LoadBalancer Ingress)で、 <port>はServiceの詳細出力におけるPortです。minikubeを使用している場合、minikube service my-serviceを実行することでHello Worldアプリケーションをブラウザで自動的に 開かれます。

    正常なリクエストに対するレスポンスは、helloメッセージです:

     Hello Kubernetes!
    

クリーンアップ

Serviceを削除する場合、次のコマンドを実行します:

kubectl delete services my-service

Deployment、ReplicaSet、およびHello Worldアプリケーションが動作しているPodを削除する場合、次のコマンドを実行します:

kubectl delete deployment hello-world

次の項目

connecting applications with servicesにて詳細を学ぶことができます。

4.2 - 例: Redisを使用したPHPのゲストブックアプリケーションのデプロイ

このチュートリアルでは、KubernetesとDockerを使用した、シンプルなマルチティアのウェブアプリケーションのビルドとデプロイの方法を紹介します。この例は、以下のコンポーネントから構成されています。

  • ゲストブックのエントリーを保存するための、シングルインスタンスのRedisマスター
  • 読み込みデータ配信用の、複数のレプリケーションされたRedisインスタンス
  • 複数のウェブフロントエンドのインスタンス

目標

  • Redisのマスターを起動する。
  • Redisのスレーブを起動する。
  • ゲストブックのフロントエンドを起動する。
  • フロントエンドのServiceを公開して表示を確認する。
  • クリーンアップする。

始める前に

Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

バージョンを確認するには次のコマンドを実行してください: kubectl version.

Redisのマスターを起動する

ゲストブックアプリケーションでは、データを保存するためにRedisを使用します。ゲストブックはRedisのマスターインスタンスにデータを書き込み、複数のRedisのスレーブインスタンスからデータを読み込みます。

RedisのマスターのDeploymentを作成する

以下のマニフェストファイルは、シングルレプリカのRedisのマスターPodを実行するDeploymentコントローラーを指定しています。

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: redis-master
  labels:
    app: redis
spec:
  selector:
    matchLabels:
      app: redis
      role: master
      tier: backend
  replicas: 1
  template:
    metadata:
      labels:
        app: redis
        role: master
        tier: backend
    spec:
      containers:
      - name: master
        image: k8s.gcr.io/redis:e2e  # or just image: redis
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 6379
  1. マニフェストファイルをダウンロードしたディレクトリ内で、ターミナルウィンドウを起動します。

  2. redis-master-deployment.yamlファイルから、RedisのマスターのDeploymentを適用します。

    kubectl apply -f https://k8s.io/examples/application/guestbook/redis-master-deployment.yaml
    
  3. Podのリストを問い合わせて、RedisのマスターのPodが実行中になっていることを確認します。

    kubectl get pods
    

    結果は次のようになるはずです。

    NAME                            READY     STATUS    RESTARTS   AGE
    redis-master-1068406935-3lswp   1/1       Running   0          28s
    
  4. 次のコマンドを実行して、RedisのマスターのPodからログを表示します。

    kubectl logs -f POD-NAME
    

RedisのマスターのServiceを作成する

ゲストブックアプリケーションは、データを書き込むためにRedisのマスターと通信する必要があります。そのためには、Serviceを適用して、トラフィックをRedisのマスターのPodへプロキシーしなければなりません。Serviceは、Podにアクセスするためのポリシーを指定します。

apiVersion: v1
kind: Service
metadata:
  name: redis-master
  labels:
    app: redis
    role: master
    tier: backend
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    app: redis
    role: master
    tier: backend
  1. 次のredis-master-service.yamlから、RedisのマスターのServiceを適用します。

    kubectl apply -f https://k8s.io/examples/application/guestbook/redis-master-service.yaml
    
  2. Serviceのリストを問い合わせて、RedisのマスターのServiceが実行中になっていることを確認します。

    kubectl get service
    

    The response should be similar to this:

    NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
    kubernetes     ClusterIP   10.0.0.1     <none>        443/TCP    1m
    redis-master   ClusterIP   10.0.0.151   <none>        6379/TCP   8s
    

Redisのスレーブを起動する

Redisのマスターは1つのPodですが、レプリカのRedisのスレーブを追加することで、トラフィックの需要を満たすための高い可用性を持たせることができます。

RedisのスレーブのDeploymentを作成する

Deploymentはマニフェストファイル内に書かれた設定に基づいてスケールします。ここでは、Deploymentオブジェクトは2つのレプリカを指定しています。

もし1つもレプリカが実行されていなければ、このDeploymentは2つのレプリカをコンテナクラスター上で起動します。逆に、もしすでに2つ以上のレプリカが実行されていれば、実行中のレプリカが2つになるようにスケールダウンします。

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: redis-slave
  labels:
    app: redis
spec:
  selector:
    matchLabels:
      app: redis
      role: slave
      tier: backend
  replicas: 2
  template:
    metadata:
      labels:
        app: redis
        role: slave
        tier: backend
    spec:
      containers:
      - name: slave
        image: gcr.io/google_samples/gb-redisslave:v3
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        env:
        - name: GET_HOSTS_FROM
          value: dns
          # Using `GET_HOSTS_FROM=dns` requires your cluster to
          # provide a dns service. As of Kubernetes 1.3, DNS is a built-in
          # service launched automatically. However, if the cluster you are using
          # does not have a built-in DNS service, you can instead
          # access an environment variable to find the master
          # service's host. To do so, comment out the 'value: dns' line above, and
          # uncomment the line below:
          # value: env
        ports:
        - containerPort: 6379
  1. redis-slave-deployment.yamlファイルから、RedisのスレーブのDeploymentを適用します。

    kubectl apply -f https://k8s.io/examples/application/guestbook/redis-slave-deployment.yaml
    
  2. Podのリストを問い合わせて、RedisのスレーブのPodが実行中になっていることを確認します。

    kubectl get pods
    

    結果は次のようになるはずです。

    NAME                            READY     STATUS              RESTARTS   AGE
    redis-master-1068406935-3lswp   1/1       Running             0          1m
    redis-slave-2005841000-fpvqc    0/1       ContainerCreating   0          6s
    redis-slave-2005841000-phfv9    0/1       ContainerCreating   0          6s
    

RedisのスレーブのServiceを作成する

ゲストブックアプリケーションは、データを読み込むためにRedisのスレーブと通信する必要があります。Redisのスレーブが発見できるようにするためには、Serviceをセットアップする必要があります。Serviceは一連のPodに対する透過的なロードバランシングを提供します。

apiVersion: v1
kind: Service
metadata:
  name: redis-slave
  labels:
    app: redis
    role: slave
    tier: backend
spec:
  ports:
  - port: 6379
  selector:
    app: redis
    role: slave
    tier: backend
  1. 次のredis-slave-service.yamlファイルから、RedisのスレーブのServiceを適用します。

    kubectl apply -f https://k8s.io/examples/application/guestbook/redis-slave-service.yaml
    
  2. Serviceのリストを問い合わせて、RedisのスレーブのServiceが実行中になっていることを確認します。

    kubectl get services
    

    結果は次のようになるはずです。

    NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
    kubernetes     ClusterIP   10.0.0.1     <none>        443/TCP    2m
    redis-master   ClusterIP   10.0.0.151   <none>        6379/TCP   1m
    redis-slave    ClusterIP   10.0.0.223   <none>        6379/TCP   6s
    

ゲストブックのフロントエンドをセットアップして公開する

ゲストブックアプリケーションには、HTTPリクエストをサーブするPHPで書かれたウェブフロントエンドがあります。このアプリケーションは、書き込みリクエストに対してはredis-master Serviceに、読み込みリクエストに対してはredis-slave Serviceに接続するように設定されています。

ゲストブックのフロントエンドのDeploymentを作成する

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: frontend
  labels:
    app: guestbook
spec:
  selector:
    matchLabels:
      app: guestbook
      tier: frontend
  replicas: 3
  template:
    metadata:
      labels:
        app: guestbook
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google-samples/gb-frontend:v4
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        env:
        - name: GET_HOSTS_FROM
          value: dns
          # Using `GET_HOSTS_FROM=dns` requires your cluster to
          # provide a dns service. As of Kubernetes 1.3, DNS is a built-in
          # service launched automatically. However, if the cluster you are using
          # does not have a built-in DNS service, you can instead
          # access an environment variable to find the master
          # service's host. To do so, comment out the 'value: dns' line above, and
          # uncomment the line below:
          # value: env
        ports:
        - containerPort: 80
  1. frontend-deployment.yamlファイルから、フロントエンドのDeploymentを適用します。

    kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-deployment.yaml
    
  2. Podのリストを問い合わせて、3つのフロントエンドのレプリカが実行中になっていることを確認します。

    kubectl get pods -l app.kubernetes.io/name=guestbook -l app.kubernetes.io/component=frontend
    

    結果は次のようになるはずです。

    NAME                        READY     STATUS    RESTARTS   AGE
    frontend-3823415956-dsvc5   1/1       Running   0          54s
    frontend-3823415956-k22zn   1/1       Running   0          54s
    frontend-3823415956-w9gbt   1/1       Running   0          54s
    

フロントエンドのServiceを作成する

適用したredis-slaveおよびredis-master Serviceは、コンテナクラスター内部からのみアクセス可能です。これは、デフォルトのServiceのtypeがClusterIPであるためです。ClusterIPは、Serviceが指している一連のPodに対して1つのIPアドレスを提供します。このIPアドレスはクラスター内部からのみアクセスできます。

もしゲストの人にゲストブックにアクセスしてほしいのなら、フロントエンドServiceを外部から見えるように設定しなければなりません。そうすれば、クライアントはコンテナクラスターの外部からServiceにリクエストを送れるようになります。Minikubeでは、ServiceをNodePortでのみ公開できます。

apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # comment or delete the following line if you want to use a LoadBalancer
  type: NodePort 
  # if your cluster supports it, uncomment the following to automatically create
  # an external load-balanced IP for the frontend service.
  # type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: guestbook
    tier: frontend
  1. frontend-service.yamlファイルから、フロントエンドのServiceを提供します。

    kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-service.yaml
    
  2. Serviceのリストを問い合わせて、フロントエンドのServiceが実行中であることを確認します。

    kubectl get services
    

    結果は次のようになるはずです。

    NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
    frontend       NodePort    10.0.0.112   <none>       80:31323/TCP   6s
    kubernetes     ClusterIP   10.0.0.1     <none>        443/TCP        4m
    redis-master   ClusterIP   10.0.0.151   <none>        6379/TCP       2m
    redis-slave    ClusterIP   10.0.0.223   <none>        6379/TCP       1m
    

フロントエンドのServiceをNodePort経由で表示する

このアプリケーションをMinikubeやローカルのクラスターにデプロイした場合、ゲストブックを表示するためのIPアドレスを見つける必要があります。

  1. 次のコマンドを実行すると、フロントエンドServiceに対するIPアドレスを取得できます。

    minikube service frontend --url
    

    結果は次のようになるはずです。

    http://192.168.99.100:31323
    
  2. IPアドレスをコピーして、ブラウザー上でページを読み込み、ゲストブックを表示しましょう。

フロントエンドのServiceをLoadBalancer経由で表示する

もしfrontend-service.yamlマニフェストをtype: LoadBalancerでデプロイした場合、ゲストブックを表示するためのIPアドレスを見つける必要があります。

  1. 次のコマンドを実行すると、フロントエンドServiceに対するIPアドレスを取得できます。

    kubectl get service frontend
    

    結果は次のようになるはずです。

    NAME       TYPE        CLUSTER-IP      EXTERNAL-IP        PORT(S)        AGE
    frontend   ClusterIP   10.51.242.136   109.197.92.229     80:32372/TCP   1m
    
  2. 外部IPアドレス(EXTERNAL-IP)をコピーして、ブラウザー上でページを読み込み、ゲストブックを表示しましょう。

ウェブフロントエンドをスケールする

サーバーがDeploymentコントローラーを使用するServiceとして定義されているため、スケールアップやスケールダウンは簡単です。

  1. 次のコマンドを実行すると、フロントエンドのPodの数をスケールアップできます。

    kubectl scale deployment frontend --replicas=5
    
  2. Podのリストを問い合わせて、実行中のフロントエンドのPodの数を確認します。

    kubectl get pods
    

    結果は次のようになるはずです。

    NAME                            READY     STATUS    RESTARTS   AGE
    frontend-3823415956-70qj5       1/1       Running   0          5s
    frontend-3823415956-dsvc5       1/1       Running   0          54m
    frontend-3823415956-k22zn       1/1       Running   0          54m
    frontend-3823415956-w9gbt       1/1       Running   0          54m
    frontend-3823415956-x2pld       1/1       Running   0          5s
    redis-master-1068406935-3lswp   1/1       Running   0          56m
    redis-slave-2005841000-fpvqc    1/1       Running   0          55m
    redis-slave-2005841000-phfv9    1/1       Running   0          55m
    
  3. 次のコマンドを実行すると、フロントエンドのPodの数をスケールダウンできます。

    kubectl scale deployment frontend --replicas=2
    
  4. Podのリストを問い合わせて、実行中のフロントエンドのPodの数を確認します。

    kubectl get pods
    

    結果は次のようになるはずです。

    NAME                            READY     STATUS    RESTARTS   AGE
    frontend-3823415956-k22zn       1/1       Running   0          1h
    frontend-3823415956-w9gbt       1/1       Running   0          1h
    redis-master-1068406935-3lswp   1/1       Running   0          1h
    redis-slave-2005841000-fpvqc    1/1       Running   0          1h
    redis-slave-2005841000-phfv9    1/1       Running   0          1h
    

クリーンアップ

DeploymentとServiceを削除すると、実行中のPodも削除されます。ラベルを使用すると、複数のリソースを1つのコマンドで削除できます。

  1. 次のコマンドを実行すると、すべてのPod、Deployment、Serviceが削除されます。

    kubectl delete deployment -l app=redis
    kubectl delete service -l app=redis
    kubectl delete deployment -l app=guestbook
    kubectl delete service -l app=guestbook
    

    結果は次のようになるはずです。

    deployment.apps "redis-master" deleted
    deployment.apps "redis-slave" deleted
    service "redis-master" deleted
    service "redis-slave" deleted
    deployment.apps "frontend" deleted
    service "frontend" deleted
    
  2. Podのリストを問い合わせて、実行中のPodが存在しないことを確認します。

    kubectl get pods
    

    結果は次のようになるはずです。

    No resources found.
    

次の項目

4.3 - 例: PHP / Redisを使用したゲストブックの例にロギングとメトリクスを追加する

このチュートリアルは、Redisを使用したPHPのゲストブックのチュートリアルを前提に作られています。Elasticが開発したログ、メトリクス、ネットワークデータを転送するオープンソースの軽量データシッパーであるBeatsを、ゲストブックと同じKubernetesクラスターにデプロイします。BeatsはElasticsearchに対してデータの収集、分析、インデックス作成を行うため、結果の運用情報をKibana上で表示・分析できるようになります。この例は、以下のコンポーネントから構成されます。

目標

  • Redisを使用したPHPのゲストブックを起動する。
  • kube-state-metricsをインストールする。
  • KubernetesのSecretを作成する。
  • Beatsをデプロイする。
  • ログとメトリクスのダッシュボードを表示する。

始める前に

Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

バージョンを確認するには次のコマンドを実行してください: kubectl version.

追加で以下の作業が必要です。

Redisを使用したPHPのゲストブックを起動する

このチュートリアルは、Redisを使用したPHPのゲストブックのチュートリアルを前提に作られています。もしゲストブックアプリケーションが実行中なら、そのアプリケーションを監視できます。もしまだ実行中のアプリケーションがなければ、ゲストブックのデプロイの手順を行い、クリーンアップのステップは実行しないでください。ゲストブックが起動したら、このページに戻ってきてください。

Cluster role bindingを追加する

クラスターレベルのrole bindingを作成して、kube-state-metricsとBeatsをクラスターレベルで(kube-system内に)デプロイできるようにします。

kubectl create clusterrolebinding cluster-admin-binding \
 --clusterrole=cluster-admin --user=<k8sのプロバイダーアカウントと紐付いたあなたのメールアドレス>

kube-state-metricsをインストールする

Kubernetesのkube-state-metricsは、Kubernetes APIサーバーをlistenして、オブジェクトの状態に関するメトリクスを生成する単純なサービスです。Metricbeatはこれらのメトリクスを報告します。kube-state-metricsをゲストブックが実行されているKubernetesクラスターに追加しましょう。

kube-state-metricsが起動しているか確認する

kubectl get pods --namespace=kube-system | grep kube-state

必要に応じてkube-state-metricsをインストールする

git clone https://github.com/kubernetes/kube-state-metrics.git kube-state-metrics
kubectl apply -f kube-state-metrics/examples/standard
kubectl get pods --namespace=kube-system | grep kube-state-metrics

kube-state-metricsがRunningかつreadyの状態になっていることを確認します。

kubectl get pods -n kube-system -l app.kubernetes.io/name=kube-state-metrics

結果は次のようになります。

NAME                                 READY   STATUS    RESTARTS   AGE
kube-state-metrics-89d656bf8-vdthm   1/1     Running     0          21s

GitHubリポジトリのElasticの例をクローンする

git clone https://github.com/elastic/examples.git

これ以降のコマンドはexamples/beats-k8s-send-anywhereディレクトリ内のファイルを参照するため、カレントディレクトリを変更します。

cd examples/beats-k8s-send-anywhere

KubernetesのSecretを作成する

KubernetesのSecretとは、パスワード、トークン、または鍵などの小さなサイズの機密データを含んだオブジェクトのことです。このような機密情報はPodのspecやイメージの中に置くことも不可能ではありませんが、Secretオブジェクトの中に置くことで、情報の使用方法を適切に制御したり、誤って公開してしまうリスクを減らすことができます。

セルフマネージド

Elastic Cloud上のElasticsearch Serviceに接続する場合は、マネージドサービスタブに切り替えてください。

クレデンシャルを設定する

セルフマネージドのElasticsearchとKibanaへ接続する場合、KubernetesのSecretを作成するために編集するべきファイルは4つあります(セルフマネージドとは、事実上Elastic Cloud以外で実行されているElasticsearch Serviceを指します)。ファイルは次の4つです。

  1. ELASTICSEARCH_HOSTS
  2. ELASTICSEARCH_PASSWORD
  3. ELASTICSEARCH_USERNAME
  4. KIBANA_HOST

これらのファイルにElasticsearchクラスターとKibanaホストの情報を設定してください。ここでは例をいくつか示します(こちらの設定も参照してください)。

ELASTICSEARCH_HOSTS

  1. Elastic Elasticsearch Helm Chartで作成したnodeGroupの場合。

    ["http://elasticsearch-master.default.svc.cluster.local:9200"]
    
  2. Mac上で単一のElasticsearchノードが実行されており、BeatsがDocker for Macで実行中の場合。

    ["http://host.docker.internal:9200"]
    
  3. 2ノードのElasticsearchがVM上または物理ハードウェア上で実行中の場合。

    ["http://host1.example.com:9200", "http://host2.example.com:9200"]
    

ELASTICSEARCH_HOSTSを編集します。

vi ELASTICSEARCH_HOSTS

ELASTICSEARCH_PASSWORD

パスワードだけを書きます。空白、クォート、<>などの文字は書かないでください。

<yoursecretpassword>

ELASTICSEARCH_PASSWORDを編集します。

vi ELASTICSEARCH_PASSWORD

ELASTICSEARCH_USERNAME

ユーザー名だけを書きます。空白、クォート、<>などの文字は書かないでください。

<Elasticsearchに追加するユーザー名>

ELASTICSEARCH_USERNAMEを編集します。

vi ELASTICSEARCH_USERNAME

KIBANA_HOST

  1. Elastic Kibana Helm Chartで作成したKibanaインスタンスが実行中の場合。defaultというサブドメインは、default Namespaceを指します。もしHelm Chartを別のNamespaceにデプロイした場合、サブドメインは異なります。

    "kibana-kibana.default.svc.cluster.local:5601"
    
  2. Mac上でKibanaインスタンスが実行中で、BeatsがDocker for Macで実行中の場合。

    "host.docker.internal:5601"
    
  3. 2つのElasticsearchノードが、VMまたは物理ハードウェア上で実行中の場合。

    "host1.example.com:5601"
    

KIBANA_HOSTを編集します。

vi KIBANA_HOST

KubernetesのSecretを作成する

次のコマンドを実行すると、KubernetesのシステムレベルのNamespace(kube-system)に、たった今編集したファイルを元にSecretが作成されます。

kubectl create secret generic dynamic-logging \
  --from-file=./ELASTICSEARCH_HOSTS \
  --from-file=./ELASTICSEARCH_PASSWORD \
  --from-file=./ELASTICSEARCH_USERNAME \
  --from-file=./KIBANA_HOST \
  --namespace=kube-system

マネージドサービス

このタブは、Elastic Cloud上のElasticsearch Serviceの場合のみ必要です。もしセルフマネージドのElasticsearchとKibanaのDeployment向けにSecretをすでに作成した場合、Beatsをデプロイするに進んでください。

クレデンシャルを設定する

Elastic Cloud上のマネージドElasticsearch Serviceに接続する場合、KubernetesのSecretを作成するために編集する必要があるのは、次の2つのファイルです。

  1. ELASTIC_CLOUD_AUTH
  2. ELASTIC_CLOUD_ID

Deploymentを作成するときに、Elasticsearch Serviceのコンソールから提供された情報を設定してください。以下に例を示します。

ELASTIC_CLOUD_ID

devk8s:ABC123def456ghi789jkl123mno456pqr789stu123vwx456yza789bcd012efg345hijj678klm901nop345zEwOTJjMTc5YWQ0YzQ5OThlN2U5MjAwYTg4NTIzZQ==

ELASTIC_CLOUD_AUTH

ユーザー名、コロン(:)、パスワードだけを書きます。空白やクォートは書かないでください。

elastic:VFxJJf9Tjwer90wnfTghsn8w

必要なファイルを編集する

vi ELASTIC_CLOUD_ID
vi ELASTIC_CLOUD_AUTH

KubernetesのSecretを作成する

次のコマンドを実行すると、KubernetesのシステムレベルのNamespace(kube-system)に、たった今編集したファイルを元にSecretが作成されます。

kubectl create secret generic dynamic-logging \
  --from-file=./ELASTIC_CLOUD_ID \
  --from-file=./ELASTIC_CLOUD_AUTH \
  --namespace=kube-system

Beatsをデプロイする

マニフェストファイルはBeatごとに提供されます。これらのマニフェストファイルは、上で作成したSecretを使用して、BeatsをElasticsearchおよびKibanaサーバーに接続するように設定します。

Filebeatについて

Filebeatは、Kubernetesのノードと、ノード上で実行している各Pod内のコンテナから、ログを収集します。FilebeatはDaemonSetとしてデプロイされます。FilebeatはKubernetesクラスター上で実行されているアプリケーションを自動検出することもできます。起動時にFilebeatは既存のコンテナをスキャンし、それらに対して適切な設定を立ち上げ、その後、新しいstart/stopイベントを監視します。

Filebeatが、ゲストブックアプリケーションでデプロイしたRedisコンテナからRedisのログを特定・解析できるように自動検出を設定する例を示します。この設定はfilebeat-kubernetes.yamlファイル内にあります。

- condition.contains:
    kubernetes.labels.app: redis
  config:
    - module: redis
      log:
        input:
          type: docker
          containers.ids:
            - ${data.kubernetes.container.id}
      slowlog:
        enabled: true
        var.hosts: ["${data.host}:${data.port}"]

この設定により、Filebeatは、appラベルにredisという文字列が含まれるコンテナを検出したときにredis Filebeatモジュールを適用するようになります。redisモジュールには、input typeとしてdockerを使用することで(このRedisコンテナの標準出力のストリームと関連付けられた、Kubernetesノード上のファイルを読み取ることで)コンテナからlogストリームを収集する機能があります。さらに、このモジュールには、コンテナのメタデータとして提供された適切なPodのホストとポートと接続することにより、Redisのslowlogエントリーを収集する機能もあります。

Filebeatをデプロイする

kubectl create -f filebeat-kubernetes.yaml

検証する

kubectl get pods -n kube-system -l k8s-app=filebeat-dynamic

Metricbeatについて

Metricbeatの自動検出はFilebeatと同じ方法で設定します。以下にMetricbeatにおけるRedisコンテナの自動検出の設定を示します。この設定はmetricbeat-kubernetes.yamlファイル内にあります。

- condition.equals:
    kubernetes.labels.tier: backend
  config:
    - module: redis
      metricsets: ["info", "keyspace"]
      period: 10s

      # Redis hosts
      hosts: ["${data.host}:${data.port}"]

この設定により、Metricbeatは、tierラベルにbackendという文字列が含まれるコンテナを検出したときにredis Metricbeatモジュールを適用するようになります。redisモジュールには、コンテナのメタデータとして提供された適切なPodのホストとポートと接続することにより、コンテナからinfoおよびkeyspaceメトリクスを収集する機能があります。

Metricbeatをデプロイする

kubectl create -f metricbeat-kubernetes.yaml

検証する

kubectl get pods -n kube-system -l k8s-app=metricbeat

Packetbeatについて

Packetbeatの設定は、FilebeatやMetricbeatとは異なります。コンテナのラベルに対するパターンマッチを指定する代わりに、関連するプロトコルとポート番号に基づいた設定を書きます。以下に示すのは、ポート番号のサブセットです。

packetbeat.interfaces.device: any

packetbeat.protocols:
- type: dns
  ports: [53]
  include_authorities: true
  include_additionals: true

- type: http
  ports: [80, 8000, 8080, 9200]

- type: mysql
  ports: [3306]

- type: redis
  ports: [6379]

packetbeat.flows:
  timeout: 30s
  period: 10s

Packetbeatをデプロイする

kubectl create -f packetbeat-kubernetes.yaml

検証する

kubectl get pods -n kube-system -l k8s-app=packetbeat-dynamic

Kibanaで表示する

ブラウザでKibanaを開き、Dashboardアプリケーションを開きます。検索バーでKubernetesと入力して、KubernetesのためのMetricbeatダッシュボードを開きます。このダッシュボードでは、NodeやDeploymentなどの状態のレポートが表示されます。

DashboardページでPacketbeatと検索し、Packetbeat overviewを表示します。

同様に、ApacheおよびRedisのためのDashboardを表示します。それぞれに対してログとメトリクスのDashboardが表示されます。Apache Metricbeat dashboardには何も表示されていないはずです。Apache Filebeat dashboardを表示して、ページの最下部までスクロールしてApacheのエラーログを確認します。ログを読むと、Apacheのメトリクスが表示されない理由が分かります。

Metricbeatを有効にしてApacheのメトリクスを取得するには、mod-status設定ファイルを含んだConfigMapを追加してゲストブックを再デプロイすることで、server-statusを有効にします。

Deploymentをスケールして新しいPodが監視されるのを確認する

存在するDeploymentを一覧します。

kubectl get deployments

出力は次のようになります。

NAME            READY   UP-TO-DATE   AVAILABLE   AGE
frontend        3/3     3            3           3h27m
redis-master    1/1     1            1           3h27m
redis-slave     2/2     2            2           3h27m

frontendのPodを2つにスケールダウンします。

kubectl scale --replicas=2 deployment/frontend

出力は次のようになります。

deployment.extensions/frontend scaled

frontendのPodを再び3つにスケールアップします。

kubectl scale --replicas=3 deployment/frontend

Kibana上で変更を表示する

スクリーンショットを確認し、指定されたフィルターを追加して、ビューにカラムを追加します。赤い枠の右下を見ると、ScalingReplicaSetというエントリーが確認できます。そこからリストを上に見てゆくと、イメージのpull、ボリュームのマウント、Podのスタートなどのイベントが確認できます。

Kibana Discover

クリーンアップ

DeploymentとServiceを削除すると、実行中のすべてのPodも削除されます。ラベルを使って複数のリソースを1つのコマンドで削除します。

  1. 次のコマンドを実行して、すべてのPod、Deployment、Serviceを削除します。

    kubectl delete deployment -l app=redis
    kubectl delete service -l app=redis
    kubectl delete deployment -l app=guestbook
    kubectl delete service -l app=guestbook
    kubectl delete -f filebeat-kubernetes.yaml
    kubectl delete -f metricbeat-kubernetes.yaml
    kubectl delete -f packetbeat-kubernetes.yaml
    kubectl delete secret dynamic-logging -n kube-system
    
  2. Podの一覧を問い合わせて、実行中のPodがなくなったことを確認します。

    kubectl get pods
    

    結果は次のようになるはずです。

    No resources found.
    

次の項目

5 - ステートフルアプリケーション

5.1 - StatefulSetの基本

このチュートリアルでは、StatefulSetを使用したアプリケーションを管理するための基本を説明します。StatefulSetのPodを作成、削除、スケール、そして更新する方法について紹介します。

始める前に

このチュートリアルを始める前に、以下のKubernetesの概念について理解しておく必要があります。

目標

StatefulSetはステートフルアプリケーションや分散システムで使用するために存在します。しかし、Kubernetes上のステートフルアプリケーションや分散システムは、広範で複雑なトピックです。StatefulSetの基本的な機能を示すという目的のため、また、ステートフルアプリケーションを分散システムと混同しないようにするために、ここでは、Statefulsetを使用する単純なウェブアプリケーションのデプロイを行います。

このチュートリアルを終えると、以下のことが理解できるようになります。

  • StatefulSetの作成方法
  • StatefulSetがどのようにPodを管理するのか
  • StatefulSetの削除方法
  • StatefulSetのスケール方法
  • StatefulSetが管理するPodの更新方法

StatefulSetを作成する

はじめに、以下の例を使ってStatefulSetを作成しましょう。これは、コンセプトのStatefulSetのページで使ったものと同じような例です。nginxというheadless Serviceを作成し、webというStatefulSet内のPodのIPアドレスを公開します。

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

上の例をダウンロードして、web.yamlという名前で保存します。

ここでは、ターミナルウィンドウを2つ使う必要があります。1つ目のターミナルでは、kubectl getを使って、StatefulSetのPodの作成を監視します。

kubectl get pods -w -l app=nginx

2つ目のターミナルでは、kubectl applyを使って、web.yamlに定義されたheadless ServiceとStatefulSetを作成します。

kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created

上のコマンドを実行すると、2つのPodが作成され、それぞれのPodでNGINXウェブサーバーが実行されます。nginxServiceを取得してみましょう。

kubectl get service nginx
NAME      TYPE         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx     ClusterIP    None         <none>        80/TCP    12s

そして、webStatefulSetを取得して、2つのリソースの作成が成功したことも確認します。

kubectl get statefulset web
NAME      DESIRED   CURRENT   AGE
web       2         1         20s

順序付きPodの作成

n 個のレプリカを持つStatefulSetは、Podをデプロイするとき、1つずつ順番に作成し、 {0..n-1} という順序付けを行います。1つ目のターミナルでkubectl getコマンドの出力を確認しましょう。最終的に、以下の例のような出力が表示されるはずです。

kubectl get pods -w -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     0/1       Pending   0          0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         19s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         18s

web-0Podが Running (Pod Phaseを参照)かつ Ready (Pod Conditionstypeを参照)の状態になるまでは、web-1Podが起動していないことに注目してください。

StatefulSet内のPod

StatefulSet内のPodは、ユニークな順序インデックスと安定したネットワーク識別子を持ちます。

Podの順序インデックスを確かめる

StatefulSetのPodを取得します。

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          1m
web-1     1/1       Running   0          1m

StatefulSetのコンセプトで説明したように、StatefulSet内のPodは安定したユニークな識別子を持ちます。この識別子は、StatefulSetコントローラーによって各Podに割り当てられる、ユニークな順序インデックスに基づいて付けられます。Podの名前は、<statefulsetの名前>-<順序インデックス>という形式です。webStatefulSetは2つのレプリカを持つため、web-0web-1という2つのPodを作成します。

安定したネットワーク識別子の使用

各Podは、順序インデックスに基づいた安定したホスト名を持ちます。kubectl execを使用して、各Pod内でhostnameコマンドを実行してみましょう。

for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done
web-0
web-1

kubectl runを使用して、dnsutilsパッケージのnslookupコマンドを提供するコンテナを実行します。Podのホスト名に対してnslookupを実行すると、クラスター内のDNSアドレスが確認できます。

kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm

これにより、新しいシェルが起動します。新しいシェルで、次のコマンドを実行します。

# このコマンドは、dns-testコンテナのシェルで実行してください
nslookup web-0.nginx

出力は次のようになります。

Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.6

nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.6

(コンテナのシェルを終了するために、exitコマンドを実行してください。)

headless serviceのCNAMEは、SRVレコードを指しています(1つのレコードがRunningかつReadyのPodに対応します)。SRVレコードは、PodのIPアドレスを含むAレコードを指します。

1つ目のターミナルで、StatefulSetのPodを監視します。

kubectl get pod -w -l app=nginx

2つ目のターミナルで、kubectl deleteを使用して、StatefulSetのすべてのPodを削除します。

kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted

StatefulSetがPodを再起動して、2つのPodがRunningかつReadyの状態に移行するのを待ちます。

kubectl get pod -w -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     0/1       ContainerCreating   0          0s
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         34s

kubectl execkubectl runコマンドを使用して、Podのホスト名とクラスター内DNSエントリーを確認します。まず、Podのホスト名を見てみましょう。

for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
web-0
web-1

その後、次のコマンドを実行します。

kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm /bin/sh

これにより、新しいシェルが起動します。新しいシェルで、次のコマンドを実行します。

# このコマンドは、dns-testコンテナのシェルで実行してください
nslookup web-0.nginx

出力は次のようになります。

Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.7

nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.8

(コンテナのシェルを終了するために、exitコマンドを実行してください。)

Podの順序インデックス、ホスト名、SRVレコード、そしてAレコード名は変化していませんが、Podに紐付けられたIPアドレスは変化する可能性があります。このチュートリアルで使用しているクラスターでは、IPアドレスは変わりました。このようなことがあるため、他のアプリケーションがStatefulSet内のPodに接続するときには、IPアドレスで指定しないことが重要です。

StatefulSetの有効なメンバーを探して接続する必要がある場合は、headless ServiceのCNAME(nginx.default.svc.cluster.local)をクエリしなければなりません。CNAMEに紐付けられたSRVレコードには、StatefulSet内のRunnningかつReadyなPodだけが含まれます。

アプリケーションがlivenessとreadinessをテストするコネクションのロジックをすでに実装している場合、PodのSRVレコード(web-0.nginx.default.svc.cluster.localweb-1.nginx.default.svc.cluster.local)をPodが安定しているものとして使用できます。PodがRunning and Readyな状態に移行すれば、アプリケーションはPodのアドレスを発見できるようになります。

安定したストレージへの書き込み

web-0およびweb-1のためのPersistentVolumeClaimを取得しましょう。

kubectl get pvc -l app=nginx

出力は次のようになります。

NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           48s
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           48s

StatefulSetコントローラーは、2つのPersistentVolumeにバインドされた2つのPersistentVolumeClaimを作成しています。

このチュートリアルで使用しているクラスターでは、PersistentVolumeの動的なプロビジョニングが設定されているため、PersistentVolumeが自動的に作成されてバインドされています。

デフォルトでは、NGINXウェブサーバーは/usr/share/nginx/html/index.htmlに置かれたindexファイルを配信します。StatefulSetのspec内のvolumeMountsフィールドによって、/usr/share/nginx/htmlディレクトリがPersistentVolume上にあることが保証されます。

Podのホスト名をindex.htmlファイルに書き込むことで、NGINXウェブサーバーがホスト名を配信することを検証しましょう。

for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

1つ目のターミナルで、StatefulSetのPodを監視します。

kubectl get pod -w -l app=nginx

2つ目のターミナルで、StatefulSetのすべてのPodを削除します。

kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted

1つ目のターミナルでkubectl getコマンドの出力を確認して、すべてのPodがRunningかつReadyの状態に変わるまで待ちます。

kubectl get pod -w -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     0/1       ContainerCreating   0          0s
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         34s

ウェブサーバーがホスト名を配信し続けていることを確認します。

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

もしweb-0およびweb-1が再スケジュールされたとしても、Podは同じホスト名を配信し続けます。これは、PodのPersistentVolumeClaimに紐付けられたPersistentVolumeが、PodのvolumeMountsに再マウントされるためです。web-0web-1がどんなノードにスケジュールされたとしても、PodのPersistentVolumeは適切なマウントポイントにマウントされます。

StatefulSetをスケールする

StatefulSetのスケールとは、レプリカ数を増減することを意味します。これは、replicasフィールドを更新することによって実現できます。StatefulSetのスケールには、kubectl scalekubectl patchのどちらも使用できます。

スケールアップ

1つ目のターミナルで、StatefulSet内のPodを監視します。

kubectl get pods -w -l app=nginx

2つ目のターミナルで、kubectl scaleを使って、レプリカ数を5にスケールします。

kubectl scale sts web --replicas=5
statefulset.apps/web scaled

1つ目のターミナルのkubectl getコマンドの出力を確認して、3つの追加のPodがRunningかつReadyの状態に変わるまで待ちます。

kubectl get pods -w -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2h
web-1     1/1       Running   0          2h
NAME      READY     STATUS    RESTARTS   AGE
web-2     0/1       Pending   0          0s
web-2     0/1       Pending   0         0s
web-2     0/1       ContainerCreating   0         0s
web-2     1/1       Running   0         19s
web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         0s
web-3     0/1       ContainerCreating   0         0s
web-3     1/1       Running   0         18s
web-4     0/1       Pending   0         0s
web-4     0/1       Pending   0         0s
web-4     0/1       ContainerCreating   0         0s
web-4     1/1       Running   0         19s

StatefulSetコントローラーはレプリカ数をスケールします。 StatefulSetを作成するで説明したように、StatefulSetコントローラーは各Podを順序インデックスに従って1つずつ作成し、次のPodを起動する前に、1つ前のPodがRunningかつReadyの状態になるまで待ちます。

スケールダウン

1つ目のターミナルで、StatefulSetのPodを監視します。

kubectl get pods -w -l app=nginx

2つ目のターミナルで、kubectl patchコマンドを使用して、StatefulSetを3つのレプリカにスケールダウンします。

kubectl patch sts web -p '{"spec":{"replicas":3}}'
statefulset.apps/web patched

web-4およびweb-3がTerminatingの状態になるまで待ちます。

kubectl get pods -w -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          3h
web-1     1/1       Running             0          3h
web-2     1/1       Running             0          55s
web-3     1/1       Running             0          36s
web-4     0/1       ContainerCreating   0          18s
NAME      READY     STATUS    RESTARTS   AGE
web-4     1/1       Running   0          19s
web-4     1/1       Terminating   0         24s
web-4     1/1       Terminating   0         24s
web-3     1/1       Terminating   0         42s
web-3     1/1       Terminating   0         42s

順序付きPodを削除する

コントローラーは、順序インデックスの逆順に1度に1つのPodを削除し、次のPodを削除する前には、各Podが完全にシャットダウンするまで待機しています。

StatefulSetのPersistentVolumeClaimを取得しましょう。

kubectl get pvc -l app=nginx
NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-2   Bound     pvc-e1125b27-b508-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-3   Bound     pvc-e1176df6-b508-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-4   Bound     pvc-e11bb5f8-b508-11e6-932f-42010a800002   1Gi        RWO           13h

まだ、5つのPersistentVolumeClaimと5つのPersistentVolumeが残っています。安定したストレージへの書き込みを読むと、StatefulSetのPodが削除されても、StatefulSetのPodにマウントされたPersistentVolumeは削除されないと書かれています。このことは、StatefulSetのスケールダウンによってPodが削除された場合にも当てはまります。

StatefulSetsを更新する

Kubernetes 1.7以降では、StatefulSetコントローラーは自動アップデートをサポートしています。使われる戦略は、StatefulSet APIオブジェクトのspec.updateStrategyフィールドによって決まります。この機能はコンテナイメージのアップグレード、リソースのrequestsやlimits、ラベル、StatefulSet内のPodのアノテーションの更新時に利用できます。有効なアップデートの戦略は、RollingUpdateOnDeleteの2種類です。

RollingUpdateは、StatefulSetのデフォルトのアップデート戦略です。

RollingUpdate

RollingUpdateアップデート戦略は、StatefulSetの保証を尊重しながら、順序インデックスの逆順にStatefulSet内のすべてのPodをアップデートします。

webStatefulSetにpatchを当てて、RollingUpdateアップデート戦略を適用しましょう。

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate"}}}'
statefulset.apps/web patched

1つ目のターミナルで、webStatefulSetに再度patchを当てて、コンテナイメージを変更します。

kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"gcr.io/google_containers/nginx-slim:0.8"}]'
statefulset.apps/web patched

2つ目のターミナルで、StatefulSet内のPodを監視します。

kubectl get pod -l app=nginx -w

出力は次のようになります。

NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          7m
web-1     1/1       Running   0          7m
web-2     1/1       Running   0          8m
web-2     1/1       Terminating   0         8m
web-2     1/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Pending   0         0s
web-2     0/1       Pending   0         0s
web-2     0/1       ContainerCreating   0         0s
web-2     1/1       Running   0         19s
web-1     1/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         6s
web-0     1/1       Terminating   0         7m
web-0     1/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Pending   0         0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         10s

StatefulSet内のPodは、順序インデックスの逆順に更新されました。StatefulSetコントローラーは各Podを終了させ、次のPodを更新する前に、新しいPodがRunningかつReadyの状態に変わるまで待機します。ここで、StatefulSetコントローラーは順序インデックスの前のPodがRunningかつReadyの状態になるまで次のPodの更新を始めず、現在の状態へのアップデートに失敗したPodがあった場合、そのPodをリストアすることに注意してください。

すでにアップデートを受け取ったPodは、アップデートされたバージョンにリストアされます。まだアップデートを受け取っていないPodは、前のバージョンにリストアされます。このような方法により、もし途中で失敗が起こっても、コントローラーはアプリケーションが健全な状態を保ち続けられるようにし、更新が一貫したものになるようにします。

Podを取得して、コンテナイメージを確認してみましょう。

for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
k8s.gcr.io/nginx-slim:0.8
k8s.gcr.io/nginx-slim:0.8
k8s.gcr.io/nginx-slim:0.8

現在、StatefulSet内のすべてのPodは、前のコンテナイメージを実行しています。

ステージングアップデート

RollingUpdateアップデート戦略にpartitionパラメーターを使用すると、StatefulSetへのアップデートをステージングすることができます。ステージングアップデートを利用すれば、StatefulSet内のすべてのPodを現在のバージョンにしたまま、StatefulSetの.spec.templateを変更することが可能になります。

webStatefulSetにpatchを当てて、updateStrategyフィールドにpartitionを追加しましょう。

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
statefulset.apps/web patched

StatefulSetに再度patchを当てて、コンテナイメージを変更します。

kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"k8s.gcr.io/nginx-slim:0.7"}]'
statefulset.apps/web patched

StatefulSet内のPodを削除します。

kubectl delete pod web-2
pod "web-2" deleted

PodがRunningかつReadyになるまで待ちます。

kubectl get pod -l app=nginx -w
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          4m
web-1     1/1       Running             0          4m
web-2     0/1       ContainerCreating   0          11s
web-2     1/1       Running   0         18s

Podのコンテナイメージを取得します。

kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
k8s.gcr.io/nginx-slim:0.8

アップデート戦略がRollingUpdateであっても、StatefulSetが元のコンテナを持つPodをリストアしたことがわかります。これは、Podの順序インデックスがupdateStrategyで指定したpartitionより小さいためです。

カナリア版をロールアウトする

ステージングアップデートのときに指定したpartitionを小さくすることで、変更をテストするためのカナリア版をロールアウトできます。

StatefulSetにpatchを当てて、partitionを小さくします。

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
statefulset.apps/web patched

web-2がRunningかつReadyの状態になるまで待ちます。

kubectl get pod -l app=nginx -w
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          4m
web-1     1/1       Running             0          4m
web-2     0/1       ContainerCreating   0          11s
web-2     1/1       Running   0         18s

Podのコンテナを取得します。

kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
k8s.gcr.io/nginx-slim:0.7

partitionを変更すると、StatefulSetコントローラーはPodを自動的に更新します。Podの順序インデックスがpartition以上の値であるためです。

web-1Podを削除します。

kubectl delete pod web-1
pod "web-1" deleted

web-1PodがRunningかつReadyになるまで待ちます。

kubectl get pod -l app=nginx -w

出力は次のようになります。

NAME      READY     STATUS        RESTARTS   AGE
web-0     1/1       Running       0          6m
web-1     0/1       Terminating   0          6m
web-2     1/1       Running       0          2m
web-1     0/1       Terminating   0         6m
web-1     0/1       Terminating   0         6m
web-1     0/1       Terminating   0         6m
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         18s

web-1Podのコンテナイメージを取得します。

kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
k8s.gcr.io/nginx-slim:0.8

Podの順序インデックスがpartitionよりも小さいため、web-1は元の設定のコンテナイメージにリストアされました。partitionを指定すると、StatefulSetの.spec.templateが更新されたときに、順序インデックスがそれ以上の値を持つすべてのPodがアップデートされます。partitionよりも小さな順序インデックスを持つPodが削除されたり終了されたりすると、元の設定のPodにリストアされます。

フェーズロールアウト

カナリア版をロールアウトするのと同じような方法でパーティションされたローリングアップデートを使用すると、フェーズロールアウト(例: 線形、幾何級数的、指数関数的ロールアウト)を実行できます。フェーズロールアウトを実行するには、コントローラーがアップデートを途中で止めてほしい順序インデックスをpartitionに設定します。

現在、partitionは2に設定されています。partitionを0に設定します。

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'
statefulset.apps/web patched

StatefulSet内のすべてのPodがRunningかつReadyの状態になるまで待ちます。

kubectl get pod -l app=nginx -w

出力は次のようになります。

NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          3m
web-1     0/1       ContainerCreating   0          11s
web-2     1/1       Running             0          2m
web-1     1/1       Running   0         18s
web-0     1/1       Terminating   0         3m
web-0     1/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Pending   0         0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         3s

StatefulSet内のPodのコンテナイメージの詳細を取得します。

for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
k8s.gcr.io/nginx-slim:0.7
k8s.gcr.io/nginx-slim:0.7
k8s.gcr.io/nginx-slim:0.7

partition0に移動することで、StatefulSetがアップデート処理を続けられるようにできます。

OnDelete

OnDeleteアップデート戦略は、(1.6以前の)レガシーな動作を実装しています。このアップデート戦略を選択すると、StatefulSetの.spec.templateフィールドへ変更を加えても、StatefulSetコントローラーが自動的にPodを更新しなくなります。この戦略を選択するには、.spec.template.updateStrategy.typeOnDeleteを設定します。

StatefulSetを削除する

StatefulSetは、非カスケードな削除とカスケードな削除の両方をサポートしています。非カスケードな削除では、StatefulSetが削除されても、StatefulSet内のPodは削除されません。カスケードな削除では、StatefulSetとPodが一緒に削除されます。

非カスケードな削除

1つ目のターミナルで、StatefulSet内のPodを監視します

kubectl get pods -w -l app=nginx

kubectl deleteを使用して、StatefulSetを削除します。このとき、--cascade=orphanパラメーターをコマンドに与えてください。このパラメーターは、Kubernetesに対して、StatefulSetだけを削除して配下のPodは削除しないように指示します。

kubectl delete statefulset web --cascade=orphan
statefulset.apps "web" deleted

Podを取得して、ステータスを確認します。

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          6m
web-1     1/1       Running   0          7m
web-2     1/1       Running   0          5m

webが削除されても、すべてのPodはまだRunningかつReadyの状態のままです。web-0を削除します。

kubectl delete pod web-0
pod "web-0" deleted

StatefulSetのPodを取得します。

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-1     1/1       Running   0          10m
web-2     1/1       Running   0          7m

webStatefulSetはすでに削除されているため、web-0は再起動しません。

1つ目のターミナルで、StatefulSetのPodを監視します。

kubectl get pods -w -l app=nginx

2つ目のターミナルで、StatefulSetを再作成します。もしnginxServiceを削除しなかった場合(この場合は削除するべきではありませんでした)、Serviceがすでに存在することを示すエラーが表示されます。

kubectl apply -f web.yaml
statefulset.apps/web created
service/nginx unchanged

このエラーは無視してください。このメッセージは、すでに存在する nginx というheadless Serviceを作成しようと試みたということを示しているだけです。

1つ目のターミナルで、kubectl getコマンドの出力を確認します。

kubectl get pods -w -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-1     1/1       Running   0          16m
web-2     1/1       Running   0          2m
NAME      READY     STATUS    RESTARTS   AGE
web-0     0/1       Pending   0          0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         18s
web-2     1/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m

webStatefulSetが再作成されると、最初にweb-0を再実行します。web-1はすでにRunningかつReadyの状態であるため、web-0がRunningかつReadyの状態に移行すると、StatefulSetは単純にこのPodを選びます。StatefulSetをreplicasを2にして再作成したため、一度web-0が再作成されて、web-1がすでにRunningかつReadyの状態であることが判明したら、web-2は停止されます。

Podのウェブサーバーが配信しているindex.htmlファイルのコンテンツをもう一度見てみましょう。

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

たとえStatefulSetとweb-0Podの両方が削除されても、Podは最初にindex.htmlファイルに書き込んだホスト名をまだ配信しています。これは、StatefulSetがPodに紐付けられたPersistentVolumeを削除しないためです。StatefulSetを再作成してweb-0を再実行すると、元のPersistentVolumeが再マウントされます。

カスケードな削除

1つ目のターミナルで、StatefulSet内のPodを監視します。

kubectl get pods -w -l app=nginx

2つ目のターミナルで、StatefulSetをもう一度削除します。今回は、--cascade=orphanパラメーターを省略します。

kubectl delete statefulset web
statefulset.apps "web" deleted

1つ目のターミナルで実行しているkubectl getコマンドの出力を確認し、すべてのPodがTerminatingの状態に変わるまで待ちます。

kubectl get pods -w -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          11m
web-1     1/1       Running   0          27m
NAME      READY     STATUS        RESTARTS   AGE
web-0     1/1       Terminating   0          12m
web-1     1/1       Terminating   0         29m
web-0     0/1       Terminating   0         12m
web-0     0/1       Terminating   0         12m
web-0     0/1       Terminating   0         12m
web-1     0/1       Terminating   0         29m
web-1     0/1       Terminating   0         29m
web-1     0/1       Terminating   0         29m

スケールダウンのセクションで見たように、順序インデックスの逆順に従って、Podは一度に1つずつ終了します。StatefulSetコントローラーは、次のPodを終了する前に、前のPodが完全に終了するまで待ちます。

kubectl delete service nginx
service "nginx" deleted

さらにもう一度、StatefulSetとheadless Serviceを再作成します。

kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created

StatefulSet上のすべてのPodがRunningかつReadyの状態に変わったら、Pod上のindex.htmlファイルのコンテンツを取得します。

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

StatefulSetを完全に削除して、すべてのPodが削除されたとしても、PersistentVolumeがマウントされたPodが再生成されて、web-0web-1はホスト名の配信を続けます。

最後に、webStatefulSetを削除します。

kubectl delete service nginx
service "nginx" deleted

そして、nginxServiceも削除します。

kubectl delete statefulset web
statefulset "web" deleted

Pod管理ポリシー

分散システムによっては、StatefulSetの順序の保証が不必要であったり望ましくない場合もあります。こうしたシステムでは、一意性と同一性だけが求められます。この問題に対処するために、Kubernetes 1.7でStatefulSet APIオブジェクトに.spec.podManagementPolicyが導入されました。

OrderedReadyのPod管理

OrderedReadyのPod管理はStatefulSetのデフォルトの設定です。StatefulSetコントローラーに対して、これまでに紹介したような順序の保証を尊重するように指示します。

ParallelのPod管理

ParallelのPod管理では、StatefulSetコントローラーに対して、PodがRunningかつReadyの状態や完全に停止するまで待たないように指示し、すべてのPodを並列に起動または停止させるようにします。

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  podManagementPolicy: "Parallel"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

上の例をダウンロードして、web-parallel.yamlという名前でファイルに保存してください。

このマニフェストは、.spec.podManagementPolicyParallelに設定されている以外は、前にダウンロードしたwebStatefulSetと同一です。

1つ目のターミナルで、StatefulSet内のPodを監視します。

kubectl get pod -l app=nginx -w

2つ目のターミナルで、マニフェスト内のStatefulSetとServiceを作成します。

kubectl apply -f web-parallel.yaml
service/nginx created
statefulset.apps/web created

1つ目のターミナルで実行したkubectl getコマンドの出力を確認します。

kubectl get pod -l app=nginx -w
NAME      READY     STATUS    RESTARTS   AGE
web-0     0/1       Pending   0          0s
web-0     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-1     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         10s
web-1     1/1       Running   0         10s

StatefulSetコントローラーはweb-0web-1を同時に起動しています。

2つ目のターミナルで、StatefulSetをスケールしてみます。

kubectl scale statefulset/web --replicas=4
statefulset.apps/web scaled

kubectl getコマンドを実行しているターミナルの出力を確認します。

web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         7s
web-3     0/1       ContainerCreating   0         7s
web-2     1/1       Running   0         10s
web-3     1/1       Running   0         26s

StatefulSetが2つのPodを実行し、1つ目のPodがRunningかつReadyの状態になるのを待たずに2つ目のPodを実行しているのがわかります。

クリーンアップ

2つのターミナルが開かれているはずなので、クリーンアップの一部としてkubectlコマンドを実行する準備ができています。

kubectl delete sts web
# stsは、statefulsetの略です。

kubectl getを監視すると、Podが削除されていく様子を確認できます。

kubectl get pod -l app=nginx -w
web-3     1/1       Terminating   0         9m
web-2     1/1       Terminating   0         9m
web-3     1/1       Terminating   0         9m
web-2     1/1       Terminating   0         9m
web-1     1/1       Terminating   0         44m
web-0     1/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-3     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-1     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-2     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-1     0/1       Terminating   0         44m
web-1     0/1       Terminating   0         44m
web-1     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-3     0/1       Terminating   0         9m
web-3     0/1       Terminating   0         9m
web-3     0/1       Terminating   0         9m

削除の間、StatefulSetはすべてのPodを並列に削除し、順序インデックスが1つ前のPodが停止するのを待つことはありません。

kubectl getコマンドを実行しているターミナルを閉じて、nginxServiceを削除します。

kubectl delete svc nginx

5.2 - 例: Persistent Volumeを使用したWordpressとMySQLをデプロイする

このチュートリアルでは、WordPressのサイトとMySQLデータベースをMinikubeを使ってデプロイする方法を紹介します。2つのアプリケーションとも、データを保存するためにPersistentVolumeとPersistentVolumeClaimを使用します。

PersistentVolume(PV)とは、管理者が手動でプロビジョニングを行うか、StorageClassを使ってKubernetesによって動的にプロビジョニングされた、クラスター内のストレージの一部です。PersistentVolumeClaim(PVC)は、PVによって満たすことができる、ユーザーによるストレージへのリクエストのことです。PersistentVolumeとPersistentVolumeClaimは、Podのライフサイクルからは独立していて、Podの再起動、Podの再スケジューリング、さらにはPodの削除が行われたとしても、その中のデータは削除されずに残ります。

目標

  • PersistentVolumeClaimとPersistentVolumeを作成する
  • 以下を含むkustomization.yamlを作成する
    • Secret generator
    • MySQLリソースの設定
    • WordPressリソースの設定
  • kustomizationディレクトリをkubectl apply -k ./で適用する
  • クリーンアップする

始める前に

Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

バージョンを確認するには次のコマンドを実行してください: kubectl version. このページで示された例は、kubectl 1.14以降で動作します。

以下の設定ファイルをダウンロードします。

  1. mysql-deployment.yaml

  2. wordpress-deployment.yaml

PersistentVolumeClaimとPersistentVolumeを作成する

MySQLとWordpressはそれぞれ、データを保存するためのPersistentVolumeを必要とします。各PersistentVolumeClaimはデプロイの段階で作成されます。

多くのクラスタ環境では、デフォルトのStorageClassがインストールされています。StorageClassがPersistentVolumeClaim中で指定されていなかった場合、クラスターのデフォルトのStorageClassが代わりに使われます。

PersistentVolumeClaimが作成されるとき、StorageClassの設定に基づいてPersistentVolumeが動的にプロビジョニングされます。

kustomization.yamlを作成する

Secret generatorを追加する

Secretとは、パスワードやキーのような機密性の高いデータ片を保存するためのオブジェクトです。バージョン1.14からは、kubectlがkustomizationファイルを使用したKubernetesオブジェクトの管理をサポートしています。kustomization.yaml内のgeneratorによってSecretを作成することができます。

以下のコマンドを実行して、kustomization.yamlの中にSecret generatorを追加します。YOUR_PASSWORDの部分を使いたいパスワードに置換してください。

cat <<EOF >./kustomization.yaml
secretGenerator:
- name: mysql-pass
  literals:
  - password=YOUR_PASSWORD
EOF

MySQLとWordPressのためのリソースの設定を追加する

以下のマニフェストには、シングルインスタンスのMySQLのDeploymentが書かれています。MySQLコンテナはPersistentVolumeを/var/lib/mysqlにマウントします。MYSQL_ROOT_PASSWORD環境変数には、Secretから得られたデータベースのパスワードが設定されます。

apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim

以下のマニフェストには、シングルインスタンスのWordPressのDeploymentが書かれています。WordPressコンテナはPersistentVolumeをウェブサイトのデータファイルのために/var/www/htmlにマウントします。WORDPRESS_DB_HOST環境変数に上で定義したMySQLのServiceの名前を設定すると、WordPressはServiceによってデータベースにアクセスします。WORDPRESS_DB_PASSWORD環境変数には、kustomizeが生成したSecretから得たデータベースのパスワードが設定されます。

apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: frontend
  type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
      - image: wordpress:4.8-apache
        name: wordpress
        env:
        - name: WORDPRESS_DB_HOST
          value: wordpress-mysql
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: wordpress-persistent-storage
          mountPath: /var/www/html
      volumes:
      - name: wordpress-persistent-storage
        persistentVolumeClaim:
          claimName: wp-pv-claim
  1. MySQLのDeploymentの設定ファイルをダウンロードします。

    curl -LO https://k8s.io/examples/application/wordpress/mysql-deployment.yaml
    
  2. WordPressの設定ファイルをダウンロードします。

    curl -LO https://k8s.io/examples/application/wordpress/wordpress-deployment.yaml
    
  3. これらをkustomization.yamlファイルに追加します。

cat <<EOF >>./kustomization.yaml
resources:
  - mysql-deployment.yaml
  - wordpress-deployment.yaml
EOF

適用と確認

kustomization.yamlには、WordPressのサイトとMySQLデータベースのためのすべてのリソースが含まれています。次のコマンドでこのディレクトリを適用できます。

kubectl apply -k ./

これで、すべてのオブジェクトが存在していることを確認できます。

  1. 次のコマンドを実行して、Secretが存在していることを確認します。

    kubectl get secrets
    

    結果は次のようになるはずです。

    NAME                    TYPE                                  DATA   AGE
    mysql-pass-c57bb4t7mf   Opaque                                1      9s
    
  2. 次のコマンドを実行して、PersistentVolumeが動的にプロビジョニングされていることを確認します。

    kubectl get pvc
    

    結果は次のようになるはずです。

    NAME             STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS       AGE
    mysql-pv-claim   Bound     pvc-8cbd7b2e-4044-11e9-b2bb-42010a800002   20Gi       RWO            standard           77s
    wp-pv-claim      Bound     pvc-8cd0df54-4044-11e9-b2bb-42010a800002   20Gi       RWO            standard           77s
    
  3. 次のコマンドを実行して、Podが実行中であることを確認します。

    kubectl get pods
    

    結果は次のようになるはずです。

    NAME                               READY     STATUS    RESTARTS   AGE
    wordpress-mysql-1894417608-x5dzt   1/1       Running   0          40s
    
  4. 次のコマンドを実行して、Serviceが実行中であることを確認します。

    kubectl get services wordpress
    

    結果は次のようになるはずです。

    NAME        TYPE            CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
    wordpress   LoadBalancer    10.0.0.89    <pending>     80:32406/TCP   4m
    
  5. 次のコマンドを実行して、WordPress ServiceのIPアドレスを取得します。

    minikube service wordpress --url
    

    結果は次のようになるはずです。

    http://1.2.3.4:32406
    
  6. IPアドレスをコピーして、ブラウザーで読み込み、サイトを表示しましょう。

    WordPressによりセットアップされた次のスクリーンショットのようなページが表示されるはずです。

    wordpress-init

クリーンアップ

  1. 次のコマンドを実行して、Secret、Deployment、Service、およびPersistentVolumeClaimを削除します。

    kubectl delete -k ./
    

次の項目

5.3 - 例: StatefulSetを使用したCassandraのデプロイ

このチュートリアルでは、Apache CassandraをKubernetes上で実行する方法を紹介します。 データベースの一種であるCassandraには、データの耐久性(アプリケーションの 状態)を提供するために永続ストレージが必要です。 この例では、カスタムのCassandraのseed providerにより、Cassandraクラスターに参加した新しいCassandraインスタンスを検出できるようにします。

StatefulSetを利用すると、ステートフルなアプリケーションをKubernetesクラスターにデプロイするのが簡単になります。 このチュートリアルで使われている機能のより詳しい情報は、StatefulSetを参照してください。

目標

  • Cassandraのheadless Serviceを作成して検証する。
  • StatefulSetを使用してCassandra ringを作成する。
  • StatefulSetを検証する。
  • StatefulSetを編集する。
  • StatefulSetとPodを削除する。

始める前に

Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

このチュートリアルを完了するには、PodServiceStatefulSetの基本についてすでに知っている必要があります。

Minikubeのセットアップに関する追加の設定手順

Cassandraのheadless Serviceを作成する

Kubernetesでは、Serviceは同じタスクを実行するPodの集合を表します。

以下のServiceは、Cassandra Podとクラスター内のクライアント間のDNSルックアップに使われます:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: cassandra
  name: cassandra
spec:
  clusterIP: None
  ports:
  - port: 9042
  selector:
    app: cassandra

cassandra-service.yamlファイルから、Cassandra StatefulSetのすべてのメンバーをトラッキングするServiceを作成します。

kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-service.yaml

検証 (オプション)

Cassandra Serviceを取得します。

kubectl get svc cassandra

結果は次のようになります。

NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
cassandra   ClusterIP   None         <none>        9042/TCP   45s

cassandraという名前のServiceが表示されない場合、作成に失敗しています。よくある問題のトラブルシューティングについては、Serviceのデバッグを読んでください。

StatefulSetを使ってCassandra ringを作成する

以下に示すStatefulSetマニフェストは、3つのPodからなるCassandra ringを作成します。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: cassandra
  labels:
    app: cassandra
spec:
  serviceName: cassandra
  replicas: 3
  selector:
    matchLabels:
      app: cassandra
  template:
    metadata:
      labels:
        app: cassandra
    spec:
      terminationGracePeriodSeconds: 1800
      containers:
      - name: cassandra
        image: gcr.io/google-samples/cassandra:v13
        imagePullPolicy: Always
        ports:
        - containerPort: 7000
          name: intra-node
        - containerPort: 7001
          name: tls-intra-node
        - containerPort: 7199
          name: jmx
        - containerPort: 9042
          name: cql
        resources:
          limits:
            cpu: "500m"
            memory: 1Gi
          requests:
            cpu: "500m"
            memory: 1Gi
        securityContext:
          capabilities:
            add:
              - IPC_LOCK
        lifecycle:
          preStop:
            exec:
              command: 
              - /bin/sh
              - -c
              - nodetool drain
        env:
          - name: MAX_HEAP_SIZE
            value: 512M
          - name: HEAP_NEWSIZE
            value: 100M
          - name: CASSANDRA_SEEDS
            value: "cassandra-0.cassandra.default.svc.cluster.local"
          - name: CASSANDRA_CLUSTER_NAME
            value: "K8Demo"
          - name: CASSANDRA_DC
            value: "DC1-K8Demo"
          - name: CASSANDRA_RACK
            value: "Rack1-K8Demo"
          - name: POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
        readinessProbe:
          exec:
            command:
            - /bin/bash
            - -c
            - /ready-probe.sh
          initialDelaySeconds: 15
          timeoutSeconds: 5
        # These volume mounts are persistent. They are like inline claims,
        # but not exactly because the names need to match exactly one of
        # the stateful pod volumes.
        volumeMounts:
        - name: cassandra-data
          mountPath: /cassandra_data
  # These are converted to volume claims by the controller
  # and mounted at the paths mentioned above.
  # do not use these in production until ssd GCEPersistentDisk or other ssd pd
  volumeClaimTemplates:
  - metadata:
      name: cassandra-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: fast
      resources:
        requests:
          storage: 1Gi
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: fast
provisioner: k8s.io/minikube-hostpath
parameters:
  type: pd-ssd

cassandra-statefulset.yamlファイルから、CassandraのStatefulSetを作成します:

# cassandra-statefulset.yaml を編集せずにapplyできる場合は、このコマンドを使用してください
kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml

クラスターに合わせてcassandra-statefulset.yamlを編集する必要がある場合、 https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml をダウンロードして、修正したバージョンを保存したフォルダからマニフェストを適用してください。

# cassandra-statefulset.yaml をローカルで編集する必要がある場合、このコマンドを使用してください
kubectl apply -f cassandra-statefulset.yaml

CassandraのStatefulSetを検証する

  1. CassandraのStatefulSetを取得します

    kubectl get statefulset cassandra
    

    結果は次のようになるはずです:

    NAME        DESIRED   CURRENT   AGE
    cassandra   3         0         13s
    

    StatefulSetリソースがPodを順番にデプロイします。

  2. Podを取得して順序付きの作成ステータスを確認します

    kubectl get pods -l="app=cassandra"
    

    結果は次のようになるはずです:

    NAME          READY     STATUS              RESTARTS   AGE
    cassandra-0   1/1       Running             0          1m
    cassandra-1   0/1       ContainerCreating   0          8s
    

    3つすべてのPodのデプロイには数分かかる場合があります。デプロイが完了すると、同じコマンドは次のような結果を返します:

    NAME          READY     STATUS    RESTARTS   AGE
    cassandra-0   1/1       Running   0          10m
    cassandra-1   1/1       Running   0          9m
    cassandra-2   1/1       Running   0          8m
    
  3. 1番目のPodの中でCassandraのnodetoolを実行して、ringのステータスを表示します。

    kubectl exec -it cassandra-0 -- nodetool status
    

    結果は次のようになるはずです:

    Datacenter: DC1-K8Demo
    ======================
    Status=Up/Down
    |/ State=Normal/Leaving/Joining/Moving
    --  Address     Load       Tokens       Owns (effective)  Host ID                               Rack
    UN  172.17.0.5  83.57 KiB  32           74.0%             e2dd09e6-d9d3-477e-96c5-45094c08db0f  Rack1-K8Demo
    UN  172.17.0.4  101.04 KiB  32           58.8%             f89d6835-3a42-4419-92b3-0e62cae1479c  Rack1-K8Demo
    UN  172.17.0.6  84.74 KiB  32           67.1%             a6a1e8c2-3dc5-4417-b1a0-26507af2aaad  Rack1-K8Demo
    

CassandraのStatefulSetを変更する

kubectl editを使うと、CassandraのStatefulSetのサイズを変更できます。

  1. 次のコマンドを実行します。

    kubectl edit statefulset cassandra
    

    このコマンドを実行すると、ターミナルでエディタが起動します。変更が必要な行はreplicasフィールドです。 以下の例は、StatefulSetファイルの抜粋です:

    # Please edit the object below. Lines beginning with a '#' will be ignored,
    # and an empty file will abort the edit. If an error occurs while saving this file will be
    # reopened with the relevant failures.
    #
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      creationTimestamp: 2016-08-13T18:40:58Z
      generation: 1
      labels:
      app: cassandra
      name: cassandra
      namespace: default
      resourceVersion: "323"
      uid: 7a219483-6185-11e6-a910-42010a8a0fc0
    spec:
      replicas: 3
    
  2. レプリカ数を4に変更し、マニフェストを保存します。

    これで、StatefulSetが4つのPodを実行するようにスケールされました。

  3. CassandraのStatefulSetを取得して、変更を確かめます:

    kubectl get statefulset cassandra
    

    結果は次のようになるはずです:

    NAME        DESIRED   CURRENT   AGE
    cassandra   4         4         36m
    

クリーンアップ

StatefulSetを削除したりスケールダウンしても、StatefulSetに関係するボリュームは削除されません。 StatefulSetに関連するすべてのリソースを自動的に破棄するよりも、データの方がより貴重であるため、安全のためにこのような設定になっています。

  1. 次のコマンドを実行して(単一のコマンドにまとめています)、CassandraのStatefulSetに含まれるすべてのリソースを削除します:

    grace=$(kubectl get pod cassandra-0 -o=jsonpath='{.spec.terminationGracePeriodSeconds}') \
      && kubectl delete statefulset -l app=cassandra \
      && echo "Sleeping ${grace} seconds" 1>&2 \
      && sleep $grace \
      && kubectl delete persistentvolumeclaim -l app=cassandra
    
  2. 次のコマンドを実行して、CassandraをセットアップしたServiceを削除します:

    kubectl delete service -l app=cassandra
    

Cassandraコンテナの環境変数

このチュートリアルのPodでは、Googleのコンテナレジストリgcr.io/google-samples/cassandra:v13イメージを使用しました。このDockerイメージはdebian-baseをベースにしており、OpenJDK 8が含まれています。

このイメージには、Apache Debianリポジトリの標準のCassandraインストールが含まれます。 環境変数を利用すると、cassandra.yamlに挿入された値を変更できます。

環境変数 デフォルト値
CASSANDRA_CLUSTER_NAME 'Test Cluster'
CASSANDRA_NUM_TOKENS 32
CASSANDRA_RPC_ADDRESS 0.0.0.0

次の項目

6 - クラスター

6.1 - AppArmorを使用してコンテナのリソースへのアクセスを制限する

FEATURE STATE: Kubernetes v1.4 [beta]

AppArmorは、Linux標準のユーザー・グループをベースとしたパーミッションを補完するLinuxカーネルのセキュリティモジュールであり、プログラムのアクセスを限定されたリソースセットに制限するために利用されます。AppArmorを設定することで、任意のアプリケーションの攻撃サーフェイスとなりうる面を減らしたり、より優れた多重の防御を提供できます。AppArmorは、たとえばLinuxのcapability、ネットワークアクセス、ファイルのパーミッションなど、特定のプログラムやコンテナに必要なアクセスを許可するようにチューニングされたプロファイルにより設定を行います。各プロファイルは、許可されなかったリソースへのアクセスをブロックするenforcingモードと、ルール違反を報告するだけのcomplainモードのいずれかで実行できます。

AppArmorを利用すれば、コンテナに許可することを制限したりシステムログを通してよりよい監査を提供することで、デプロイをよりセキュアにする助けになります。しかし、AppArmorは銀の弾丸ではなく、アプリケーションコードの悪用からの防御を強化できるだけであることを心に留めておくことが重要です。制限の強い優れたプロファイルを提供し、アプリケーションとクラスターを別の角度から強化することが重要です。

目標

  • プロファイルをノードに読み込む方法の例を見る
  • Pod上でプロファイルを矯正する方法を学ぶ
  • プロファイルが読み込まれたかを確認する方法を学ぶ
  • プロファイルに違反した場合に何が起こるのかを見る
  • プロファイルが読み込めなかった場合に何が起こるのかを見る

始める前に

以下のことを確認してください。

  1. Kubernetesのバージョンがv1.4以上であること。KubernetesのAppArmorのサポートはv1.4で追加されました。v1.4より古いバージョンのKubernetesのコンポーネントは、新しいAppArmorのアノテーションを認識できないため、AppArmorの設定を与えたとしても黙って無視されてしまいます。Podが期待した保護を確実に受けられるようにするためには、次のようにノードのKubeletのバージョンを確認することが重要です。

    kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {@.status.nodeInfo.kubeletVersion}\n{end}'
    
    gke-test-default-pool-239f5d02-gyn2: v1.4.0
    gke-test-default-pool-239f5d02-x1kf: v1.4.0
    gke-test-default-pool-239f5d02-xwux: v1.4.0
    
  2. AppArmorカーネルモジュールが有効であること。LinuxカーネルがAppArmorプロファイルを強制するためには、AppArmorカーネルモジュールのインストールと有効化が必須です。UbuntuやSUSEなどのディストリビューションではデフォルトで有効化されますが、他の多くのディストリビューションでのサポートはオプションです。モジュールが有効になっているかチェックするには、次のように/sys/module/apparmor/parameters/enabledファイルを確認します。

    cat /sys/module/apparmor/parameters/enabled
    Y
    

    KubeletがAppArmorをサポートしていれば(>= v1.4)、カーネルモジュールが有効になっていない場合にはAppArmorオプションが付いたPodを拒否します。

  1. コンテナランタイムがAppArmorをサポートしていること。現在、Kubernetesがサポートするすべての一般的なコンテナランタイム、DockerCRI-Ocontainerdなどは、AppArmorをサポートしています。関連するランタイムのドキュメントを参照して、クラスターがAppArmorを利用するための要求を満たしているかどうかを検証してください。

  2. プロファイルが読み込まれていること。AppArmorがPodに適用されるのは、各コンテナが実行されるべきAppArmorプロファイルを指定したときです。もし指定されたプロファイルがまだカーネルに読み込まれていなければ、Kubelet(>= v1.4)はPodを拒否します。どのプロファイルがノードに読み込まれているのかを確かめるには、次のようなコマンドを実行して/sys/kernel/security/apparmor/profilesをチェックします。

    ssh gke-test-default-pool-239f5d02-gyn2 "sudo cat /sys/kernel/security/apparmor/profiles | sort"
    
    apparmor-test-deny-write (enforce)
    apparmor-test-audit-write (enforce)
    docker-default (enforce)
    k8s-nginx (enforce)
    

    ノード上でのプロファイルの読み込みの詳細については、プロファイルを使用したノードのセットアップを参照してください。

KubeletのバージョンがAppArmorサポートに対応しているもの(>= v1.4)である限り、Kubeletは必要条件を1つでも満たさないAppArmorオプションが付けられたPodをリジェクトします。また、ノード上のAppArmorのサポートは、次のようにready conditionのメッセージで確認することもできます(ただし、この機能は将来のリリースで削除される可能性があります)。

kubectl get nodes -o=jsonpath='{range .items[*]}{@.metadata.name}: {.status.conditions[?(@.reason=="KubeletReady")].message}{"\n"}{end}'
gke-test-default-pool-239f5d02-gyn2: kubelet is posting ready status. AppArmor enabled
gke-test-default-pool-239f5d02-x1kf: kubelet is posting ready status. AppArmor enabled
gke-test-default-pool-239f5d02-xwux: kubelet is posting ready status. AppArmor enabled

Podをセキュアにする

AppArmorのプロファイルは各コンテナごとに指定します。Podのコンテナで実行するAppArmorのプロファイルを指定するには、Podのメタデータに次のようなアノテーションを追加します。

container.apparmor.security.beta.kubernetes.io/<container_name>: <profile_ref>

ここで、<container_name>はプロファイルを適用するコンテナの名前であり、<profile_ref>には適用するプロファイルを指定します。profile_refは次の値のうち1つを指定します。

  • runtime/default: ランタイムのデフォルトのプロファイルを適用する
  • localhost/<profile_name>: <profile_name>という名前でホストにロードされたプロファイルを適用する
  • unconfined: いかなるプロファイルもロードされないことを示す

アノテーションとプロファイルの名前のフォーマットの詳細については、APIリファレンスを参照してください。

KubernetesのAppArmorの強制では、まずはじめにすべての前提条件が満たされているかどうかをチェックします。その後、強制を行うためにプロファイルの選択をコンテナランタイムに委ねます。前提条件が満たされなかった場合、Podはリジェクトされ、実行されません。

プロファイルが適用されたかどうか確認するには、AppArmor securityオプションがコンテナ作成イベントに一覧されているかどうかを確認します。

kubectl get events | grep Created
22s        22s         1         hello-apparmor     Pod       spec.containers{hello}   Normal    Created     {kubelet e2e-test-stclair-node-pool-31nt}   Created container with docker id 269a53b202d3; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]

proc attrを調べることで、コンテナのルートプロセスが正しいプロファイルで実行されているかどうかを直接確認することもできます。

kubectl exec <pod_name> -- cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)

この例は、クラスターがすでにAppArmorのサポート付きでセットアップ済みであることを前提としています。

まず、使用したいプロファイルをノード上に読み込む必要があります。このプロファイルは、すべてのファイル書き込みを拒否します。

#include <tunables/global>

profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
  #include <abstractions/base>

  file,

  # Deny all file writes.
  deny /** w,
}

Podがどのノードにスケジュールされるかは予測できないため、プロファイルはすべてのノードに読み込ませる必要があります。この例では、単純にSSHを使ってプロファイルをインストールしますが、プロファイルを使用したノードのセットアップでは、他のアプローチについて議論しています。

NODES=(
    # SSHでアクセス可能なノードのドメイン名
    gke-test-default-pool-239f5d02-gyn2.us-central1-a.my-k8s
    gke-test-default-pool-239f5d02-x1kf.us-central1-a.my-k8s
    gke-test-default-pool-239f5d02-xwux.us-central1-a.my-k8s)
for NODE in ${NODES[*]}; do ssh $NODE 'sudo apparmor_parser -q <<EOF
#include <tunables/global>

profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
  #include <abstractions/base>

  file,

  # Deny all file writes.
  deny /** w,
}
EOF'
done

次に、deny-writeプロファイルを使用した単純な "Hello AppArmor" Podを実行します。

apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor
  annotations:
    # Tell Kubernetes to apply the AppArmor profile "k8s-apparmor-example-deny-write".
    # Note that this is ignored if the Kubernetes node is not running version 1.4 or greater.
    container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-deny-write
spec:
  containers:
  - name: hello
    image: busybox
    command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
kubectl create -f ./hello-apparmor.yaml

Podイベントを確認すると、PodコンテナがAppArmorプロファイル "k8s-apparmor-example-deny-write" を使用して作成されたことがわかります。

kubectl get events | grep hello-apparmor
14s        14s         1         hello-apparmor   Pod                                Normal    Scheduled   {default-scheduler }                           Successfully assigned hello-apparmor to gke-test-default-pool-239f5d02-gyn2
14s        14s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Pulling     {kubelet gke-test-default-pool-239f5d02-gyn2}   pulling image "busybox"
13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Pulled      {kubelet gke-test-default-pool-239f5d02-gyn2}   Successfully pulled image "busybox"
13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Created     {kubelet gke-test-default-pool-239f5d02-gyn2}   Created container with docker id 06b6cd1c0989; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]
13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Started     {kubelet gke-test-default-pool-239f5d02-gyn2}   Started container with docker id 06b6cd1c0989

コンテナがこのプロファイルで実際に実行されていることを確認するために、コンテナのproc attrをチェックします。

kubectl exec hello-apparmor -- cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)

最後に、ファイルへの書き込みを行おうとすることで、プロファイルに違反すると何が起こるか見てみましょう。

kubectl exec hello-apparmor touch -- /tmp/test
touch: /tmp/test: Permission denied
error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1

まとめとして、読み込まれていないプロファイルを指定しようとするとどうなるのか見てみましょう。

kubectl create -f /dev/stdin <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor-2
  annotations:
    container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-allow-write
spec:
  containers:
  - name: hello
    image: busybox
    command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
EOF
pod/hello-apparmor-2 created
kubectl describe pod hello-apparmor-2
Name:          hello-apparmor-2
Namespace:     default
Node:          gke-test-default-pool-239f5d02-x1kf/
Start Time:    Tue, 30 Aug 2016 17:58:56 -0700
Labels:        <none>
Annotations:   container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write
Status:        Pending
Reason:        AppArmor
Message:       Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded
IP:
Controllers:   <none>
Containers:
  hello:
    Container ID:
    Image:     busybox
    Image ID:
    Port:
    Command:
      sh
      -c
      echo 'Hello AppArmor!' && sleep 1h
    State:              Waiting
      Reason:           Blocked
    Ready:              False
    Restart Count:      0
    Environment:        <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-dnz7v (ro)
Conditions:
  Type          Status
  Initialized   True
  Ready         False
  PodScheduled  True
Volumes:
  default-token-dnz7v:
    Type:    Secret (a volume populated by a Secret)
    SecretName:    default-token-dnz7v
    Optional:   false
QoS Class:      BestEffort
Node-Selectors: <none>
Tolerations:    <none>
Events:
  FirstSeen    LastSeen    Count    From                        SubobjectPath    Type        Reason        Message
  ---------    --------    -----    ----                        -------------    --------    ------        -------
  23s          23s         1        {default-scheduler }                         Normal      Scheduled     Successfully assigned hello-apparmor-2 to e2e-test-stclair-node-pool-t1f5
  23s          23s         1        {kubelet e2e-test-stclair-node-pool-t1f5}             Warning        AppArmor    Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded

PodのステータスはPendingとなり、Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded(PodはAppArmorを強制できません: プロファイル "k8s-apparmor-example-allow-write" はロードされていません)という役に立つエラーメッセージが表示されています。同じメッセージのイベントも記録されています。

管理

プロファイルを使用したノードのセットアップ

現在、KubernetesはAppArmorのプロファイルをノードに読み込むネイティブの仕組みは提供していません。しかし、プロファイルをセットアップする方法は、以下のように様々な方法があります。

  • 各ノード上に正しいプロファイルがロードされていることを保証するPodを実行するDaemonSetを利用する方法。ここに実装例があります。
  • ノードの初期化時に初期化スクリプト(例: Salt、Ansibleなど)や初期化イメージを使用する。
  • で示したような方法で、プロファイルを各ノードにコピーし、SSHで読み込む。

スケジューラーはどのプロファイルがどのノードに読み込まれているのかがわからないため、すべてのプロファイルがすべてのノードに読み込まれていなければなりません。もう1つのアプローチとしては、各プロファイル(あるいはプロファイルのクラス)ごとにノードラベルを追加し、node selectorを用いてPodが必要なプロファイルを読み込んだノードで実行されるようにする方法もあります。

PodSecurityPolicyを使用したプロファイルの制限

PodSecurityPolicy extensionが有効になっている場合、クラスタ全体でAppArmorn制限が適用されます。PodSecurityPolicyを有効にするには、apiserver上で次のフラグを設定する必要があります。

--enable-admission-plugins=PodSecurityPolicy[,others...]

AppArmorのオプションはPodSecurityPolicy上でアノテーションとして指定します。

apparmor.security.beta.kubernetes.io/defaultProfileName: <profile_ref>
apparmor.security.beta.kubernetes.io/allowedProfileNames: <profile_ref>[,others...]

defaultProfileNameオプションには、何も指定されなかった場合にコンテナにデフォルトで適用されるプロファイルを指定します。allowedProfileNamesオプションには、Podコンテナの実行が許可されるプロファイルのリストを指定します。両方のオプションが指定された場合、デフォルトは許可されなければいけません。プロファイルはコンテナ上で同じフォーマットで指定されます。完全な仕様については、APIリファレンスを参照してください。

AppArmorの無効化

クラスタ上でAppArmorを利用可能にしたくない場合、次のコマンドラインフラグで無効化できます。

--feature-gates=AppArmor=false

無効化すると、AppArmorプロファイルを含むPodは"Forbidden"エラーで検証に失敗します。ただし、デフォルトのdockerは非特権Pod上では"docker-default"というプロファイルを常に有効化し(AppArmorカーネルモジュールが有効である場合)、フィーチャーゲートで無効化したとしても有効化し続けることに注意してください。AppArmorを無効化するオプションは、AppArmorが一般利用(GA)になったときに削除される予定です。

AppArmorを使用するKubernetes v1.4にアップグレードする

クラスタをv1.4にアップグレードするために、AppArmorに関する操作は必要ありません。ただし、既存のPodがAppArmorのアノテーションを持っている場合、検証(またはPodSecurityPolicy admission)は行われません。もしpermissiveなプロファイルがノードに読み込まれていた場合、悪意のあるユーザーがPodの権限を上述のdocker-defaultより昇格させるために、permissiveなプロファイルを再適用する恐れがあります。これが問題となる場合、apparmor.security.beta.kubernetes.ioのアノテーションを含むすべてのPodのクラスターをクリーンアップすることを推奨します。

一般利用可能(General Availability)への更新パス

AppArmorが一般利用可能(GA)になったとき、現在アノテーションで指定しているオプションはフィールドに変換されます。移行中のすべてのアップグレードとダウングレードの経路をサポートするのは非常に微妙であるため、以降が必要になったときに詳細に説明する予定です。最低2リリースの間はフィールドとアノテーションの両方がサポートされるようにする予定です。最低2リリースの後は、アノテーションは明示的に拒否されるようになります。

Profilesの作成

AppArmorのプロファイルを正しく指定するのはやっかいな作業です。幸い、その作業を補助するツールがいくつかあります。

  • aa-genprofおよびaa-logprofは、アプリケーションの動作とログを監視することによりプロファイルのルールを生成します。詳しい説明については、AppArmor documentationを参照してください。
  • baneは、Docker向けのAppArmorのプロファイル・ジェネレータです。簡略化されたプロファイル言語を使用しています。

プロファイルの生成には、アプリケーションを開発用ワークステーション上でDockerで実行することを推奨します。しかし、実際にPodが実行されるKubernetesノード上でツールを実行してはいけない理由はありません。

AppArmorに関する問題をデバッグするには、システムログをチェックして、特に何が拒否されたのかを確認できます。AppArmorのログはdmesgにverboseメッセージを送り、エラーは通常システムログまたはjournalctlで確認できます。詳しい情報は、AppArmor failuresで提供されています。

APIリファレンス

Podアノテーション

コンテナが実行するプロファイルを指定します。

  • key: container.apparmor.security.beta.kubernetes.io/<container_name> ここで、<container_name>はPod内のコンテナの名前を一致させます。Pod内の各コンテナごとに別々のプロファイルを指定できます。
  • value: 下で説明するプロファイルのリファレンス

プロファイルのリファレンス

  • runtime/default: デフォルトのランタイムプロファイルを指します。
    • (PodSecurityPolicyのデフォルトを設定せずに)プロファイルを指定しない場合と同等ですが、AppArmorを有効化する必要があります。
    • Dockerの場合、非特権コンテナではdocker-defaultプロファイルが選択され、特権コンテナではunconfined(プロファイルなし)が選択されます。
  • localhost/<profile_name>: 名前で指定されたノード(localhost)に読み込まれたプロファイルを指します。
  • unconfined: これは実質的にコンテナ上のAppArmorを無効化します。

これ以外のプロファイルリファレンスはすべて無効です。

PodSecurityPolicyアノテーション

何も指定されなかった場合にコンテナに適用するデフォルトのプロファイルは、以下のように指定します。

  • key: apparmor.security.beta.kubernetes.io/defaultProfileName
  • value: 上で説明したプロファイルのリファレンス

Podコンテナが指定することを許可するプロファイルのリストは、以下のように指定します。

  • key: apparmor.security.beta.kubernetes.io/allowedProfileNames
  • value: カンマ区切りの上述のプロファイルリファレンスのリスト
    • プロファイル名ではエスケープしたカンマは不正な文字ではありませんが、ここでは明示的に許可されません。

次の項目

追加のリソースとしては以下のものがあります。

7 - Service

7.1 - 送信元IPを使用する

Kubernetesクラスター内で実行されているアプリケーションは、Serviceという抽象化を経由して、他のアプリケーションや外の世界との発見や通信を行います。このドキュメントでは、異なる種類のServiceに送られたパケットの送信元IPに何が起こるのか、そして必要に応じてこの振る舞いを切り替える方法について説明します。

始める前に

用語

このドキュメントでは、以下の用語を使用します。

NAT
ネットワークアドレス変換(network address translation)
送信元NAT
パケットの送信元のIPを置換します。このページでは、通常ノードのIPアドレスを置換することを意味します。
送信先NAT
パケットの送信先のIPを置換します。このページでは、通常PodのIPアドレスを置換することを意味します。
VIP
Kubernetes内のすべてのServiceなどに割り当てられる仮想IPアドレス(virtual IP address)です。
kube-proxy
すべてのノード上でServiceのVIPを管理するネットワークデーモンです。

前提条件

Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

以下の例では、HTTPヘッダー経由で受け取ったリクエストの送信元IPをエコーバックする、小さなnginxウェブサーバーを使用します。次のコマンドでウェブサーバーを作成できます。

kubectl create deployment source-ip-app --image=k8s.gcr.io/echoserver:1.4

出力は次のようになります。

deployment.apps/source-ip-app created

目標

  • 単純なアプリケーションを様々な種類のService経由で公開する
  • それぞれの種類のServiceがどのように送信元IPのNATを扱うかを理解する
  • 送信元IPを保持することに関わるトレードオフを理解する

Type=ClusterIPを使用したServiceでの送信元IP

kube-proxyがiptablesモード(デフォルト)で実行されている場合、クラスター内部からClusterIPに送られたパケットに送信元のNATが行われることは決してありません。kube-proxyが実行されているノード上でhttp://localhost:10249/proxyModeにリクエストを送って、kube-proxyのモードを問い合わせてみましょう。

kubectl get nodes

出力は次のようになります。

NAME                           STATUS     ROLES    AGE     VERSION
kubernetes-node-6jst   Ready      <none>   2h      v1.13.0
kubernetes-node-cx31   Ready      <none>   2h      v1.13.0
kubernetes-node-jj1t   Ready      <none>   2h      v1.13.0

これらのノードの1つでproxyモードを取得します(kube-proxyはポート10249をlistenしています)。

# このコマンドは、問い合わせを行いたいノード上のシェルで実行してください。
curl http://localhost:10249/proxyMode

出力は次のようになります。

iptables

source IPアプリのServiceを作成することで、送信元IPが保持されているかテストできます。

kubectl expose deployment source-ip-app --name=clusterip --port=80 --target-port=8080

出力は次のようになります。

service/clusterip exposed
kubectl get svc clusterip

出力は次のようになります。

NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
clusterip    ClusterIP   10.0.170.92   <none>        80/TCP    51s

そして、同じクラスター上のPodからClusterIPにアクセスします。

kubectl run busybox -it --image=busybox --restart=Never --rm

出力は次のようになります。

Waiting for pod default/busybox to be running, status is Pending, pod ready: false
If you don't see a command prompt, try pressing enter.

これで、Podの内部でコマンドが実行できます。

# このコマンドは、"kubectl run" のターミナルの内部で実行してください
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1460 qdisc noqueue
    link/ether 0a:58:0a:f4:03:08 brd ff:ff:ff:ff:ff:ff
    inet 10.244.3.8/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::188a:84ff:feb0:26a5/64 scope link
       valid_lft forever preferred_lft forever

そして、wgetを使用してローカルのウェブサーバーに問い合わせます。

# "10.0.170.92" の部分をService名が"clusterip"のIPv4アドレスに置き換えてください
wget -qO - 10.0.170.92
CLIENT VALUES:
client_address=10.244.3.8
command=GET
...

client_addressは常にクライアントのPodのIPアドレスになります。これは、クライアントのPodとサーバーのPodが同じノード内にあっても異なるノードにあっても変わりません。

Type=NodePortを使用したServiceでの送信元IP

Type=NodePortを使用したServiceに送られたパケットは、デフォルトで送信元のNATが行われます。NodePort Serviceを作ることでテストできます。

kubectl expose deployment source-ip-app --name=nodeport --port=80 --target-port=8080 --type=NodePort

出力は次のようになります。

service/nodeport exposed
NODEPORT=$(kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services nodeport)
NODES=$(kubectl get nodes -o jsonpath='{ $.items[*].status.addresses[?(@.type=="InternalIP")].address }')

クラウドプロバイダーで実行する場合、上に示したnodes:nodeportに対してファイアウォールのルールを作成する必要があるかもしれません。それでは、上で割り当てたノードポート経由で、クラスターの外部からServiceにアクセスしてみましょう。

for node in $NODES; do curl -s $node:$NODEPORT | grep -i client_address; done

出力は次のようになります。

client_address=10.180.1.1
client_address=10.240.0.5
client_address=10.240.0.3

これらは正しいクライアントIPではなく、クラスターのinternal IPであることがわかります。ここでは、次のようなことが起こっています。

  • クライアントがパケットをnode2:nodePortに送信する
  • node2は、パケット内の送信元IPアドレスを自ノードのIPアドレスに置換する(SNAT)
  • node2は、パケット内の送信先IPアドレスをPodのIPアドレスに置換する
  • パケットはnode1にルーティングされ、endpointにルーティングされる
  • Podからの応答がnode2にルーティングされて戻ってくる
  • Podからの応答がクライアントに送り返される

図で表すと次のようになります。

graph LR; client(client)-->node2[Node 2]; node2-->client; node2-. SNAT .->node1[Node 1]; node1-. SNAT .->node2; node1-->endpoint(Endpoint); classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; class node1,node2,endpoint k8s; class client plain;

クライアントのIPが失われることを回避するために、Kubernetesにはクライアントの送信元IPを保持する機能があります。service.spec.externalTrafficPolicyの値をLocalに設定すると、kube-proxyはローカルに存在するエンドポイントへのプロキシーリクエストだけをプロキシーし、他のノードへはトラフィックを転送しなくなります。このアプローチでは、オリジナルの送信元IPアドレスが保持されます。ローカルにエンドポイントが存在しない場合には、そのノードに送信されたパケットは損失します。そのため、エンドポイントに到達するパケットに適用する可能性のあるパケット処理ルールでは、送信元IPが正しいことを信頼できます。

次のようにしてservice.spec.externalTrafficPolicyフィールドを設定します。

kubectl patch svc nodeport -p '{"spec":{"externalTrafficPolicy":"Local"}}'

出力は次のようになります。

service/nodeport patched

そして、再度テストしてみます。

for node in $NODES; do curl --connect-timeout 1 -s $node:$NODEPORT | grep -i client_address; done

出力は次のようになります。

client_address=198.51.100.79

今度は、正しいクライアントIPが含まれる応答が1つだけ得られました。これは、エンドポイントのPodが実行されているノードから来たものです。

ここでは、次のようなことが起こっています。

  • クライアントがパケットをエンドポイントが存在しないnode2:nodePortに送信する
  • パケットが損失する
  • クライアントがパケットをエンドポイントが存在するnode1:nodePortに送信する
  • node1は、正しい送信元IPを持つパケットをエンドポイントにルーティングする

図で表すと次のようになります。

graph TD; client --> node1[Node 1]; client(client) --x node2[Node 2]; node1 --> endpoint(endpoint); endpoint --> node1; classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; class node1,node2,endpoint k8s; class client plain;

Type=LoadBalancerを使用したServiceでの送信元IP

Type=LoadBalancerを使用したServiceに送られたパケットは、デフォルトで送信元のNATが行われます。Ready状態にあるすべてのスケジュール可能なKubernetesのNodeは、ロードバランサーからのトラフィックを受付可能であるためです。そのため、エンドポイントが存在しないノードにパケットが到達した場合、システムはエンドポイントが存在するノードにパケットをプロシキーします。このとき、(前のセクションで説明したように)パケットの送信元IPがノードのIPに置換されます。

ロードバランサー経由でsource-ip-appを公開することで、これをテストできます。

kubectl expose deployment source-ip-app --name=loadbalancer --port=80 --target-port=8080 --type=LoadBalancer

出力は次のようになります。

service/loadbalancer exposed

ServiceのIPアドレスを表示します。

kubectl get svc loadbalancer

出力は次のようになります。

NAME           TYPE           CLUSTER-IP    EXTERNAL-IP       PORT(S)   AGE
loadbalancer   LoadBalancer   10.0.65.118   203.0.113.140     80/TCP    5m

次に、Serviceのexternal-ipにリクエストを送信します。

curl 203.0.113.140

出力は次のようになります。

CLIENT VALUES:
client_address=10.240.0.5
...

しかし、Google Kubernetes EngineやGCE上で実行している場合、同じservice.spec.externalTrafficPolicyフィールドをLocalに設定すると、ロードバランサーからのトラフィックを受け付け可能なノードのリストから、Serviceエンドポイントが存在しないノードが強制的に削除されます。この動作は、ヘルスチェックを意図的に失敗させることによって実現されています。

図で表すと次のようになります。

Source IP with externalTrafficPolicy

アノテーションを設定することで動作をテストできます。

kubectl patch svc loadbalancer -p '{"spec":{"externalTrafficPolicy":"Local"}}'

Kubernetesにより割り当てられたservice.spec.healthCheckNodePortフィールドをすぐに確認します。

kubectl get svc loadbalancer -o yaml | grep -i healthCheckNodePort

出力は次のようになります。

  healthCheckNodePort: 32122

service.spec.healthCheckNodePortフィールドは、/healthzでhealth checkを配信しているすべてのノード上のポートを指しています。次のコマンドでテストできます。

kubectl get pod -o wide -l run=source-ip-app

出力は次のようになります。

NAME                            READY     STATUS    RESTARTS   AGE       IP             NODE
source-ip-app-826191075-qehz4   1/1       Running   0          20h       10.180.1.136   kubernetes-node-6jst

curlを使用して、さまざまなノード上の/healthzエンドポイントからデータを取得します。

# このコマンドは選んだノードのローカル上で実行してください
curl localhost:32122/healthz
1 Service Endpoints found

ノードが異なると、得られる結果も異なる可能性があります。

# このコマンドは、選んだノード上でローカルに実行してください
curl localhost:32122/healthz
No Service Endpoints Found

コントロールプレーン上で実行中のコントローラーは、クラウドのロードバランサーを割り当てる責任があります。同じコントローラーは、各ノード上のポートやパスを指すHTTPのヘルスチェックも割り当てます。エンドポイントが存在しない2つのノードがヘルスチェックに失敗するまで約10秒待った後、curlを使用してロードバランサーのIPv4アドレスに問い合わせます。

curl 203.0.113.140

出力は次のようになります。

CLIENT VALUES:
client_address=198.51.100.79
...

クロスプラットフォームのサポート

Type=LoadBalancerを使用したServiceで送信元IPを保持する機能を提供しているのは一部のクラウドプロバイダだけです。実行しているクラウドプロバイダによっては、以下のように異なる方法でリクエストを満たす場合があります。

  1. クライアントとのコネクションをプロキシーが終端し、ノードやエンドポイントとの接続には新しいコネクションが開かれる。このような場合、送信元IPは常にクラウドのロードバランサーのものになり、クライアントのIPにはなりません。

  2. クライアントからロードバランサーのVIPに送信されたリクエストが、中間のプロキシーではなく、クライアントの送信元IPとともにノードまで到達するようなパケット転送が使用される。

1つめのカテゴリーのロードバランサーの場合、真のクライアントIPと通信するために、 HTTPのForwardedヘッダーやX-FORWARDED-FORヘッダー、proxy protocolなどの、ロードバランサーとバックエンドの間で合意されたプロトコルを使用する必要があります。2つ目のカテゴリーのロードバランサーの場合、Serviceのservice.spec.healthCheckNodePortフィールドに保存されたポートを指すHTTPのヘルスチェックを作成することで、上記の機能を活用できます。

クリーンアップ

Serviceを削除します。

kubectl delete svc -l app=source-ip-app

Deployment、ReplicaSet、Podを削除します。

kubectl delete deployment source-ip-app

次の項目