仮想化通信

日本仮想化技術株式会社の公式エンジニアブログ

Kubernetesと永続ストレージの使い方

Kubernetesは現在、バックエンドエンジンとしてDockerを利用しています。 Podの作成はYAML形式でエディターで書いて、kubectl createコマンドで簡単に作成できます。

作成したPodはkubectl execコマンドを使って、bashシェルなどにログインすれば仮想マシンにアクセスする様に環境にログインして操作できます。

ただ、何も特別な設定をしていない状態でPodを作成すると、非永続のストレージしか利用できません。従ってPod内のコンテナーで再起動が走ってしまうとデータが消去されてしまいます。

たとえば次のようなYAMLファイルでPodを作成したとします。

kind: Pod
apiVersion: v1
metadata:
  name: mywebpod
spec:
  containers:
    - name: myfrontend
      image: nginx:latest
      ports:
      - containerPort: 80
        hostPort: 50080

NGINXのDockerイメージでは、Web rootは/usr/share/nginx/htmlがデフォルト設定になっています。確認のため、ここに任意のindex.htmlを作成してみましょう。

$ kubectl exec -it mywebpod bash
root@mywebpod:/# ls /usr/share/nginx/html
50x.html  index.html
(Web rootを確認)

root@mywebpod:/# echo "<html><h1>hello world</h1></html>" > /usr/share/nginx/html/index.html
(適当な内容で上書き)

root@mywebpod:/# cat /usr/share/nginx/html/index.html
<html><h1>hello world</h1></html>
(ファイルを確認)

ではこのコンテナーのプロセスを再起動してみましょう。一旦シェルから抜けて、docker stopdocker startコマンドでコンテナーを再起動してみます。一度プロセスが終了されたため、index.htmlはデフォルトのページに戻ってしまいます。

~$ docker ps |grep mywebpod
b1fb6d922358        nginx@sha256:0fb320e2a1b1620b4905facb3447e3d84ad36da0b2c8aa8fe3a5a81d1187b884                                 "nginx -g 'daemon ..."   9 minutes ago       Up 9 minutes                            k8s_myfrontend_mywebpod_default_f827a307-6235-11e8-babe-984be167d804_0
150426c756a4        k8s.gcr.io/pause-amd64:3.1                                                                                    "/pause"                 9 minutes ago       Up 9 minutes                            k8s_POD_mywebpod_default_f827a307-6235-11e8-babe-984be167d804_0

~$ docker stop b1fb6d922358
b1fb6d922358
~$ docker start b1fb6d922358
b1fb6d922358
(コンテナーを再起動)

$ kubectl exec -it mywebpod bash
(Podのシェルにログイン)

root@mywebpod:/# cat /usr/share/nginx/html/index.html
(初期ページに戻ってしまった)

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

と、いうわけでデータを永続して持っておきたい場合、永続ストレージが必要であるという事がわかります。

Kubernetesと永続ストレージ

本当はこの辺りを書きたかったのですが、Kubernetes公式のドキュメントやRed Hat社のドキュメントが纏まっていたので、リンクだけ貼り付けておきます。

Kubernetesと永続ストレージについては公式のドキュメントを参照して欲しいのですが、色々ある用語のうち、特に重要なキーワードは次の二つです。

Persistent Volumes

ざっくりいうと、永続ストレージを定義するためのものです。

PersistentVolumeClaims

ざっくりいうと、Podに紐づけるストレージを要求するためのものです。

Kubernetesで利用できるストレージはStorage Classesに纏まっています。今回はその中からNFS、Redis、Cephの使い方についてまとめます。

NFS共有ボリュームをPodにマウントする

NFS共有ボリュームをPodにマウントするにはNFSサービスと共用ボリュームがあらかじめ必要です。 まず、次のようなYAMLを記述して、PVとPVCについて定義します。

