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

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

チュートリアル

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

基本

設定

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

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

サービス

セキュリティ

次の項目

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

1 - Hello Minikube

このチュートリアルでは、minikubeを使用して、Kubernetes上でサンプルアプリケーションを動かす方法を紹介します。 このチュートリアルはNGINXを利用してすべての要求をエコーバックするコンテナイメージを提供します。

目標

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

始める前に

このチュートリアルは、minikubeがセットアップ済みであることを前提としています。 インストール手順はminikube startStep 1 を参照してください。

また、kubectlをインストールする必要があります。 インストール手順はツールのインストールを参照してください。

minikubeクラスターの作成

minikube start 

ダッシュボードを開く

Kubernetesダッシュボードを開きます。これには二通りの方法があります:

新しいターミナルを開き、次のコマンドを実行します:

# 新しいターミナルを起動し、以下を実行したままにします minikube dashboard 

minikube startを実行したターミナルに戻ります。

minikubeが自動的にWebブラウザーを開くことを望まない場合、dashboardサブコマンドを--urlフラグと共に実行します。 minikubeは、お好みのブラウザーで開くことができるURLを出力します。

新しいターミナルを開き、次のコマンドを実行します:

# 新しいターミナルを起動し、以下を実行したままにします minikube dashboard --url 

URLをコピー&ペーストし、ブラウザーで開きます。 minikube startを実行したターミナルに戻ります。

Deploymentの作成

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

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

    # Webサーバーを含むテストコンテナイメージを実行する kubectl create deployment hello-node --image=registry.k8s.io/e2e-test-images/agnhost:2.53 -- /agnhost netexec --http-port=8080 
  2. Deploymentを確認します:

    kubectl get deployments 

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

    NAME READY UP-TO-DATE AVAILABLE AGE hello-node 1/1 1 1 1m 

    (Podが利用可能になるまで時間がかかる場合があります。"0/1"と表示された場合は、数秒後にもう一度確認してみてください。)

  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 
  6. Podで実行されているコンテナのアプリケーションログを確認します(Podの名前はkubectl get podsで取得したものに置き換えてください)。

    kubectl logs hello-node-5f76cf6ccf-br9b5 

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

    I0911 09:19:26.677397 1 log.go:195] Started HTTP server on port 8080 I0911 09:19:26.677586 1 log.go:195] Started UDP server on port 8081 

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をクラスター外部に公開したいことを示しています。

    テストイメージ内のアプリケーションコードはTCPの8080番ポートのみを待ち受けます。 kubectl exposeで8080番ポート以外を公開した場合、クライアントはそのポートに接続できません。

  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 

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

アドオンの有効化

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 

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

    The 'metrics-server' addon is 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の出力を確認します:

    kubectl top pods 

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

    NAME CPU(cores) MEMORY(bytes) hello-node-ccf4b9788-4jn97 1m 6Mi 

    次のメッセージが表示された場合は、しばらく待ってから再度実行してください:

    error: Metrics API not available 
  5. metrics-serverを無効化します:

    minikube addons disable metrics-server 

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

    metrics-server was successfully disabled 

クリーンアップ

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

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

minikubeクラスターを停止します

minikube stop 

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

# オプション minikube delete 

Kubernetesの学習で再度minikubeを使用したい場合、minikubeのVMを削除する必要はありません。

まとめ

このページでは、minikubeクラスターを立ち上げて実行するための基本的な部分を説明しました。 これでアプリケーションをデプロイする準備が整いました。

次の項目

2 - Kubernetesの基本を学ぶ

Kubernetesの基本

このチュートリアルでは、Kubernetesクラスターオーケストレーションシステムの基本について学びます。各モジュールには、Kubernetesの主な機能と概念に関する背景情報と、一緒に進めるためのチュートリアルが含まれています。

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

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

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

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


2.1 - クラスターの作成

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

Kubernetesクラスターとは何かを学ぶ。 Minikubeとは何かを学ぶ。 Kubernetesクラスターを起動する。

目標

  • Kubernetesクラスターとは何かを学ぶ
  • Minikubeとは何かを学ぶ
  • Kubernetesクラスターをローカルで動かす

Kubernetesクラスター

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

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

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

まとめ:

  • Kubernetesクラスター
  • Minikube

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


クラスターダイアグラム


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

ノードは、Kubernetesクラスターのワーカーマシンとして機能するVMまたは物理マシンです。各ノードにはKubeletがあり、これはノードを管理し、Kubernetesコントロールプレーンと通信するためのエージェントです。ノードにはcontainerdCRI-O などのコンテナ操作を処理するためのツールもあるはずです。プロダクションのトラフィックを処理するKubernetesクラスターには、最低3つのノードが必要です。これは、1ノードがダウンすると、etcd メンバーとコントロールプレーンのインスタンスの両方を同時に失うリスクがあり、冗長性が損なわれてしまうからです。このリスクを軽減するには、コントロールプレーンノードを複数構成することで対応できます。

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

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

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

Kubernetesが何であるかがわかったので、Hello Minikube に行き、最初のクラスターを動かしましょう!

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

2.2.1 - Deploymentを作成するためにkubectlを使う

目標

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

KubernetesのDeployment

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

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

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

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

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

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

最初のDeploymentには、NGINXを使用して全てのリクエストをエコーバックする、Dockerコンテナにパッケージ化されたhello-nodeアプリケーションを使用します。 (まだhello-nodeアプリケーションを作成して、コンテナを使用してデプロイしていない場合、Hello Minikube tutorialの通りにやってみましょう。)

kubectlもインストールされている必要があります。 インストールが必要な場合は、ツールのインストールからインストールしてください。

Deploymentが何であるかがわかったので、最初のアプリケーションをデプロイしましょう!

kubectlの基本

kubectlコマンドの一般的な書式はkubectl action resourceです。

これは指定された resource(nodedeploymentなど)に対して指定された action(createdescribedeleteなど)を実行します。 指定可能なパラメーターに関する追加情報を取得するために、サブコマンドの後に--helpを使うこともできます(例:kubectl get nodes --help)。

kubectl versionコマンドを実行して、kubectlがクラスターと通信できるように設定されていることを確認してください。

kubectlがインストールされていて、クライアントとサーバーの両方のバージョンを確認できることを確認してください。

クラスター内のノードを表示するには、kubectl get nodesコマンドを実行します。

利用可能なノードが表示されます。 後で、KubernetesはNodeの利用可能なリソースに基づいてアプリケーションをデプロイする場所を選択します。

アプリケーションをデプロイする

最初のアプリケーションをkubectl create deploymentコマンドでKubernetesにデプロイしてみましょう。 デプロイ名とアプリケーションイメージの場所を指定する必要があります(Docker Hub外でホストされているイメージはリポジトリの完全なURLを含める必要があります)。

kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1 

素晴らしい!Deploymentを作成して、最初のアプリケーションをデプロイしました。 これによっていくつかのことが実行されました。

  • アプリケーションのインスタンスを実行可能な適切なノードを探しました(利用可能なノードは1つしかない)。
  • アプリケーションをそのノードで実行するためのスケジュールを行いました。
  • 必要な場合にインスタンスを新しいノードで再スケジュールするような設定を行いました。

Deploymentの一覧を得るにはkubectl get deploymentsコマンドを使います。

kubectl get deployments 

アプリケーションの単一のインスタンスを実行しているDeploymentが1つあることが分かります。 インスタンスはノード上のコンテナ内で実行されています。

アプリケーションを見る

Kubernetes内部で動作しているPodは、プライベートに隔離されたネットワーク上で動作しています。 デフォルトでは、同じKubernetesクラスター内の他のPodやServiceからは見えますが、そのネットワークの外からは見えません。 kubectlを使用する場合、アプリケーションと通信するためにAPIエンドポイントを通じてやりとりしています。

アプリケーションをKubernetesクラスターの外部に公開するための他のオプションについては、後ほどModule 4で説明します。 また、これは基本的なチュートリアルであるため、ここではPodとは何かについては詳しく説明しません。

kubectl proxyコマンドによって、通信をクラスター全体のプライベートネットワークに転送するプロキシを作成することができます。 プロキシはcontrol-Cキーを押すことで終了させることができ、実行中は何も出力されません。

プロキシを実行するにはもう一つターミナルウィンドウを開く必要があります。

kubectl proxy 

ホスト(端末)とKubernetesクラスター間の接続ができました。 プロキシによって端末からAPIへの直接アクセスが可能となります。

プロキシエンドポイントを通してホストされている全てのAPIを確認することができます。 例えば、curlコマンドを使って、APIを通じて直接バージョンを調べることができます。

curl http://localhost:8001/version 

APIサーバーはPod名に基づいて各Pod用のエンドポイントを自動的に作成し、プロキシからもアクセスできるようにします。

まずPod名を取得する必要があるので、環境変数POD_NAMEに格納しておきます。

export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}') echo Name of the Pod: $POD_NAME 

以下を実行することで、プロキシされたAPIを通してPodにアクセスすることができます。

curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME:8080/proxy/ 

プロキシを使わずに新しいDeploymentにアクセスするには、Module 4で説明するServiceが必要です。

次の項目

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つのノードで実行できます。


このチュートリアルを終えたら、 Serviceを使ったアプリケーションの公開 へ進んでください。

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: registry.k8s.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=guestbook -l tier=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 - クラスターレベルでのPodセキュリティの標準の適用

Podセキュリティアドミッション(PSA)は、ベータへ進み、v1.23以降でデフォルトで有効になっています。 Podセキュリティアドミッションは、Podが作成される際に、Podセキュリティの標準の適用の認可を制御するものです。 このチュートリアルでは、クラスター内の全ての名前空間に標準設定を適用することで、クラスターレベルでbaseline Podセキュリティの標準を強制する方法を示します。

Podセキュリティの標準を特定の名前空間に適用するには、名前空間レベルでのPodセキュリティの標準の適用を参照してください。

v1.34以外のKubernetesバージョンを実行している場合は、そのバージョンのドキュメントを確認してください。

始める前に

ワークステーションに以下をインストールしてください:

このチュートリアルでは、完全な制御下にあるKubernetesクラスターの何を設定できるかをデモンストレーションします。 コントロールプレーンを設定できない管理されたクラスターのPodセキュリティアドミッションに対しての設定方法を知りたいのであれば、名前空間レベルでのPodセキュリティの標準の適用を参照してください。

適用する正しいPodセキュリティの標準の選択

