第6章 Kubernetes におけるストレージのプロビジョニング

6.1. 概要

このセクションでは、Kubernetes でのストレージのプロビジョニング方法について説明します。

本書の演習を開始する前に、Kubernetes をセットアップしておく必要があります。

Kubernetes がセットアップされていない場合は、「Kubernetes によるコンテナーのオーケストレーション」の指示に従ってください。

6.2. Kubernetes 永続ボリューム

このセクションでは、Kubernetes の永続ボリュームについて概観します。以下の例では、nginx を使用して永続ボリュームからコンテンツを提供する方法について説明します。

このセクションでは、読者が Kubernetes を基本的に理解されており、Kubernetes クラスターを稼働しておられることを想定しています。

Kubernetes の永続ボリューム (PV) は、インフラストラクチャーの基礎となるストレージ容量の実際の構成要素を表しています。Kubernetes を使用してマウントを実行する前に、まずマウントするストレージを作成する必要があります。クラスター管理者は、Kubernetes でマウントできるよう GCE ディスクを作成し、NFS 共有をエクスポートする必要があります。

永続ボリュームは、GCE 永続ディスク、NFS 共有、および AWS ElasticBlockStore ボリュームなどの「ネットワークボリューム」用に設計されています。HostPath が開発およびテストを容易にするために組み込まれています。以下の例では、ローカルの HostPath を作成します。

重要! HostPath を機能させるには、単一ノードクラスターを実行する必要があります。Kubernetes は現時点ではホスト上のローカルストレージをサポートしません。Pod は HostPath のある正しいノードに常に必ず配置される訳ではありません。

// this will be nginx's webroot
$ mkdir /tmp/data01
$ echo 'I love Kubernetes storage!' > /tmp/data01/index.html

物理ボリュームを API サーバーに提示して作成します。

$ kubectl create -f examples/persistent-volumes/volumes/local-01.yaml
$ kubectl get pv

NAME        LABELS       CAPACITY            ACCESSMODES             STATUS              CLAIM
pv0001      map[]        10737418240         RWO                 Available

6.2.1. ストレージの要求

Kubernetes のユーザーは、Pod 用に永続ストレージを要求します。基礎となるプロビジョニングの性質については、ユーザーが理解している必要はありません。ただし、ユーザーはストレージの要求を使用でき、ストレージを使用する数多くの Pod とは別にそのストレージのライフサイクルを管理できることを知っておく必要があります。

要求は、それらを使用する Pod と同じ名前空間に作成される必要があります。

$ kubectl create -f examples/persistent-volumes/claims/claim-01.yaml
$ kubectl get pvc

NAME                LABELS              STATUS              VOLUME
myclaim-1           map[]

バックグラウンドプロセスは、この要求をボリュームに一致させることを試行します。要求の状態は最終的には以下のようになります。

$ kubectl get pvc

NAME        LABELS    STATUS    VOLUME
myclaim-1   map[]     Bound     f5c3a89a-e50a-11e4-972f-80e6500a981e


$ kubectl get pv

NAME                LABELS              CAPACITY            ACCESSMODES         STATUS    CLAIM
pv0001              map[]               10737418240         RWO                 Bound     myclaim-1 / 6bef4c40-e50b-11e4-972f-80e6500a981e

6.2.2. 要求をボリュームとして使用する

要求は Pod のボリュームとして使用されます。Kubernetes は要求を使用してそれがバインドされた PV を検索します。次に、PV は Pod に公開されます。

$ kubectl create -f examples/persistent-volumes/simpletest/pod.yaml

$ kubectl get pods

POD       IP           CONTAINER(S)   IMAGE(S)   HOST                  LABELS    STATUS    CREATED
mypod     172.17.0.2   myfrontend     nginx      127.0.0.1/127.0.0.1   <none>    Running   12 minutes>


$ kubectl create -f examples/persistent-volumes/simpletest/service.json
$ kubectl get services

NAME              LABELS                                    SELECTOR            IP           PORT(S)
frontendservice   <none>                                    name=frontendhttp   10.0.0.241   3000/TCP
kubernetes        component=apiserver,provider=kubernetes   <none>              10.0.0.2     443/TCP
kubernetes-ro     component=apiserver,provider=kubernetes   <none>              10.0.0.1     80/TCP

6.2.3. 次のステップ

