Kubernetesハンズオン - 6. 永続ボリュームの利用
ここまで扱ってきたhello-pksアプリケーションはステートレスなアプリケーションであり、
データの永続化は必要ありませんでした。
Kubernetes自体はステートレスアプリケーションだけでなく、データベースのようなステートフルなワークロードにも対応しています。
データの永続化のためには永続ボリュームを使用します。
実はこれまでにVolume自体は扱ってきました。
ConfigMapやSecretをファイルシステムにマウントした際にVolumeの定義をしましたし、
Init Containersでの処理内容をコンテナに引き継ぐためにPod内でのみ利用可能なemptyDirというVolumeを使いました。
ここではPodが破棄されても存在し続けるVolumeについて扱います。
Podに実際にアタッチするストレージである永続ボリュームはPersistentVolumeというリソースで表現されます。
DeveloperはPodにアタッチする際にPersistentVolumeClaimというリソースを作成し、
Kubernetesに対して、必要なspecのPersistentVolumeを要求します。その結果、必要なspecに合ったPersistentVolumeがPodにアタッチされます。
PersistentVolumeの作成は静的に行う方法と動的に行う方法があります。
静的な場合は(通常は)Kubernetes管理者がPersistentVolumeを予め作成し、DeveloperがPersistentVolumeClaimで仕様要求する形になります。