Podのセキュリティアドミッションは、以下のモードでビルトインのPodセキュリティの標準の適用を促します: enforceauditwarn。 設定に最適なPodセキュリティの標準を選択するにあたって助けになる情報を収集するために、以下を行ってください:

  1. Podセキュリティの標準を適用していないクラスターを作成します:

    kind create cluster --name psa-wo-cluster-pss 

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

    Creating cluster "psa-wo-cluster-pss" ... ✓ Ensuring node image (kindest/node:v1.34.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-psa-wo-cluster-pss" You can now use your cluster with: kubectl cluster-info --context kind-psa-wo-cluster-pss Thanks for using kind! 😊 
  2. kubectl contextを新しいクラスターにセットします:

    kubectl cluster-info --context kind-psa-wo-cluster-pss 

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

    Kubernetes control plane is running at https://127.0.0.1:61350 CoreDNS is running at https://127.0.0.1:61350/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. 
  3. クラスター内の名前空間の一覧を取得します:

    kubectl get ns 

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

    NAME STATUS AGE default Active 9m30s kube-node-lease Active 9m32s kube-public Active 9m32s kube-system Active 9m32s local-path-storage Active 9m26s 
  4. 異なるPodセキュリティの標準が適用されたときに何が起きるかを理解するために、-dry-run=serverを使います:

    1. privileged

      kubectl label --dry-run=server --overwrite ns --all \ pod-security.kubernetes.io/enforce=privileged 

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

      namespace/default labeled namespace/kube-node-lease labeled namespace/kube-public labeled namespace/kube-system labeled namespace/local-path-storage labeled 
    2. baseline

      kubectl label --dry-run=server --overwrite ns --all \ pod-security.kubernetes.io/enforce=baseline 

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

      namespace/default labeled namespace/kube-node-lease labeled namespace/kube-public labeled Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "baseline:latest" Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged namespace/kube-system labeled namespace/local-path-storage labeled 
    3. restricted

      kubectl label --dry-run=server --overwrite ns --all \ pod-security.kubernetes.io/enforce=restricted 

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

      namespace/default labeled namespace/kube-node-lease labeled namespace/kube-public labeled Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "restricted:latest" Warning: coredns-7bb9c7b568-hsptc (and 1 other pod): unrestricted capabilities, runAsNonRoot != true, seccompProfile Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile namespace/kube-system labeled Warning: existing pods in namespace "local-path-storage" violate the new PodSecurity enforce level "restricted:latest" Warning: local-path-provisioner-d6d9f7ffc-lw9lh: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile namespace/local-path-storage labeled 

この出力から、privileged Podセキュリティの標準を適用すると、名前空間のどれにも警告が示されないことに気付くでしょう。 これに対し、baselinerestrictの標準ではどちらも、とりわけkube-system名前空間に対して警告が示されています。

モード、バージョン、標準のセット

このセクションでは、latestバージョンに以下のPodセキュリティの標準を適用します:

  • enforceモードでbaseline標準。
  • warnおよびauditモードでrestricted標準。

baseline Podセキュリティの標準は、免除リストを短く保てて、かつ既知の特権昇格を防ぐような、利便性のある中庸を提供します。

加えて、kube-system内の失敗からPodを守るために、適用されるPodセキュリティの標準の対象から名前空間を免除します。

環境にPodセキュリティアドミッションを実装する際には、以下の点を考慮してください:

  1. クラスターに適用されるリスク状況に基づくと、restrictedのようにより厳格なPodセキュリティの標準のほうが、より良い選択肢かもしれません。

  2. kube-system名前空間の免除は、Podがその名前空間でprivilegedとして実行するのを許容することになります。 実世界で使うにあたっては、以下の最小権限の原則に従ってkube-systemへのアクセスを制限する厳格なRBACポリシーを適用することを、Kubernetesプロジェクトは強く推奨します。 上記の標準を実装するには、次のようにします:

  3. 目的のPodセキュリティの標準を実装するために、Podセキュリティアドミッションコントローラーで利用可能な設定ファイルを作成します:

    mkdir -p /tmp/pss cat <<EOF > /tmp/pss/cluster-level-pss.yaml apiVersion: apiserver.config.k8s.io/v1 kind: AdmissionConfiguration plugins: - name: PodSecurity configuration: apiVersion: pod-security.admission.config.k8s.io/v1 kind: PodSecurityConfiguration defaults: enforce: "baseline" enforce-version: "latest" audit: "restricted" audit-version: "latest" warn: "restricted" warn-version: "latest" exemptions: usernames: [] runtimeClasses: [] namespaces: [kube-system] EOF 
  4. クラスターの作成中にこのファイルを取り込むAPIサーバーを設定します:

    cat <<EOF > /tmp/pss/cluster-config.yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane kubeadmConfigPatches: - | kind: ClusterConfiguration apiServer: extraArgs: admission-control-config-file: /etc/config/cluster-level-pss.yaml extraVolumes: - name: accf hostPath: /etc/config mountPath: /etc/config readOnly: false pathType: "DirectoryOrCreate" extraMounts: - hostPath: /tmp/pss containerPath: /etc/config # optional: if set, the mount is read-only. # default false readOnly: false # optional: if set, the mount needs SELinux relabeling. # default false selinuxRelabel: false # optional: set propagation mode (None, HostToContainer or Bidirectional) # see https://kubernetes.io/docs/concepts/storage/volumes/#mount-propagation # default None propagation: None EOF 
  5. 目的のPodセキュリティの標準を適用するために、Podセキュリティアドミッションを使うクラスターを作成します:

    kind create cluster --name psa-with-cluster-pss --config /tmp/pss/cluster-config.yaml 

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

    Creating cluster "psa-with-cluster-pss" ... ✓ Ensuring node image (kindest/node:v1.34.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-psa-with-cluster-pss" You can now use your cluster with: kubectl cluster-info --context kind-psa-with-cluster-pss Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂 
  6. kubectlをこのクラスターに向けます:

    kubectl cluster-info --context kind-psa-with-cluster-pss 

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

    Kubernetes control plane is running at https://127.0.0.1:63855 CoreDNS is running at https://127.0.0.1:63855/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. 
  7. デフォルトの名前空間にPodを作成します:

    kubectl apply -f https://k8s.io/examples/security/example-baseline-pod.yaml 

    Podは正常に開始されますが、出力には警告が含まれます:

    Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost") pod/nginx created 

後片付け

では、上記で作成したクラスターを、以下のコマンドを実行して削除します:

kind delete cluster --name psa-with-cluster-pss 
kind delete cluster --name psa-wo-cluster-pss 

次の項目

5.2 - 名前空間レベルでのPodセキュリティの標準の適用

Podセキュリティアドミッション(PSA)は、ベータへ進み、v1.23以降でデフォルトで有効になっています。 Podセキュリティアドミッションは、Podが作成される際に、Podセキュリティの標準の適用の認可を制御するものです。 このチュートリアルでは、一度に1つの名前空間でbaseline Podセキュリティ標準を強制します。

Podセキュリティの標準を複数の名前空間に一度にクラスターレベルで適用することもできます。やり方についてはクラスターレベルでのPodセキュリティの標準の適用を参照してください。

始める前に

ワークステーションに以下をインストールしてください:

クラスターの作成

  1. 以下のようにKinDクラスターを作成します。

    kind create cluster --name psa-ns-level 

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

    Creating cluster "psa-ns-level" ... ✓ Ensuring node image (kindest/node:v1.34.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-psa-ns-level" You can now use your cluster with: kubectl cluster-info --context kind-psa-ns-level Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/ 
  2. kubectl のコンテキストを新しいクラスターにセットします:

    kubectl cluster-info --context kind-psa-ns-level 

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

    Kubernetes control plane is running at https://127.0.0.1:50996 CoreDNS is running at https://127.0.0.1:50996/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. 

名前空間の作成

exampleと呼ぶ新しい名前空間を作成します:

kubectl create ns example 

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

namespace/example created 

名前空間へのPodセキュリティの標準チェックの有効化

  1. ビルトインのPod Security Admissionでサポートされているラベルを使って、この名前空間のPodセキュリティの標準を有効にします。 このステップでは、baseline Podセキュリティの標準の最新バージョンに合わないPodについて警告するチェックを設定します。

    kubectl label --overwrite ns example \  pod-security.kubernetes.io/warn=baseline \  pod-security.kubernetes.io/warn-version=latest 
  2. ラベルを使って、任意の名前空間に対して複数のPodセキュリティの標準チェックを設定できます。 以下のコマンドは、baseline Podセキュリティの標準をenforce(強制)としますが、restricted Podセキュリティの標準には最新バージョンに準じてwarn(警告)およびaudit(監査)とします(デフォルト値)。

    kubectl label --overwrite ns example \  pod-security.kubernetes.io/enforce=baseline \  pod-security.kubernetes.io/enforce-version=latest \  pod-security.kubernetes.io/warn=restricted \  pod-security.kubernetes.io/warn-version=latest \  pod-security.kubernetes.io/audit=restricted \  pod-security.kubernetes.io/audit-version=latest 

Podセキュリティの標準の強制の実証

  1. example名前空間内にbaseline Podを作成します:

    kubectl apply -n example -f https://k8s.io/examples/security/example-baseline-pod.yaml 

    Podは正常に起動しますが、出力には警告が含まれています。例えば:

    Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost") pod/nginx created 
  2. default名前空間内にbaseline Podを作成します:

    kubectl apply -n default -f https://k8s.io/examples/security/example-baseline-pod.yaml 

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

    pod/nginx created 

example名前空間にだけ、Podセキュリティの標準のenforceと警告の設定が適用されました。 default名前空間内では、警告なしに同じPodを作成できました。

後片付け

では、上記で作成したクラスターを、以下のコマンドを実行して削除します:

kind delete cluster --name psa-ns-level 

次の項目

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

FEATURE STATE: Kubernetes v1.31 [stable] (enabled by default: true)

このページでは、ノードでAppArmorプロファイルを読み込み、それらのプロファイルをPodに適用する方法を説明します。 AppArmorを使用してPodを制限するKubernetesの仕組みについて詳しく知りたい場合は、PodとコンテナのためのLinuxカーネルのセキュリティ制約をご覧ください。

目標

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

始める前に

AppArmorはカーネルモジュールおよびKubernetesのオプション機能です。 そのため、先に進む前にノード上でAppArmorがサポートされていることを確認してください:

  1. AppArmorカーネルモジュールが有効であること。 LinuxカーネルがAppArmorプロファイルを強制するためには、AppArmorカーネルモジュールのインストールと有効化が必須です。 UbuntuやSUSEなどのディストリビューションではデフォルトで有効化されますが、他の多くのディストリビューションでのサポートはオプションです。 モジュールが有効になっているかどうかを確認するためには、/sys/module/apparmor/parameters/enabledファイルを確認します:

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

    Kubeletは、AppArmorが明示的に設定されたPodを受け入れる前にホスト上でAppArmorが有効になっているかどうかを検証します。

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

  3. プロファイルが読み込まれていること。 AppArmorがPodに適用されるのは、各コンテナが実行するAppArmorプロファイルを指定したときです。 もし指定されたプロファイルがまだカーネルに読み込まれていなければ、Kubeletは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) 

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

Podをセキュアにする

AppArmorプロファイルは、Podレベルまたはコンテナレベルで指定することができます。 コンテナのAppArmorプロファイルは、Podのプロファイルよりも優先されます。

securityContext:  appArmorProfile:  type: <profile_type> 

ここで、<profile_type>には次の値のうち1つを指定します:

  • RuntimeDefault: ランタイムのデフォルトのプロファイルを適用します。
  • Localhost: ホストに読み込まれたプロファイルを適用します(下記を参照してください)。
  • unconfined: AppArmorなしで実行します。

AppArmorプロファイルAPIの詳細については、AppArmorによる制限の指定を参照してください。

プロファイルが適用されたかどうかを確認するには、proc attrを調べることでコンテナのルートプロセスが正しいプロファイルで実行されているかどうかを確認します:

kubectl exec <pod_name> -- cat /proc/1/attr/current 

出力は以下のようになるはずです:

cri-containerd.apparmor.d (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を使ってプロファイルをインストールしますが、プロファイルを使用したノードのセットアップでは、他のアプローチについて議論しています。

# この例では、ノード名がホスト名と一致し、SSHで到達可能であることを前提としています。 NODES=($( kubectl get node -o jsonpath='{.items[*].status.addresses[?(.type == "Hostname")].address}' ))  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 spec:  securityContext:  appArmorProfile:  type: Localhost  localhostProfile: k8s-apparmor-example-deny-write  containers:  - name: hello  image: busybox:1.28  command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ] 
kubectl create -f hello-apparmor.yaml 

/proc/1/attr/currentを確認することで、コンテナがこのプロファイルで実際に実行されていることを検証できます:

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 spec:  securityContext:  appArmorProfile:  type: Localhost  localhostProfile: k8s-apparmor-example-allow-write  containers:  - name: hello  image: busybox:1.28  command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ] EOF 
pod/hello-apparmor-2 created 

Podは正常に作成されましたが、さらに調査すると、PodがPendingで止まっていることがわかります:

kubectl describe pod hello-apparmor-2 
Name: hello-apparmor-2 Namespace: default Node: gke-test-default-pool-239f5d02-x1kf/10.128.0.27 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 ... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 10s default-scheduler Successfully assigned default/hello-apparmor to gke-test-default-pool-239f5d02-x1kf Normal Pulled 8s kubelet Successfully pulled image "busybox:1.28" in 370.157088ms (370.172701ms including waiting) Normal Pulling 7s (x2 over 9s) kubelet Pulling image "busybox:1.28" Warning Failed 7s (x2 over 8s) kubelet Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found k8s-apparmor-example-allow-write Normal Pulled 7s kubelet Successfully pulled image "busybox:1.28" in 90.980331ms (91.005869ms including waiting) 

イベントにはエラーメッセージとその理由が表示されます。 具体的な文言はランタイムによって異なります:

 Warning Failed 7s (x2 over 8s) kubelet Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found 

管理

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

Kubernetes 1.34は、AppArmorプロファイルをノードに読み込むネイティブの仕組みは提供していません。 プロファイルは、カスタムインフラストラクチャーやKubernetes Security Profiles Operatorなどのツールを通じて読み込むことができます。

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

Profilesの作成

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

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

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

AppArmorによる制限の指定

セキュリティコンテキスト内のAppArmorプロファイル

appArmorProfileは、コンテナのsecurityContextまたはPodのsecurityContextのいずれかで指定できます。 プロファイルがPodレベルで設定されている場合、Pod内のすべてのコンテナ(initコンテナ、サイドカーコンテナ、およびエフェメラルコンテナを含む)のデフォルトのプロファイルとして使用されます。 Podとコンテナの両方でAppArmorプロファイルが設定されている場合は、コンテナのプロファイルが使用されます。

AppArmorプロファイルには2つのフィールドがあります:

type (必須) - 適用されるAppArmorプロファイルの種類を示します。 有効なオプションは以下の通りです:

Localhost
ノードに事前に読み込まれているプロファイル(localhostProfileで指定します)。
RuntimeDefault
コンテナランタイムのデフォルトのプロファイル。
Unconfined
AppArmorによる制限を適用しません。

localhostProfile - ノード上に読み込まれている、使用すべきプロファイルの名前。 このプロファイルは、ノード上であらかじめ設定されている必要があります。 このオプションは、typeLocalhostの場合にのみ指定する必要があります。

次の項目

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

5.4 - seccompを使用してコンテナのシステムコールを制限する

FEATURE STATE: Kubernetes v1.19 [stable]

seccomp(SECure COMPuting mode)はLinuxカーネル2.6.12以降の機能です。 この機能を用いると、ユーザー空間からカーネルに対して発行できるシステムコールを制限することで、プロセス権限のサンドボックスを構築することができます。 Kubernetesでは、ノード上で読み込んだseccompプロファイルをPodやコンテナに対して自動で適用することができます。

あなたのワークロードが必要とする権限を特定するのは、実際には難しい作業になるかもしれません。 このチュートリアルでは、ローカルのKubernetesクラスターでseccompプロファイルを読み込むための方法を説明し、seccompプロファイルのPodへの適用方法について学んだ上で、コンテナプロセスに対して必要な権限のみを付与するためのseccompプロファイルを作成する方法を概観していきます。

目標

  • ノードでseccompプロファイルを読み込む方法を学ぶ。
  • seccompプロファイルをコンテナに適用する方法を学ぶ。
  • コンテナプロセスが生成するシステムコールの監査出力を確認する。
  • seccompプロファイルが指定されない場合の挙動を確認する。
  • seccompプロファイルの違反を確認する。
  • きめ細やかなseccompプロファイルの作成方法を学ぶ。
  • コンテナランタイムの標準seccompプロファイルを適用する方法を学ぶ。

始める前に

このチュートリアルのステップを完了するためには、kindkubectlをインストールしておく必要があります。

このチュートリアルで利用するコマンドは、Dockerをコンテナランタイムとして利用していることを前提としています。 (kindが作成するクラスターは内部的に異なるコンテナランタイムを利用する可能性があります)。 Podmanを使うこともできますが、チュートリアルを完了するためには、所定の手順に従う必要があります。

このチュートリアルでは、現時点(v1.25以降)でのベータ機能を利用する設定例をいくつか示しますが、その他の例についてはGAなseccomp関連機能を用いています。 利用するKubernetesバージョンを対象としたクラスターの正しい設定がなされていることを確認してください。

チュートリアル内では、サンプルをダウンロードするためにcurlを利用します。 この手順については、ほかの好きなツールを用いて実施してもかまいません。

サンプルのseccompプロファイルをダウンロードする

プロファイルの内容については後で解説しますので、まずはクラスターで読み込むためのseccompプロファイルをprofiles/ディレクトリ内にダウンロードしましょう。

{  "defaultAction": "SCMP_ACT_LOG" }

{  "defaultAction": "SCMP_ACT_ERRNO" }

{  "defaultAction": "SCMP_ACT_ERRNO",  "architectures": [  "SCMP_ARCH_X86_64",  "SCMP_ARCH_X86",  "SCMP_ARCH_X32"  ],  "syscalls": [  {  "names": [  "accept4",  "epoll_wait",  "pselect6",  "futex",  "madvise",  "epoll_ctl",  "getsockname",  "setsockopt",  "vfork",  "mmap",  "read",  "write",  "close",  "arch_prctl",  "sched_getaffinity",  "munmap",  "brk",  "rt_sigaction",  "rt_sigprocmask",  "sigaltstack",  "gettid",  "clone",  "bind",  "socket",  "openat",  "readlinkat",  "exit_group",  "epoll_create1",  "listen",  "rt_sigreturn",  "sched_yield",  "clock_gettime",  "connect",  "dup2",  "epoll_pwait",  "execve",  "exit",  "fcntl",  "getpid",  "getuid",  "ioctl",  "mprotect",  "nanosleep",  "open",  "poll",  "recvfrom",  "sendto",  "set_tid_address",  "setitimer",  "writev",  "fstatfs",  "getdents64",  "pipe2",  "getrlimit"  ],  "action": "SCMP_ACT_ALLOW"  }  ] }

次のコマンドを実行してください:

mkdir ./profiles curl -L -o profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json curl -L -o profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json curl -L -o profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json ls profiles 

最終的に3つのプロファイルが確認できるはずです:

audit.json fine-grained.json violation.json 

kindでローカルKubernetesクラスターを構築する

手軽な方法として、kindを利用することで、seccompプロファイルを読み込んだ単一ノードクラスターを構築できます。 kindはKubernetesをDocker内で稼働させるため、クラスターの各ノードはコンテナとなります。 これにより、ノード上にファイルを展開するのと同じように、各コンテナのファイルシステムに対してファイルをマウントすることが可能です。

apiVersion: kind.x-k8s.io/v1alpha4 kind: Cluster nodes: - role: control-plane  extraMounts:  - hostPath: "./profiles"  containerPath: "/var/lib/kubelet/seccomp/profiles"

kindの設定サンプルをダウンロードして、kind.yamlの名前で保存してください:

curl -L -O https://k8s.io/examples/pods/security/seccomp/kind.yaml 

ノードのコンテナイメージを設定する際には、特定のKubernetesバージョンを指定することもできます。 この設定方法の詳細については、kindのドキュメント内の、ノードの項目を参照してください。 このチュートリアルではKubernetes v1.34を使用することを前提とします。

ベータ機能として、Unconfinedにフォールバックするのではなく、コンテナランタイムがデフォルトで推奨するプロファイルを利用するようにKubernetesを設定することもできます。 この機能を試したい場合、これ以降の手順に進む前に、全ワークロードに対する標準seccompプロファイルとしてRuntimeDefaultを使用するを参照してください。

kindの設定ファイルを設置したら、kindクラスターを作成します:

kind create cluster --config=kind.yaml 

Kubernetesクラスターが準備できたら、単一ノードクラスターが稼働しているDockerコンテナを特定してください:

docker ps 

kind-control-planeという名前のコンテナが稼働していることが確認できるはずです。 出力は次のようになるでしょう:

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6a96207fed4b kindest/node:v1.18.2 "/usr/local/bin/entr…" 27 seconds ago Up 24 seconds 127.0.0.1:42223->6443/tcp kind-control-plane 

このコンテナのファイルシステムを観察すると、profiles/ディレクトリがkubeletのデフォルトのseccompパスとして正しく読み込まれていることを確認できるはずです。 Pod内でコマンドを実行するためにdocker execを使います:

# 6a96207fed4bを"docker ps"で確認したコンテナIDに変更してください。 docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles 
audit.json fine-grained.json violation.json 

kind内で稼働しているkubeletがseccompプロファイルを利用可能であることを確認しました。

コンテナランタイムの標準seccompプロファイルを利用してPodを作成する

ほとんどのコンテナランタイムは、許可・拒否の対象とする標準的なシステムコールの集合を提供しています。

PodやContainerのセキュリティコンテキストでseccompタイプをRuntimeDefaultに設定すると、コンテナランタイムが提供するデフォルトのプロファイルをワークロードに適用することができます。

Pod内の全てのContainerに対してRuntimeDefault seccompプロファイルを要求するマニフェストは次のようなものです:

apiVersion: v1 kind: Pod metadata:  name: default-pod  labels:  app: default-pod spec:  securityContext:  seccompProfile:  type: RuntimeDefault  containers:  - name: test-container  image: hashicorp/http-echo:1.0  args:  - "-text=just made some more syscalls!"  securityContext:  allowPrivilegeEscalation: false

このPodを作成してみます:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/default-pod.yaml 
kubectl get pod default-pod 

Podが正常に起動できていることを確認できるはずです:

NAME READY STATUS RESTARTS AGE default-pod 1/1 Running 0 20s 

次のセクションに進む前に、Podを削除します:

kubectl delete pod default-pod --wait --now 

システムコール監査のためのseccompプロファイルを利用してPodを作成する

最初に、新しいPodにプロセスの全システムコールを記録するためのaudit.jsonプロファイルを適用します。

このPodのためのマニフェストは次の通りです:

apiVersion: v1 kind: Pod metadata:  name: audit-pod  labels:  app: audit-pod spec:  securityContext:  seccompProfile:  type: Localhost  localhostProfile: profiles/audit.json  containers:  - name: test-container  image: hashicorp/http-echo:1.0  args:  - "-text=just made some syscalls!"  securityContext:  allowPrivilegeEscalation: false

クラスター内にPodを作成します:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/audit-pod.yaml 

このプロファイルは、いかなるシステムコールも制限しないため、Podは正常に起動するはずです。

kubectl get pod audit-pod 
NAME READY STATUS RESTARTS AGE audit-pod 1/1 Running 0 30s 

このコンテナが公開するエンドポイントとやりとりするために、kindのコントロールプレーンコンテナの内部からこのエンドポイントにアクセスできるように、NodePort Serviceを作成します。

kubectl expose pod audit-pod --type NodePort --port 5678 

どのポートがノード上のServiceに割り当てられたのかを確認しましょう。

kubectl get service audit-pod 

次のような出力が得られます:

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE audit-pod NodePort 10.111.36.142 <none> 5678:32373/TCP 72s 

ここまで来れば、kindのコントロールプレーンコンテナの内部からエンドポイントに対して、Serviceが公開するポートを通じてcurlで接続することができます。 docker execを使って、コントロールプレーンコンテナに属するコンテナの中からcurlを実行しましょう:

# 6a96207fed4bと32373を、それぞれ"docker ps"で確認したコンテナIDとポート番号に変更してください。 docker exec -it 6a96207fed4b curl localhost:32373 
just made some syscalls! 

プロセスが実行されていることは確認できましたが、実際にどんなシステムコールが発行されているのでしょうか? このPodはローカルクラスターで稼働しているため、発行されたシステムコールを/var/log/syslogで確認することができるはずです。 新しいターミナルを開いて、http-echoが発行するシステムコールをtailしてみましょう:

# あなたのマシンのログは"/var/log/syslog"以外の場所にあるかもしれません。 tail -f /var/log/syslog | grep 'http-echo' 

すでにhttp-echoが発行したいくつかのシステムコールのログが見えているはずです。 コントロールプレーンコンテナ内から再度curlを実行すると、新たにログに追記された内容が出力されます。

例えば、次のような出力が得られるでしょう:

Jul 6 15:37:40 my-machine kernel: [369128.669452] audit: type=1326 audit(1594067860.484:14536): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=51 compat=0 ip=0x46fe1f code=0x7ffc0000 Jul 6 15:37:40 my-machine kernel: [369128.669453] audit: type=1326 audit(1594067860.484:14537): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=54 compat=0 ip=0x46fdba code=0x7ffc0000 Jul 6 15:37:40 my-machine kernel: [369128.669455] audit: type=1326 audit(1594067860.484:14538): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000 Jul 6 15:37:40 my-machine kernel: [369128.669456] audit: type=1326 audit(1594067860.484:14539): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=288 compat=0 ip=0x46fdba code=0x7ffc0000 Jul 6 15:37:40 my-machine kernel: [369128.669517] audit: type=1326 audit(1594067860.484:14540): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=0 compat=0 ip=0x46fd44 code=0x7ffc0000 Jul 6 15:37:40 my-machine kernel: [369128.669519] audit: type=1326 audit(1594067860.484:14541): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000 Jul 6 15:38:40 my-machine kernel: [369188.671648] audit: type=1326 audit(1594067920.488:14559): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000 Jul 6 15:38:40 my-machine kernel: [369188.671726] audit: type=1326 audit(1594067920.488:14560): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000 

各行のsyscall=エントリに着目することで、http-echoプロセスが必要とするシステムコールを理解していくことができるでしょう。 このプロセスが利用する全てのシステムコールを網羅するものではないかもしれませんが、このコンテナのseccompプロファイルを作成する上での基礎とすることは可能です。

次のセクションに進む前にServiceとPodを削除します:

kubectl delete service audit-pod --wait kubectl delete pod audit-pod --wait --now 

違反が発生するseccompプロファイルでPodを作成する

デモとして、いかなるシステムコールも許可しないseccompプロファイルをPodに適用してみましょう。

マニフェストは次の通りです:

apiVersion: v1 kind: Pod metadata:  name: violation-pod  labels:  app: violation-pod spec:  securityContext:  seccompProfile:  type: Localhost  localhostProfile: profiles/violation.json  containers:  - name: test-container  image: hashicorp/http-echo:1.0  args:  - "-text=just made some syscalls!"  securityContext:  allowPrivilegeEscalation: false

クラスターにPodを作成してみます:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/violation-pod.yaml 

Podを作成しても問題が発生します。 Podの状態を確認すると、起動に失敗していることが確認できるはずです。

kubectl get pod violation-pod 
NAME READY STATUS RESTARTS AGE violation-pod 0/1 CrashLoopBackOff 1 6s 

直前の事例で見てきたように、http-echoプロセスは多くのシステムコールを必要とします。 ここでは"defaultAction": "SCMP_ACT_ERRNO"が設定されているため、あらゆるシステムコールに対してseccompがエラーを発生させました。

この構成はとてもセキュアですが、有意義な処理は何もできないことを意味します。 私たちが実際にやりたいのは、ワークロードが必要とする権限のみを付与することです。

次のセクションに進む前にPodを削除します。

kubectl delete pod violation-pod --wait --now 

必要なシステムコールのみを許可するseccompプロファイルを用いてPodを作成する

fine-grained.jsonプロファイルの内容を見れば、最初の例で"defaultAction":"SCMP_ACT_LOG"を設定していた際に、ログに表示されたシステムコールが含まれていることに気づくでしょう。 今回のプロファイルでも"defaultAction": "SCMP_ACT_ERRNO"を設定しているものの、"action": "SCMP_ACT_ALLOW"ブロックで明示的に一連のシステムコールを許可しています。 理想的な状況下であれば、コンテナが正常に稼働することに加え、syslogへのメッセージ送信を見ることはなくなるでしょう。

この事例のマニフェストは次の通りです:

apiVersion: v1 kind: Pod metadata:  name: fine-pod  labels:  app: fine-pod spec:  securityContext:  seccompProfile:  type: Localhost  localhostProfile: profiles/fine-grained.json  containers:  - name: test-container  image: hashicorp/http-echo:1.0  args:  - "-text=just made some syscalls!"  securityContext:  allowPrivilegeEscalation: false

クラスター内にPodを作成します:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/fine-pod.yaml 
kubectl get pod fine-pod 

Podは正常に起動しているはずです:

NAME READY STATUS RESTARTS AGE fine-pod 1/1 Running 0 30s 

新しいターミナルを開いて、http-echoからのシステムコールに関するログエントリをtailで監視しましょう:

# あなたのマシンのログは"/var/log/syslog"以外の場所にあるかもしれません。 tail -f /var/log/syslog | grep 'http-echo' 

次のPodをNodePort Serviceで公開します:

kubectl expose pod fine-pod --type NodePort --port 5678 

ノード上のServiceに割り当てられたポートを確認します:

kubectl get service fine-pod 

出力は次のようになるでしょう:

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE fine-pod NodePort 10.111.36.142 <none> 5678:32373/TCP 72s 

kindのコントロールプレーンコンテナの内部から、curlを用いてエンドポイントにアクセスします:

# 6a96207fed4bと32373を、それぞれ"docker ps"で確認したコンテナIDとポート番号に変更してください。 docker exec -it 6a96207fed4b curl localhost:32373 
just made some syscalls! 

syslogには何も出力されないはずです。 なぜなら、このプロファイルは必要なシステムコールを全て許可しており、一覧にないシステムコールが呼び出された時にのみエラーを発生させるように構成しているためです。 これはセキュリティの観点からすると理想的なシチュエーションといえますが、seccompプロファイルを作成するためのプログラムの解析には多少の労力が必要です。 多くの労力を割かなくてもこれに近いセキュリティが得られるシンプルな手法があったなら、きっと素晴らしいものになるでしょう。

次のセクションに進む前にServiceとPodを削除します:

kubectl delete service fine-pod --wait kubectl delete pod fine-pod --wait --now 

全ワークロードに対する標準seccompプロファイルとしてRuntimeDefaultを使用する

FEATURE STATE: Kubernetes v1.27 [stable]

標準seccompプロファイルを指定するためには、この機能を利用したい全てのノードで--seccomp-defaultコマンドラインフラグを用いてkubeletを起動する必要があります。

この機能を有効化すると、kubeletはコンテナランタイムが定義するRuntimeDefaultのseccompプロファイルをデフォルトで使用するようになり、Unconfinedモード(seccomp無効化)になることはありません。 コンテナランタイムが用意する標準プロファイルは、ワークロードの機能性を維持しつつ、強力な標準セキュリティルールの一式を用意することを目指しています。 標準のプロファイルはコンテナランタイムやリリースバージョンによって異なる可能性があります。 例えば、CRI-Oとcontainerdの標準プロファイルを比較してみるとよいでしょう。

いくつかのワークロードでは、他のワークロードよりもシステムコール制限を少なくすることが必要な場合があります。 つまり、RuntimeDefaultを適用する場合、こうしたワークロードの実行が失敗する可能性があります。 このような障害を緩和するために、次のような対策を講じることができます:

  • ワークロードを明示的にUnconfinedとして稼働させる。
  • SeccompDefault機能をノードで無効化する。 また、機能を無効化したノードに対してワークロードが配置されていることを確認しておく。
  • ワークロードを対象とするカスタムseccompプロファイルを作成する。

実運用環境に近いクラスターに対してこの機能を展開する場合、クラスター全体に対して変更をロールアウトする前に、一部ノードのみを対象にしてこのフィーチャーゲートを有効化し、ワークロードが実行できることを検証しておくことをお勧めします。

クラスターに対してとりうるアップグレード・ダウングレード戦略について更に詳細な情報を知りたい場合は、関連するKubernetes Enhancement Proposal (KEP)であるEnable seccomp by defaultを参照してください。

Kubernetes 1.34では、specで特定のseccompプロファイルを指定していないPodに対して、デフォルトで適用するseccompプロファイルを設定することができます。 ただし、この標準seccompプロファイルを利用する場合、対象とする全ノードでこの機能を有効化しておく必要があります。

稼働中のKubernetes 1.34クラスターでこの機能を有効化する場合、kubeletに--seccomp-defaultコマンドラインフラグを付与して起動するか、Kubernetesの設定ファイルでこの機能を有効化する必要があります。 このフィーチャーゲートをkindで有効化する場合、kindが最低限必要なKubernetesバージョンを満たしていて、かつkindの設定ファイルSeccompDefaultを有効化してください:

kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes:  - role: control-plane  image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c  kubeadmConfigPatches:  - |  kind: JoinConfiguration  nodeRegistration:  kubeletExtraArgs:  seccomp-default: "true"  - role: worker  image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c  kubeadmConfigPatches:  - |  kind: JoinConfiguration  nodeRegistration:  kubeletExtraArgs:  seccomp-default: "true" 

クラスターの準備ができたら、Podを実行します:

kubectl run --rm -it --restart=Never --image=alpine alpine -- sh 

このコマンドで標準のseccompプロファイルを紐付けられるはずです。 この結果を確認するには、docker exec経由でcrictl inspectを実行することで、kindワーカー上のコンテナを確認します:

docker exec -it kind-worker bash -c \  'crictl inspect $(crictl ps --name=alpine -q) | jq .info.runtimeSpec.linux.seccomp' 
{  "defaultAction": "SCMP_ACT_ERRNO",  "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],  "syscalls": [  {  "names": ["..."]  }  ] } 

次の項目

Linuxのseccompについて更に学びたい場合は、次の記事を参考にすると良いでしょう:

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

6.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: registry.k8s.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 

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

# このコマンドは、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内のRunningかつ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 
registry.k8s.io/nginx-slim:0.8 registry.k8s.io/nginx-slim:0.8 registry.k8s.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":"registry.k8s.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}}' 
registry.k8s.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}}' 
registry.k8s.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}}' 
registry.k8s.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 
registry.k8s.io/nginx-slim:0.7 registry.k8s.io/nginx-slim:0.7 registry.k8s.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: registry.k8s.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 