この中で重要なのは「persistentVolumeReclaimPolicy」です。この設定はデフォルトはRecycleが設定されており、簡易的なクリーンアップが施された上で新たに割り当てられます。データを保持したい場合は「Retain」に設定する必要があります。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv1
  labels:
    volume: my-volume1
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce                                       #ストレージのアクセスモード
  persistentVolumeReclaimPolicy: Retain    #保持オプション
  storageClassName: slow
  mountOptions:              #NFSマウントオプション
    - hard
    - nfsvers=3
  nfs:
    path: "/dist-volume"   #NFS共有ボリュームパス
    server: 172.17.14.100      #NFSサーバーのIPアドレス
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 2Gi
  storageClassName: slow
  selector:
    matchLabels:
      volume: my-volume1

pv1にmy-volume1と言う名前のラベルを定義して、PVCでmy-volume1のボリュームを要求しています。 このPVCにはpvc1という名前を設定しています。

あとは、Podを作成するときにpersistentVolumeClaimを指定することで、定義ずみのPVから要求したボリュームをPodに割り当てる事ができます。以下、PodのYAMLの例です。

本例はmynfspodと言う名前のPodにpvc1を割り当てる例です。

apiVersion: v1
kind: Pod
metadata:
  name: mynfspod
spec:
  containers:
    - name: myfrontend
      image: nginx:latest
      ports:
      - containerPort: 80
        hostPort: 51080

      volumeMounts:
      - mountPath: "/usr/share/nginx/html"
        name: mynfspod
  volumes:
    - name: mynfspod
      persistentVolumeClaim:
        claimName: pvc1

次のように実行すると、PodにNFS共有ボリュームがマウントされている事が確認できます。

% kubectl exec -it mynfspod -- df -hT
Filesystem               Type   Size  Used Avail Use% Mounted on
none                     aufs   275G   12G  250G   5% /
tmpfs                    tmpfs   24G     0   24G   0% /dev
tmpfs                    tmpfs   24G     0   24G   0% /sys/fs/cgroup
/dev/sda1                ext4   275G   12G  250G   5% /etc/hosts
shm                      tmpfs   64M     0   64M   0% /dev/shm
172.17.14.100:/dist-volume nfs     27G  1.2G   26G   5% /usr/share/nginx/html
tmpfs                    tmpfs   24G   12K   24G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs                    tmpfs   24G     0   24G   0% /sys/firmware

Redisを永続ボリューム用途でPodにマウントする

次にRedisをPodで使う方法を説明します。RedisはいわゆるNoSQLデータベースのことで、オンメモリーのストレージとしても使うことができます。DockerやKubernetesでRedisを使うには、公式のDockerイメージを使ってコンテナーを起動するだけで利用できます。

このDockerイメージの実態は、redis-serverデーモンによって特定のディレクトリーのデータを永続化しているだけです。各Podの中でredis-serverを動作させます。特定の外部サーバー(NFS,iSCSIといった)が必要ないために手軽に利用できる反面、オンメモリストレージなので大きいデータのやり取りは不向きかもしれません。というより要注意です。

以下は公式のRedisイメージを使ってPodを起動するYAMLの例です。

apiVersion: v1
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: redis
    volumeMounts:
    - name: redis-storage
      mountPath: /data/redis
  volumes:
  - name: redis-storage
    emptyDir: {}

上記の内容のYAMLでPodを起動すると、ディレクトリー/data/redisがマウントされるのが確認できます。

% kubectl exec -it redis bash
root@redis:/data# ls -F
redis/
(起動したPodに/data/redisがある事が確認できる)

root@redis:/data# echo "TEST File" > /data/redis/test.data
(適当に書きこむ)

%  docker stop 5c61f8fdb56f
5c61f8fdb56f
%  docker start 5c61f8fdb56f
(workerノードでコンテナーのプロセスを終了し、再開する)

% kubectl exec -it redis bash
(シェルに再ログインして)

root@redis:/data# cat /data/redis/test.data
TEST File
(書き込んだデータはまだ残っていた)

