## 前半部分を省略 ##
## Rook/Cephを利用する場合、最下行に"storageClassName: rook-ceph-block"を指定するおと
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
storageClassName: rook-ceph-block
Statelessがその場限りの情報だけで動くアプリケーションであるのと対照的に、Statefulアプリケーションはショッピングカートなど画面遷移などの経過情報を保持するタイプのアプリケーションです。
StatefulSet Basics
k8s公式サイト StatefulSet Basics を別のウィンドウで開き見比べながら進めてください。
英語の原文は平易に書かれていますので、そちらを中心に進めて必要に応じてこのページに戻ってください。
ここでは公式チュートリアルObjectivesにも書かれているとおり、Deploymentではなくこれまで利用してこなかったStatefulSetを使用します。
Creating a StatefulSet (web.yaml)
このチュートリアルでは、YAMLファイルを自分で環境に合わせて反映させる必要があります。
公式ガイドの指示では、次のように指示されています。
-
web.yamlをダウンロードし保存してください
-
kubectl -n $(id -un) apply -f web.yamlで反映する (kubectlのコマンドラインはSCCP用に-n $(id -un)を加えています)
ゼミ室10の環境では、volumeClaimTemplates:の記述に問題があるためこのまま実行することはできません。
PV&PVCを参考に、ゼミ室の環境に合せて、設定を変更します。 今回は"ReadWriteOnce"が指定されているためBlockStorageを使用します。
編集済みのweb.yamlファイルは次のリンクからダウンロードすることもできます。
編集したweb.yamlファイルの反映
公式サイトの指示では、あらかじめ下記のコマンドを実行するよう指示されています。 (SCCP用に-n $(id -un)を追加)
$ kubectl -n $(id -un) get pods -w -l app=nginx
これにより、Podが追加されたり削除されるとその変化の様子が画面に出力されます。
別の端末ウィンドウを開いて、次のコマンドを実行し、変更したweb.yamlファイルの内容を読み込ませます。
$ kubectl -n $(id -un) apply -f web.yaml
service/nginx created
statefulset.apps/web configured
先ほど$ kubectl -n $(id -un) get pods -w -l app=nginxを実行しておいた端末では、新しくPodが作成される課程が表示されているはずです。
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 0/1 ContainerCreating 0 10s
web-0 1/1 Running 0 10s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 0/1 ContainerCreating 0 5s
web-1 1/1 Running 0 6s
次のような方法でも、設定の状況を確認することができます。
$ kubectl -n $(id -un) get all
NAME READY STATUS RESTARTS AGE
pod/web-0 1/1 Running 0 2m3s
pod/web-1 1/1 Running 0 113s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx ClusterIP None <none> 80/TCP 2m11s
NAME READY AGE
statefulset.apps/web 2/2 2m12s
## allではPVCの情報が表示されないため、個別にPVCの状況を確認する
$ kubectl -n $(id -un) get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-2a45eb6c-fc4b-4ac3-a28c-5ee7d3eeb328 1Gi RWO rook-ceph-block 26m
www-web-1 Bound pvc-5edf7ade-2b19-4f78-a717-4f259540e13c 1Gi RWO rook-ceph-block 26m
公式チュートリアルでは、StatefulSetについての説明が続きますが、kubectlコマンドを実行している部分は、kubectl -n $(id -un)で置き換えれば、問題なく実行できます。$ alias kubectl="kubectl -n $(id -un)"
のようなエイリアスを設定しても構いません。
StatefulSet Basics の解説
このチュートリアルは、Webブラウザからアクセスすることを目的とはしていません。
kubectl -n $(id -un) run コマンドなどを利用して、kubectlコマンドからホスト名を確認したり、スケール・アップする方法を解説することが目的だからです。
内部DNSによる名前解決
Using stable network identitiesのセクションでは、2つのPodのホスト名を確認しています。
$ for i in 0 1; do kubectl -n $(id -un) exec "web-$i" -- sh -c 'uname -n'; done
またdns-test Podを一時的に起動することで、kuberentes内部でホスト名がどのように設定されるのか確認できるように説明されています。
$ kubectl -n $(id -un) run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
## "/ #"はプロンプト
/ # nslookup web-0.nginx
nslookup web-0.nginx
を実行した結果は次のようになります。
Server: 169.254.25.10
Address 1: 169.254.25.10
Name: web-0.nginx
Address 1: 10.233.105.219 web-0.nginx.yasu-abe.svc.cluster.local
最後に表示されている 10.233.105.219 はIPアドレスを示しますが、Kubernetes内部ではIPアドレスは頻繁に変更される可能性があるため、ホスト名を使って互いに通信を行うようにします。
FQDN(Fully Qualified Domain Name, 完全修飾名)は、web-0.nginx.yasu-abe.svc.cluster.local です。
PodのFQDNは、<Pod>.<Service>.<Namespace>.svc.cluster.local という命名規則を持っています。
web-0 や web-0.nginx あるいは、web-0.nginx.yasu-abe でもアクセスが可能です。yasu-abeはnamespaceが入りますので実行するユーザーに応じて変化します。
nslookupコマンドを利用して、FQDNの部分的な名前でも認識される事を確認しましょう。
/ # nslookup web-0
/ # nslookup web-0.nginx.yasu-abe
/ # nslookup web-0.nginx.yasu-abe.svc
## エラーになる例
/ # nslookup web-0.nginx.yasu-abe.svc.cluster
エラーになるか否かは設定次第ですが、最後のcluster.localでは、これを分割する用途は想定できないため設定されていないのだと思われます。
名前解決についてはPod内部から /etc/resolv.conf がどのように設定されているか確認してください。
/ # cat /etc/resolv.conf
search yasu-abe.svc.cluster.local svc.cluster.local cluster.local
nameserver 169.254.25.10
options ndots:5
これによって自身のnamespace上にあるPodを優先し、他のnamespaceが指定されている場合にはそのIPアドレスを検索するように設定されています。
また nginx とService名を検索した場合にはDNSラウンドロビンによる名前解決が行われます。
/ # nslookup nginx
/ # nslookup nginx
## 1回目
Server: 169.254.25.10
Address 1: 169.254.25.10
Name: nginx.yasu-abe
Address 1: 10.233.105.219 web-0.nginx.yasu-abe.svc.cluster.local
Address 2: 10.233.115.79 web-1.nginx.yasu-abe.svc.cluster.local
## 2回目
Server: 169.254.25.10
Address 1: 169.254.25.10
Name: nginx.yasu-abe
Address 1: 10.233.115.79 web-1.nginx.yasu-abe.svc.cluster.local
Address 2: 10.233.105.219 web-0.nginx.yasu-abe.svc.cluster.local
タイミングによって綺麗にAddress 1とAddress 2が入れ替わらないかもしれませんが、何回か試すと異なる順番でIPアドレスに返答することが分かるはずです。
web-0とweb-1のWebサーバーにWebブラウザからアクセスする方法
このチュートリアルではKubernetesクラスターの外から、例えばWebブラウザからのアクセス等は想定していません。
それでもWebブラウザからアクセスしたい場合にはどのようにすれば良いでしょうか。
このチュートリアルでは、web.yamlファイルに設定されているService定義は、ClusterIPを割り当てないHeadlessと呼ばれる設定になっています。現状では、Webブラウザ等からWebサーバー(app:nginx)にアクセスする方法がありません。
このチュートリアルでは次のようにホスト名を表示するindex.htmlを作成し、各Pod内部からcurlコマンドを利用してその内容にアクセスしています。
$ for i in 0 1; do kubectl -n $(id -un) exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done
$ for i in 0 1; do kubectl -n $(id -un) exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1
Webブラウザからアクセスするためには追加のServiceオブジェクトが必要です。
次の内容のYAMLファイル(例えば"tutorial-stateful.svc-nginx-ext.yaml")を準備して反映($ kubectl -n $(id -un) apply -f <filename>)させます。
apiVersion: v1
kind: Service
metadata:
name: nginx-ext
labels:
app: nginx
spec:
ports:
- port: 80
name: web
selector:
app: nginx
type: LoadBalancer
このYAMLファイルは次のリンクからもダウンロードできます。
この反映が終ると、外部からWebサーバーに接続するためのIPアドレスが割り当てられます。
$ kubectl -n $(id -un) apply -f tutorial-stateful.svc-nginx-ext.yaml
$ kubectl -n $(id -un) get svc -l app=nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 2m56s
nginx-ext LoadBalancer 10.233.39.104 192.168.100.xxx 80:31723/TCP 3m18s
この場合は、svc/nginx-ext がtype: LoadBalancer を指定したことで192.168.100.xxxが割り当てられているので、Webブラウザから、このIPを指定しコンテンツを表示させることができます。
$ browse http://192.168.100.xxx/
Pod数の増減について
Scaling a StatefulSet のセクションでは、Pod数を増減させる kubectl scale
コマンドについて説明しています。
## Pod数を5に増加させるscaleコマンド
$ kubectl -n $(id -un) scale statefulsets/web --replicas=5
## Pod数が増加する様子を観察
$ kubectl -n $(id -un) get pod -w -l app=nginx
kubectl get pod -w
コマンドを終了する場合には、C-c を入力します。
またチュートリアルの中ではScaling Downの中で、kubectl patch
コマンドを実行する方法が紹介されています。
## kubectl scale statefulsets/web --replicas=3 と同じコマンド
$ kubectl -n $(id -un) patch statefulsets/web -p '{"spec":{"replicas":3}}'
またPodの数を増減させてもPVCは削除されません。
$ kubectl -n $(id -un) get pvc -l app=nginx
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-f99b6813-007f-4e9c-b8b3-50337237bef9 1Gi RWO rook-ceph-block 20h
www-web-1 Bound pvc-294097b3-cf32-4b3c-a7ce-790703a8ce2e 1Gi RWO rook-ceph-block 20h
www-web-2 Bound pvc-258824e7-3bd6-4d9e-9173-0bdf75c35b95 1Gi RWO rook-ceph-block 83m
www-web-3 Bound pvc-8a8811fa-07a8-49d4-b70d-4ebf5879f5e2 1Gi RWO rook-ceph-block 83m
www-web-4 Bound pvc-8c7ad97f-166f-4de4-b30d-29ad0458b10c 1Gi RWO rook-ceph-block 82m
前のセクションで実行した$ for i in 0 1; do kubectl -n $(id -un) exec "web-$i" — sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done
では最初の2つのPod(www-web-0, www-web-1)だけにホスト名が書かれたindex.htmlファイルが配置されています。
$ for i in 0 1 2; do kubectl -n $(id -un) exec -i -t "web-$i" -- curl http://localhost/; done
3つのPodが稼動している状況では次のような結果になります。
web-0
web-1
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.11.1</center>
</body>
</html>
DeploymentとStatefulSetの違い
今回利用した例ではweb.yamlにStatefulSetの定義が書かれているPodのname:や、volumeClaimTemplatesのname:ではwwwとしか指定されていませんが、replicas:に指定された数に応じて自動的に0..Nまでの番号が割り当てられています。
ポイントは、DeploymentSetではPodの名前はランダムで、何かしらのPVCが割り当てられますが、ホスト名とPVCとの対応はランダムになる点と比べて、StatefulSetではPodの名前とPVCの名前は1対1で対応することで、Podを削除しても、この関係は維持されます。
反面、StatefulSetは個別のPodが永続的なホスト名を持ち、Podと対応するPVCを割り当てられます。 Podを削除しても同じ名前のPodが再度作成され、以前と同じPVCが必ず割り当てられます。
データベースサーバーなどが、ホスト名を設定ファイルに書き込み、自身と他を区別する仕組みがある場合には、StatefulSetが有効です。
今回の例だけでは、この違いや使い分けを理解することは難しいかもしれませんが、Deploymentだけでは対応できない場面がある事は理解してください。
また、説明をよく読むと分かりますが、別ウィンドウを使い $ kubectl -n $(id -un) get pods -w -l app=nginxの出力を観察しながら操作し、Podが削除されても同じ名前で作成されたり、名前が連番になるように生成される様子を確認してください。
Example: Deploying WordPress and MySQL with Persistent Volumes
ここからは別のチュートリアルになります。
公式サイトのExample: Deploying WordPress and MySQL with Persistent Volumesについて説明します。
ここではkubectl -n $(id -un) apply -k .
のようなコマンドを利用したいため、空の作業用のディレクトリを準備し、cdしてから作業を開始してください。
## 適当な作業用ディレクトリを作成する例
$ TD=$(date +%Y%m%d.%H%M).stateful-mysql-example
$ echo ${TD}
$ mkdir ${TD}
$ cd ${TD}
kustomization.yamlという名前ファイルを作成するために、catコマンドと、ヒアドキュメント(Here-Documents)を利用していますが、emacsなどでも良いので、次のような内容のファイルを、kustomization.yamlという名前で作成してください。
secretGenerator:
- name: mysql-pass
literals:
- password=secret
resources:
- mysql-deployment.yaml
- wordpress-deployment.yaml
パスワードを指定するsecretの部分は、変更することが可能です。
次にkustomization.yamlファイルを配置した場所にmysql-deployment.yamlとwordpress-deployment.yamlの2つのファイルを準備します。
$ curl -LO https://kubernetes.io/examples/application/wordpress/mysql-deployment.yaml
$ curl -LO https://kubernetes.io/examples/application/wordpress/wordpress-deployment.yaml
ここまで終えると、次のように3つのファイルだけを含むディレクトリで作業しているはずです。
$ ls -l
total 12
-rw-r--r-- 1 yasu-abe prof 68 Nov 29 07:58 kustomization.yaml
-rw-r--r-- 1 yasu-abe prof 1442 Nov 29 07:58 mysql-deployment.yaml
-rw-r--r-- 1 yasu-abe prof 1341 Nov 29 07:59 wordpress-deployment.yaml
まだ、このままでは、Apply and Verifyに進めないので、YAMLファイルを修正します。
YAMLファイルの修正
先ほどと同様にPersistentVolumeClaimはそのままでは動かないためRookに対応した修正を行ないます。
--- mysql-deployment.yaml 2019-10-09 10:22:39.434291231 +0000
+++ mysql-deployment-rook.yaml 2019-10-09 10:22:27.022010709 +0000
@@ -24,6 +24,7 @@
resources:
requests:
storage: 20Gi
+ storageClassName: rook-ceph-block
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
wordpress-deployment.yamlファイルにも同様の修正を行います。
--- wordpress-deployment.yaml 2019-10-09 10:26:32.099526478 +0000
+++ wordpress-deployment-rook.yaml 2019-10-09 10:27:01.144177437 +0000
@@ -24,6 +24,7 @@
resources:
requests:
storage: 20Gi
+ storageClassName: rook-ceph-block
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
修正済みのファイルは次のリンクからダウンロードできます。
3つのファイルがそろったら、k8s.io公式ガイド Apply and Verify に進みます。
$ kubectl -n $(id -un) apply -k ./
secret/mysql-pass-kd7m542fbt created
service/wordpress-mysql created
service/wordpress created
deployment.apps/wordpress-mysql created
deployment.apps/wordpress created
persistentvolumeclaim/mysql-pv-claim created
persistentvolumeclaim/wp-pv-claim created
これが成功すると、WebブラウザからWordpressに接続することができるようになります。 $ kubectl -n $(id -un) get svcの出力結果から、IPアドレスを確認します。
$ kubectl -n $(id -un) get svc wordpress
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/wordpress LoadBalancer 10.233.62.214 192.168.100.xxx 80:32336/TCP 9m59s
このように表示されたEXTERNAL-IPのアドレス(この場合は、192.168.100.xxx)にWebブラウザからアクセスしてください。
$ browse http://192.168.100.xxx
公式チュートリアルの中でminikubeコマンドを実行している部分は、このSCCPの環境では実行できないので実行しないでください。
またError establishing a database connectionのようなメッセージがWebブラウザに表示される場合があります。
この場合にはPodを再起動し、再度アクセスしてください。
$ kubectl -n $(id -un) delete pod -l app=wordpress,tier=frontend
Podが起動してからWebブラウザでアクセスし直してください。
Deploying WordPress and MySQL with Persistent Volumes の解説
公式のチュートリアルではあまり説明は多くありません。
まずこの例ではWordpressというWebアプリケーションとデータを保持するMySQLデータベースサーバーによって構成されています。
$ kubectl -n $(id -un) get pod,svc -l app=wordpress
NAME READY STATUS RESTARTS AGE
pod/wordpress-d6f9dbd9b-nwx7w 1/1 Running 0 54s
pod/wordpress-mysql-9c6c5b8c9-rqwrw 1/1 Running 0 54s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/wordpress LoadBalancer 10.233.46.124 192.168.100.172 80:30765/TCP 20h
service/wordpress-mysql ClusterIP None <none> 3306/TCP 20h
WordpressからMySQLへの接続はホスト名wordpress-mysqlを通じて行われます。
これは wordpress-deployment.yaml の中で WORDPRESS_DB_HOST として定義されています。
また接続に利用するパスワード同じYAMLファイルで次のようにSecretオブジェクトを参照するよう設定されています。
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
この書き方は実際には不十分な指定方法です。
deployment/wordpressオブジェクトの定義を確認しましょう。
## deploy/wordpress定義の全体を表示する方法
$ kubectl -n $(id -un) get deploy/wordpress -o yaml | jq .
## deploy/wordpress定義の一部を表示する方法
$ kubectl -n $(id -un) get deploy/wordpress -o jsonpath='{..spec.containers[0].env[?(@.name == "WORDPRESS_DB_PASSWORD")]}' | jq .
これは次のように一部を表示させた場合の出力です。
{
"name": "WORDPRESS_DB_PASSWORD",
"valueFrom": {
"secretKeyRef": {
"key": "password",
"name": "mysql-pass-m4d885dchh"
}
}
}
この中で定義ファイルでは指定されていない -m4d885dchh が追加されています。
これは `kubectl apply
コマンドに指定した -k
フラグによって自動的に行われています。
別の方法としては手動でSecretオブジェクトを管理することもできますし、これまで指定してきた過去のパスワードの定義を保持したいなどの理由があればkustomization.yamlを利用することもできます。