チュートリアル 本セクションにはチュートリアルが含まれています。チュートリアルでは、単一のタスク よりも大きな目標を達成する方法を示します。通常、チュートリアルにはいくつかのセクションがあり、各セクションには一連のステップがあります。各チュートリアルを進める前に、後で参照できるように標準化された用語集 ページをブックマークしておくことをお勧めします。
基本 設定 ステートレスアプリケーション ステートフルアプリケーション サービス セキュリティ 次の項目 チュートリアルのページタイプについての情報は、Content Page Types を参照してください。
1 - Hello Minikube このチュートリアルでは、minikubeを使用して、Kubernetes上でサンプルアプリケーションを動かす方法を紹介します。
このチュートリアルはNGINXを利用してすべての要求をエコーバックするコンテナイメージを提供します。
目標 minikubeへのサンプルアプリケーションのデプロイ アプリケーションの実行 アプリケーションログの確認 始める前に このチュートリアルは、minikubeがセットアップ済みであることを前提としています。
インストール手順はminikube start の Step 1 を参照してください。
備考: Step 1, Installation の手順のみ実行してください。それ以降の手順はこのページで説明します。また、kubectlをインストールする必要があります。
インストール手順はツールのインストール を参照してください。
minikubeクラスターの作成 ダッシュボードを開く Kubernetesダッシュボードを開きます。これには二通りの方法があります:
新しい ターミナルを開き、次のコマンドを実行します:
# 新しいターミナルを起動し、以下を実行したままにします
minikube dashboard
minikube startを実行したターミナルに戻ります。
備考: dashboardコマンドは、ダッシュボードアドオンを有効にし、デフォルトのWebブラウザーでプロキシを開きます。
ダッシュボード上で、DeploymentやServiceなどのKubernetesリソースを作成できます。
ターミナルから直接ブラウザーを起動させずに、WebダッシュボードのURLを取得する方法については、「URLをコピー&ペースト」タブを参照してください。
デフォルトでは、ダッシュボードはKubernetesの仮想ネットワーク内部からのみアクセス可能です。
dashboardコマンドは、Kubernetes仮想ネットワークの外部からダッシュボードにアクセス可能にするための一時的なプロキシを作成します。
プロキシを停止するには、Ctrl+Cを実行してプロセスを終了します。
dashboardコマンドが終了した後も、ダッシュボードはKubernetesクラスター内で実行を続けます。
再度dashboardコマンドを実行すれば、新しい別のプロキシを作成してダッシュボードにアクセスできます。
minikubeが自動的にWebブラウザーを開くことを望まない場合、dashboardサブコマンドを--urlフラグと共に実行します。
minikubeは、お好みのブラウザーで開くことができるURLを出力します。
新しい ターミナルを開き、次のコマンドを実行します:
# 新しいターミナルを起動し、以下を実行したままにします
minikube dashboard --url
URLをコピー&ペーストし、ブラウザーで開きます。
minikube startを実行したターミナルに戻ります。
Deploymentの作成 KubernetesのPod は、コンテナの管理やネットワーキングの目的でまとめられた、1つ以上のコンテナのグループです。このチュートリアルのPodがもつコンテナは1つのみです。KubernetesのDeployment はPodの状態を確認し、Podのコンテナが停止した場合には再起動します。DeploymentはPodの作成やスケールを管理するために推奨される方法(手段)です。
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
Deploymentを確認します:
出力は下記のようになります:
NAME READY UP-TO-DATE AVAILABLE AGE
hello-node 1/1 1 1 1m
(Podが利用可能になるまで時間がかかる場合があります。"0/1"と表示された場合は、数秒後にもう一度確認してみてください。)
Podを確認します:
出力は下記のようになります:
NAME READY STATUS RESTARTS AGE
hello-node-5f76cf6ccf-br9b5 1/1 Running 0 1m
クラスターイベントを確認します:
kubectlで設定を確認します:
Podで実行されているコンテナのアプリケーションログを確認します(Podの名前はkubectl get podsで取得したものに置き換えてください)。
備考: kubectl logsコマンドの引数hello-node-5f76cf6ccf-br9b5は、kubectl get podsコマンドで取得したPodの名前に置き換えてください。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を公開する必要があります。
警告: agnhostコンテナには/shellエンドポイントがあり、デバッグには便利ですが、インターネットに公開するのは危険です。
インターネットに接続されたクラスターや、プロダクション環境のクラスターで実行しないでください。kubectl exposeコマンドを使用してPodをインターネットに公開します:
kubectl expose deployment hello-node --type= LoadBalancer --port= 8080
--type=LoadBalancerフラグはServiceをクラスター外部に公開したいことを示しています。
テストイメージ内のアプリケーションコードはTCPの8080番ポートのみを待ち受けます。
kubectl exposeで8080番ポート以外を公開した場合、クライアントはそのポートに接続できません。
作成したServiceを確認します:
出力は下記のようになります:
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を作成します。
次のコマンドを実行します:
minikube service hello-node
アプリケーションとその応答が表示されるブラウザーウィンドウが開きます。
アドオンの有効化 minikubeはビルトインのアドオン があり、有効化、無効化、あるいはローカルのKubernetes環境に公開することができます。
サポートされているアドオンをリストアップします:
出力は下記のようになります:
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
ここでは例としてmetrics-serverのアドオンを有効化します:
minikube addons enable metrics-server
出力は下記のようになります:
The 'metrics-server' addon is enabled
作成された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
metrics-serverの出力を確認します:
出力は下記のようになります:
NAME CPU(cores) MEMORY(bytes)
hello-node-ccf4b9788-4jn97 1m 6Mi
次のメッセージが表示された場合は、しばらく待ってから再度実行してください:
error: Metrics API not available
metrics-serverを無効化します:
minikube addons disable metrics-server
出力は下記のようになります:
metrics-server was successfully disabled
クリーンアップ クラスターに作成したリソースをクリーンアップします:
kubectl delete service hello-node
kubectl delete deployment hello-node
minikubeクラスターを停止します
(オプション)minikubeのVMを削除します:
Kubernetesの学習で再度minikubeを使用したい場合、minikubeのVMを削除する必要はありません。
まとめ このページでは、minikubeクラスターを立ち上げて実行するための基本的な部分を説明しました。
これでアプリケーションをデプロイする準備が整いました。
次の項目 2 - Kubernetesの基本を学ぶ Kubernetesの基本 このチュートリアルでは、Kubernetesクラスターオーケストレーションシステムの基本について学びます。各モジュールには、Kubernetesの主な機能と概念に関する背景情報と、一緒に進めるためのチュートリアルが含まれています。
このチュートリアルでは、以下のことを学ぶことができます:
コンテナ化されたアプリケーションをクラスターにデプロイ Deploymentのスケーリング 新しいソフトウェアのバージョンでコンテナ化されたアプリケーションをアップデート コンテナ化されたアプリケーションのデバッグ Kubernetesはどんなことができるの? モダンなWebサービスでは、ユーザーはアプリケーションが24時間365日利用可能であることを期待しており、開発者はそれらのアプリケーションの新しいバージョンを1日に数回デプロイすることを期待しています。コンテナ化は、パッケージソフトウェアがこれらの目標を達成するのを助け、アプリケーションをダウンタイムなしでリリース、アップデートできるようにします。Kubernetesを使用すると、コンテナ化されたアプリケーションをいつでもどこでも好きなときに実行できるようになり、それらが機能するために必要なリソースとツールを見つけやすくなります。Kubernetesは、コンテナオーケストレーションにおけるGoogleのこれまでの経験と、コミュニティから得られた最善のアイデアを組み合わせて設計された、プロダクションレディなオープンソースプラットフォームです。
2.1 - クラスターの作成 Kubernetesクラスター について学び、Minikubeを使ってシンプルなクラスターを作成します。
2.1.1 - Minikubeを使ったクラスターの作成 Kubernetesクラスターとは何かを学ぶ。
Minikubeとは何かを学ぶ。
Kubernetesクラスターを起動する。
目標 Kubernetesクラスターとは何かを学ぶ Minikubeとは何かを学ぶ Kubernetesクラスターをローカルで動かす Kubernetesクラスター Kubernetesは、単一のユニットとして機能するように接続された、可用性の高いコンピューターのクラスターをまとめあげます。 Kubernetesの抽象化により、コンテナ化されたアプリケーションを個々のマシンに特に結び付けることなくクラスターにデプロイできます。この新しいデプロイモデルを利用するには、アプリケーションを個々のホストから切り離す方法でアプリケーションをパッケージ化(つまり、コンテナ化)する必要があります。コンテナ化されたアプリケーションは、アプリケーションがホストに深く統合されたパッケージとして特定のマシンに直接インストールされていた従来のデプロイモデルよりも柔軟で、より迅速に利用可能です。Kubernetesはより効率的な方法で、クラスター全体のアプリケーションコンテナの配布とスケジューリングを自動化します。 Kubernetesはオープンソースのプラットフォームであり、プロダクションレディです。
Kubernetesクラスターは以下の2種類のリソースで構成されています:
コントロールプレーン がクラスターを管理するノード がアプリケーションを動かすワーカーとなるKubernetesは、コンピュータークラスター内およびコンピュータークラスター間でのアプリケーションコンテナの配置(スケジューリング)および実行を調整する、プロダクショングレードのオープンソースプラットフォームです。
コントロールプレーンはクラスターの管理を担当します。 コントロールプレーンは、アプリケーションのスケジューリング、望ましい状態の維持、アプリケーションのスケーリング、新しい更新のロールアウトなど、クラスター内のすべての動作をまとめあげます。
ノードは、Kubernetesクラスターのワーカーマシンとして機能するVMまたは物理マシンです。 各ノードにはKubeletがあり、これはノードを管理し、Kubernetesコントロールプレーンと通信するためのエージェントです。ノードにはcontainerd や CRI-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 Deploymentは、アプリケーションのインスタンスを作成および更新する責務があります。
備考: このチュートリアルでは、AMD64アーキテクチャを必要とするコンテナを使用します。
異なるCPUアーキテクチャのコンピューターでminikubeを使用する場合は、AMD64をエミュレートできるドライバーでminikubeを使用してみてください。
例えば、Docker Desktopドライバーはこれが可能です。実行中のKubernetesクラスター を入手すると、その上にコンテナ化アプリケーションをデプロイすることができます。
そのためには、KubernetesのDeployment の設定を作成します。
DeploymentはKubernetesにあなたのアプリケーションのインスタンスを作成し、更新する方法を指示します。
Deploymentを作成すると、KubernetesコントロールプレーンはDeployment内に含まれるアプリケーションインスタンスをクラスター内の個々のノードで実行するようにスケジュールします。
アプリケーションインスタンスが作成されると、Kubernetes Deploymentコントローラーは、それらのインスタンスを継続的に監視します。
インスタンスをホストしているノードが停止、削除された場合、Deploymentコントローラーはそのインスタンスをクラスター内の別のノード上のインスタンスと置き換えます。
これは、マシンの故障やメンテナンスに対処するためのセルフヒーリングの仕組みを提供しています。
オーケストレーションが登場する前の世界では、インストールスクリプトを使用してアプリケーションを起動することはよくありましたが、マシン障害が発生した場合に復旧する事はできませんでした。
アプリケーションのインスタンスを作成し、それらをノード間で実行し続けることで、Kubernetes Deploymentはアプリケーションの管理に根本的に異なるアプローチを提供します。
Kubernetes上にはじめてのアプリケーションをデプロイする Kubernetesにデプロイするには、アプリケーションをサポートされているコンテナ形式のいずれかにパッケージ化する必要があります。
Kubernetesのコマンドラインインターフェースであるkubectl を使用して、Deploymentを作成、管理できます。
kubectlはKubernetes APIを使用してクラスターと対話します。
このモジュールでは、Kubernetesクラスターでアプリケーションを実行するDeploymentを作成するために必要な、最も一般的なkubectlコマンドについて学びます。
Deploymentを作成するときは、アプリケーションのコンテナイメージと実行するレプリカの数を指定する必要があります。
Deploymentを更新することで、あとでその情報を変更できます。
ブートキャンプのModule 5 とModule 6 では、Deploymentをどのようにスケール、更新できるかについて説明します。
最初のDeploymentには、NGINXを使用して全てのリクエストをエコーバックする、Dockerコンテナにパッケージ化されたhello-nodeアプリケーションを使用します。
(まだhello-nodeアプリケーションを作成して、コンテナを使用してデプロイしていない場合、Hello Minikube tutorial の通りにやってみましょう。)
kubectlもインストールされている必要があります。
インストールが必要な場合は、ツールのインストール からインストールしてください。
Deploymentが何であるかがわかったので、最初のアプリケーションをデプロイしましょう!
kubectlの基本 kubectlコマンドの一般的な書式はkubectl action resourceです。
これは指定された resource (node、deploymentなど)に対して指定された action (create、describe、deleteなど)を実行します。
指定可能なパラメーターに関する追加情報を取得するために、サブコマンドの後に--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コマンドを使います。
アプリケーションの単一のインスタンスを実行しているDeploymentが1つあることが分かります。
インスタンスはノード上のコンテナ内で実行されています。
アプリケーションを見る Kubernetes内部で動作しているPod は、プライベートに隔離されたネットワーク上で動作しています。
デフォルトでは、同じKubernetesクラスター内の他のPodやServiceからは見えますが、そのネットワークの外からは見えません。
kubectlを使用する場合、アプリケーションと通信するためにAPIエンドポイントを通じてやりとりしています。
アプリケーションをKubernetesクラスターの外部に公開するための他のオプションについては、後ほどModule 4 で説明します。
また、これは基本的なチュートリアルであるため、ここではPodとは何かについては詳しく説明しません。
kubectl proxyコマンドによって、通信をクラスター全体のプライベートネットワークに転送するプロキシを作成することができます。
プロキシはcontrol-Cキーを押すことで終了させることができ、実行中は何も出力されません。
プロキシを実行するにはもう一つターミナルウィンドウを開く必要があります。
ホスト(端末)とKubernetesクラスター間の接続ができました。
プロキシによって端末からAPIへの直接アクセスが可能となります。
プロキシエンドポイントを通してホストされている全てのAPIを確認することができます。
例えば、curlコマンドを使って、APIを通じて直接バージョンを調べることができます。
curl http://localhost:8001/version
備考: ポート8001にアクセスできない場合は、上で起動したkube proxyがもう一つのターミナルで実行されていることを確認してください。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は1つ以上のアプリケーションコンテナ(Dockerなど)のグループであり、共有ストレージ(ボリューム)、IPアドレス、それらの実行方法に関する情報が含まれています。
ノード Podは常にノード 上で動作します。ノードはKubernetesではワーカーマシンであり、クラスターによって仮想、物理マシンのどちらであってもかまいません。各ノードはマスターによって管理されます。ノードは複数のPodを持つことができ、Kubernetesマスターはクラスター内のノード間でPodのスケジュールを自動的に処理します。マスターの自動スケジューリングは各ノードで利用可能なリソースを考慮に入れます。
すべてのKubernetesノードでは少なくとも以下のものが動作します。
Kubelet: Kubernetesマスターとノード間の通信を担当するプロセス。マシン上で実行されているPodとコンテナを管理します。 レジストリからコンテナイメージを取得し、コンテナを解凍し、アプリケーションを実行することを担当する、Dockerのようなコンテナランタイム。 コンテナ同士が密接に結合され、ディスクなどのリソースを共有する必要がある場合は、コンテナを1つのPodにまとめてスケジュールする必要があります。
kubectlを使ったトラブルシューティング モジュール2 では、kubectlのコマンドラインインターフェースを使用しました。モジュール3でもこれを使用して、デプロイされたアプリケーションとその環境に関する情報を入手します。最も一般的な操作は、次のkubectlコマンドで実行できます。
kubectl get - リソースの一覧を表示kubectl describe - 単一リソースに関する詳細情報を表示kubectl logs - 単一Pod上の単一コンテナ内のログを表示kubectl exec - 単一Pod上の単一コンテナ内でコマンドを実行これらのコマンドを使用して、アプリケーションがいつデプロイされたか、それらの現在の状況、実行中の場所、および構成を確認することができます。
クラスターのコンポーネントとコマンドラインの詳細についてわかったので、次にデプロイしたアプリケーションを探索してみましょう。
ノードはKubernetesではワーカーマシンであり、クラスターに応じてVMまたは物理マシンになります。 複数のPodを1つのノードで実行できます。
2.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は、一連のPodにトラフィックをルーティングします。Serviceは、アプリケーションに影響を与えることなく、KubernetesでPodが死んだり複製したりすることを可能にする抽象概念です。(アプリケーションのフロントエンドおよびバックエンドコンポーネントなどの)依存Pod間の検出とルーティングは、Kubernetes Serviceによって処理されます。
Serviceは、ラベルとセレクタを使用して一連のPodを照合します。これは、Kubernetes内のオブジェクトに対する論理操作を可能にするグループ化のプリミティブです。ラベルはオブジェクトに付けられたkey/valueのペアであり、さまざまな方法で使用できます。
開発、テスト、および本番用のオブジェクトを指定する バージョンタグを埋め込む タグを使用してオブジェクトを分類する ラベルは、作成時またはそれ以降にオブジェクトにアタッチでき、いつでも変更可能です。Serviceを使用してアプリケーションを公開し、いくつかのラベルを適用してみましょう。
2.5 - アプリケーションのスケーリング 2.5.1 - アプリケーションの複数インスタンスを実行 目標 kubectlを使用してアプリケーションをスケールする アプリケーションのスケーリング 前回のモジュールでは、Deployment を作成し、それをService 経由で公開しました。該当のDeploymentでは、アプリケーションを実行するためのPodを1つだけ作成しました。トラフィックが増加した場合、ユーザーの需要に対応するためにアプリケーションをスケールする必要があります。
スケーリング は、Deploymentのレプリカの数を変更することによって実現可能です。
kubectl create deploymentコマンドの--replicasパラメーターを使用することで、最初から複数のインスタンスを含むDeploymentを作成できます。
Deploymentをスケールアウトすると、新しいPodが作成され、使用可能なリソースを持つノードにスケジュールされます。スケールすると、Podの数が増えて新たな望ましい状態になります。KubernetesはPodのオートスケーリング もサポートしていますが、このチュートリアルでは範囲外です。スケーリングを0に設定することも可能で、指定された配置のすべてのPodを終了させます。
アプリケーションの複数インスタンスを実行するには、それらすべてにトラフィックを分散する方法が必要になります。Serviceには、公開されたDeploymentのすべてのPodにネットワークトラフィックを分散する統合ロードバランサがあります。Serviceは、エンドポイントを使用して実行中のPodを継続的に監視し、トラフィックが使用可能なPodにのみ送信されるようにします。
スケーリングは、Deploymentのレプリカの数を変更することによって実現可能です。
アプリケーションの複数のインスタンスを実行すると、ダウンタイムなしでローリングアップデートを実行できます。それについては、次のモジュールで学習します。それでは、オンラインのターミナルを使って、アプリケーションをデプロイしてみましょう。
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にのみトラフィックを負荷分散します。
次の対話型チュートリアルでは、アプリケーションを新しいバージョンにアップデートし、ロールバックも実行します。
3 - 設定 3.1 - サイドカーコンテナの導入 このセクションは、ワークロード向けに新しい組み込みのサイドカーコンテナ 機能を導入する方を対象としています。
サイドカーコンテナは、ブログ記事 で述べられているように、新しいコンセプトではありません。
Kubernetesは、このコンセプトを実装するために、1つのPod内に複数のコンテナを実行することを許可します。
通常のコンテナとしてサイドカーコンテナを実行するには多くの制限がありますが、新しい組み込みのサイドカーコンテナのサポートで解消されます。
FEATURE STATE:
GA since Kubernetes v1.33
More information about this feature This is a stable feature in Kubernetes, and has been since version v1.33. It was first available in the v1.28 release. You can no longer disable or opt out of this feature or behavior (it is locked); if you explicitly set a value for the associated feature gate SidecarContainers , Kubernetes ignores it but does not report any error.
目標 サイドカーコンテナの必要性を理解する サイドカーコンテナに関する問題をトラブルシューティングできるようになる あらゆるワークロードに対して、サイドカーコンテナを普遍的に「注入する」選択肢を理解する 始める前に
Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。
このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。
まだクラスターがない場合、minikube を使って作成するか、
以下のいずれかのKubernetesプレイグラウンドも使用できます:
作業するKubernetesサーバーは次のバージョン以降のものである必要があります: 1.29.
バージョンを確認するには次のコマンドを実行してください: kubectl version.
サイドカーコンテナの概要 サイドカーコンテナは、同じPod 内でメインのアプリケーションコンテナと共に実行されるセカンダリコンテナです。
これらのコンテナは、ロギング、モニタリング、セキュリティ、データ同期などの追加サービスや機能を提供することで、メインのアプリケーションコードを直接変更することなく、プライマリ アプリケーションコンテナ の機能を強化または拡張するために使用されます。
サイドカーコンテナ のコンセプトページで詳細を読むことができます。
サイドカーコンテナのコンセプトは新しいものではなく、このコンセプトには複数の実装があります。
Podを定義する人が実行したいサイドカーコンテナだけでなく、Podの実行開始前にいくつかのアドオン がPodを変更することで、追加のサイドカーコンテナが存在している場合があることもわかります。
それらの追加のサイドカーを 注入する メカニズムは、多くの場合Mutating Webhook です。
例えば、サービスメッシュアドオンは、異なるPod間の通信において、相互TLSと暗号化の設定をするサイドカーを注入することがあります。
サイドカーコンテナのコンセプト自体は目新しいものではありませんが、Kubernetesにおけるこの機能のネイティブ実装は新しいものです。
そして、どの新機能にもあるように、この機能の導入には一定の課題が生じる可能性があります。
このチュートリアルでは、エンドユーザーだけでなく、サイドカーコンテナの作成者も経験しうる課題と解決策を取り上げます。
組み込みのサイドカーコンテナの利点 サイドカーコンテナに関してKubernetesのネイティブサポートを利用することには、いくつかの利点があります:
ネイティブサイドカーコンテナを、Initコンテナ の前に起動するように設定できます。 組み込みのサイドカーコンテナは、それらが最後に終了することを保証するよう作成されます。
サイドカーコンテナは、すべての通常のコンテナが完了して終了すると、SIGTERMシグナルによって終了します。
サイドカーコンテナがグレースフルシャットダウンされない場合、サイドカーコンテナを終了させるためにSIGKILLシグナルが使用されます。 Jobにおいて、PodがrestartPolicy: OnFailureまたはrestartPolicy: Neverである場合、ネイティブサイドカーコンテナはPodの完了をブロックしません。
レガシーサイドカーコンテナでは、この状況を処理するために特別な配慮を必要とします。 また、Jobにおいて、PodのrestartPolicy: Neverによって通常のコンテナが再起動されないとしても、組み込みのサイドカーコンテナは、それらが完了するたびに再起動されます。 更なる情報について学習するには、Initコンテナとの違い を参照してください。
組み込みのサイドカーコンテナの導入 SidecarContainersフィーチャーゲート は、Kubernetesバージョン1.29でベータ版となり、デフォルトで有効化されています。
一部のクラスターでは、この機能が無効化されていたり、この機能と互換性のないソフトウェアがインストールされていたりすることがあります。
このような場合、Podが拒否されたり、サイドカーコンテナによってPodの起動がブロックされたりすることで、Podが使用不能になる可能性があります。
Podが単に初期化中に止まってしまうため、この状況を検知するのは容易です。
しかし問題の原因が明らかでないことが多いです。
以下は、ワークロードにサイドカーコンテナを導入する際に考慮すべきことや、トラブルシューティングの手順です。
フィーチャーゲートが有効化されていることを確認する 最初のステップとして、APIサーバーとノードの両方がKubernetesバージョン1.29以降であることを確認してください。
以前のバージョンで動いているノードがあり、フィーチャーゲートが有効化されていないクラスターでは、正しく動作しません。
Note この機能は、バージョン1.28のノードでも有効化できます。
バージョン1.28では、組み込みサイドカーコンテナの終了時の挙動が異なっていました。
サイドカーコンテナの動作を、その古い挙動に合わせることは推奨されていません。
ただし、起動順序のみが懸念事項である場合は、上記で述べた対象を、「フィーチャーゲートを有効化した状態で、バージョン1.28を実行しているノード」と読み替えることができます。フィーチャーゲートが、コントロールプレーン内のAPIサーバーと 全てのノードで有効化されていることを確認してください。
フィーチャーゲートが有効かどうかを確認する方法の1つとしては、このようなコマンドを実行することです:
APIサーバー用:
kubectl get --raw /metrics | grep kubernetes_feature_enabled | grep SidecarContainers
個々のノード用:
kubectl get --raw /api/v1/nodes/<node-name>/proxy/metrics | grep kubernetes_feature_enabled | grep SidecarContainers
以下のような出力が表示された場合:
kubernetes_feature_enabled{name="SidecarContainers",stage="BETA"} 1
これは機能が有効化されていることを意味します。
この機能の検証時に問題に遭遇した場合、それはサードパーティ製ツールやMutating Webhookのいずれかが正しく動作していないことを示している可能性があります。
SidecarContainersフィーチャーゲートが有効な場合、PodのAPIには新しいフィールドが追加されます。
一部のツールやMutating Webhookは、より古いバージョンのKubernetes APIで構築されている可能性があります。
ツールが様々なパッチ戦略を使用してPodオブジェクトを変更する際に、未知のフィールドをそのまま渡す場合、これは問題とはなりません。
しかし、未知のフィールドを取り除いてしまうツールも存在します。
もしそのようなツールを使用している場合、それらをKubernetes APIクライアントコードのバージョン1.28以降で再コンパイルしなければなりません。
これを確認する方法は、Mutating Admissionを通過したPodでkubectl describe podコマンドを使用することです。
いずれかのツールが新しいフィールド(restartPolicy:Always)を取り除いた場合、コマンド出力に表示されません。
このような問題に遭遇した場合、オブジェクト全体の更新ではなく、パッチ戦略のいずれかを使用してオブジェクトの変更をするよう、ツールやWebhookの作成者に助言してください。
Note Mutating Webhookはいくつかの条件に基づいてPodを更新する可能性があります。
したがって、サイドカーコンテナは一部のPodでは動作し、他のPodでは動作しないことがあります。サイドカーの自動注入 自動的にサイドカーを注入するソフトウェアを使用している場合、ネイティブサイドカーコンテナを使用できるようにするために、いくつかの戦略を取ることができます。
基本的にはどの戦略も、サイドカーが注入されるPodが、この機能をサポートするノードに配置されるかどうかを決定するために採用できる選択肢です。
一例として、Istioコミュニティでのこの会話 をたどることができます。
この議論は、下記に列挙した選択肢について検討されています。
サイドカーをサポートするノードに配置されるPodにマークを付ける。
ノードラベルとノードアフィニティを使用することで、サイドカーコンテナをサポートするノードと、それらのノードに配置されるPodにマークを付けることができます。 注入時にノードの互換性を確認する。
サイドカー注入中には、ノードの互換性を確認するために以下の戦略を使うことができます:ノードのバージョンを問い合わせ、フィーチャーゲートがバージョン1.29以降で有効になっていると仮定する ノードのPrometheusメトリクスを問い合わせ、フィーチャーゲートが有効かどうかの状態を確認する APIサーバーに対して、サポートされるバージョンスキュー でノードが動作していると仮定する ノードの互換性を検出する独自の方法が他にも存在する可能性がある ユニバーサルサイドカーインジェクターを開発する。
ユニバーサルサイドカーインジェクターの考え方は、サイドカーコンテナを、通常のコンテナとしてもネイティブサイドカーコンテナとしても注入することです。
そして、どちらのコンテナが動作するかを実行時のロジックで判定します。
ユニバーサルサイドカーインジェクターは、リクエストを2回分計上するため非効率ですが、特殊なケースでは運用可能な解決策として考えることもできます。1つの方法は、ネイティブサイドカーコンテナの起動時に、ノードバージョンを検出し、そのバージョンがサイドカー機能をサポートしない場合は即座に終了することです。 実行時の機能検知の設計を検討します:コンテナがお互いに通信できるように空のディレクトリを定義する restartPolicy=Alwaysを指定したInitコンテナを注入し、それをNativeSidecarと呼ぶことにする。NativeSidecarは、最初の実行を示すファイルを空のディレクトリに書き込み、終了コード0で即座に終了しなければならない。NativeSidecarは、再起動時(ネイティブサイドカーがサポートされている場合)に、ファイルがすでに空のディレクトリ内に存在することを確認し、そのファイルを変更する。
これは、組み込みのサイドカーコンテナがサポートされ、実行されていることを示している。通常のコンテナを注入し、それをOldWaySidecarと呼ぶことにする。 OldWaySidecarは、起動時に空のディレクトリ内にファイルが存在するかどうかを確認する。NativeSidecarが実行されていないことをファイルが示している場合、それはサイドカー機能がサポートされていないと推定し、自らがサイドカーであることを前提として動作する。NativeSidecarが実行されていることをファイルが示している場合、何もせず永久にスリープする(PodがrestartPolicy=Alwaysのとき)、または終了コード0で即座に終了する(PodがrestartPolicy!=Alwaysのとき)。 次の項目 3.2 - 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 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 pod/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を削除します:
次の項目
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の作成 クラスターにて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アプリケーションが起動しています。
Deploymentに関する情報を表示します:
kubectl get deployments hello-world
kubectl describe deployments hello-world
ReplicaSetオブジェクトに関する情報を表示します:
kubectl get replicasets
kubectl describe replicasets
Deploymentを公開するServiceオブジェクトを作成します。
kubectl expose deployment hello-world --type=LoadBalancer --name=my-service
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
備考: `type=LoadBalancer`のServiceは外部のクラウドプロバイダーによってサポートされており、ここでは扱いません。詳細は[こちらのページ](/ja/docs/concepts/services-networking/service/#loadbalancer)を参照してください。
備考: 外部IPアドレスが\<pending\>と表示されている場合は、しばらく待ってから同じコマンドを実行してください。
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です。
先ほどの出力にて、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
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のスレーブを起動する。 ゲストブックのフロントエンドを起動する。 フロントエンドの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
マニフェストファイルをダウンロードしたディレクトリ内で、ターミナルウィンドウを起動します。
redis-master-deployment.yamlファイルから、RedisのマスターのDeploymentを適用します。
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-master-deployment.yaml
Podのリストを問い合わせて、RedisのマスターのPodが実行中になっていることを確認します。
結果は次のようになるはずです。
NAME READY STATUS RESTARTS AGE
redis-master-1068406935-3lswp 1/1 Running 0 28s
次のコマンドを実行して、RedisのマスターのPodからログを表示します。
備考: POD-NAMEの部分を実際のPodの名前に書き換えてください。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
次のredis-master-service.yamlから、RedisのマスターのServiceを適用します。
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-master-service.yaml
Serviceのリストを問い合わせて、Redisのマスターの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-masterという名前のServiceを、前に定義したラベルにマッチする一連のラベル付きで作成します。これにより、ServiceはネットワークトラフィックをRedisのマスターのPodへとルーティングできるようになります。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
redis-slave-deployment.yamlファイルから、RedisのスレーブのDeploymentを適用します。
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-slave-deployment.yaml
Podのリストを問い合わせて、RedisのスレーブのPodが実行中になっていることを確認します。
結果は次のようになるはずです。
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
次のredis-slave-service.yamlファイルから、RedisのスレーブのServiceを適用します。
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-slave-service.yaml
Serviceのリストを問い合わせて、RedisのスレーブのServiceが実行中になっていることを確認します。
結果は次のようになるはずです。
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
frontend-deployment.yamlファイルから、フロントエンドのDeploymentを適用します。
kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-deployment.yaml
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でのみ公開できます。
備考: 一部のクラウドプロバイダーでは、Google Compute EngineやGoogle Kubernetes Engineなど、外部のロードバランサーをサポートしているものがあります。もしクラウドプロバイダーがロードバランサーをサポートしていて、それを使用したい場合は、type: NodePortという行を単に削除またはコメントアウトして、type: LoadBalancerのコメントアウトを外せば使用できます。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
frontend-service.yamlファイルから、フロントエンドのServiceを提供します。
kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-service.yaml
Serviceのリストを問い合わせて、フロントエンドのServiceが実行中であることを確認します。
結果は次のようになるはずです。
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アドレスを見つける必要があります。
次のコマンドを実行すると、フロントエンドServiceに対するIPアドレスを取得できます。
minikube service frontend --url
結果は次のようになるはずです。
http://192.168.99.100:31323
IPアドレスをコピーして、ブラウザー上でページを読み込み、ゲストブックを表示しましょう。
フロントエンドのServiceをLoadBalancer経由で表示する もしfrontend-service.yamlマニフェストをtype: LoadBalancerでデプロイした場合、ゲストブックを表示するためのIPアドレスを見つける必要があります。
次のコマンドを実行すると、フロントエンド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
外部IPアドレス(EXTERNAL-IP)をコピーして、ブラウザー上でページを読み込み、ゲストブックを表示しましょう。
ウェブフロントエンドをスケールする サーバーがDeploymentコントローラーを使用するServiceとして定義されているため、スケールアップやスケールダウンは簡単です。
次のコマンドを実行すると、フロントエンドのPodの数をスケールアップできます。
kubectl scale deployment frontend --replicas= 5
Podのリストを問い合わせて、実行中のフロントエンドのPodの数を確認します。
結果は次のようになるはずです。
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
次のコマンドを実行すると、フロントエンドのPodの数をスケールダウンできます。
kubectl scale deployment frontend --replicas= 2
Podのリストを問い合わせて、実行中のフロントエンドのPodの数を確認します。
結果は次のようになるはずです。
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つのコマンドで削除できます。
次のコマンドを実行すると、すべての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
Podのリストを問い合わせて、実行中のPodが存在しないことを確認します。
結果は次のようになるはずです。
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オブジェクトの中に置くことで、情報の使用方法を適切に制御したり、誤って公開してしまうリスクを減らすことができます。
備考: ここでは2種類の手順を紹介します。1つはセルフマネージド な(自分のサーバーで実行中またはElastic Helm Chartを使用して構築された)ElasticsearchおよびKibanaのためのもので、もう1つはマネージドサービス のElastic CloudのElasticsearch Serviceのための別の手順です。このチュートリアルで使う種類のElasticsearchおよびKibanaのシステムのためのSecretだけを作成してください。セルフマネージド Elastic Cloud上のElasticsearch Serviceに接続する場合は、マネージドサービス タブに切り替えてください。
クレデンシャルを設定する セルフマネージドのElasticsearchとKibanaへ接続する場合、KubernetesのSecretを作成するために編集するべきファイルは4つあります(セルフマネージドとは、事実上Elastic Cloud以外で実行されているElasticsearch Serviceを指します)。ファイルは次の4つです。
ELASTICSEARCH_HOSTS ELASTICSEARCH_PASSWORD ELASTICSEARCH_USERNAME KIBANA_HOST これらのファイルにElasticsearchクラスターとKibanaホストの情報を設定してください。ここでは例をいくつか示します(こちらの設定 も参照してください)。
ELASTICSEARCH_HOSTSElastic Elasticsearch Helm Chartで作成したnodeGroupの場合。
[ "http://elasticsearch-master.default.svc.cluster.local:9200" ]
Mac上で単一のElasticsearchノードが実行されており、BeatsがDocker for Macで実行中の場合。
[ "http://host.docker.internal:9200" ]
2ノードのElasticsearchがVM上または物理ハードウェア上で実行中の場合。
[ "http://host1.example.com:9200" , "http://host2.example.com:9200" ]
ELASTICSEARCH_HOSTSを編集します。
ELASTICSEARCH_PASSWORDパスワードだけを書きます。空白、クォート、<>などの文字は書かないでください。
<yoursecretpassword>
ELASTICSEARCH_PASSWORDを編集します。
vi ELASTICSEARCH_PASSWORD
ELASTICSEARCH_USERNAMEユーザー名だけを書きます。空白、クォート、<>などの文字は書かないでください。
<Elasticsearchに追加するユーザー名>
ELASTICSEARCH_USERNAMEを編集します。
vi ELASTICSEARCH_USERNAME
KIBANA_HOSTElastic Kibana Helm Chartで作成したKibanaインスタンスが実行中の場合。defaultというサブドメインは、default Namespaceを指します。もしHelm Chartを別のNamespaceにデプロイした場合、サブドメインは異なります。
"kibana-kibana.default.svc.cluster.local:5601"
Mac上でKibanaインスタンスが実行中で、BeatsがDocker for Macで実行中の場合。
"host.docker.internal:5601"
2つのElasticsearchノードが、VMまたは物理ハードウェア上で実行中の場合。
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つのファイルです。
ELASTIC_CLOUD_AUTH 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とは異なります。コンテナのラベルに対するパターンマッチを指定する代わりに、関連するプロトコルとポート番号に基づいた設定を書きます。以下に示すのは、ポート番号のサブセットです。
備考: サービスを標準ポート以外で実行している場合、そのポート番号をfilebeat.yaml内の適切なtypeに追加し、PacketbeatのDaemonSetを削除・再作成してください。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を一覧します。
出力は次のようになります。
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のスタートなどのイベントが確認できます。
クリーンアップ DeploymentとServiceを削除すると、実行中のすべてのPodも削除されます。ラベルを使って複数のリソースを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
Podの一覧を問い合わせて、実行中のPodがなくなったことを確認します。
結果は次のようになるはずです。
No resources found.
次の項目
5 - セキュリティ 5.1 - クラスターレベルでのPodセキュリティの標準の適用
Note このチュートリアルは、新しいクラスターにのみ適用されます。Podセキュリティアドミッション(PSA)は、ベータへ進み 、v1.23以降でデフォルトで有効になっています。
Podセキュリティアドミッションは、Podが作成される際に、Podセキュリティの標準 の適用の認可を制御するものです。
このチュートリアルでは、クラスター内の全ての名前空間に標準設定を適用することで、クラスターレベルでbaseline Podセキュリティの標準を強制する方法を示します。
Podセキュリティの標準を特定の名前空間に適用するには、名前空間レベルでのPodセキュリティの標準の適用 を参照してください。
v1.37以外のKubernetesバージョンを実行している場合は、そのバージョンのドキュメントを確認してください。
始める前に ワークステーションに以下をインストールしてください:
このチュートリアルでは、完全な制御下にあるKubernetesクラスターの何を設定できるかをデモンストレーションします。
コントロールプレーンを設定できない管理されたクラスターのPodセキュリティアドミッションに対しての設定方法を知りたいのであれば、名前空間レベルでのPodセキュリティの標準の適用 を参照してください。
適用する正しいPodセキュリティの標準の選択 Podのセキュリティアドミッション は、以下のモードでビルトインのPodセキュリティの標準 の適用を促します: enforce、audit、warn。
設定に最適なPodセキュリティの標準を選択するにあたって助けになる情報を収集するために、以下を行ってください:
Podセキュリティの標準を適用していないクラスターを作成します:
kind create cluster --name psa-wo-cluster-pss
出力は次のようになります:
Creating cluster "psa-wo-cluster-pss" ...
✓ Ensuring node image (kindest/node:v1.37.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! 😊
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'.
クラスター内の名前空間の一覧を取得します:
出力は次のようになります:
NAME STATUS AGE
default Active 9m30s
kube-node-lease Active 9m32s
kube-public Active 9m32s
kube-system Active 9m32s
local-path-storage Active 9m26s
異なるPodセキュリティの標準が適用されたときに何が起きるかを理解するために、-dry-run=serverを使います:
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
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
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セキュリティの標準を適用すると、名前空間のどれにも警告が示されないことに気付くでしょう。
これに対し、baselineとrestrictの標準ではどちらも、とりわけkube-system名前空間に対して警告が示されています。
モード、バージョン、標準のセット このセクションでは、latestバージョンに以下のPodセキュリティの標準を適用します:
enforceモードでbaseline標準。warnおよびauditモードでrestricted標準。baseline Podセキュリティの標準は、免除リストを短く保てて、かつ既知の特権昇格を防ぐような、利便性のある中庸を提供します。
加えて、kube-system内の失敗からPodを守るために、適用されるPodセキュリティの標準の対象から名前空間を免除します。
環境にPodセキュリティアドミッションを実装する際には、以下の点を考慮してください:
クラスターに適用されるリスク状況に基づくと、restrictedのようにより厳格なPodセキュリティの標準のほうが、より良い選択肢かもしれません。
kube-system名前空間の免除は、Podがその名前空間でprivilegedとして実行するのを許容することになります。
実世界で使うにあたっては、以下の最小権限の原則に従ってkube-systemへのアクセスを制限する厳格なRBACポリシーを適用することを、Kubernetesプロジェクトは強く推奨します。
上記の標準を実装するには、次のようにします:
目的の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
備考: pod-security.admission.config.k8s.io/v1設定はv1.25+での対応です。
v1.23とv1.24では
v1beta1 を使用してください。
v1.22では
v1alpha1 を使用してください。
クラスターの作成中にこのファイルを取り込む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
備考: macOSでDocker Desktopとkind を利用している場合は、Preferences > Resources > File Sharing のメニュー項目からShared Directoryとして/tmpを追加できます。目的の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.37.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 🙂
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'.
デフォルトの名前空間に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セキュリティの標準の適用
Note このチュートリアルは、新しいクラスターにのみ適用されます。Podセキュリティアドミッション(PSA)は、ベータへ進み 、v1.23以降でデフォルトで有効になっています。
Podセキュリティアドミッションは、Podが作成される際に、Podセキュリティの標準 の適用の認可を制御するものです。
このチュートリアルでは、一度に1つの名前空間でbaseline Podセキュリティ標準を強制します。
Podセキュリティの標準を複数の名前空間に一度にクラスターレベルで適用することもできます。やり方についてはクラスターレベルでのPodセキュリティの標準の適用 を参照してください。
始める前に ワークステーションに以下をインストールしてください:
クラスターの作成 以下のようにKinDクラスターを作成します。
kind create cluster --name psa-ns-level
出力は次のようになります:
Creating cluster "psa-ns-level" ...
✓ Ensuring node image (kindest/node:v1.37.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/
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セキュリティの標準チェックの有効化 ビルトインの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
ラベルを使って、任意の名前空間に対して複数の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セキュリティの標準の強制の実証 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
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を使用してコンテナのリソースへのアクセスを制限する This is a stable feature in Kubernetes, and has been since the 1.31 release. You can no longer toggle this feature (the associated feature gate has been removed).
このページでは、ノードでAppArmorプロファイルを読み込み、それらのプロファイルをPodに適用する方法を説明します。
AppArmorを使用してPodを制限するKubernetesの仕組みについて詳しく知りたい場合は、PodとコンテナのためのLinuxカーネルのセキュリティ制約 をご覧ください。
目標 プロファイルをノードに読み込む方法の例を見る Pod上でプロファイルを強制する方法を学ぶ プロファイルが読み込まれたかを確認する方法を学ぶ プロファイルに違反した場合に何が起こるのかを見る プロファイルが読み込めなかった場合に何が起こるのかを見る 始める前に AppArmorはカーネルモジュールおよびKubernetesのオプション機能です。
そのため、先に進む前にノード上でAppArmorがサポートされていることを確認してください:
AppArmorカーネルモジュールが有効であること。
LinuxカーネルがAppArmorプロファイルを強制するためには、AppArmorカーネルモジュールのインストールと有効化が必須です。
UbuntuやSUSEなどのディストリビューションではデフォルトで有効化されますが、他の多くのディストリビューションでのサポートはオプションです。
モジュールが有効になっているかどうかを確認するためには、/sys/module/apparmor/parameters/enabledファイルを確認します:
cat /sys/module/apparmor/parameters/enabled
Y
Kubeletは、AppArmorが明示的に設定されたPodを受け入れる前にホスト上でAppArmorが有効になっているかどうかを検証します。
コンテナランタイムがAppArmorをサポートしていること。
Kubernetesがサポートするcontainerd やCRI-O などのすべての一般的なコンテナランタイムは、AppArmorをサポートしています。
関連するランタイムのドキュメントを参照して、クラスターがAppArmorを利用するための要求を満たしているかどうかを検証してください。
プロファイルが読み込まれていること。
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をセキュアにする
備考: Kubernetes v1.30より前では、AppArmorはアノテーションを通じて指定されていました。
この非推奨となったAPIに関するドキュメントを表示するには、ドキュメントのバージョンセレクターを使用してください。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.37は、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による制限の指定
注意: Kubernetes v1.30より前では、AppArmorはアノテーションを通じて指定されていました。
この非推奨となったAPIに関するドキュメントを表示するには、ドキュメントのバージョンセレクターを使用してください。セキュリティコンテキスト内のAppArmorプロファイル appArmorProfileは、コンテナのsecurityContextまたはPodのsecurityContextのいずれかで指定できます。
プロファイルがPodレベルで設定されている場合、Pod内のすべてのコンテナ(initコンテナ、サイドカーコンテナ、およびエフェメラルコンテナを含む)のデフォルトのプロファイルとして使用されます。
Podとコンテナの両方でAppArmorプロファイルが設定されている場合は、コンテナのプロファイルが使用されます。
AppArmorプロファイルには2つのフィールドがあります:
type (必須) - 適用されるAppArmorプロファイルの種類を示します。
有効なオプションは以下の通りです:
Localhostノードに事前に読み込まれているプロファイル(localhostProfileで指定します)。 RuntimeDefaultコンテナランタイムのデフォルトのプロファイル。 UnconfinedAppArmorによる制限を適用しません。 localhostProfile - ノード上に読み込まれている、使用すべきプロファイルの名前。
このプロファイルは、ノード上であらかじめ設定されている必要があります。
このオプションは、typeがLocalhostの場合にのみ指定する必要があります。
次の項目 追加のリソースとしては以下のものがあります:
5.4 - seccompを使用してコンテナのシステムコールを制限する FEATURE STATE:
GA since Kubernetes v1.19
seccomp(SECure COMPuting mode)はLinuxカーネル2.6.12以降の機能です。
この機能を用いると、ユーザー空間からカーネルに対して発行できるシステムコールを制限することで、プロセス権限のサンドボックスを構築することができます。
Kubernetesでは、ノード 上で読み込んだseccompプロファイルをPodやコンテナに対して自動で適用することができます。
あなたのワークロードが必要とする権限を特定するのは、実際には難しい作業になるかもしれません。
このチュートリアルでは、ローカルのKubernetesクラスターでseccompプロファイルを読み込むための方法を説明し、seccompプロファイルのPodへの適用方法について学んだ上で、コンテナプロセスに対して必要な権限のみを付与するためのseccompプロファイルを作成する方法を概観していきます。
目標 ノードでseccompプロファイルを読み込む方法を学ぶ。 seccompプロファイルをコンテナに適用する方法を学ぶ。 コンテナプロセスが生成するシステムコールの監査出力を確認する。 seccompプロファイルが指定されない場合の挙動を確認する。 seccompプロファイルの違反を確認する。 きめ細やかなseccompプロファイルの作成方法を学ぶ。 コンテナランタイムの標準seccompプロファイルを適用する方法を学ぶ。 始める前に このチュートリアルのステップを完了するためには、kind とkubectl をインストールしておく必要があります。
このチュートリアルで利用するコマンドは、Docker をコンテナランタイムとして利用していることを前提としています。
(kindが作成するクラスターは内部的に異なるコンテナランタイムを利用する可能性があります)。
Podman を使うこともできますが、チュートリアルを完了するためには、所定の手順 に従う必要があります。
このチュートリアルでは、現時点(v1.25以降)でのベータ機能を利用する設定例をいくつか示しますが、その他の例についてはGAなseccomp関連機能を用いています。
利用するKubernetesバージョンを対象としたクラスターの正しい設定 がなされていることを確認してください。
チュートリアル内では、サンプルをダウンロードするためにcurlを利用します。
この手順については、ほかの好きなツールを用いて実施してもかまいません。
備考: securityContextにprivileged: trueが設定されているContainerに対しては、seccompプロファイルを適用することができません。
特権コンテナは常にUnconfinedな状態で動作します。サンプルのseccompプロファイルをダウンロードする プロファイルの内容については後で解説しますので、まずはクラスターで読み込むためのseccompプロファイルをprofiles/ディレクトリ内にダウンロードしましょう。
audit.json
violation.json
fine-grained.json {
"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.37を使用することを前提とします。
ベータ機能として、Unconfinedにフォールバックするのではなく、コンテナランタイム がデフォルトで推奨するプロファイルを利用するようにKubernetesを設定することもできます。
この機能を試したい場合、これ以降の手順に進む前に、全ワークロードに対する標準seccompプロファイルとしてRuntimeDefaultを使用する を参照してください。
kindの設定ファイルを設置したら、kindクラスターを作成します:
kind create cluster --config= kind.yaml
Kubernetesクラスターが準備できたら、単一ノードクラスターが稼働しているDockerコンテナを特定してください:
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に設定すると、コンテナランタイムが提供するデフォルトのプロファイルをワークロードに適用することができます。
備考: seccompDefaultの
設定 を有効化している場合、他にseccompプロファイルを定義しなくても、Podは
RuntimeDefault seccompプロファイルを使用します。
seccompDefaultが無効の場合のデフォルトは
Unconfinedです。
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 備考: 過去のバージョンのKubernetesでは、
アノテーション を用いてseccompの挙動を制御することができました。
Kubernetes 1.37におけるseccompの設定では
.spec.securityContextフィールドのみをサポートしており、このチュートリアルではKubernetes 1.37における手順を解説しています。
クラスター内に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
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:
GA since Kubernetes v1.27
標準seccompプロファイルを指定するためには、この機能を利用したい全てのノードで--seccomp-defaultコマンドラインフラグ を用いてkubeletを起動する必要があります。
この機能を有効化すると、kubeletはコンテナランタイムが定義するRuntimeDefaultのseccompプロファイルをデフォルトで使用するようになり、Unconfinedモード(seccomp無効化)になることはありません。
コンテナランタイムが用意する標準プロファイルは、ワークロードの機能性を維持しつつ、強力な標準セキュリティルールの一式を用意することを目指しています。
標準のプロファイルはコンテナランタイムやリリースバージョンによって異なる可能性があります。
例えば、CRI-Oとcontainerdの標準プロファイルを比較してみるとよいでしょう。
備考: この機能を有効化しても、PodやContainerなどの
securityContext.seccompProfileフィールドは変更されず、非推奨のアノテーションがワークロードに追加されることもありません。
したがって、ユーザーはワークロードの設定を変更せずにロールバックすることが可能です。
crictl inspectのようなツールを使うことで、コンテナがどのseccompプロファイルを利用しているのかを確認できます。
いくつかのワークロードでは、他のワークロードよりもシステムコール制限を少なくすることが必要な場合があります。
つまり、RuntimeDefaultを適用する場合、こうしたワークロードの実行が失敗する可能性があります。
このような障害を緩和するために、次のような対策を講じることができます:
ワークロードを明示的にUnconfinedとして稼働させる。 SeccompDefault機能をノードで無効化する。
また、機能を無効化したノードに対してワークロードが配置されていることを確認しておく。ワークロードを対象とするカスタムseccompプロファイルを作成する。 実運用環境に近いクラスターに対してこの機能を展開する場合、クラスター全体に対して変更をロールアウトする前に、一部ノードのみを対象にしてこのフィーチャーゲートを有効化し、ワークロードが実行できることを検証しておくことをお勧めします。
クラスターに対してとりうるアップグレード・ダウングレード戦略について更に詳細な情報を知りたい場合は、関連するKubernetes Enhancement Proposal (KEP)であるEnable seccomp by default を参照してください。
Kubernetes 1.37では、specで特定のseccompプロファイルを指定していないPodに対して、デフォルトで適用するseccompプロファイルを設定することができます。
ただし、この標準seccompプロファイルを利用する場合、対象とする全ノードでこの機能を有効化しておく必要があります。
稼働中のKubernetes 1.37クラスターでこの機能を有効化する場合、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の概念について理解しておく必要があります。
備考: このチュートリアルでは、クラスターがPersistentVolumeの動的なプロビジョニングが行われるように設定されていることを前提としています。クラスターがそのように設定されていない場合、チュートリアルを始める前に1GiBのボリュームを2つ手動でプロビジョニングする必要があります。目標 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 Conditions のtypeを参照)の状態になるまでは、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-0とweb-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 execとkubectl 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.local、web-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
備考: 上記のcurlコマンドに対して代わりに403 Forbidden というレスポンスが返ってくる場合、volumeMountsでマウントしたディレクトリのパーミッションを修正する必要があります(これは、hostPathボリュームを使用したときに起こるバグ が原因です)。この問題に対処するには、上のcurlコマンドを再実行する前に、次のコマンドを実行します。
for i in 0 1; do kubectl exec web-$i -- chmod 755 /usr/share/nginx/html; done
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-0とweb-1がどんなノードにスケジュールされたとしても、PodのPersistentVolumeは適切なマウントポイントにマウントされます。
StatefulSetをスケールする StatefulSetのスケールとは、レプリカ数を増減することを意味します。これは、replicasフィールドを更新することによって実現できます。StatefulSetのスケールには、kubectl scale と
kubectl 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のアノテーションの更新時に利用できます。有効なアップデートの戦略は、RollingUpdateとOnDeleteの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は、前のコンテナイメージを実行しています。
備考: kubectl rollout status sts/<name>を使って、StatefulSetへのローリングアップデートの状態を確認することもできます。ステージングアップデート 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を削除します。
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を削除します。
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
partitionを0に移動することで、StatefulSetがアップデート処理を続けられるようにできます。
OnDelete OnDeleteアップデート戦略は、(1.6以前の)レガシーな動作を実装しています。このアップデート戦略を選択すると、StatefulSetの.spec.templateフィールドへ変更を加えても、StatefulSetコントローラーが自動的にPodを更新しなくなります。この戦略を選択するには、.spec.template.updateStrategy.typeにOnDeleteを設定します。
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を削除します。
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が完全に終了するまで待ちます。
備考: カスケードな削除ではStatefulSetがPodとともに削除されますが、StatefulSetと紐付けられたheadless Serviceは削除されません。そのため、nginxServiceは手動で削除する必要があります。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-0とweb-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.podManagementPolicyがParallelに設定されている以外は、前にダウンロードした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-0とweb-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を削除します。
備考: このチュートリアルで使用したPersistentVolumeのための永続ストレージも削除する必要があります。
すべてのストレージが再利用できるようにするために、環境、ストレージの設定、プロビジョニング方法に基づいて必要な手順に従ってください。
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の削除が行われたとしても、その中のデータは削除されずに残ります。
警告: シングルインスタンスのWordPressとMySQLのPodを使用しているため、ここで行うデプロイは本番のユースケースには適しません。WordPressを本番環境にデプロイするときは、
WordPress Helm Chart を使用することを検討してください。
備考: このチュートリアルで提供されるファイルは、GAとなっているDeployment APIを使用しているため、Kubernetesバージョン1.9以降のためのものになっています。もしこのチュートリアルを古いバージョンのKubernetesで使いたい場合は、APIのバージョンを適切にアップデートするか、このチュートリアルの古いバージョンを参照してください。目標 PersistentVolumeClaimとPersistentVolumeを作成する 以下を含むkustomization.yamlを作成するSecret generator MySQLリソースの設定 WordPressリソースの設定 kustomizationディレクトリをkubectl apply -k ./で適用する クリーンアップする 始める前に
Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。
このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。
まだクラスターがない場合、minikube を使って作成するか、
以下のいずれかのKubernetesプレイグラウンドも使用できます:
バージョンを確認するには次のコマンドを実行してください: kubectl version.
このページで示された例は、
kubectl 1.14以降で動作します。
以下の設定ファイルをダウンロードします。
mysql-deployment.yaml
wordpress-deployment.yaml
PersistentVolumeClaimとPersistentVolumeを作成する MySQLとWordpressはそれぞれ、データを保存するためのPersistentVolumeを必要とします。各PersistentVolumeClaimはデプロイの段階で作成されます。
多くのクラスター環境では、デフォルトのStorageClassがインストールされています。StorageClassがPersistentVolumeClaim中で指定されていなかった場合、クラスターのデフォルトのStorageClassが代わりに使われます。
PersistentVolumeClaimが作成されるとき、StorageClassの設定に基づいてPersistentVolumeが動的にプロビジョニングされます。
警告: ローカルのクラスターでは、デフォルトのStorageClassにはhostPathプロビジョナーが使われます。hostPathボリュームは開発およびテストにのみ適しています。hostPathボリュームでは、データはPodがスケジュールされたノード上の/tmp内に保存されます。そのため、もしPodが死んだり、クラスター上の他のノードにスケジュールされたり、ノードが再起動すると、データは失われます。
備考: hostPathプロビジョナーを使用する必要があるクラスターを立ち上げたい場合は、--enable-hostpath-provisionerフラグを controller-manager コンポーネントで設定する必要があります。備考: Google Kubernetes Engine上で動作するKubernetesクラスターを使っている場合は、
このガイド に従ってください。
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
MySQLのDeploymentの設定ファイルをダウンロードします。
curl -LO https://k8s.io/examples/application/wordpress/mysql-deployment.yaml
WordPressの設定ファイルをダウンロードします。
curl -LO https://k8s.io/examples/application/wordpress/wordpress-deployment.yaml
これらをkustomization.yamlファイルに追加します。
cat <<EOF >>./kustomization.yaml
resources:
- mysql-deployment.yaml
- wordpress-deployment.yaml
EOF
適用と確認 kustomization.yamlには、WordPressのサイトとMySQLデータベースのためのすべてのリソースが含まれています。次のコマンドでこのディレクトリを適用できます。
これで、すべてのオブジェクトが存在していることを確認できます。
次のコマンドを実行して、Secretが存在していることを確認します。
結果は次のようになるはずです。
NAME TYPE DATA AGE
mysql-pass-c57bb4t7mf Opaque 1 9s
次のコマンドを実行して、PersistentVolumeが動的にプロビジョニングされていることを確認します。
備考: PVがプロビジョニングされてバインドされるまでに、最大で数分かかる場合があります。
結果は次のようになるはずです。
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
次のコマンドを実行して、Podが実行中であることを確認します。
備考: PodのStatusが`Running`の状態になる前に、最大で数分かかる場合があります。
結果は次のようになるはずです。
NAME READY STATUS RESTARTS AGE
wordpress-mysql-1894417608-x5dzt 1/1 Running 0 40s
次のコマンドを実行して、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
備考: MinikubeではServiceを`NodePort`経由でしか公開できません。EXTERNAL-IPは常にpendingのままになります。
次のコマンドを実行して、WordPress ServiceのIPアドレスを取得します。
minikube service wordpress --url
結果は次のようになるはずです。
http://1.2.3.4:32406
IPアドレスをコピーして、ブラウザーで読み込み、サイトを表示しましょう。
WordPressによりセットアップされた次のスクリーンショットのようなページが表示されるはずです。
警告: WordPressのインストールをこのページのまま放置してはいけません。もしほかのユーザーがこのページを見つけた場合、その人はインスタンス上にウェブサイトをセットアップして、悪意のあるコンテンツの配信に利用できてしまいます。 ユーザー名とパスワードを決めてWordPressをインストールするか、このインスタンスを削除してください。クリーンアップ 次のコマンドを実行して、Secret、Deployment、Service、およびPersistentVolumeClaimを削除します。
次の項目 6.3 - 例: StatefulSetを使用したCassandraのデプロイ このチュートリアルでは、Apache Cassandra をKubernetes上で実行する方法を紹介します。
データベースの一種であるCassandraには、データの耐久性(アプリケーションの 状態 )を提供するために永続ストレージが必要です。
この例では、カスタムのCassandraのseed providerにより、Cassandraクラスターに参加した新しいCassandraインスタンスを検出できるようにします。
StatefulSet を利用すると、ステートフルなアプリケーションをKubernetesクラスターにデプロイするのが簡単になります。
このチュートリアルで使われている機能のより詳しい情報は、StatefulSet を参照してください。
備考: CassandraとKubernetesは、ともにクラスターのメンバーを表すために ノード という用語を使用しています。このチュートリアルでは、StatefulSetに属するPodはCassandraのノードであり、Cassandraクラスター( ring と呼ばれます)のメンバーでもあります。これらのPodがKubernetesクラスター内で実行されるとき、Kubernetesのコントロールプレーンは、PodをKubernetesのNode 上にスケジュールします。
Cassandraノードが開始すると、 シードリスト を使ってring上の他のノードの検出が始まります。
このチュートリアルでは、Kubernetesクラスター内に現れた新しいCassandra Podを検出するカスタムのCassandraのseed providerをデプロイします。
目標 Cassandraのheadless Service を作成して検証する。 StatefulSet を使用してCassandra ringを作成する。StatefulSetを検証する。 StatefulSetを編集する。 StatefulSetとPod を削除する。 始める前に Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。
このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。
まだクラスターがない場合、minikube を使って作成するか、
以下のいずれかのKubernetesプレイグラウンドも使用できます:
このチュートリアルを完了するには、Pod 、Service 、StatefulSet の基本についてすでに知っている必要があります。
Minikubeのセットアップに関する追加の設定手順 注意: Minikube は、デフォルトでは1024MiBのメモリと1CPUに設定されます。
デフォルトのリソース設定で起動したMinikubeでは、このチュートリアルの実行中にリソース不足のエラーが発生してしまいます。このエラーを回避するためにはMinikubeを次の設定で起動してください:
minikube start --memory 5120 --cpus= 4
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を作成します。
備考: この例ではMinikubeのデフォルトのプロビジョナーを使用しています。
クラウドを使用している場合、StatefulSetを更新してください。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を検証する CassandraのStatefulSetを取得します
kubectl get statefulset cassandra
結果は次のようになるはずです:
NAME DESIRED CURRENT AGE
cassandra 3 0 13s
StatefulSetリソースがPodを順番にデプロイします。
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
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のサイズを変更できます。
次のコマンドを実行します。
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
レプリカ数を4に変更し、マニフェストを保存します。
これで、StatefulSetが4つのPodを実行するようにスケールされました。
CassandraのStatefulSetを取得して、変更を確かめます:
kubectl get statefulset cassandra
結果は次のようになるはずです:
NAME DESIRED CURRENT AGE
cassandra 4 4 36m
クリーンアップ StatefulSetを削除したりスケールダウンしても、StatefulSetに関係するボリュームは削除されません。
StatefulSetに関連するすべてのリソースを自動的に破棄するよりも、データの方がより貴重であるため、安全のためにこのような設定になっています。
警告: ストレージクラスやreclaimポリシーによっては、PersistentVolumeClaim を削除すると、関連するボリュームも削除される可能性があります。PersistentVolumeClaimの削除後にもデータにアクセスできるとは決して想定しないでください。次のコマンドを実行して(単一のコマンドにまとめています)、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
次のコマンドを実行して、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_TOKENS32CASSANDRA_RPC_ADDRESS0.0.0.0
次の項目 6.4 - 分散システムコーディネーターZooKeeperの実行 このチュートリアルでは、StatefulSet 、PodDisruptionBudgets 、Podアンチアフィニティ を使って、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 Service 、Service 、PodDisruptionBudget 、StatefulSet を含んでいます。
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-0、zk-1、zk-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.1、server.2、server.3のプロパティの、1、2、3は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のspecのvolumeClaimTemplatesフィールドは、各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を生成します。
StatefulSetのPersistentVolumeClaimsを取得するために、以下のコマンドを使います。
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のコンテナtemplateのvolumeMountsセクションは、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 StatefulSetはRollingUpdateアップデート戦略を使うように設定されています。
サーバーに割り当てられる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に指示します。
topologyKeyのkubernetes.io/hostnameは、ドメインが固有ノードであることを示しています。
異なるルール、ラベル、セレクターを使って、物理・ネットワーク・電源といった障害ドメイン全体に広がるアンサンブルにこのテクニックを広げることができます。
メンテナンス時の存続 このセクションでは、ノードをcordon(スケジュール不可化)およびdorain(解放)します。もし共有クラスターでこのチュートリアルを試しているのであれば、これがほかのテナントに有害な影響を及ぼさないことを確認してください。
前のセクションでは、計画外のノード障害に備えてどのようにPodをノード全体に広げるかを示しましたが、計画されたメンテナンスのため引き起こされる一時的なノード障害に対して計画する必要もあります。
クラスター内のノードを取得するために、以下のコマンドを使います。
このチュートリアルでは、4つのノードのあるクラスターを仮定しています。
クラスターが4つよりも多くある場合には、4つのノード以外全てをcordonするためにkubectl cordon を使ってください。
ノードを4つに制約することで、以下のメンテナンスシミュレーションにおいてzookeeper Podをスケジュールした時に、KubernetesがアフィニティとPodDisruptionBudget制約に遭遇することを保証します。
zk-pdbのPodDisruptionBudgetを取得するために、以下のコマンドを使います。
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を確認してみましょう:
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クラスターアドオン が必要です。
備考: もしServiceの環境変数が望ましくないなら(想定しているプログラムの環境変数と競合する可能性がある、処理する変数が多すぎる、DNSだけ使いたい、など)、
pod spec で
enableServiceLinksのフラグを
falseにすることで、このモードを無効化できます。
環境変数 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 README
やCoreDNSのインストール を参照して有効化してください。
テストするために、別の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
NAME TYPE DATA AGE
nginxsecret kubernetes.io/tls 2 1m
configmapも同様:
kubectl create configmap nginxconfigmap --from-file= default.conf
configmap/nginxconfigmap created
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 | tr -d '\n'
cat /d/tmp/nginx.key | base64 | tr -d '\n'
以下のような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-nginxのTypeをNodePortから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での送信元IPkube-proxyがiptablesモード (デフォルト)で実行されている場合、クラスター内部からClusterIPに送られたパケットに送信元のNATが行われることは決してありません。kube-proxyが実行されているノード上でhttp://localhost:10249/proxyModeにリクエストを送って、kube-proxyのモードを問い合わせてみましょう。
出力は次のようになります。
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での送信元IPType=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での送信元IPType=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にリクエストを送信します。
出力は次のようになります。
CLIENT VALUES:
client_address=10.240.0.5
...
しかし、Google Kubernetes EngineやGCE上で実行している場合、同じservice.spec.externalTrafficPolicyフィールドをLocalに設定すると、ロードバランサーからのトラフィックを受け付け可能なノードのリストから、Serviceエンドポイントが存在しない ノードが強制的に削除されます。この動作は、ヘルスチェックを意図的に失敗させることによって実現されています。
図で表すと次のようになります。
アノテーションを設定することで動作をテストできます。
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アドレスに問い合わせます。
出力は次のようになります。
CLIENT VALUES:
client_address=198.51.100.79
...
クロスプラットフォームのサポート Type=LoadBalancerを使用したServiceで送信元IPを保持する機能を提供しているのは一部のクラウドプロバイダーだけです。実行しているクラウドプロバイダーによっては、以下のように異なる方法でリクエストを満たす場合があります。
クライアントとのコネクションをプロキシが終端し、ノードやエンドポイントとの接続には新しいコネクションが開かれる。このような場合、送信元IPは常にクラウドのロードバランサーのものになり、クライアントのIPにはなりません。
クライアントからロードバランサーの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について調べます:
この出力は以下のようなものになります:
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が削除される時には、古いエンドポイントも削除されます。
次の項目