Redisの公式イメージはDebian版とAlpine Linux版が存在します。試しにUbuntuでRedisを使うためのイメージを作ってみましたので、次のようなYAMLファイルを書けばUbuntu 16.04でRedisを使うことができます。イメージをベースとして設定を変えて使うこともできます。役立ちそうでしたらご利用ください。

apiVersion: v1
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: ytooyama/ubuntu-redis
    tty: true
    volumeMounts:
    - name: redis-storage
      mountPath: /var/lib/redis
  volumes:
  - name: redis-storage
    emptyDir: {}

CephをPodにマウントする

CephをKubernetesで使うにはRookを使うと簡単です。 まず、ソースをダウンロードします。2018年6月14日時点は0.7.1が最新です。

% wget https://github.com/rook/rook/archive/v0.7.1.zip
% unzip v0.7.1.zip
% cd ~/rook-0.7.1/cluster/examples/kubernetes

次に三つのコマンドを実行すると利用することができます。

% kubectl create -f rook-operator.yaml
% kubectl create -f rook-cluster.yaml
% kubectl create -f rook-storageclass.yaml

デフォルトではrook-cluster.yamlで定義している「dataDirHostPath」のパスが共有領域として使われます。

ポイントとして、ホスト上の物理デバイスをKubernetesからアクセスするため、Kubernetesの各ノード(masterとworker)が特権モードで動作している必要があるようです。Jujuの場合はこちらの設定をtrueにする必要があります。

f:id:virtualtech:20180528195759p:plain:w360

rookはrookとrook-systemと言う名前のネームスペースを作り、その中で動作します。デプロイ中のログやPodの状況を確認するには、Kubernetesのダッシュボードを表示して確認する他、コマンドで確認することもできます。

次のように全てのネームスペース上のPodを確認して、kubectl logsコマンドでそれぞれ個別のログを確認できます。

% kubectl get pods --all-namespaces
% kubectl logs rook-operator-5c89ff9496-kjq8m --namespace=rook-system

準備ができたら、次のようなYAMLファイルを用意して、Podを作成してみましょう。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
  labels:
    app: myclaim
spec:
  storageClassName: rook-block
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi
---
kind: Pod
apiVersion: v1
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx:latest
      volumeMounts:
      - mountPath: "/usr/share/nginx/html/"
        name: mypod-nginxroot
  volumes:
    - name: mypod-nginxroot
      persistentVolumeClaim:
        claimName: myclaim

うまくいくと、次の例と同じようにPVとPVCを表示できるはずです。

% kubectl create -f test-pvcpod.yml
persistentvolumeclaim "myclaim" created
pod "mypod" created
(Podを作成)

% kubectl get pv,pvc
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM             STORAGECLASS   REASON    AGE
persistentvolume/pvc-f64ee657-6267-11e8-85de-984be167d804   2Gi        RWO            Delete           Bound     default/myclaim   rook-block               1m

NAME                            STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myclaim   Bound     pvc-f64ee657-6267-11e8-85de-984be167d804   2Gi        RWO            rook-block     1m
(PVとPVCを表示)

$ kubectl get -f test-pvcpod.yml
NAME                            STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myclaim   Bound     pvc-f64ee657-6267-11e8-85de-984be167d804   2Gi        RWO            rook-block     24s
(Podの状態を確認)

NAME        READY     STATUS    RESTARTS   AGE
pod/mypod   1/1       Running   0          24s

% kubectl exec -it mypod -- df -h
Filesystem      Size  Used Avail Use% Mounted on
none            275G   14G  248G   6% /
tmpfs            24G     0   24G   0% /dev
tmpfs            24G     0   24G   0% /sys/fs/cgroup
/dev/sda1       275G   14G  248G   6% /etc/hosts
shm              64M     0   64M   0% /dev/shm
/dev/rbd0       2.0G  3.0M  1.8G   1% /usr/share/nginx/html
tmpfs            24G   12K   24G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs            24G     0   24G   0% /sys/firmware
(マウントされていることを確認)

と言うわけで、今回はKubernetesの永続ストレージの使い方まとめでした。