nginx が提供しているコンテンツを表示するためにサービスエンドポイントを照会します。"forbidden" エラーが出た場合は、SELinux (# setenforce 0) を無効にします。

# curl 10.0.0.241:3000
I love Kubernetes storage!

6.3. ボリューム

Kubernetes は各種のストレージ機能を「ボリューム」として抽象化します。

ボリュームは、Pod 定義の volumes セクションで定義されます。ボリュームにあるデータのソースは (1) リモート NFS 共有、(2) iSCSI ターゲット、(3) 空のディレクトリー、または (4) ホスト上のローカルディレクトリーのいずれかになります。

Pod 定義の volumes セクションでは複数のボリュームを定義することができます。各ボリュームは、マウント手順の実行時に Pod 内の固有 ID として使用される (Pod のコンテキスト内の) 固有名を持つ必要があります。

これらのボリュームは、いったん定義されると Pod 定義の containers セクションに定義されるコンテナーにマウントできます。各コンテナーでは複数のボリュームをマウントできます。一方、単一ボリュームを複数のコンテナーにマウントすることもできます。コンテナー定義の volumeMounts セクションでは、ボリュームをマウントする必要のある場所を指定します。

6.3.1. 例

apiVersion: v1beta3
kind: Pod
metadata:
  name: nfs-web
spec:
  volumes:
    # List of volumes to use, i.e. *what* to mount
    - name: myvolume
      < volume details, see below >
    - name: mysecondvolume
      < volume details, see below >

  containers:
    - name: mycontainer
      volumeMounts:
        # List of mount directories, i.e. *where* to mount
        # We want to mount 'myvolume' into /usr/share/nginx/html
        - name: myvolume
          mountPath: /usr/share/nginx/html/
        # We want to mount 'mysecondvolume' into /var/log
        - name: mysecondvolume
          mountPath: /var/log/

6.4. Kubernetes および SELinux パーミッション

Kubernetes が適切に機能するには、ホストとコンテナー間で共有されるディレクトリーへのアクセスがなければなりません。SELinux はデフォルトでは、Kubernetes がその共有ディレクトリーにアクセスすることをブロックします。通常このブロックは得策と言えます。脆弱なコンテナーがホストにアクセスしてダメージを加えることは誰もが阻止したいことであるためです。この状況では、SELinux の共有の阻止に向けた介入なしに、ディレクトリーをホストと Pod 間で共有されるようにする必要があります。

以下は一例になります。ディレクトリー /srv/my-data を Atomic Host と Pod で共有する必要がある場合、/srv/my-data を SELinux ラベルの svirt_sandbox_file_t で、明示的に再度ラベル付けする必要があります。(ホストにある) このディレクトリーにこのラベルが付けられることにより、SELinux はコンテナーに対し、ディレクトリーの読み取りおよびディレクトリーへの書き込みを許可します。以下は、svirt_sandbox_file_t ラベルを /srv/my-data ディレクトリーに割り当てるコマンドです。

$ chcon -R -t svirt_sandbox_file_t /srv/my-data

以下の例は、この手順を実行するためのステップを示しています。

ステップ 1

ホストから /srv/my-data を使用するコンテナーを HTML ルートとして定義します。

 {
  "apiVersion": "v1beta3",
  "kind": "Pod",
  "metadata": {
    "name": "host-test"
  },
  "spec": {
    "containers": [
      {
        "name": "host-test",
        "image": "nginx",
        "privileged": false,
        "volumeMounts": [
          {
            "name": "srv",
            "mountPath": "/usr/share/nginx/html",
            "readOnly": false
          }
        ]
      }
    ],
    "volumes": [
      {
        "name": "srv",
        "hostPath": {
          "path": "/srv/my-data"
        }
      }
    ]
  }
}

ステップ 2

コンテナーホストで以下のコマンドを実行し、SELinux が nginx コンテナーの/srv/my-data への読み取りアクセスを拒否していることを確認します。

$ mkdir /srv/my-data
$ echo "Hello world" > /srv/my-data/index.html
$ curl <IP address of the container>

以下の出力が表示されます。

<html>
<head><title>403 Forbidden</title></head>
...

ステップ 3

ラベル svirt_sandbox_file_t をディレクトリー /srv/my-data に適用します。

$ chcon -R -t svirt_sandbox_file_t /srv/my-data

ステップ 4

curl を使用してコンテナーにアクセスし、ラベルが有効になったことを確認します。

$ curl <IP address of the container>
Hello world

curl コマンドが "Hello world" を返す場合、SELinux ラベルは適切に適用されていることになります。

詳細は、この点についての情報を追跡している BZ#1222060[ を参照してください。

6.5. NFS

以下のシナリオをテストするには、NFS 共有が事前に準備されている必要があります。この例では、NFS 共有を Pod にマウントします。

以下の例では、NFS 共有を /usr/share/nginx/html/ にマウントし、nginx webserver を実行します。

ステップ 1

nfs-web.yaml という名前のファイルを作成します。

apiVersion: v1beta3
kind: Pod
metadata:
  name: nfs-web
spec:
  volumes:
    - name: www
      nfs:
        # Use real NFS server address here.
        server: 192.168.100.1
        # Use real NFS server export directory.
        path: "/www"
        readOnly: true
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: tcp
      volumeMounts:
          # 'name' must match the volume name below.
          - name: www
            # Where to mount the volume.
            mountPath: "/usr/share/nginx/html/"

ステップ 2

Pod の起動:

$ kubectl create -f nfs-web.yaml

Kubernetes は 192.168.100.1:/www を nginx コンテナー内の /usr/share/nginx/html/` にマウントし、これを実行します。

ステップ 3

webserver が NFS 共有からデータを受信することを確認します。

$ curl 172.17.0.6
Hello from NFS

トラブルシューティング

403 Forbidden error: webserver から "403 Forbidden" の応答を受信する場合、以下のコマンドを実行し、SELinux が Docker コンテナーに対して NFS 経由のデータの読み取りを許可していることを確認します。

$ setsebool -P virt_use_nfs 1

6.6. iSCSI

ステップ 1 iSCSI ターゲットが適切に設定されていることを確認します。すべての Kubernetes ノードに iSCSl ターゲットから LUN を割り当てる十分な特権があることを確認します。

ステップ 2 以下の Pod 定義を含む iscsi-web.yaml という名前のファイルを作成します。

apiVersion: v1beta3
kind: Pod
metadata:
  name: iscsi-web
spec:
  volumes:
    - name: www
      iscsi:
        # Address of the iSCSI target portal
        targetPortal: "192.168.100.98:3260"
        # IQN of the portal
        iqn: "iqn.2003-01.org.linux-iscsi.iscsi.x8664:sn.63b56adc495d"
        # LUN we want to mount
        lun: 0
        # Filesystem on the LUN
        fsType: ext4
        readOnly: false
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: tcp
      volumeMounts:
          # 'name' must match the volume name below.
          - name: www
            # Where to mount he volume.
            mountPath: "/usr/share/nginx/html/"

ステップ 3

Pod の作成:

$ kubectl create -f iscsi-web.yaml

ステップ 4

Kubernetes は iSCSI ターゲットにログインし、LUN 0 を割り当て (通常は /dev/sdXYZ)、指定のファイルシステム (本書の例では ext4) を nginx コンテナー内の /usr/share/nginx/html/ にマウントして、これを実行します。

ステップ 5

web サーバーが iSCSI ボリュームのデータを使用していることを確認します。

$ curl 172.17.0.6
Hello from iSCSI

6.7. Google Compute Engine

Google Compute Engine 永続ディスク (GCE PD)

クラスターを Google Compute Engine で実行している場合、永続ディスクを永続ストレージソースとして使用できます。以下の例では、GCE PD から html コンテンツを提供する Pod を作成します。

ステップ 1

GCE SDK がセットアップされている場合、以下のコマンドを使用して永続ディスクを作成します。

$ gcloud compute disks create --size=250GB {Persistent Disk Name}

または、GCE web インターフェースでディスクを作成することもできます。GCE SDK をセットアップする必要がある場合は、こちら の指示に従ってください。

ステップ 2

gce-pd-web.yaml という名前のファイルを作成します。

apiVersion: v1beta3
kind: Pod
metadata:
  name: gce-web
spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: tcp
      volumeMounts:
        - name: html-pd
          mountPath: "/usr/share/nginx/html"
  volumes:
    - name: html-pd
      gcePersistentDisk:
        # Add the name of your persistent disk below
        pdName: {Persistent Disk Name}
        fsType: ext4

ステップ 3

Pod の作成:

$ kubectl create -f gce-pd-web.yaml

Kubernetes は Pod を作成し、ディスクを割り当てますが、これをフォーマットしたり、マウントしたりすることはできません。これはバグによる問題ですが、Kubernetes の今後のバージョンで修正される予定です。この問題を回避するために、次のステップに進んでください。

ステップ 4

永続ディスクをフォーマットし、マウントします。

ステップ 5

ディスクは仮想マシンに割り当てられ、デバイスは scsi-0Google_PersistentDisk_{Persistent Disk Name} という名前で /dev/disk/by-id/` に表示されます。このディスクがすでにフォーマットされており、データが含まれている場合は、次のステップに進みます。そうでない場合は root で以下のコマンドを実行し、これをフォーマットします。

$ mkfs.ext4 /dev/disk/by-id/scsi-0Google_PersistentDisk_{Persistent Disk Name}

ステップ 6

ディスクがフォーマットされている場合、これを Kubernetes が予想する場所にマウントします。以下のコマンドを root として実行します。

# mkdir -p /var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/{Persistent Disk Name} && mount /dev/disk/by-id/scsi-0Google_PersistentDisk_{Persistent Disk Name} /var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/{Persistent Disk Name}

注意: mkdir コマンドおよび mount コマンドは上記のように連続して実行される必要があります。Kubernetes の削除により、何もマウントされていないことが確認されるとディレクトリーが削除されるためです。

ステップ 7

ディスクがマウントされており、正しい SELinux コンテキストが付与されているはずです。root で以下を実行します。

$ sudo chcon -R -t svirt_sandbox_file_t /var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/{Persistent Disk Name}

ステップ 8

webserver が提供するデータを作成します。

$ echo "Hello world" >  /var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/{Persistent Disk Name}/index.html

ステップ 9

Pod から HTML を取得できるはずです。

$ curl {IP address of the container}
Hello World!