6.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 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:8.0  name: mysql  env:  - name: MYSQL_ROOT_PASSWORD  valueFrom:  secretKeyRef:  name: mysql-pass  key: password  - name: MYSQL_DATABASE  value: wordpress  - name: MYSQL_USER  value: wordpress  - name: MYSQL_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 ./ 

次の項目

6.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: 500  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_TOKENS32
CASSANDRA_RPC_ADDRESS0.0.0.0

次の項目

6.4 - 分散システムコーディネーターZooKeeperの実行

このチュートリアルでは、StatefulSetPodDisruptionBudgetsPodアンチアフィニティを使って、Kubernetes上でのApache Zookeeperの実行をデモンストレーションします。

始める前に

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

少なくとも4つのノードのクラスターが必要で、各ノードは少なくとも2つのCPUと4GiBのメモリが必須です。このチュートリアルでは、クラスターのノードをcordonおよびdrainします。 つまり、クラスターがそのノードの全てのPodを終了して退去させ、ノードが一時的にスケジュールできなくなる、ということです。 このチュートリアル専用のクラスターを使うか、起こした破壊がほかのテナントに干渉しない確証を得ることをお勧めします。

このチュートリアルでは、クラスターがPersistentVolumeの動的なプロビジョニングが行われるように設定されていることを前提としています。 クラスターがそのように設定されていない場合、チュートリアルを始める前に20GiBのボリュームを3つ、手動でプロビジョニングする必要があります。