動的な場合は、PersistentVolumeClaimで仕様要求に対して、オンデマンドでPersistentVolumeが作成されます。
この場合はProvisionerがデプロイされている必要がありますが、これはKubernetesのインストレーション方法に依って異なります。
今回は動的に生成する方法のみ説明します。
目次
PersistentVolumeの動的作成
PersistentVolumeのProvisionerが登録されている場合は、PersistentVolumeを動的に作成できます。 この場合は事前にPersistentVolumeのリソースを用意する必要はなく、PersistentVolumeClaimだけ作成すれば良いです。
Provisionerが登録されているかどうかはkubectl get storageclassでStorageClassリソースが登録されているかどうかで確認できます。
kubectl get storageclass
出力例
$ kubectl get storageclass
NAME PROVISIONER AGE
standard (default) kubernetes.io/aws-ebs 252d
StorageClassが登録されていない場合は、こちらを参照して作成してください。
mysql-pvc.ymlを作成して、次の内容を記述してください。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
kubectl applyでPersistentVolumeClaimをデプロイします。
kubectl apply -f mysql-pvc.yml
出力結果
persistentvolumeclaim/mysql-pvc created
kubectl get pv,pvcでPersistentVolumeとPersistentVolumeClaimの一覧を確認してください。
kubectl get pv,pvc
出力結果例
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-56d358ab-de0f-11e9-812f-06c9c16b7d62 1Gi RWO Delete Bound default/mysql-pvc standard 20s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/mysql-pvc Bound pvc-56d358ab-de0f-11e9-812f-06c9c16b7d62 1Gi RWO standard 23s
PersistentVolumeClaimに対応したPersistentVolumeが自動で作成されていることがわかります。
動的なPersistentVolume作成は便利なので、基本的にはこちらを使えば良いですが、Volumeに関する詳細な設定が必要な場合はPersistentVolumeを静的に作成してください。
PersistentVolumeClaimの利用
PersistentVolumeをコンテナで使用します。ここではMySQLデータベースのコンテナを起動し、PersistentVolumeをアタッチします。
mysql.ymlを作成して、次の内容を記述してください。
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: remove-lost-found
image: busybox
command:
- sh
- -c
- |
rm -fr /var/lib/mysql/lost+found
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
containers:
- image: mysql:5.7
name: mysql
env:
- name: MYSQL_DATABASE
value: demo
- name: MYSQL_ROOT_PASSWORD
value: password
- name: MYSQL_USER
value: pksuser
- name: MYSQL_PASSWORD
value: pkspassword
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pvc
spec.template.spec.volumes[].persistentVolumeClaim.claimNameに作成済みのPersistentVolumeClaim名を指定してください。
spec.template.spec.initContainersの部分は本質的ではありませんが、VsphereVolumeなどPersistentVolumeの種類によってはマウントしたディスク上にlost+foundディレクトリが作成され、これが原因でMySQLが起動しないことがあります。これを避けるために、initContainersの処理でlost+foundディレクトリを削除しています。
このmanifestをデプロイしてください。
kubectl apply -f mysql.yml
出力結果
deployment.apps/mysql created
作成直後はMySQLのPodのSTATUSがInitになっています。
kubectl get pod -l app=mysql
出力結果
NAME READY STATUS RESTARTS AGE
mysql-6788b57948-84pj2 0/1 Init:0/1 0 10s
しばらくした後、再度実行して、MySQLのPodのSTATUSがRunningになっていることを確認してください。
出力結果
NAME READY STATUS RESTARTS AGE
mysql-6788b57948-84pj2 1/1 Running 0 44s
kubectl exec <Pod名>コマンドを使ってPod上で任意のコマンドを実行できます。
これを用いてmysql-xxxxxxx-xxxxxPod上でmysqlコマンドを実行し、localhostのMySQLにアクセスしましょう。
次のコマンドを実行してください。(Pod名は変更してください)
kubectl exec mysql-6788b57948-84pj2 -ti -- mysql -u pksuser -ppkspassword demo
または次のようにPod名を取得して実行できます。
kubectl exec $(kubectl get pod -l app=mysql -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}') -ti -- mysql -u pksuser -ppkspassword demo
出力結果
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.27 MySQL Community Server (GPL)
Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
続けて次のコマンドを実行し、テーブルの作成とデータの投入を行ってください。
CREATE TABLE hello (
id INT AUTO_INCREMENT PRIMARY KEY,
text VARCHAR(255)
);
INSERT INTO hello(text) VALUES('Hello World!');
出力結果
mysql> CREATE TABLE hello (
-> id INT AUTO_INCREMENT PRIMARY KEY,
-> text VARCHAR(255)
-> );
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO hello(text) VALUES('Hello World!');
Query OK, 1 row affected (0.00 sec)
quitを入力し、mysqlコマンドを終了してください。
出力結果
mysql> quit
Bye
作成したデータを参照するために、次のコマンドを実行してください。(Pod名は変更してください)
kubectl exec mysql-6788b57948-84pj2 -- mysql -u pksuser -ppkspassword demo -e 'SELECT * FROM hello'
または次のようにPod名を取得して実行できます。
kubectl exec $(kubectl get pod -l app=mysql -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}') -- mysql -u pksuser -ppkspassword demo -e 'SELECT * FROM hello'
出力結果
id text
1 Hello World!
mysql: [Warning] Using a password on the command line interface can be insecure.
ここで一度Podを削除しましょう。
kubectl delete pod -l app=mysql
出力結果
pod "mysql-6788b57948-84pj2" deleted
しばらくするとReplicaSetにより、Podが復旧します。Pod一覧を確認してください。
kubectl get pod -l app=mysql
出力結果
NAME READY STATUS RESTARTS AGE
mysql-6788b57948-m5zbl 1/1 Running 0 19s
新しく作成されたPod上でmysqlコマンドを実行して、先ほど登録したデータを取得できることを確認してください。
kubectl exec mysql-6788b57948-m5zbl -- mysql -ppassword demo -e 'SELECT * FROM hello'
または次のようにPod名を取得して実行できます。
kubectl exec $(kubectl get pod -l app=mysql -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}') -- mysql -u pksuser -ppkspassword demo -e 'SELECT * FROM hello'
出力結果
mysql: [Warning] Using a password on the command line interface can be insecure.
id text
1 Hello World!
Podが削除されてもPersistentVolumeに格納したデータは失われないことが確認できました。
MySQL用manifestの更新
ここからはこれまで説明した内容を使って、manifestを更新します。
PersistentVolumeの学習とは直接関係がないので、時間がない場合はスキップしてください。
サービスとして公開
他のコンテナからMySQLにアクセスできるようにServiceを作成し、MySQLのコンテナをクラスタ内に公開します。
mysql-service.ymlを作成して、次の内容を記述してください。
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
ports:
- port: 3306
selector:
app: mysql
clusterIP: None
ClusterIP: Noneを指定することにより、内部DNSでサービス名を解決するとPodのIPが直接返ります(Headlessモード)。
このmanifestをデプロイしてください。
kubectl apply -f mysql-service.yml
出力結果
service/mysql created
同一Namespaceからはホスト名をService名、すなわちmysqlでMySQLにアクセス可能です。
kubectl run -it --rm --image=mysql:5.7 --restart=Never mysql-client -- mysql -h mysql -u pksuser -ppkspassword demo -e 'SELECT * FROM hello'
出力結果
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+--------------+
| id | text |
+----+--------------+
| 1 | Hello World! |
+----+--------------+
pod "mysql-client" deleted
Namespaceが異なる場合、Service名.Namespace、ここではdefault Namespaceを使用しているため、mysql.defaultでアクセス可能です。あるいは完全修飾名でmysql.default.svc.cluster.localとしても良いです。
この場合は内部DNS(KubeDNS)によって名前解決されます。
kubectl run -it --rm --image=mysql:5.7 --restart=Never mysql-client -- mysql -h mysql.default.svc.cluster.local -u pksuser -ppkspassword demo -e 'SELECT * FROM hello'
パスワードをSecretに保存
パスワード(MYSQL_ROOT_PASSWORD)をenv.valueにハードコードすることは運用上はあり得ません。
パスワードはSecretに保存し、env.valueFromで参照するようにしましょう。
mysql-secret.ymlを作成して、次の内容を記述してください。
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
stringData:
MYSQL_ROOT_PASSWORD: password
MYSQL_USER: pksuser
MYSQL_PASSWORD: pkspassword
このmanifestをデプロイしてください。
kubectl apply -f mysql-secret.yml
出力結果
secret/mysql-secret created
mysql.ymlを次の内容に更新してください。
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
# ...
template:
# ...
spec:
initContainers:
# ...
containers:
- image: mysql:5.7
name: mysql
env:
- name: MYSQL_DATABASE
value: demo
# ここから変更
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_ROOT_PASSWORD
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_USER
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_PASSWORD
# ここまで
# ...
更新内容を適用してください。
kubectl apply -f mysql.yml
出力結果
deployment.apps/mysql configured
パスワードを変更した場合は、環境変数
MYSQL_ROOT_PASSWORDの変更だけでは反映されません。mysqlコマンドで直接変更を反映してください。あるいはkubectl delete -f mysql-pvc.ymlでPersistent Volume ClaimとPersistent Volume削除して、上記の手順をやり直してください。
設定ファイルをConfigMapに保存
mysql Dockerイメージのデフォルトcharacter_set_serverはlatin1です。次のコマンドで確認できます。
kubectl run -it --rm --image=mysql:5.7 --restart=Never mysql-client -- mysql -h mysql -u pksuser -ppkspassword -e 'show variables like "chara%";'
出力結果
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
pod "mysql-client" deleted
日本語を登録できるようにcharsetをutf8mb4に変更します。/etc/mysql/conf.d/charset.cnfに次のファイルを配置すれば良いです。
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
[client]
default-character-set=utf8mb4
このファイルをConfigMapに格納し、コンテナ上にマウントしましょう。
mysql-config.ymlを作成して、次の内容を記述してください。
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
charset.cnf: |-
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
[client]
default-character-set=utf8mb4
このmanifestをデプロイしてください。
kubectl apply -f mysql-config.yml
出力結果
configmap/mysql-config created
このConfigMapを/etc/mysql/conf.d/charset.cnfにマウントするように、
mysql.ymlを次の内容に更新してください。
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
# ...
template:
# ...
spec:
initContainers:
# ...
containers:
- image: mysql:5.7
name: mysql
# ...
volumeMounts:
# ...
# ここから追加
- name: mysql-config
mountPath: /etc/mysql/conf.d
readOnly: true
# ここまで
volumes:
# ...
# ここから追加
- name: mysql-config
configMap:
name: mysql-config
items:
- key: charset.cnf
path: charset.cnf
# ここまで
更新内容を適用してください。
kubectl apply -f mysql.yml
出力結果
deployment.apps/mysql configured
次のコマンドで変更が反映されたことを確認してください。
kubectl run -it --rm --image=mysql:5.7 --restart=Never mysql-client -- mysql -h mysql -u pksuser -ppkspassword -e 'show variables like "chara%";'
出力結果
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
pod "mysql-client" deleted
次のコマンドを実行してください。
kubectl run -it --rm --image=mysql:5.7 --restart=Never mysql-client -- mysql -h mysql -u pksuser -ppkspassword demo -e 'INSERT INTO hello(text) VALUES("🍺"); SELECT * FROM hello'
出力結果
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+--------------+
| id | text |
+----+--------------+
| 1 | Hello World! |
| 2 | 🍺 |
+----+--------------+
pod "mysql-client" deleted
ここまでのmysql.ymlを再掲します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: remove-lost-found
image: busybox
command:
- sh
- -c
- |
rm -fr /var/lib/mysql/lost+found
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
containers:
- image: mysql:5.7
name: mysql
env:
- name: MYSQL_DATABASE
value: demo
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_ROOT_PASSWORD
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_USER
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_PASSWORD
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
- name: mysql-config
mountPath: /etc/mysql/conf.d
readOnly: true
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pvc
- name: mysql-config
configMap:
name: mysql-config
items:
- key: charset.cnf
path: charset.cnf
マニフェストは---で区切って一つのYAMLにまとめることができます。Secret以外は一つにまとめた方が管理しやすい場合があります。
mysql-secret.yml以外を次のように1つのYAMLファイルににまとめられます。mysql.ymlを作成して、次の内容を記述してください。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
ports:
- port: 3306
selector:
app: mysql
clusterIP: None
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
charset.cnf: |-
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
[client]
default-character-set=utf8mb4
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: remove-lost-found
image: busybox
command:
- sh
- -c
- |
rm -fr /var/lib/mysql/lost+found
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
containers:
- image: mysql:5.7
name: mysql
env:
- name: MYSQL_DATABASE
value: demo
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_ROOT_PASSWORD
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_USER
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_PASSWORD
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
- name: mysql-config
mountPath: /etc/mysql/conf.d
readOnly: true
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pvc
- name: mysql-config
configMap:
name: mysql-config
items:
- key: charset.cnf
path: charset.cnf
kubectl apply -f mysql.ymlを実行しても変更はないので、unchangedが出力されます。
persistentvolumeclaim/mysql-pvc unchanged
service/mysql unchanged
configmap/mysql-config unchanged
deployment.apps/mysql unchanged
アプリケーションからMySQLにアクセス
作成したMySQLに対してhello-pksアプリケーションからアクセスします。
まず、helloテーブルはDropしてください。
kubectl run -it --rm --image=mysql:5.7 --restart=Never mysql-client -- mysql -h mysql -u pksuser -ppkspassword demo -e 'DROP TABLE hello;'
データベースアクセスに対応したバージョン(0.0.5)のhello-pksアプリケーションをデプロイします。
hello-pks.ymlに次に内容を記述してください。
kind: Service
apiVersion: v1
metadata:
name: hello-pks
spec:
type: LoadBalancer
selector:
app: hello-pks
ports:
- protocol: TCP
port: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-pks
spec:
replicas: 2
selector:
matchLabels:
app: hello-pks
template:
metadata:
labels:
app: hello-pks
spec:
containers:
- image: making/hello-pks:0.0.5 # <--バージョン変更
name: hello-pks
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: mysql
## ここから重要
- name: SPRING_DATASOURCE_URL
value: jdbc:mysql://mysql:3306/demo?useSSL=false
- name: SPRING_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_USER
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_PASSWORD
## ここまで重要
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
timeoutSeconds: 3
failureThreshold: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/info
port: 8080
initialDelaySeconds: 20
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 1
SPRING_PROFILES_ACTIVE=mysqlはMySQL用の設定を定義したapplication-mysql.propertiesを読み込ませるための設定であり、
アプリケーション固有の設定です。この設定を行わないとインメモリデータベースであるH2が使用され、アプリケーションが終了するとデータも揮発するように作られています。
KubernetesにデプロイしたMySQLにSpring Bootアプリケーションがアクセスする上で重要なのは、SPRING_DATASOURCE_URL、SPRING_DATASOURCE_USERNAME、SPRING_DATASOURCE_PASSWORDのオーバーライドです。
MySQLはURLは内部DNS(KubeDNS)を利用します。ユーザー名とパスワードはSecretから取得します。ここではMySQLのPodに設定したSecretと同じものを使用しました。
次のコマンドを実行してアプリケーションをデプロイしてください。
kubectl apply -f hello-pks.yml
出力結果
service/hello-pks created
deployment.apps/hello-pks created
kubectl get svc hello-pks
出力結果例
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-pks LoadBalancer 10.100.200.64 a6223ec55de1211e9812f06c9c16b7d6-177704515.ap-northeast-1.elb.amazonaws.com 8080:31222/TCP 9s
EXTERNAL-IP列のIPまたはHost名を確認し、http://<EXTERNAL-IP>:8080にアクセスしてください。

"TWEETS"ボタンをクリックしてください。

NameとTweetを埋めて"SUBMIT"ボタンをクリックしてください。MySQLにTweetが保存されます。

リソースの削除
次のコマンドで、作成したリソースをまとめて削除してください。
kubectl delete -f mysql.yml -f mysql-secret.yml -f hello-pks.yml
出力結果
persistentvolumeclaim "mysql-pvc" deleted
service "mysql" deleted
configmap "mysql-config" deleted
deployment.apps "mysql" deleted
secret "mysql-secret" deleted
service "hello-pks" deleted
deployment.apps "hello-pks" deleted