Kubernetes: Volume Snapshotの検証

#概要
　Kubernetes v1.12よりVolume SnapshotがAlphaサポートされました。このVolume SnapshotはCSI対応のストレージでサポートされています。また、CSIの仕様としてはCSI spec v0.3からSnapshotがサポートしています。つまり、Snapshotを使用するためには、Kubernetes v1.12以降かつCSI spec v0.3以降のCSI対応のストレージが必要となります。(CSIの説明は「[KubernetesにおけるContainer Storage Interface (CSI)の概要と検証](https://qiita.com/ysakashita/items/4b56c2577f67f1b141e5)」を参照)
 　2018/12現在でVolume Snapshotが利用できるCSI Driverは、[Kubernetes CSI Documentation](https://kubernetes-csi.github.io/docs/Implement-Snapshot-Feature.html)によると以下のストレージです。

- GCE PD
- OpenSDS
- Ceph RDB
- GlusterFS

　また、上記に加え検証・開発用に用意されているHostPathを利用したCSI DriverであるCSI HostPath Driverも対応しています。そこで本検証では、[CSI HostPath Driver](https://kubernetes-csi.github.io/docs/Example.html)を使いVolume Snapshotの動作検証を行います。

#検証
本検証は以下の環境で行います。
CSI Hostpath Driverを使うため、1 nodeのKubernetesです。

- Kubernetes v1.13.0 (kubeadm で構築、1 node構成(masterのみ))

##Kubernetesの設定
VolumeSnapshotを利用するためには、kubeletとkube-apiserverに`--feature-gates`で機能を有効にする必要があります。
まずは、kubeletの設定を行います。KubernetesのNodeにsshなどでログインし`/etc/default/kubelet`に`--feature-gates`を追加します。

```/etc/default/kubelet
KUBELET_EXTRA_ARGS=--feature-gates=VolumeSnapshotDataSource=true,KubeletPluginsWatcher=true,CSINodeInfo=true,CSIDriverRegistry=true
```

追加後に、kubeletを再起動します。

```
$ sudo systemctl daemon-reload
$ sudo systemctl restart kubelet
```

次に、kube-apiserverの設定を行います。
`/etc/kubernetes/manifests/kube-apiserver.yaml`に`--feature-gates`を追加します。

```kube-apiserver.yaml
spec:
  containers:
  - command:
    - kube-apiserver
...
    - --feature-gates=VolumeSnapshotDataSource=true,KubeletPluginsWatcher=true,CSINodeInfo=true,CSIDriverRegistry=true
...
    image: k8s.gcr.io/kube-apiserver:v1.13.0
```

この`kube-apiserver.yaml`は編集を行うと、自動でkube-apiserverを再起動してくれます。これで、Kubernetes自身の設定は終わりです。

##CSI HostPath Driverのセットアップ
CSI HostPath Driverをセットアップします。
今回のセットアップでは、以下を順にデプロイしていきます。

- CRD
  - CSIDriverRegistry
  - CSINodeInfo
- RBAC
  - CSI provisioner用
  - CSI attacher用
  - CSI driver register用
  - CSI snapshotter用 
- CSI Hostpath provisioner
- CSI Hostpath attacher
- CSI Hostpath plugin
- CSI Hostpath snapshotter

###CRDのデプロイ
はじめに、CRD(Custom Resource Definition)のCSIDriverRegistryとCSINodeInfoをデプロイします。
CSIDriverRegistryのManifestを以下に示します。

```csidriver.yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: csidrivers.csi.storage.k8s.io
spec:
  group: csi.storage.k8s.io
  names:
    kind: CSIDriver
    plural: csidrivers
  scope: Cluster
  validation:
    openAPIV3Schema:
      properties:
        spec:
          description: Specification of the CSI Driver.
          properties:
            attachRequired:
              description: Indicates this CSI volume driver requires an attach operation,
                and that Kubernetes should call attach and wait for any attach operation
                to complete before proceeding to mount.
              type: boolean
            podInfoOnMountVersion:
              description: Indicates this CSI volume driver requires additional pod
                information (like podName, podUID, etc.) during mount operations.
              type: string
  version: v1alpha1
```

CSINodeInfoのManifestを以下に示します。

```csinodeinfo.yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: csinodeinfos.csi.storage.k8s.io
spec:
  group: csi.storage.k8s.io
  names:
    kind: CSINodeInfo
    plural: csinodeinfos
  scope: Cluster
  validation:
    openAPIV3Schema:
      properties:
        csiDrivers:
          description: List of CSI drivers running on the node and their properties.
          items:
            properties:
              driver:
                description: The CSI driver that this object refers to.
                type: string
              nodeID:
                description: The node from the driver point of view.
                type: string
              topologyKeys:
                description: List of keys supported by the driver.
                items:
                  type: string
                type: array
          type: array
  version: v1alpha1
```

CSIDriverRegistryとCSINodeInfoをデプロイします。

```
$ kubectl create -f csidriver.yaml
$ kubectl create -f csinodeinfo.yaml
```

###RBACのデプロイ
CSI Hostpath provisionerを実行するServiceAccountとRBAC(Role Based Access Control)のManifestを以下に示します。

```csi-provisioner-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: csi-provisioner
  namespace: default

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: external-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
  - apiGroups: ["snapshot.storage.k8s.io"]
    resources: ["volumesnapshots"]
    verbs: ["get", "list"]
  - apiGroups: ["snapshot.storage.k8s.io"]
    resources: ["volumesnapshotcontents"]
    verbs: ["get", "list"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: csi-provisioner-role
subjects:
  - kind: ServiceAccount
    name: csi-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: external-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: external-provisioner-cfg
rules:
- apiGroups: [""]
  resources: ["endpoints"]
  verbs: ["get", "watch", "list", "delete", "update", "create"]

---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: csi-provisioner-role-cfg
  namespace: default
subjects:
  - kind: ServiceAccount
    name: csi-provisioner
    namespace: default
roleRef:
  kind: Role
  name: external-provisioner-cfg
  apiGroup: rbac.authorization.k8s.io
```

作成したManifestをデプロイします。

```
$ kubectl create -f csi-provisioner-rbac.yaml
```

次に、CSI Hostpath attacherを実行するServiceAccountとRBACのManifestを以下に示します。

```csi-attacher-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: csi-attacher
  namespace: default

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: external-attacher-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["csi.storage.k8s.io"]
    resources: ["csinodeinfos"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["volumeattachments"]
    verbs: ["get", "list", "watch", "update"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: csi-attacher-role
subjects:
  - kind: ServiceAccount
    name: csi-attacher
    namespace: default
roleRef:
  kind: ClusterRole
  name: external-attacher-runner
  apiGroup: rbac.authorization.k8s.io

---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: external-attacher-cfg
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "watch", "list", "delete", "update", "create"]

---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: csi-attacher-role-cfg
  namespace: default
subjects:
  - kind: ServiceAccount
    name: csi-attacher
    namespace: default
roleRef:
  kind: Role
  name: external-attacher-cfg
  apiGroup: rbac.authorization.k8s.io
```

作成したManifestをデプロイします。

```
$ kubectl create -f csi-attacher-rbac.yaml
```

CSI Hostpath driverを実行するServiceAccountとRBACのManifestを以下に示します。

```csi-driver-registrar-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: csi-driver-registrar
  namespace: default

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: driver-registrar-runner
rules:
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: csi-driver-registrar-role
subjects:
  - kind: ServiceAccount
    name: csi-driver-registrar
    namespace: default
roleRef:
  kind: ClusterRole
  name: driver-registrar-runner
  apiGroup: rbac.authorization.k8s.io
```

作成したManifestをデプロイします。

```
$ kubectl create -f csi-driver-registrar-rbac.yaml
```

RBAC関連の最後に、CSI Hostpath snapshotterを実行するServiceAccountとRBACのManifestを以下に示します。

```csi-snapshotter-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: csi-snapshotter

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: external-snapshotter-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]
  - apiGroups: ["snapshot.storage.k8s.io"]
    resources: ["volumesnapshotclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["snapshot.storage.k8s.io"]
    resources: ["volumesnapshotcontents"]
    verbs: ["create", "get", "list", "watch", "update", "delete"]
  - apiGroups: ["snapshot.storage.k8s.io"]
    resources: ["volumesnapshots"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["apiextensions.k8s.io"]
    resources: ["customresourcedefinitions"]
    verbs: ["create", "list", "watch", "delete"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: csi-snapshotter-role
subjects:
  - kind: ServiceAccount
    name: csi-snapshotter
    namespace: default
roleRef:
  kind: ClusterRole
  name: external-snapshotter-runner
  apiGroup: rbac.authorization.k8s.io
```

作成したManifestをデプロイします。

```
$ kubectl create -f csi-snapshotter-rbac.yaml
```

以上で事前準備のRBAC関連のデプロイが終わりました。

### CSI HostPath provisionerのデプロイ
次に、CSI HostPath provisionerをデプロイします。
デプロイするCSI HostPath provisionerのManifestを以下に示します。

```csi-hostpath-provisioner.yaml
kind: Service
apiVersion: v1
metadata:
  name: csi-hostpath-provisioner 
  labels:
    app: csi-hostpath-provisioner 
spec:
  selector:
    app: csi-hostpath-provisioner 
  ports:
    - name: dummy
      port: 12345

---
kind: StatefulSet
apiVersion: apps/v1
metadata:
  name: csi-hostpath-provisioner
spec:
  serviceName: "csi-hostpath-provisioner"
  replicas: 1
  selector:
    matchLabels:
      app: csi-hostpath-provisioner
  template:
    metadata:
      labels:
        app: csi-hostpath-provisioner
    spec:
      serviceAccountName: csi-provisioner
      containers:
        - name: csi-provisioner
          image: quay.io/k8scsi/csi-provisioner:v0.4.1
          args:
            - "--provisioner=csi-hostpath"
            - "--csi-address=$(ADDRESS)"
            - "--connection-timeout=15s"
          env:
            - name: ADDRESS
              value: /csi/csi.sock
          imagePullPolicy: Always
          volumeMounts:
            - mountPath: /csi
              name: socket-dir
      volumes:
        - hostPath:
            path: /var/lib/kubelet/plugins/csi-hostpath
            type: DirectoryOrCreate
          name: socket-dir
```

CSI HostPath provisionerをデプロイします。

```
$ kubectl create -f csi-hostpath-provisioner.yaml
```

###CSI HostPath attacherのデプロイ
続いて、CSI HostPath attacherをデプロイしていきます。
デプロイするCSI HostPath attacherのManifestを以下に示します。

```csi-hostpath-attacher.yaml
kind: Service
apiVersion: v1
metadata:
  name: csi-hostpath-attacher
  labels:
    app: csi-hostpath-attacher
spec:
  selector:
    app: csi-hostpath-attacher
  ports:
    - name: dummy
      port: 12345

---
kind: StatefulSet
apiVersion: apps/v1
metadata:
  name: csi-hostpath-attacher
spec:
  serviceName: "csi-hostpath-attacher"
  replicas: 1
  selector:
    matchLabels:
      app: csi-hostpath-attacher
  template:
    metadata:
      labels:
        app: csi-hostpath-attacher
    spec:
      serviceAccountName: csi-attacher
      containers:
        - name: csi-attacher
          image: quay.io/k8scsi/csi-attacher:v0.4.1
          args:
            - --v=5
            - --csi-address=$(ADDRESS)
          env:
            - name: ADDRESS
              value: /csi/csi.sock
          imagePullPolicy: Always
          volumeMounts:
          - mountPath: /csi
            name: socket-dir
      volumes:
        - hostPath:
            path: /var/lib/kubelet/plugins/csi-hostpath
            type: DirectoryOrCreate
          name: socket-dir
```

CSI HostPath attacherをデプロイします。

```
$ kubectl create -f csi-hostpath-attacher.yaml
```

### CSI HostPath pluginのデプロイ
次に、CSI HostPath pluginをデプロイします。
デプロイするCSI HostPath pluginのManifestを以下に示します。

```csi-hostpathplugin.yaml
kind: DaemonSet
apiVersion: apps/v1
metadata:
  name: csi-hostpathplugin
spec:
  selector:
    matchLabels:
      app: csi-hostpathplugin
  template:
    metadata:
      labels:
        app: csi-hostpathplugin
    spec:
      serviceAccountName: csi-driver-registrar
      hostNetwork: true
      containers:
        - name: driver-registrar
          image: quay.io/k8scsi/driver-registrar:v0.4.1
          args:
            - --v=5
            - --csi-address=/csi/csi.sock
            - --kubelet-registration-path=/var/lib/kubelet/plugins/csi-hostpath/csi.sock
          env:
            - name: KUBE_NODE_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: spec.nodeName
          imagePullPolicy: Always
          volumeMounts:
          - mountPath: /csi
            name: socket-dir
          - mountPath: /registration
            name: registration-dir
        - name: hostpath
          image: quay.io/k8scsi/hostpathplugin:v0.4.1
          args:
            - "--v=5"
            - "--endpoint=$(CSI_ENDPOINT)"
            - "--nodeid=$(KUBE_NODE_NAME)"
          env:
            - name: CSI_ENDPOINT
              value: unix:///csi/csi.sock
            - name: KUBE_NODE_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: spec.nodeName
          imagePullPolicy: Always
          securityContext:
            privileged: true
          volumeMounts:
            - mountPath: /csi
              name: socket-dir
            - mountPath: /var/lib/kubelet/pods
              mountPropagation: Bidirectional
              name: mountpoint-dir
      volumes:
        - hostPath:
            path: /var/lib/kubelet/plugins/csi-hostpath
            type: DirectoryOrCreate
          name: socket-dir
        - hostPath:
            path: /var/lib/kubelet/pods
            type: DirectoryOrCreate
          name: mountpoint-dir
        - hostPath:
            path: /var/lib/kubelet/plugins
            type: Directory
          name: registration-dir
```

CSI HostPath pluginをデプロイします。

```
$ kubectl create -f csi-hostpathplugin.yaml
```

### CSI HostPath snapshotterのデプロイ
最後に、Volume Snapshotを使うためのサイドカーコンテナCSI HostPath snapshotterをデプロイします。
以下に、CSI HostPath snapshotterのManifestを示します。

```csi-hostpath-snapshotter.yaml
kind: Service
apiVersion: v1
metadata:
  name: csi-hostpath-snapshotter
  labels:
    app: csi-hostpath-snapshotter
spec:
  selector:
    app: csi-hostpath-snapshotter
  ports:
    - name: dummy
      port: 12345

---
kind: StatefulSet
apiVersion: apps/v1
metadata:
  name: csi-hostpath-snapshotter
spec:
  serviceName: "csi-hostpath-snapshotter"
  replicas: 1
  selector:
    matchLabels:
      app: csi-hostpath-snapshotter
  template:
    metadata:
      labels:
        app: csi-hostpath-snapshotter
    spec:
      serviceAccount: csi-snapshotter
      containers:
        - name: csi-snapshotter
          image: quay.io/k8scsi/csi-snapshotter:v0.4.1
          args:
            - "--csi-address=$(ADDRESS)"
            - "--connection-timeout=15s"
          env:
            - name: ADDRESS
              value: /csi/csi.sock
          imagePullPolicy: Always
          volumeMounts:
            - mountPath: /csi
              name: socket-dir
      volumes:
        - hostPath:
            path: /var/lib/kubelet/plugins/csi-hostpath
            type: DirectoryOrCreate
          name: socket-dir
```

CSI HostPath snapshotterをデプロイします。

```
$ kunectl create -f csi-hostpath-snapshotter.yaml
```

これで、CSI HostPath Driverのデプロイが完了です。
次から、このCSI HostPath Driverを使いVolume Snapshotの検証を行います。

##Volume Snapshotの動作検証
いよいよ、CSI HostPath Driverを使いVolume Snapshotを検証します。

###StorageClassのデプロイ
まず初めに、CSI HostPath Driverを使い、PV(PersistentVolume)を作成するためのStorageClassをデプロイします。
以下に、StorageClassのManifestを示します。

```csi-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: csi-hostpath-sc
provisioner: csi-hostpath
reclaimPolicy: Delete
volumeBindingMode: Immediate
```

StorageClassをデプロイし確認します。

```
$ kubectl create -f csi-storageclass.yaml

$ kubectl get sc
NAME              PROVISIONER    AGE
csi-hostpath-sc   csi-hostpath   4h8m
```

###VolumeSnapshotClassのデプロイ
次に、VolumeSnapshotClassをデプロイします。
VolumeSnapshotClassはSnapshotにおけるStorageClassのような働きをするリソースになります。
以下に、VolumeSnapshotClassのMainfestを示します。

```csi-snapshotclass.yaml
apiVersion: snapshot.storage.k8s.io/v1alpha1
kind: VolumeSnapshotClass
metadata:
  name: csi-hostpath-snapclass
snapshotter: csi-hostpath
```

VolumeSnapshotClassをデプロイし確認します。

```
$ kubectl create -f csi-snapshotclass.yaml

$ kubectl get VolumeSnapshotClass
NAME                     AGE
csi-hostpath-snapclass   4h
```

以上で、Volume Snapshotを利用する準備が整いました。

###Snapshotの元となるPVの準備
Snapshotを実行する前に、元となるPV(PersistentVolume)、PVC(PersistentVolumeClaim)およびPodをデプロイします。
以下に、PVCのManifestを示します。

```csi-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: csi-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: csi-hostpath-sc 
```

storageClassNameにはデプロイしたCSI HostPath Driver用StorageClassの`csi-hostpath-sc`を指定します。
作成したPVCをデプロイし確認します。

```
$ kubectl create -f csi-pvc.yaml

$ kubectl get pvc
NAME      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
csi-pvc   Bound    pvc-5a8d9855-fc78-11e8-af45-080027571559   1Gi        RWO            csi-hostpath-sc   9s

$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS      REASON   AGE
pvc-5a8d9855-fc78-11e8-af45-080027571559   1Gi        RWO            Delete           Bound    default/csi-pvc   csi-hostpath-sc            12s
```

PVCがデプロイされると、Dynamic Provisioningにより、PVCの要求にマッチしたPVがStorageClass(`csi-hostpath-sc`)から作成されます。
次に、デプロイしたPVをマウントするPodをデプロイします。
PodのManifestを以下に示します。

```csi-app.yaml
kind: Pod
apiVersion: v1
metadata:
  name: my-csi-app
spec:
  containers:
    - name: my-frontend
      image: busybox
      volumeMounts:
      - mountPath: "/data"
        name: my-csi-volume
      command: [ "sleep", "1000000" ]
  volumes:
    - name: my-csi-volume
      persistentVolumeClaim:
        claimName: csi-pvc
```

このPodでは`/data`ディレクトリにCSI HostPath Driverで作成された永続ストレージのPVをマウントします。
Podをデプロイします。

```
$ kubectl create -f csi-app.yaml
```

デプロイされたPodへ入り、PVにデータを書き込みます。

```
$ kubectl get pod |grep my-csi-app
my-csi-app                   1/1     Running   0          35s

$ kubectl exec -ti my-csi-app /bin/sh
/ # cd /data
/data # echo "hoge" > hello.txt
/data # cat hello.txt 
hoge
/data # exit
```

PVに`hello.txt`ファイルを作成し、`hoge`という文字列を書き込みました。

###Snapshotの作成
では準備が全て整ったので、Snapshotを行います。
Snapshotは、VolumeSnapshotリソースをデプロイすることで作成します。
VolumeSnapshotのManifestを以下に示します。

```csi-snapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1alpha1
kind: VolumeSnapshot
metadata:
  name: snapshot-20181210
spec:
  snapshotClassName: csi-hostpath-snapclass
  source:
    name: csi-pvc
    kind: PersistentVolumeClaim
```

VolumeSnapshotでは、spec.snapshotClassNameに先ほどデプロイしたVolumeSnapshotClass(`csi-hostpath-snapclass`)を指定します。
spec.sourceには、先ほど準備したPVにBoundされているPVC(`csi-pvc`)を指定します。
また、今回は作成するSnapshotが、いつの時点のデータのSnapshotかわかりやすくするため、日付を含んだ名前をspec.metadata.nameに設定しています。
VolumeSnapshotをデプロイし確認します。

```
$ kubectl create -f csi-snapshot.yaml 
volumesnapshot.snapshot.storage.k8s.io/snapshot-20181210 created

$ kubectl get VolumeSnapshot
NAME                AGE
snapshot-20181210   14s
```

次に、Snapshotを確認するために、再度PV上のファイル(`hello.txt`)に新たな文字列(`foo`)を追記します。

```
$ kubectl exec -ti my-csi-app /bin/sh
/ # cd data
/data # ls
hello.txt
/data # cat hello.txt
hoge
/data # echo "foo" >>hello.txt
/data # cat hello.txt 
hoge
foo
/data # exit
```

###SnapshotからのRestore
作成したSnapshotからRestoreします。
Restoreでは、作成したVolumeSnapshotを指定したPVCをデプロイし、Snapshotをとった時点のデータが保存されたPVを作成します。
以下に、Restore用のPVCのManifestを示します。

```csi-restore.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: restore-20181210
spec:
  storageClassName: csi-hostpath-sc
  dataSource:
    name: snapshot-20181210
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
```

spec.dataSourceにRestoreするVolumeSnapshotを指定します。ここでは先ほどSnapshotをとったVolumeSnapshotの`snapshot-20181210`を指定しています。
Restore用のPVCをデプロイします。

```
$ kubectl create -f csi-restore.yaml 
```

PVCがデプロイされると、Dynamic ProvisioningによりPVが自動生成されます。
この作成されたRestore用のPVをマウントするPodをデプロイします。
ここでは、先ほど作成した`csi-app.yaml`をコピー＆編集し、`csi-app2.yaml`を作成しています。
PodのManifestを以下に示します。

```csi-app2.yaml
kind: Pod
apiVersion: v1
metadata:
  name: my-csi-app2
spec:
  containers:
    - name: my-frontend
      image: busybox
      volumeMounts:
      - mountPath: "/data"
        name: my-csi-volume
      command: [ "sleep", "1000000" ]
  volumes:
    - name: my-csi-volume
      persistentVolumeClaim:
        claimName: restore-20181210
```

変更点としては、Podの名前(metadata.name)とspec.volumes.persistentVolumeClaim.claimNameです。
claimNameには`csi-restore.yaml`で作成した`restore-20181210
 `を指定しています。
Podをデプロイした後、Podに入りデータを確認してみます。

```
$ kubectl create -f csi-app2.yaml

$ kubectl exec -ti my-csi-app2 /bin/sh
/ # cd data
/data # cat hello.txt 
hoge
/data # exit
```

デプロイされた`my-csi-app2`のPodに入り、PV(`/data`)の`hello.txt`を見てみると、最初に書き込んだ文字列`hoge`のみとなっています。
つまり、

- `hoge`を書き込み
- Snapshotを取る
- `foo`を書き込み

の順で操作を行ってきたので、無事`foo`が書き込まれる前の状態のファイルにRestoreされています。
KubernetesのVolume Snapshotにより正しくSnapshotが動作しました。

##クリーンアップ
検証環境を削除します。

```
$ kubectl delete -f csi-app2.yaml
$ kubectl delete -f csi-restore.yaml
$ kubectl delete -f csi-snapshot.yaml
$ kubectl delete -f csi-app.yaml
$ kubectl delete -f csi-pvc.yaml
$ kubectl delete -f csi-snapshotclass.yaml
$ kubectl delete -f csi-storageclass.yaml
$ kubectl delete -f csi-hostpath-snapshotter.yaml
$ kubectl delete -f csi-hostpathplugin.yaml
$ kubectl delete -f csi-hostpath-provisioner.yaml
$ kubectl delete -f csi-hostpath-attacher.yaml
$ kubectl delete -f csi-snapshotter-rbac.yaml
$ kubectl delete -f csi-provisioner-rbac.yaml
$ kubectl delete -f csi-driver-registrar-rbac.yaml
$ kubectl delete -f csi-attacher-rbac.yaml
$ kubectl delete -f csinodeinfo.yaml
$ kubectl delete -f csidriver.yaml
```

以上で検証終わりです。

# 感想
　今回の検証では、Kubernetes v1.12から登場したVolume Snapshotを検証しました。
ストレージのSnapshotは、変更箇所のみSSDやHDDなどに保存されるため、ミラーなどのフルバックアップに比べて容量が少なく済むことが特徴のバックアップ機能です。
このような特徴をもったSnapshotがKubernetesから利用できるようになることで、より一層PVのユースケースが広がります。
　これまでは単にDBなどStatefulアプリケーションを動作させるためにPVを使うことが多かったのではないでしょうか。新しいユースケースの例としては「[機械学習モデルをSeldon Coreを使いKubernetesでサービス化](https://qiita.com/ysakashita/items/1a2403e0944122eeb086)」で紹介した機械学習のモデルデータの保存先にVolume Snapshotを使う方法があります。機械学習では、新しい学習データを使って何度も繰り返しモデルデータをアップデートしていく運用パターンが多いかと思います。さらには、過去の再現性や比較のために過去のモデルデータも大量に保存している組織も多いのではないでしょうか。また、このモデルデータはニューラルネットワークにおけるニューロン間の重みの数値が大半を占め、その重みの値の大半は、ニューラルネットワークのモデルを大きく変更しない限り、同じ値の重みが多いといった性質があります。つまり、このモデルデータにVolume Snaoshotを適用することで、ストレージのSnapshotの変更箇所のみ保存する特徴が活きます。これまで、過去のモデルデータも保存したいため、毎回モデルデータを更新するたびに新たにPVを作るか、もしくはコンテナの中にモデルデータを抱え込んだコンテナイメージを新規に作っていたかと思います。この方法ですと、変更されていない箇所も全て新たなデータ領域に保存されるため、ストレージを無駄遣いします。これからは、Volume Snapshotを使うことで、過去のモデルデータも保存しつつストレージの消費容量を少なくモデルデータの更新ができるようになります。
　このように、Volume SnapshotはPVを活用したコンテナのユースケースが広がる可能性を秘めた機能かと思います。今後、Volume Snapshotをうまく活用した事例がカンファレンスなどで発表されるのが非常に楽しみです。ただ、現時点で残念なことは、対応しているストレージが少ないという点です。ストレージベンダの早期の対応を期待しています。

# 参考
- [kubernetes.io: Volume Snapshots](https://kubernetes.io/docs/concepts/storage/volume-snapshots/)
- [Volume Snapshot Classes](https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes/)
- [kubernetes-csi: example](https://kubernetes-csi.github.io/docs/Example.html)