目標

このチュートリアルを終えると、以下の知識を得られます。

  • StatefulSetを使ってZooKeeperアンサンブルをデプロイする方法。
  • アンサンブルを一貫して設定する方法。
  • ZooKeeperサーバーのデプロイをアンサンブルに広げる方法。
  • 計画されたメンテナンス中もサービスが利用可能であることを保証するためにPodDisruptionBudgetsを使う方法。

ZooKeeper

Apache ZooKeeperは、分散アプリケーションのための、分散型オープンソースコーディネーションサービスです。 ZooKeeperでは、データの読み書き、および更新の監視ができます。 データは階層化されてファイルシステム内に編成され、アンサンブル(ZooKeeperサーバーのセット)内の全てのZooKeeperサーバーに複製されます。 データへの全ての操作はアトミックかつ逐次的に一貫性を持ちます。 ZooKeeperは、アンサンブル内の全てのサーバー間でステートマシンを複製するためにZab合意プロトコルを使ってこれを保証します。

アンサンブルはリーダーを選出するのにZabプロトコルを使い、選出が完了するまでデータを書き出しません。 完了すると、アンサンブルは複製するのにZabを使い、書き込みが承認されてクライアントに可視化されるより前に、全ての書き込みをクォーラムに複製することを保証します。 重み付けされたクォーラムでなければ、クォーラムは現在のリーダーを含むアンサンブルの過半数を占めるコンポーネントです。 例えばアンサンブルが3つのサーバーを持つ時、リーダーとそれ以外のもう1つのサーバーを含むコンポーネントが、クォーラムを構成します。 アンサンブルがクォーラムに達しない場合、アンサンブルはデータを書き出せません。

ZooKeeperサーバー群はそれらの全てのステートマシンをメモリに保持し、それぞれの変化をストレージメディア上の永続的なWAL(Write Ahead Log)に書き出します。 サーバーがクラッシュした時には、WALをリプレーすることで以前のステートに回復できます。 WALを際限のない増加から防ぐために、ZooKeeperサーバーは、メモリステートにあるものをストレージメディアに定期的にスナップショットします。 これらのスナップショットはメモリに直接読み込むことができ、スナップショットより前の全てのWALエントリは破棄され得ます。

ZooKeeperアンサンブルの作成

以下のマニフェストはHeadless ServiceServicePodDisruptionBudgetStatefulSetを含んでいます。

apiVersion: v1 kind: Service metadata:  name: zk-hs  labels:  app: zk spec:  ports:  - port: 2888  name: server  - port: 3888  name: leader-election  clusterIP: None  selector:  app: zk --- apiVersion: v1 kind: Service metadata:  name: zk-cs  labels:  app: zk spec:  ports:  - port: 2181  name: client  selector:  app: zk --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata:  name: zk-pdb spec:  selector:  matchLabels:  app: zk  maxUnavailable: 1 --- apiVersion: apps/v1 kind: StatefulSet metadata:  name: zk spec:  selector:  matchLabels:  app: zk  serviceName: zk-hs  replicas: 3  updateStrategy:  type: RollingUpdate  podManagementPolicy: OrderedReady  template:  metadata:  labels:  app: zk  spec:  affinity:  podAntiAffinity:  requiredDuringSchedulingIgnoredDuringExecution:  - labelSelector:  matchExpressions:  - key: "app"  operator: In  values:  - zk  topologyKey: "kubernetes.io/hostname"  containers:  - name: kubernetes-zookeeper  imagePullPolicy: Always  image: "registry.k8s.io/kubernetes-zookeeper:1.0-3.4.10"  resources:  requests:  memory: "1Gi"  cpu: "0.5"  ports:  - containerPort: 2181  name: client  - containerPort: 2888  name: server  - containerPort: 3888  name: leader-election  command:  - sh  - -c  - "start-zookeeper \  --servers=3 \  --data_dir=/var/lib/zookeeper/data \  --data_log_dir=/var/lib/zookeeper/data/log \  --conf_dir=/opt/zookeeper/conf \  --client_port=2181 \  --election_port=3888 \  --server_port=2888 \  --tick_time=2000 \  --init_limit=10 \  --sync_limit=5 \  --heap=512M \  --max_client_cnxns=60 \  --snap_retain_count=3 \  --purge_interval=12 \  --max_session_timeout=40000 \  --min_session_timeout=4000 \  --log_level=INFO"  readinessProbe:  exec:  command:  - sh  - -c  - "zookeeper-ready 2181"  initialDelaySeconds: 10  timeoutSeconds: 5  livenessProbe:  exec:  command:  - sh  - -c  - "zookeeper-ready 2181"  initialDelaySeconds: 10  timeoutSeconds: 5  volumeMounts:  - name: datadir  mountPath: /var/lib/zookeeper  securityContext:  runAsUser: 1000  fsGroup: 1000  volumeClaimTemplates:  - metadata:  name: datadir  spec:  accessModes: [ "ReadWriteOnce" ]  resources:  requests:  storage: 10Gi 

ターミナルを開き、マニフェストを作成するために kubectl applyコマンドを使います。

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

これはzk-hs Headless Service、zk-cs Service、zk-pdb PodDisruptionBudget、 zk StatefulSetを作成します。

service/zk-hs created service/zk-cs created poddisruptionbudget.policy/zk-pdb created statefulset.apps/zk created 

StatefulSetのPodを作成するStatefulSetコントローラーを監視するため、kubectl getを使います。

kubectl get pods -w -l app=zk 

zk-2 PodがRunningおよびReadyになったら、CTRL-Cでkubectlを終了してください。

NAME READY STATUS RESTARTS AGE zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 19s zk-0 1/1 Running 0 40s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 ContainerCreating 0 0s zk-1 0/1 Running 0 18s zk-1 1/1 Running 0 40s zk-2 0/1 Pending 0 0s zk-2 0/1 Pending 0 0s zk-2 0/1 ContainerCreating 0 0s zk-2 0/1 Running 0 19s zk-2 1/1 Running 0 40s 

StatefulSetコントローラーが3つのPodを作成し、各PodはZooKeeperサーバー付きのコンテナを持ちます。

リーダーの選出のファシリテート

匿名のネットワークにおいてリーダー選出を終了するアルゴリズムがないので、Zabはリーダー選出のための明示的なメンバーシップ設定を要します。 アンサンブルの各サーバーはユニーク識別子を持つ必要があり、全てのサーバーは識別子のグローバルセットを知っている必要があり、各識別子はネットワークアドレスと関連付けられている必要があります。

zk StatefulSetのPodのホスト名を取得するためにkubectl execを使います。

for i in 0 1 2; do kubectl exec zk-$i -- hostname; done 

StatefulSetコントローラーは各Podに、その順序インデックスに基づくユニークなホスト名を提供します。 ホスト名は<statefulset名>-<順序インデックス>という形をとります。 zk StatefulSetのreplicasフィールドが3にセットされているので、このセットのコントローラーは、ホスト名にそれぞれzk-0zk-1zk-2が付いた3つのPodを作成します。

zk-0 zk-1 zk-2 

ZooKeeperアンサンブルのサーバーは、ユニーク識別子として自然数を使い、それぞれのサーバーの識別子をサーバーのデータディレクトリ内のmyidというファイルに格納します。

各サーバーのmyidファイルの内容を調べるには、以下のコマンドを使います。

for i in 0 1 2; do echo "myid zk-$i";kubectl exec zk-$i -- cat /var/lib/zookeeper/data/myid; done 

識別子が自然数で順序インデックスは正の整数なので、順序に1を加算することで識別子を生成できます。

myid zk-0 1 myid zk-1 2 myid zk-2 3 

zk StatefulSet内の各Podの完全修飾ドメイン名(FQDN)を取得するには、以下のコマンドを使います。

for i in 0 1 2; do kubectl exec zk-$i -- hostname -f; done 

zk-hs Serviceは、全Podのためのドメインzk-hs.default.svc.cluster.localを作成します。

zk-0.zk-hs.default.svc.cluster.local zk-1.zk-hs.default.svc.cluster.local zk-2.zk-hs.default.svc.cluster.local 

Kubernetes DNSのAレコードは、FQDNをPodのIPアドレスに解決します。 KubernetesがPodを再スケジュールした場合、AレコードはPodの新しいIPアドレスに更新されますが、Aレコードの名前は変更されません。

ZooKeeperはそのアプリケーション設定をzoo.cfgという名前のファイルに格納します。 zk-0 Pod内のzoo.cfgファイルの内容を見るには、kubectl execを使います。

kubectl exec zk-0 -- cat /opt/zookeeper/conf/zoo.cfg 

ファイル末尾にあるserver.1server.2server.3のプロパティの、123はZooKeeperサーバーのmyidファイル内の識別子に対応します。 これらはzk StatefulSet内のPodのFQDNにセットされます。

clientPort=2181 dataDir=/var/lib/zookeeper/data dataLogDir=/var/lib/zookeeper/log tickTime=2000 initLimit=10 syncLimit=2000 maxClientCnxns=60 minSessionTimeout= 4000 maxSessionTimeout= 40000 autopurge.snapRetainCount=3 autopurge.purgeInterval=0 server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888 server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888 server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888 

合意形成

合意(consensus)プロトコルは、各参加者の識別子がユニークであることを要件としています。 Zabプロトコル内で同じユニーク識別子を主張する2つの参加者はないものとします。 これは、システム内のプロセスが、どのプロセスがどのデータをコミットしたかを同意できるようにするために必須です。 2つのPodが同じ順序値で起動されたなら、2つのZooKeeperサーバーはどちらもそれら自身を同じサーバーとして認識してしまうでしょう。

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

各PodのAレコードは、PodがReadyになった時に記入されます。そのため、ZooKeeperサーバー群のFQDNはある1つのエンドポイント、すなわちmyidファイルで設定された識別子を主張するユニークなZooKeeperサーバーに解決されます。

zk-0.zk-hs.default.svc.cluster.local zk-1.zk-hs.default.svc.cluster.local zk-2.zk-hs.default.svc.cluster.local 

これは、ZooKeeperのzoo.cfgファイル内のserversプロパティが正しく設定されたアンサンブルを表していることを保証します。

server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888 server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888 server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888 

サーバーが値のコミットを試みるためにZabプロトコルを使う時、(リーダー選出が成功していて、少なくともPodのうちの2つがRunningおよびReadyならば)それぞれのサーバーは双方の合意をとって値をコミット、あるいは、(もし双方の状態が合わなければ)それを行うことに失敗します。 あるサーバーが別のサーバーを代行して書き込みを承認する状態は発生しません。

アンサンブルの健全性テスト

最も基本的な健全性テストは、データを1つのZooKeeperサーバーに書き込み、そのデータを別のサーバーで読み取ることです。

以下のコマンドは、worldをアンサンブル内のzk-0 Podのパス/helloに書き込むのに、zkCli.shスクリプトを実行します。

kubectl exec zk-0 -- zkCli.sh create /hello world 
WATCHER:: WatchedEvent state:SyncConnected type:None path:null Created /hello 

zk-1 Podからデータを取得するには、以下のコマンドを使います。

kubectl exec zk-1 -- zkCli.sh get /hello 

zk-0に作成したデータは、アンサンブル内の全てのサーバーで利用できます。

WATCHER:: WatchedEvent state:SyncConnected type:None path:null world cZxid = 0x100000002 ctime = Thu Dec 08 15:13:30 UTC 2016 mZxid = 0x100000002 mtime = Thu Dec 08 15:13:30 UTC 2016 pZxid = 0x100000002 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5 numChildren = 0 

永続的なストレージの提供

ZooKeeperの概要のセクションで言及したように、 ZooKeeperは全てのエントリを永続的なWALにコミットし、定期的にメモリ状態のスナップショットをストレージメディアに書き出します。 永続性を提供するためにWALを使用するのは、複製されたステートマシンを立てるために合意プロトコルを使うアプリケーションでよくあるテクニックです。

zk StatefulSetを削除するために、kubectl deleteコマンドを使います。

kubectl delete statefulset zk 
statefulset.apps "zk" deleted 

StatefulSet内のPodの終了を観察します。

kubectl get pods -w -l app=zk 

zk-0が完全に終了したら、CTRL-Cでkubectlを終了します。

zk-2 1/1 Terminating 0 9m zk-0 1/1 Terminating 0 11m zk-1 1/1 Terminating 0 10m zk-2 0/1 Terminating 0 9m zk-2 0/1 Terminating 0 9m zk-2 0/1 Terminating 0 9m zk-1 0/1 Terminating 0 10m zk-1 0/1 Terminating 0 10m zk-1 0/1 Terminating 0 10m zk-0 0/1 Terminating 0 11m zk-0 0/1 Terminating 0 11m zk-0 0/1 Terminating 0 11m 

zookeeper.yamlのマニフェストを再適用します。

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

これはzk StatefulSetオブジェクトを作成しますが、マニフェストのその他のAPIオブジェクトはすでに存在しているので変更されません。

StatefulSetコントローラーがStatefulSetのPodを再作成するのを見てみます。

kubectl get pods -w -l app=zk 

zk-2 PodがRunningおよびReadyになったら、CTRL-Cでkubectlを終了します。

NAME READY STATUS RESTARTS AGE zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 19s zk-0 1/1 Running 0 40s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 ContainerCreating 0 0s zk-1 0/1 Running 0 18s zk-1 1/1 Running 0 40s zk-2 0/1 Pending 0 0s zk-2 0/1 Pending 0 0s zk-2 0/1 ContainerCreating 0 0s zk-2 0/1 Running 0 19s zk-2 1/1 Running 0 40s 

健全性テストで入力した値をzk-2 Podから取得するには、以下のコマンドを使います。

kubectl exec zk-2 zkCli.sh get /hello 

zk StatefulSet内の全てのPodを終了して再作成したにもかかわらず、アンサンブルは元の値をなおも供給します。

WATCHER:: WatchedEvent state:SyncConnected type:None path:null world cZxid = 0x100000002 ctime = Thu Dec 08 15:13:30 UTC 2016 mZxid = 0x100000002 mtime = Thu Dec 08 15:13:30 UTC 2016 pZxid = 0x100000002 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5 numChildren = 0 

zk StatefulSetのspecvolumeClaimTemplatesフィールドは、各PodにプロビジョニングされるPersistentVolumeを指定します。

volumeClaimTemplates:  - metadata:  name: datadir  annotations:  volume.alpha.kubernetes.io/storage-class: anything  spec:  accessModes: [ "ReadWriteOnce" ]  resources:  requests:  storage: 20Gi 

StatefulSetコントローラーは、StatefulSet内の各PodのためにPersistentVolumeClaimを生成します。

StatefulSetPersistentVolumeClaimsを取得するために、以下のコマンドを使います。

kubectl get pvc -l app=zk 

StatefulSetがそのPodを再作成した時、StatefulSetはPodのPersistentVolumeを再マウントします。

NAME STATUS VOLUME CAPACITY ACCESSMODES AGE datadir-zk-0 Bound pvc-bed742cd-bcb1-11e6-994f-42010a800002 20Gi RWO 1h datadir-zk-1 Bound pvc-bedd27d2-bcb1-11e6-994f-42010a800002 20Gi RWO 1h datadir-zk-2 Bound pvc-bee0817e-bcb1-11e6-994f-42010a800002 20Gi RWO 1h 

StatefulSetのコンテナtemplatevolumeMountsセクションは、ZooKeeperサーバーのデータディレクトリにあるPersistentVolumeをマウントします。

volumeMounts: - name: datadir  mountPath: /var/lib/zookeeper 

zk StatefulSet内のPodが(再)スケジュールされると、ZooKeeperサーバーのデータディレクトリにマウントされた同じPersistentVolumeを常に得ます。 Podが再スケジュールされたとしても、全ての書き込みはZooKeeperサーバーのWALおよび全スナップショットに行われ、永続性は残ります。

一貫性のある設定の保証

リーダーの選出のファシリテートおよび合意形成のセクションで記したように、ZooKeeperのアンサンブル内のサーバー群は、リーダーを選出しクォーラムを形成するための一貫性のある設定を必要とします。 また、プロトコルがネットワーク越しで正しく動作するために、Zabプロトコルの一貫性のある設定も必要です。 この例では、設定を直接マニフェストに埋め込むことで一貫性のある設定を達成します。

zk StatefulSetを取得しましょう。

kubectl get sts zk -o yaml 
… command: - sh - -c - "start-zookeeper \ --servers=3 \ --data_dir=/var/lib/zookeeper/data \ --data_log_dir=/var/lib/zookeeper/data/log \ --conf_dir=/opt/zookeeper/conf \ --client_port=2181 \ --election_port=3888 \ --server_port=2888 \ --tick_time=2000 \ --init_limit=10 \ --sync_limit=5 \ --heap=512M \ --max_client_cnxns=60 \ --snap_retain_count=3 \ --purge_interval=12 \ --max_session_timeout=40000 \ --min_session_timeout=4000 \ --log_level=INFO" … 

このcommandでは、ZooKeeperサーバーを開始するためにコマンドラインパラメータで設定を渡しています。 設定をアンサンブルへ渡すのには環境変数を使うこともできます。

ログの設定

zkGenConfig.shスクリプトで生成されたファイルの1つは、ZooKeeperのログを制御します。 ZooKeeperはLog4jを使い、デフォルトではログの設定に基づいて、ログ設定に時間およびサイズベースでのローリングファイルアペンダー(ログのローテーション)を使用します。

zk StatefulSet内のPodの1つからログ設定を取得するには、以下のコマンドを使います。

kubectl exec zk-0 cat /usr/etc/zookeeper/log4j.properties 

以下のログ設定は、ZooKeeperにログの全てを標準出力ファイルストリームに書き出す処理をさせます。

zookeeper.root.logger=CONSOLE zookeeper.console.threshold=INFO log4j.rootLogger=${zookeeper.root.logger} log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold} log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n 

これはログコンテナ内のログを安全にとるための、最もシンプルと思われる方法です。 アプリケーションはログを標準出力に書き出し、Kubernetesがログのローテーションを処理してくれます。 Kubernetesは、標準出力と標準エラー出力に書き出されるアプリケーションのログがローカルストレージメディアを使い尽くさないことを保証する、健全維持ポリシーも実装しています。

Podの1つから末尾20行を取得するために、kubectl logsを使ってみます。

kubectl logs zk-0 --tail 20 

kubectl logsを利用するか、Kubernetes Dashboardから、標準出力または標準エラーに書き出されたアプリケーションログを参照できます。

2016-12-06 19:34:16,236 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52740 2016-12-06 19:34:16,237 [myid:1] - INFO [Thread-1136:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52740 (no session established for client) 2016-12-06 19:34:26,155 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52749 2016-12-06 19:34:26,155 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52749 2016-12-06 19:34:26,156 [myid:1] - INFO [Thread-1137:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52749 (no session established for client) 2016-12-06 19:34:26,222 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52750 2016-12-06 19:34:26,222 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52750 2016-12-06 19:34:26,226 [myid:1] - INFO [Thread-1138:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52750 (no session established for client) 2016-12-06 19:34:36,151 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52760 2016-12-06 19:34:36,152 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52760 2016-12-06 19:34:36,152 [myid:1] - INFO [Thread-1139:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52760 (no session established for client) 2016-12-06 19:34:36,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52761 2016-12-06 19:34:36,231 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52761 2016-12-06 19:34:36,231 [myid:1] - INFO [Thread-1140:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52761 (no session established for client) 2016-12-06 19:34:46,149 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52767 2016-12-06 19:34:46,149 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52767 2016-12-06 19:34:46,149 [myid:1] - INFO [Thread-1141:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52767 (no session established for client) 2016-12-06 19:34:46,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52768 2016-12-06 19:34:46,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52768 2016-12-06 19:34:46,230 [myid:1] - INFO [Thread-1142:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52768 (no session established for client) 

Kubernetesは多くのログソリューションを統合しています。 クラスターおよびアプリケーションに最も適合するログソリューションを選べます。 クラスターレベルのロギングとアグリゲーションとして、ログをローテートおよび輸送するためのサイドカーコンテナをデプロイすることを検討してください。

非特権ユーザーの設定

コンテナ内で特権ユーザーとしての実行をアプリケーションに許可するベストプラクティスは、議論の的です。 アプリケーションが非特権ユーザーとして動作することを組織で必須としているなら、エントリポイントがそのユーザーとして実行できるユーザーを制御するセキュリティコンテキストを利用できます。

zk StatefulSetのPod templateは、SecurityContextを含んでいます。

securityContext:  runAsUser: 1000  fsGroup: 1000 

Podのコンテナ内で、UID 1000はzookeeperユーザーに、GID 1000はzookeeperグループにそれぞれ相当します。

zk-0 PodからのZooKeeperプロセス情報を取得してみましょう。

kubectl exec zk-0 -- ps -elf 

securityContextオブジェクトのrunAsUserフィールドが1000にセットされているとおり、ZooKeeperプロセスは、rootとして実行される代わりにzookeeperユーザーとして実行されています。

F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S zookeep+ 1 0 0 80 0 - 1127 - 20:46 ? 00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground 0 S zookeep+ 27 1 0 80 0 - 1155556 - 20:46 ? 00:00:19 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg 

デフォルトでは、PodのPersistentVolumeがZooKeeperサーバーのデータディレクトリにマウントされている時、rootユーザーのみがそこにアクセス可能です。 この設定はZooKeeperのプロセスがそのWALに書き込んだりスナップショットに格納したりするのを妨げることになります。

zk-0 PodのZooKeeperデータディレクトリのファイルパーミッションを取得するには、以下のコマンドを使います。

kubectl exec -ti zk-0 -- ls -ld /var/lib/zookeeper/data 

securityContextオブジェクトのfsGroupフィールドが1000にセットされているので、PodのPersistentVolumeの所有権はzookeeperグループにセットされ、ZooKeeperのプロセスがそのデータを読み書きできるようになります。

drwxr-sr-x 3 zookeeper zookeeper 4096 Dec 5 20:45 /var/lib/zookeeper/data 

ZooKeeperプロセスの管理

ZooKeeperドキュメントでは、「You will want to have a supervisory process that manages each of your ZooKeeper server processes (JVM).(各ZooKeeperサーバープロセス(JVM)を管理する監督プロセスを持ちたくなります)」と述べています。 分散型システム内で失敗したプロセスを再起動するのにwatchdog(監督プロセス)を使うのは、典型的パターンです。 アプリケーションをKubernetesにデプロイする時には、監督プロセスのような外部ユーティリティを使うよりもむしろ、アプリケーションのwatchdogとしてKubernetesを使うべきです。

アンサンブルのアップデート

zk StatefulSetRollingUpdateアップデート戦略を使うように設定されています。 サーバーに割り当てられるcpusの数を更新するのに、kubectl patchを利用できます。

kubectl patch sts zk --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value":"0.3"}]' 
statefulset.apps/zk patched 

更新の状況を見るには、kubectl rollout statusを使います。

kubectl rollout status sts/zk 
waiting for statefulset rolling update to complete 0 pods at revision zk-5db4499664... Waiting for 1 pods to be ready... Waiting for 1 pods to be ready... waiting for statefulset rolling update to complete 1 pods at revision zk-5db4499664... Waiting for 1 pods to be ready... Waiting for 1 pods to be ready... waiting for statefulset rolling update to complete 2 pods at revision zk-5db4499664... Waiting for 1 pods to be ready... Waiting for 1 pods to be ready... statefulset rolling update complete 3 pods at revision zk-5db4499664... 

これはPod群を終了し、逆の順番で1つずつそれらを新しい設定で再作成します。 これはクォーラムがローリングアップデート中に維持されることを保証します。

履歴や過去の設定を見るには、kubectl rollout historyコマンドを使います。

kubectl rollout history sts/zk 

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

statefulsets "zk" REVISION 1 2 

変更をロールバックするには、kubectl rollout undoコマンドを使います。

kubectl rollout undo sts/zk 

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

statefulset.apps/zk rolled back 

プロセスの失敗の取り扱い

再起動ポリシーは、Pod内のコンテナのエントリポイントへのプロセスの失敗をKubernetesがどのように取り扱うかを制御します。 StatefulSet内のPodにおいて唯一妥当なRestartPolicyはAlwaysで、これはデフォルト値です。 ステートフルなアプリケーションでは、このデフォルトポリシーの上書きは絶対にすべきではありません

zk-0 Pod内で実行されているZooKeeperサーバーのプロセスツリーを調査するには、以下のコマンドを使います。

kubectl exec zk-0 -- ps -ef 

コンテナのエントリポイントとして使われるコマンドはPID 1、エントリポイントの子であるZooKeeperプロセスはPID 27となっています。

UID PID PPID C STIME TTY TIME CMD zookeep+ 1 0 0 15:03 ? 00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground zookeep+ 27 1 0 15:03 ? 00:00:03 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg 

別のターミナルで、以下のコマンドを使ってzk StatefulSet内のPodを見てみます。

kubectl get pod -w -l app=zk 

別のターミナルで、以下のコマンドを使ってPod zk-0内のZooKeeperプロセスを終了します。

kubectl exec zk-0 -- pkill java 

ZooKeeperプロセスの終了は、その親プロセスの終了を引き起こします。 コンテナのRestartPolicyはAlwaysなので、親プロセスが再起動(restart)されます。

NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 0 21m zk-1 1/1 Running 0 20m zk-2 1/1 Running 0 19m NAME READY STATUS RESTARTS AGE zk-0 0/1 Error 0 29m zk-0 0/1 Running 1 29m zk-0 1/1 Running 1 29m 

アプリケーションが、そのビジネスロジックを実装するプロセスを立ち上げるのにスクリプト(zkServer.shなど)を使っている場合、スクリプトは子プロセスとともに終了する必要があります。 これは、Kubernetesがアプリケーションのコンテナを、そのビジネスロジックを実装しているプロセスが失敗した時に再起動することを保証します。

生存性(liveness)テスト

失敗したプロセスを再起動するための設定をアプリケーションに施すのは、分散型システムの健全さを保つのに十分ではありません。 システムのプロセスが生きていることもあれば無反応なこともあり、あるいはそうでなく不健全という状況もあります。 アプリケーションのプロセスが不健全で再起動すべきであることをKubernetesに通知するには、liveness probeを使うのがよいでしょう。

zk StatefulSetのPod templateでliveness probeを指定します。

 livenessProbe:  exec:  command:  - sh  - -c  - "zookeeper-ready 2181"  initialDelaySeconds: 15  timeoutSeconds: 5 

プローブはサーバーの健全さをテストするのに、ZooKeeperのruok 4文字コマンドを使うbashスクリプトを呼び出します。

OK=$(echo ruok | nc 127.0.0.1 $1) if [ "$OK" == "imok" ]; then exit 0 else exit 1 fi 

ターミナルウィンドウで、zk StatefulSet内のPodを見るのに以下のコマンドを使います。

kubectl get pod -w -l app=zk 

別のウィンドウで、Pod zk-0のファイルシステムからzookeeper-readyスクリプトを削除するために以下のコマンドを使います。

kubectl exec zk-0 -- rm /opt/zookeeper/bin/zookeeper-ready 

ZooKeeperプロセスの失敗のためにliveness probeを使う時、アンサンブル内の不健全なプロセスが再起動されることを保証するために、Kubernetesは自動的にプロセスを再起動します。

kubectl get pod -w -l app=zk 
NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 0 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 0/1 Running 0 1h zk-0 0/1 Running 1 1h zk-0 1/1 Running 1 1h 

準備性(readiness)テスト

準備性は生存性と同じではありません。 プロセスが生きているのであれば、スケジュールされ健全です。 プロセスの準備ができたら、入力を処理できます。 生存性はなくてはならないものですが、準備性の状態には十分ではありません。 プロセスは生きてはいるが準備はできていない時、特に初期化および終了の間がそのケースに相当します。

readiness probeを指定するとKubernetesは、準備性チェックに合格するまで、アプリケーションのプロセスがネットワークトラフィックを受け取らないことを保証します。

ZooKeeperサーバーにとって、健全性は準備性を意味します。 そのため、zookeeper.yamlマニフェストからのreadiness probeは、liveness probeと同一です。

 readinessProbe:  exec:  command:  - sh  - -c  - "zookeeper-ready 2181"  initialDelaySeconds: 15  timeoutSeconds: 5 

liveness probeとreadiness probeが同一だとしても、両方を指定することが重要です。 これは、ZooKeeperアンサンブル内の健全なサーバーだけがネットワークトラフィックを受け取ることを保証します。

ノードの失敗の許容

ZooKeeperはデータの変更を正しくコミットするのにサーバーのクォーラムを必要とします。 3つのサーバーのアンサンブルにおいては、書き込みの成功のために2つのサーバーは健全でなければなりません。 クォーラムベースのシステムにおいて、可用性を保証するために、メンバーは障害ドメインにデプロイされます。 個々のマシンの損失による障害を避けるためのベストプラクティスは、同じマシン上でアプリケーションの複数のインスタンスがコロケート(同じ場所に配置)されないようにすることです。

デフォルトでKubernetesは、同じノードのStatefulSetにPodをコロケートします。 3つのサーバーアンサンブルを作成していたとして、2つのサーバーが同じノードにあり、そのノードが障害を起こした場合、ZooKeeperサービスのクライアントは、少なくともPodの1つが再スケジュールされるまで障害に見舞われることになります。

クリティカルシステムのプロセスがノードの失敗イベントで再スケジュールできるよう、追加のキャパシティを常にプロビジョンしておくべきです。 そうしておけば、障害は単にKubernetesのスケジューラーがZooKeeperのサーバーの1つを再スケジュールするまでの辛抱です。 ただし、ダウンタイムなしでノードの障害への耐性をサービスに持たせたいなら、podAntiAffinityをセットすべきです。

zk StatefulSet内のPodのノードを取得するには、以下のコマンドを使います。

for i in 0 1 2; do kubectl get pod zk-$i --template {{.spec.nodeName}}; echo ""; done 

zk StatefulSet内の全てのPodは、別々のノードにデプロイされます。

kubernetes-node-cxpk kubernetes-node-a5aq kubernetes-node-2g2d 

これはzk StatefulSet内のPodにPodAntiAffinityの指定があるからです。

affinity:  podAntiAffinity:  requiredDuringSchedulingIgnoredDuringExecution:  - labelSelector:  matchExpressions:  - key: "app"  operator: In  values:  - zk  topologyKey: "kubernetes.io/hostname" 

requiredDuringSchedulingIgnoredDuringExecutionフィールドは、topologyKeyで定義されたドメイン内でappラベルの値がzkの2つのPodが絶対にコロケートすべきでないことを、Kubernetes Schedulerに指示します。 topologyKeykubernetes.io/hostnameは、ドメインが固有ノードであることを示しています。 異なるルール、ラベル、セレクターを使って、物理・ネットワーク・電源といった障害ドメイン全体に広がるアンサンブルにこのテクニックを広げることができます。

メンテナンス時の存続

このセクションでは、ノードをcordon(スケジュール不可化)およびdorain(解放)します。もし共有クラスターでこのチュートリアルを試しているのであれば、これがほかのテナントに有害な影響を及ぼさないことを確認してください。

前のセクションでは、計画外のノード障害に備えてどのようにPodをノード全体に広げるかを示しましたが、計画されたメンテナンスのため引き起こされる一時的なノード障害に対して計画する必要もあります。

クラスター内のノードを取得するために、以下のコマンドを使います。

kubectl get nodes 

このチュートリアルでは、4つのノードのあるクラスターを仮定しています。 クラスターが4つよりも多くある場合には、4つのノード以外全てをcordonするためにkubectl cordonを使ってください。 ノードを4つに制約することで、以下のメンテナンスシミュレーションにおいてzookeeper Podをスケジュールした時に、KubernetesがアフィニティとPodDisruptionBudget制約に遭遇することを保証します。

kubectl cordon <ノード名> 

zk-pdbPodDisruptionBudgetを取得するために、以下のコマンドを使います。

kubectl get pdb zk-pdb 

max-unavailableフィールドは、zk StatefulSetの最大で1つのPodがいつでも利用できなくなる可能性があるということを、Kubernetesに指示します。

NAME MIN-AVAILABLE MAX-UNAVAILABLE ALLOWED-DISRUPTIONS AGE zk-pdb N/A 1 1 

1つ目のターミナルで、zk StatefulSet内のPodを見るのに以下のコマンドを使います。

kubectl get pods -w -l app=zk 

次に別のターミナルで、Podが現在スケジュールされているノードを取得するために、以下のコマンドを使います。

for i in 0 1 2; do kubectl get pod zk-$i --template {{.spec.nodeName}}; echo ""; done 

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

kubernetes-node-pb41 kubernetes-node-ixsl kubernetes-node-i4c4 

zk-0 Podがスケジュールされているノードをcordonおよびdrainするには、kubectl drainを使います。

kubectl drain $(kubectl get pod zk-0 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data 

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

node "kubernetes-node-pb41" cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-pb41, kube-proxy-kubernetes-node-pb41; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-o5elz pod "zk-0" deleted node "kubernetes-node-pb41" drained 

クラスターに4つのノードがあるので、kubectl drainは成功し、zk-0が別のノードに再スケジュールされます。

NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 2 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 1/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 51s zk-0 1/1 Running 0 1m 

最初のターミナルでStatefulSetのPodを見守り、zk-1がスケジュールされたノードをdrainします。

kubectl drain $(kubectl get pod zk-1 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data 

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

"kubernetes-node-ixsl" cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-ixsl, kube-proxy-kubernetes-node-ixsl; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-voc74 pod "zk-1" deleted node "kubernetes-node-ixsl" drained 

zk StatefulSetがPodのコロケーションを抑止するPodAntiAffinityルールを含んでいるので、zk-1 Podはスケジュールされず、またスケジュール可能なのは2つのノードだけなので、PodはPendingの状態のままになっています。

kubectl get pods -w -l app=zk 

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

NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 2 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 1/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 51s zk-0 1/1 Running 0 1m zk-1 1/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s 

StatefulSetのPodを見続け、zk-2がスケジュールされているノードをdrainします。

kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data 

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

node "kubernetes-node-i4c4" cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog WARNING: Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog; Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4 There are pending pods when an error occurred: Cannot evict pod as it would violate the pod's disruption budget. pod/zk-2 

kubectlを終了するためにCTRL-Cを押します。

zk-2を退去させるとzk-budget違反になってしまうので、3つ目のノードはdrainできません。ただし、ノードはcordonされたままとなります。

健全性テスト中に入力した値をzk-0から取得するには、zkCli.shを使います。

kubectl exec zk-0 zkCli.sh get /hello 

PodDisruptionBudgetが遵守されているので、サービスはまだ利用可能です。

WatchedEvent state:SyncConnected type:None path:null world cZxid = 0x200000002 ctime = Wed Dec 07 00:08:59 UTC 2016 mZxid = 0x200000002 mtime = Wed Dec 07 00:08:59 UTC 2016 pZxid = 0x200000002 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5 numChildren = 0 

最初のノードをuncordon(スケジュール可能化)するには、kubectl uncordonを使います。

kubectl uncordon kubernetes-node-pb41 

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

node "kubernetes-node-pb41" uncordoned 

zk-1はこのノードで再スケジュールされます。zk-1がRunningおよびReadyになるまで待ちます。

kubectl get pods -w -l app=zk 

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

NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 2 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 1/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 51s zk-0 1/1 Running 0 1m zk-1 1/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 12m zk-1 0/1 ContainerCreating 0 12m zk-1 0/1 Running 0 13m zk-1 1/1 Running 0 13m 

試しにzk-2がスケジュールされているノードをdrainしてみます。

kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data 

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

node "kubernetes-node-i4c4" already cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog pod "heapster-v1.2.0-2604621511-wht1r" deleted pod "zk-2" deleted node "kubernetes-node-i4c4" drained 

今度はkubectl drainは成功しました。

zk-2の再スケジュールができるように、2つ目のノードをuncordonします。

kubectl uncordon kubernetes-node-ixsl 

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

node "kubernetes-node-ixsl" uncordoned 

サービスがメンテナンス中も利用可能なままであることを保証するために、PodDisruptionBudgetsとあわせてkubectl drainを利用できます。 メンテナンスでノードがオフラインになる前にノードをcordonして、Podを退去させるのにdrainが使われている場合、Disruption Budget(停止状態の予算)を表すサービスは遵守すべきバジェットを持ちます。 クリティカルサービスでは、Podをすぐに再スケジュールできるよう、追加のキャパティを常に割り当てておくべきです。

クリーンアップ

  • クラスターの全てのノードをuncordonするために、kubectl uncordonを実行してください。
  • このチュートリアルで使ったPersistentVolumeの永続的なストレージメディアを削除する必要があります。 全てのストレージが回収されたことを確実とするために、お使いの環境、ストレージ設定、プロビジョニング方法に基いて必要な手順に従ってください。

7 - Service

7.1 - アプリケーションをServiceに接続する

コンテナに接続するためのKubernetesモデル

さて、継続的に実行され、複製されたアプリケーションができたので、これをネットワーク上に公開できます。

Kubernetesは、Podがどのホストに配置されているかにかかわらず、ほかのPodと通信できることを引き受けます。 Kubernetesは各Podにそれぞれ固有のクラスタープライベートなIPアドレスを付与するので、Pod間のリンクや、コンテナのポートとホストのポートのマップを明示的に作成する必要はありません。 これは、Pod内のコンテナは全てlocalhost上でお互いのポートに到達でき、クラスター内の全てのPodはNATなしに互いを見られるということを意味します。このドキュメントの残りの部分では、このようなネットワークモデルの上で信頼性のあるServiceを実行する方法について、詳しく述べていきます。

このチュートリアルでは概念のデモンストレーションのために、シンプルなnginx Webサーバーを例として使います。

Podをクラスターへ公開

これは前出の例でも行いましたが、もう一度やってみて、ネットワークからの観点に着目してみましょう。 nginx Podを作成し、コンテナのポート指定も記載します:

apiVersion: apps/v1 kind: Deployment metadata:  name: my-nginx spec:  selector:  matchLabels:  run: my-nginx  replicas: 2  template:  metadata:  labels:  run: my-nginx  spec:  containers:  - name: my-nginx  image: nginx  ports:  - containerPort: 80  

この設定で、あなたのクラスターにはどのノードからもアクセス可能になります。Podを実行中のノードを確認してみましょう:

kubectl apply -f ./run-my-nginx.yaml kubectl get pods -l run=my-nginx -o wide 
NAME READY STATUS RESTARTS AGE IP NODE my-nginx-3800858182-jr4a2 1/1 Running 0 13s 10.244.3.4 kubernetes-minion-905m my-nginx-3800858182-kna2y 1/1 Running 0 13s 10.244.2.5 kubernetes-minion-ljyd 

PodのIPアドレスを確認します:

kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs  POD_IP  [map[ip:10.244.3.4]]  [map[ip:10.244.2.5]] 

あなたのクラスター内のどのノードにもsshで入ることができて、双方のIPアドレスに対して問い合わせるためにcurlのようなツールを使えるようにしておくのがよいでしょう。 各コンテナはノード上でポート80を使っておらず、トラフィックをPodに流すための特別なNATルールもなんら存在しないことに注意してください。 つまり、全て同じcontainerPortを使った同じノード上で複数のnginx Podを実行でき、Serviceに割り当てられたIPアドレスを使って、クラスター内のほかのどのPodあるいはノードからもそれらにアクセスできます。 背後にあるPodにフォワードするためにホストNode上の特定のポートを充てたいというのであれば、それも可能です。とはいえ、ネットワークモデルではそのようなことをする必要がありません。

興味があれば、さらなる詳細について Kubernetesネットワークモデル を読んでください。

Serviceの作成

さて、フラットなクラスター全体のアドレス空間内でnginxを実行中のPodが得られました。 理論的にはこれらのPodと直接対話することは可能ですが、ノードが死んでしまった時には何が起きるでしょうか? ノードと一緒にPodは死に、Deploymentが新しいPodを異なるIPアドレスで作成します。 これがServiceが解決する問題です。

KubernetesのServiceは、全て同じ機能を提供する、クラスター内のどこかで実行するPodの論理的な集合を定義した抽象物です。 作成時に各Serviceは固有のIPアドレス(clusterIPとも呼ばれます)を割り当てられます。 このアドレスはServiceのライフスパンと結び付けられており、Serviceが生きている間は変わりません。 PodはServiceと対話できるよう設定され、Serviceのメンバーである複数のPodへ自動的に負荷分散されたServiceへ通信する方法を知っています。

kubectl exposeを使って、2つのnginxレプリカのためのServiceを作成できます:

kubectl expose deployment/my-nginx 
service/my-nginx exposed 

これはkubectl apply -fを以下のyamlに対して実行するのと同じです:

apiVersion: v1 kind: Service metadata:  name: my-nginx  labels:  run: my-nginx spec:  ports:  - port: 80  protocol: TCP  selector:  run: my-nginx 

この指定は、run: my-nginxラベルの付いた任意のPod上のTCPポート80を宛先とし、それを抽象化されたServiceポート(targetPortはコンテナがトラフィックを許可するポート、portは抽象化されたServiceポートで、ほかのPodがServiceにアクセスするのに使う任意のポートです)で公開するServiceを作成します。 Service定義内でサポートされているフィールドのリストを見るには、Service APIオブジェクトを参照してください。 Serviceを確認してみましょう:

kubectl get svc my-nginx 
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-nginx ClusterIP 10.0.162.149 <none> 80/TCP 21s 

前述したとおり、ServiceはPodのグループに支えられています。 これらのPodはEndpointSlicesを通して公開されています。 Serviceのセレクターは継続的に評価され、その結果はServiceに接続されているEndpointSliceにlabelsを使って「投稿(POST)」されます。

Podが死ぬと、エンドポイントとして含まれていたEndpointSliceからそのPodは自動的に削除されます。 Serviceのセレクターにマッチする新しいPodが、Serviceのために自動的にEndpointSliceに追加されます。 エンドポイントを確認し、IPアドレスが最初のステップで作成したPodと同じであることに注目してください:

kubectl describe svc my-nginx 
Name: my-nginx Namespace: default Labels: run=my-nginx Annotations: <none> Selector: run=my-nginx Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.0.162.149 IPs: 10.0.162.149 Port: <unset> 80/TCP TargetPort: 80/TCP Endpoints: 10.244.2.5:80,10.244.3.4:80 Session Affinity: None Events: <none> 
kubectl get endpointslices -l kubernetes.io/service-name=my-nginx 
NAME ADDRESSTYPE PORTS ENDPOINTS AGE my-nginx-7vzhx IPv4 80 10.244.2.5,10.244.3.4 21s 

今や、あなたのクラスター内のどのノードからもnginx Serviceに<CLUSTER-IP>:<PORT>でcurlを使用してアクセスできるはずです。Service IPは完全に仮想であり、物理的なケーブルで接続されるものではありません。どのように動作しているのか興味があれば、さらなる詳細についてサービスプロキシを読んでください。

Serviceへのアクセス

KubernetesはServiceを探す2つの主要なモードとして、環境変数とDNSをサポートしています。 前者はすぐに動かせるのに対し、後者はCoreDNSクラスターアドオンが必要です。

環境変数

PodをNode上で実行する時、kubeletはアクティブなServiceのそれぞれに環境変数のセットを追加します。 これは順序問題を生みます。なぜそうなるかの理由を見るために、実行中のnginx Podの環境を調査してみましょう(Podの名前は環境によって異なります):

kubectl exec my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE 
KUBERNETES_SERVICE_HOST=10.0.0.1 KUBERNETES_SERVICE_PORT=443 KUBERNETES_SERVICE_PORT_HTTPS=443 

Serviceについて何も言及がないことに注意してください。 これは、Serviceの前にレプリカを作成したからです。 このようにすることでの不利益のもう1つは、スケジューラーが同一のマシンに両方のPodを置く可能性があることです(もしそのマシンが死ぬと全Serviceがダウンしてしまいます)。 2つのPodを殺し、Deploymentがそれらを再作成するのを待つことで、これを正しいやり方にできます。 今回は、レプリカの前にServiceが存在します。 これにより、正しい環境変数だけでなく、(全てのノードで等量のキャパシティを持つ場合)Podに広がった、スケジューラーレベルのServiceが得られます:

kubectl scale deployment my-nginx --replicas=0; kubectl scale deployment my-nginx --replicas=2;  kubectl get pods -l run=my-nginx -o wide 
NAME READY STATUS RESTARTS AGE IP NODE my-nginx-3800858182-e9ihh 1/1 Running 0 5s 10.244.2.7 kubernetes-minion-ljyd my-nginx-3800858182-j4rm4 1/1 Running 0 5s 10.244.3.8 kubernetes-minion-905m 

Podが、いったん殺されて再作成された後、異なる名前を持ったことに気付いたでしょうか。

kubectl exec my-nginx-3800858182-e9ihh -- printenv | grep SERVICE 
KUBERNETES_SERVICE_PORT=443 MY_NGINX_SERVICE_HOST=10.0.162.149 KUBERNETES_SERVICE_HOST=10.0.0.1 MY_NGINX_SERVICE_PORT=80 KUBERNETES_SERVICE_PORT_HTTPS=443 

DNS

Kubernetesは、DNS名をほかのServiceに自動的に割り当てる、DNSクラスターアドオンServiceを提供しています。 クラスター上でそれを実行しているならば、確認できます:

kubectl get services kube-dns --namespace=kube-system 
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kube-dns ClusterIP 10.0.0.10 <none> 53/UDP,53/TCP 8m 

本セクションの以降では、長寿命のIPアドレス(my-nginx)を持つServiceと、そのIPアドレスに名前を割り当てているDNSサーバーがあることを想定しています。 ここではCoreDNSクラスターアドオン(アプリケーション名kube-dns)を使い、標準的な手法(例えばgethostbyname())を使ってクラスター内の任意のPodからServiceと対話してみます。 CoreDNSが動作していない時には、 CoreDNS READMECoreDNSのインストールを参照して有効化してください。 テストするために、別のcurlアプリケーションを実行してみましょう:

kubectl run curl --image=radial/busyboxplus:curl -i --tty 
Waiting for pod default/curl-131556218-9fnch to be running, status is Pending, pod ready: false Hit enter for command prompt 

次にenterを押し、nslookup my-nginxを実行します:

[ root@curl-131556218-9fnch:/ ]$ nslookup my-nginx Server: 10.0.0.10 Address 1: 10.0.0.10  Name: my-nginx Address 1: 10.0.162.149 

Serviceのセキュア化

これまではクラスター内からnginxサーバーだけにアクセスしてきました。 Serviceをインターネットに公開する前に、通信経路がセキュアかどうかを確かめたいところです。 そのためには次のようなものが必要です:

  • https用の自己署名証明書(まだ本人証明を用意していない場合)
  • 証明書を使うよう設定されたnginxサーバー
  • 証明書をPodからアクセスできるようにするSecret

これら全てはnginx https exampleから取得できます。 go環境とmakeツールのインストールが必要です (もしこれらをインストールしたくないときには、後述の手動手順に従ってください)。簡潔には:

make keys KEY=/tmp/nginx.key CERT=/tmp/nginx.crt kubectl create secret tls nginxsecret --key /tmp/nginx.key --cert /tmp/nginx.crt 
secret/nginxsecret created 
kubectl get secrets 
NAME TYPE DATA AGE nginxsecret kubernetes.io/tls 2 1m 

configmapも同様:

kubectl create configmap nginxconfigmap --from-file=default.conf 
configmap/nginxconfigmap created 
kubectl get configmaps 
NAME DATA AGE nginxconfigmap 1 114s 

以下に示すのは、makeを実行したときに問題が発生する場合(例えばWindowsなど)の手動手順です:

# 公開鍵と秘密鍵のペアを作成する openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /d/tmp/nginx.key -out /d/tmp/nginx.crt -subj "/CN=my-nginx/O=my-nginx" # 鍵をbase64エンコーディングに変換する cat /d/tmp/nginx.crt | base64 cat /d/tmp/nginx.key | base64 

以下のようなyamlファイルを作成するために、前のコマンドからの出力を使います。 base64エンコードされた値は、全て1行で記述する必要があります。

apiVersion: "v1" kind: "Secret" metadata:  name: "nginxsecret"  namespace: "default" type: kubernetes.io/tls data:  # 注意: 以下の値はご自身で base64 エンコードした証明書と鍵に置き換えてください。  tls.crt: "REPLACE_WITH_BASE64_CERT"  tls.key: "REPLACE_WITH_BASE64_KEY" 

では、このファイルを使ってSecretを作成します:

kubectl apply -f nginxsecrets.yaml kubectl get secrets 
NAME TYPE DATA AGE nginxsecret kubernetes.io/tls 2 1m 

Secretにある証明書を使ってhttpsサーバーを開始するために、nginxレプリカを変更します。また、Serviceは(80および443の)両方のポートを公開するようにします:

apiVersion: v1 kind: Service metadata:  name: my-nginx  labels:  run: my-nginx spec:  type: NodePort  ports:  - port: 8080  targetPort: 80  protocol: TCP  name: http  - port: 443  protocol: TCP  name: https  selector:  run: my-nginx --- apiVersion: apps/v1 kind: Deployment metadata:  name: my-nginx spec:  selector:  matchLabels:  run: my-nginx  replicas: 1  template:  metadata:  labels:  run: my-nginx  spec:  volumes:  - name: secret-volume  secret:  secretName: nginxsecret  containers:  - name: nginxhttps  image: bprashanth/nginxhttps:1.0  ports:  - containerPort: 443  - containerPort: 80  volumeMounts:  - mountPath: /etc/nginx/ssl  name: secret-volume 

nginx-secure-appマニフェストの注目すべきポイント:

  • DeploymentとServiceの指定の両方が同じファイルに含まれています。
  • nginxサーバーは、ポート80でHTTPトラフィック、ポート443でHTTPSトラフィックをサービスし、nginx Serviceは両方のポートを公開します。
  • 各コンテナは、/etc/nginx/sslにマウントされたボリューム経由で鍵にアクセスできます。 これはnginxサーバーが開始するにセットアップされます。
kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml 

この時点で、任意のノードからnginxサーバーに到達できます。

kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs  POD_IP  [map[ip:10.244.3.5]] 
node $ curl -k https://10.244.3.5 ... <h1>Welcome to nginx!</h1> 

最後の手順でcurlに-kパラメーターを与えていることに注意してください。 これは、証明書の生成時点ではnginxを実行中のPodについて何もわからないので、curlにCNameのミスマッチを無視するよう指示する必要があるからです。 Serviceを作成することで、証明書で使われているCNameと、PodがServiceルックアップ時に使う実際のDNS名とがリンクされます。 Podからこれをテストしてみましょう(単純化のため同じSecretが再利用されるので、ServiceにアクセスするのにPodが必要なのはnginx.crtだけです):

apiVersion: apps/v1 kind: Deployment metadata:  name: curl-deployment spec:  selector:  matchLabels:  app: curlpod  replicas: 1  template:  metadata:  labels:  app: curlpod  spec:  volumes:  - name: secret-volume  secret:  secretName: nginxsecret  containers:  - name: curlpod  command:  - sh  - -c  - while true; do sleep 1; done  image: radial/busyboxplus:curl  volumeMounts:  - mountPath: /etc/nginx/ssl  name: secret-volume 
kubectl apply -f ./curlpod.yaml kubectl get pods -l app=curlpod 
NAME READY STATUS RESTARTS AGE curl-deployment-1515033274-1410r 1/1 Running 0 1m 
kubectl exec curl-deployment-1515033274-1410r -- curl https://my-nginx --cacert /etc/nginx/ssl/tls.crt ... <title>Welcome to nginx!</title> ... 

Serviceの公開

アプリケーションのいくつかの部分においては、Serviceを外部IPアドレスで公開したいと思うかもしれません。 Kubernetesはこれに対して2つのやり方をサポートしています: NodePortとLoadBalancerです。 前のセクションで作成したServiceではすでにNodePortを使っていたので、ノードにパブリックIPアドレスがあれば、nginx HTTPSレプリカもトラフィックをインターネットでサービスする準備がすでに整っています。

kubectl get svc my-nginx -o yaml | grep nodePort -C 5  uid: 07191fb3-f61a-11e5-8ae5-42010af00002 spec:  clusterIP: 10.0.162.149  ports:  - name: http  nodePort: 31704  port: 8080  protocol: TCP  targetPort: 80  - name: https  nodePort: 32453  port: 443  protocol: TCP  targetPort: 443  selector:  run: my-nginx 
kubectl get nodes -o yaml | grep ExternalIP -C 1  - address: 104.197.41.11  type: ExternalIP  allocatable: --  - address: 23.251.152.56  type: ExternalIP  allocatable: ...  $ curl https://<EXTERNAL-IP>:<NODE-PORT> -k ... <h1>Welcome to nginx!</h1> 

では、クラウドロードバランサーを使うために、Serviceを再作成してみましょう。 my-nginxTypeNodePortからLoadBalancerに変更してください:

kubectl edit svc my-nginx kubectl get svc my-nginx 
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-nginx LoadBalancer 10.0.162.149 xx.xxx.xxx.xxx 8080:30163/TCP 21s 
curl https://<EXTERNAL-IP> -k ... <title>Welcome to nginx!</title> 

EXTERNAL-IP列のIPアドレスが、パブリックインターネットで利用可能なものになっています。 CLUSTER-IPはクラスター/プライベートクラウドネットワーク内でのみ利用可能なものです。

AWSにおいては、LoadBalancerタイプは、IPアドレスではなく(長い)ホスト名を使うELBを作成することに注意してください。 これは標準のkubectl get svcの出力に合わせるには長すぎ、実際それを見るにはkubectl describe service my-nginxを使う必要があります。 これは以下のような見た目になります:

kubectl describe service my-nginx ... LoadBalancer Ingress: a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com ... 

次の項目

7.2 - 送信元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=registry.k8s.io/echoserver:1.10 

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

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 

次の項目

7.3 - Podとそのエンドポイントの終了動作を探る

アプリケーションをServiceに接続するで概略を示したステップに従ってアプリケーションをServiceに接続すると、ネットワーク上で公開され、継続的に実行されて、複製されたアプリケーションが得られます。 このチュートリアルでは、Podを終了する流れを見て、gracefulな(猶予のある)接続ドレインを実装する手法を模索するための手助けをします。

Podの終了手続きとそのエンドポイント

アップグレードやスケールダウンのために、Podを終了しなければならない場面はままあります。 アプリケーションの可用性を高めるために、適切なアクティブ接続ドレインを実装することは重要でしょう。

このチュートリアルでは概念のデモンストレーションのために、シンプルなnginx Webサーバーを例として、対応するエンドポイントの状態に関連したPodの終了および削除の流れを説明します。

エンドポイント終了の流れの例

以下は、Podの終了ドキュメントに記載されている流れの例です。

1つのnginxレプリカを含むDeployment(純粋にデモンストレーション目的です)とServiceがあるとします:

apiVersion: apps/v1 kind: Deployment metadata:  name: nginx-deployment  labels:  app: nginx spec:  replicas: 1  selector:  matchLabels:  app: nginx  template:  metadata:  labels:  app: nginx  spec:  terminationGracePeriodSeconds: 120 # 非常に長い猶予期間  containers:  - name: nginx  image: nginx:latest  ports:  - containerPort: 80  lifecycle:  preStop:  exec:  # 実際の活動終了はterminationGracePeriodSecondsまでかかる可能性がある。  # この例においては、少なくともterminationGracePeriodSecondsの間は待機し、  # 120秒経過すると、コンテナは強制的に終了される。  # この間ずっとnginxはリクエストを処理し続けていることに注意。  command: [  "/bin/sh", "-c", "sleep 180"  ] 
apiVersion: apps/v1 kind: Deployment metadata:  name: nginx-deployment  labels:  app: nginx spec:  replicas: 1  selector:  matchLabels:  app: nginx  template:  metadata:  labels:  app: nginx  spec:  terminationGracePeriodSeconds: 120 # 非常に長い猶予期間  containers:  - name: nginx  image: nginx:latest  ports:  - containerPort: 80  lifecycle:  preStop:  exec:  # 実際の活動終了はterminationGracePeriodSecondsまでかかる可能性がある。  # この例においては、少なくともterminationGracePeriodSecondsの間は待機し、  # 120秒経過すると、コンテナは強制終了される。  # この間ずっとnginxはリクエストを処理し続けていることに注意。  command: [  "/bin/sh", "-c", "sleep 180"  ]  ---  apiVersion: v1 kind: Service metadata:  name: nginx-service spec:  selector:  app: nginx  ports:  - protocol: TCP  port: 80  targetPort: 80 

PodとServiceが実行中になったら、関連付けられたEndpointSliceの名前を得られます:

kubectl get endpointslice 

この出力は以下のようなものになります:

NAME ADDRESSTYPE PORTS ENDPOINTS AGE nginx-service-6tjbr IPv4 80 10.12.1.199,10.12.1.201 22m 

状態からわかるように、1つのエンドポイントが登録されていることが確認できます:

kubectl get endpointslices -o json -l kubernetes.io/service-name=nginx-service 

この出力は以下のようなものになります:

{ "addressType": "IPv4", "apiVersion": "discovery.k8s.io/v1", "endpoints": [ { "addresses": [ "10.12.1.201" ], "conditions": { "ready": true, "serving": true, "terminating": false 

では、Podを終了し、そのPodがgracefulな終了期間設定を守って終了されていることを確認してみましょう:

kubectl delete pod nginx-deployment-7768647bf9-b4b9s 

全Podについて調べます:

kubectl get pods 

この出力は以下のようなものになります:

NAME READY STATUS RESTARTS AGE nginx-deployment-7768647bf9-b4b9s 1/1 Terminating 0 4m1s nginx-deployment-7768647bf9-rkxlw 1/1 Running 0 8s 

新しいPodがスケジュールされたことを見てとれます。

新しいPodのために新しいエンドポイントが作成される間、古いエンドポイントは終了中の状態のまま残っています:

kubectl get endpointslice -o json nginx-service-6tjbr 

この出力は以下のようなものになります:

{ "addressType": "IPv4", "apiVersion": "discovery.k8s.io/v1", "endpoints": [ { "addresses": [ "10.12.1.201" ], "conditions": { "ready": false, "serving": true, "terminating": true }, "nodeName": "gke-main-default-pool-dca1511c-d17b", "targetRef": { "kind": "Pod", "name": "nginx-deployment-7768647bf9-b4b9s", "namespace": "default", "uid": "66fa831c-7eb2-407f-bd2c-f96dfe841478" }, "zone": "us-central1-c" }, { "addresses": [ "10.12.1.202" ], "conditions": { "ready": true, "serving": true, "terminating": false }, "nodeName": "gke-main-default-pool-dca1511c-d17b", "targetRef": { "kind": "Pod", "name": "nginx-deployment-7768647bf9-rkxlw", "namespace": "default", "uid": "722b1cbe-dcd7-4ed4-8928-4a4d0e2bbe35" }, "zone": "us-central1-c" 

これを使うと、終了中のアプリケーションがその状態について、接続ドレイン機能の実装目的でクライアント(ロードバランサーなど)と通信する、ということが可能です。 これらのクライアントではエンドポイントの終了を検出し、そのための特別なロジックを実装できます。

Kubernetesでは、終了中のエンドポイントのready状態は全てfalseにセットされます。 これは後方互換性のために必要な措置で、既存のロードバランサーは通常のトラフィックにはそれを使用しません。 Podの終了時にトラフィックのドレインが必要な場合、実際に準備できているかはserving状態として調べられます。

Podが削除される時には、古いエンドポイントも削除されます。

次の項目