Kubernetes + HostPath driverでCSI Snapshot


# やりたいこと
Kubernetes環境でCSI Snapshotで使用したい。調べてみると、Kubernetes 1.17段階ではSnapshot機能はBetaであり、対応しているストレージドライバも少ない。その中で使えそうなのはHost Path Driverだったので、これを使って構成してみる。

# Kubernetesインストール
## 環境
Host OS: CentOS 7.6 (Minimum) x 1 (Master,worker兼用)
Kubernetes: 1.17

## インストール
基本的には以下のサイトを参考に実施。
[Steps for Installing Kubernetes on CentOS 7](https://phoenixnap.com/kb/how-to-install-kubernetes-on-centos)

手を加える必要のあったところ。
1. 上から順番に進めると、kubeadm initの前にkubectl applyがあるが、そこは逆にする。
2. /etc/sysctl.d/master_node_nameにiptablesの設定をいれるようになっているが、/etc/sysctl.confに入れないと読み取られなかった。

1に関しては、そうしないと以下のエラーで失敗する。

```shell-session
[root@base yum.repos.d]# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
The connection to the server localhost:8080 was refused - did you specify the right host or port?
```

2に関しては、以下のようにあるので、元のままでもよいように思うが…追及できていない。

```txt:/etc/sysctl.conf
# sysctl settings are defined through files in
# /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/.
```

エラーメッセージは以下。

```shell-session
[root@base yum.repos.d]# kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=10.118.37.193
W0116 13:07:26.298286    8566 validation.go:28] Cannot validate kube-proxy config - no validator is available
W0116 13:07:26.298297    8566 validation.go:28] Cannot validate kubelet config - no validator is available
[init] Using Kubernetes version: v1.17.1
[preflight] Running pre-flight checks
        [WARNING Firewalld]: firewalld is active, please ensure ports [6443 10250] are open or your cluster may not function correctly
error execution phase preflight: [preflight] Some fatal errors occurred:
        [ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
To see the stack trace of this error execute with --v=5 or higher
```

## シングルノード構成での注意
デフォルトでは、Masterノードはコンテナを実行しない設定になっているので、その解除が必要。
https://qiita.com/nykym/items/dcc572c21885543d94c8

```shell-session
[root@master-node csi-driver-host-path]# kubectl describe node master-node.devops.local
Name:               master-node.devops.local
Roles:              master
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=master-node.devops.local
                    kubernetes.io/os=linux
                    node-role.kubernetes.io/master=
Annotations:        flannel.alpha.coreos.com/backend-data: {"VtepMAC":"ca:e4:6a:bb:8f:3e"}
                    flannel.alpha.coreos.com/backend-type: vxlan
                    flannel.alpha.coreos.com/kube-subnet-manager: true
                    flannel.alpha.coreos.com/public-ip: 10.118.37.193
                    kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Thu, 16 Jan 2020 13:51:55 +0900
Taints:             node-role.kubernetes.io/master:NoSchedule
```

以下のコマンドでTaintsの設定を変更する。

```shell-session
[root@master-node csi-driver-host-path]# kubectl taint nodes master-node.devops.local node-role.kubernetes.io/master:NoSchedule-
node/master-node.devops.local untainted
```

設定しないと、SystemのNamespaceでないpodはPendingになる(以下は失敗例)。

```shell-session
[root@master-node csi-driver-host-path]# kubectl describe snapshot-controller-0
Name:           snapshot-controller-0
Namespace:      default
Priority:       0
Node:           <none>
Labels:         app=snapshot-controller
                controller-revision-hash=snapshot-controller-66b4bc9b7d
                statefulset.kubernetes.io/pod-name=snapshot-controller-0
Annotations:    <none>
Status:         Pending
IP:             
IPs:            <none>
Controlled By:  StatefulSet/snapshot-controller
Containers:
  snapshot-controller:
    Image:      quay.io/k8scsi/snapshot-controller:v2.0.0-rc4
    Port:       <none>
    Host Port:  <none>
    Args:
      --v=5
      --leader-election=false
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from snapshot-controller-token-g4mr9 (ro)
Conditions:
  Type           Status
  PodScheduled   False 
Volumes:
  snapshot-controller-token-g4mr9:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  snapshot-controller-token-g4mr9
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason            Age                 From               Message
  ----     ------            ----                ----               -------
  Warning  FailedScheduling  86s (x36 over 51m)  default-scheduler  0/1 nodes are available: 1 node(s) had taints that the pod didn't tolerate.
```

# HostPath driver
基本的にはこちらのgithubレポジトリのREADMEを参考に実施。
https://github.com/kubernetes-csi/csi-driver-host-path

ただし、前提条件の記載やスナップショット作成用のyamlが上記レポジトリのものでは動かなかったので、以下も参考にした。
https://kubernetes.io/blog/2019/12/09/kubernetes-1-17-feature-cis-volume-snapshot-beta/

まずは前提となる以下をインストールする。
- Kubernetes Volume Snapshot CRDs
- Volume snapshot controller

```shell-session
[root@master-node gitrepo]# git clone https://github.com/kubernetes-csi/external-snapshotter.git
[root@master-node gitrepo]# cd external-snapshotter/
[root@master-node external-snapshotter]# ls config/crd/
snapshot.storage.k8s.io_volumesnapshotclasses.yaml  snapshot.storage.k8s.io_volumesnapshotcontents.yaml  snapshot.storage.k8s.io_volumesnapshots.yaml
[root@master-node external-snapshotter]# kubectl create -f config/crd
customresourcedefinition.apiextensions.k8s.io/volumesnapshotclasses.snapshot.storage.k8s.io created
customresourcedefinition.apiextensions.k8s.io/volumesnapshotcontents.snapshot.storage.k8s.io created
customresourcedefinition.apiextensions.k8s.io/volumesnapshots.snapshot.storage.k8s.io created
[root@master-node external-snapshotter]# 
[root@master-node external-snapshotter]# 
[root@master-node external-snapshotter]# kubectl create -f deploy/kubernetes/snapshot-controller
serviceaccount/snapshot-controller created
clusterrole.rbac.authorization.k8s.io/snapshot-controller-runner created
clusterrolebinding.rbac.authorization.k8s.io/snapshot-controller-role created
role.rbac.authorization.k8s.io/snapshot-controller-leaderelection created
rolebinding.rbac.authorization.k8s.io/snapshot-controller-leaderelection created
statefulset.apps/snapshot-controller created
[root@master-node external-snapshotter]# 
```

次に、HostPathドライバー本体をインストール。

```shell-session
[root@master-node ~]# mkdir gitrepo
[root@master-node ~]# cd gitrepo/
[root@master-node gitrepo]#
[root@master-node gitrepo]# yum install -y git
[root@master-node gitrepo]# git clone https://github.com/kubernetes-csi/csi-driver-host-path.git
[root@master-node gitrepo]# cd csi-driver-host-path/
[root@master-node csi-driver-host-path]# deploy/kubernetes-1.17/deploy-hostpath.sh
```


## ボリューム作成

```shell-session
[root@master-node csi-driver-host-path]# for i in ./examples/csi-storageclass.yaml ./examples/csi-pvc.yaml ./examples/csi-app.yaml; do kubectl apply -f $i; done
storageclass.storage.k8s.io/csi-hostpath-sc created
persistentvolumeclaim/csi-pvc created
pod/my-csi-app created

[root@master-node csi-driver-host-path]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS      REASON   AGE
pvc-93a40c63-0eab-47c8-92ee-34f8db56f125   1Gi        RWO            Delete           Bound    default/csi-pvc   csi-hostpath-sc            22h
[root@master-node csi-driver-host-path]# kubectl get pvc
NAME      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
csi-pvc   Bound    pvc-93a40c63-0eab-47c8-92ee-34f8db56f125   1Gi        RWO            csi-hostpath-sc   22h
[root@master-node csi-driver-host-path]#

```

## スナップショット作成

```shell-session
[root@master-node csi-driver-host-path]# kubectl apply -f examples/csi-snapshot.yaml
error: error validating "examples/csi-snapshot.yaml": error validating data: [ValidationError(VolumeSnapshot.spec): unknown field "snapshotClassName" in io.k8s.storage.snapshot.v1beta1.VolumeSnapshot.spec, ValidationError(VolumeSnapshot.spec.source): unknown field "kind" in io.k8s.storage.snapshot.v1beta1.VolumeSnapshot.spec.source, ValidationError(VolumeSnapshot.spec.source): unknown field "name" in io.k8s.storage.snapshot.v1beta1.VolumeSnapshot.spec.source]; if you choose to ignore these errors, turn validation off with --validate=false
```

ここで失敗した理由は、exampleにあるsnapshot作成のyamlのパラメータ名などが違った。正しくは以下。

```yaml:csi-snapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshot
metadata:
  name: new-snapshot-demo
spec:
  volumeSnapshotClassName: csi-hostpath-snapclass
  source:
    persistentVolumeClaimName: csi-pvc
```

```shell-session
[root@master-node csi-driver-host-path]# kubectl apply -f examples/csi-snapshot.yaml
volumesnapshot.snapshot.storage.k8s.io/new-snapshot-demo created
[root@master-node csi-driver-host-path]# kubectl get volumesnapshot
NAME                AGE
new-snapshot-demo   105s
```

## リストア
リストアする際はスナップショットを元に新しいボリューム(PVC)を作成し、それを使用する形になる。

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

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

テストとして、スナップショットが作成された状態から、ソースボリューム内のテストファイルを削除し、
スナップショットから生成したボリュームにファイルが残っていることを確認する。

```shell-session
[root@master-node csi-driver-host-path]# kubectl apply -f examples/csi-restore.yaml
persistentvolumeclaim/hpvc-restore created
[root@master-node csi-driver-host-path]# kebeubectl get pvc
NAME           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
csi-pvc        Bound    pvc-93a40c63-0eab-47c8-92ee-34f8db56f125   1Gi        RWO            csi-hostpath-sc   23h
hpvc-restore   Bound    pvc-a636c44c-77b8-49a2-bc27-dafd32d30406   1Gi        RWO            csi-hostpath-sc   8s
[root@master-node csi-driver-host-path]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                  STORAGECLASS      REASON   AGE
pvc-93a40c63-0eab-47c8-92ee-34f8db56f125   1Gi        RWO            Delete           Bound    default/csi-pvc        csi-hostpath-sc            23h
pvc-a636c44c-77b8-49a2-bc27-dafd32d30406   1Gi        RWO            Delete           Bound    default/hpvc-restore   csi-hostpath-sc            10s
[root@master-node csi-driver-host-path]# cp -p kubectl exec -it my-csi-app /bin/sh
/ # / # 
/ # 
/ # 
/ # 
/ # ls /data/ # ls /data// # ls /data/hello-world 
/data/hello-world
/ # rm /data/hello-world
/ # ls /data/
/ # 
/ # 
/ # exit
[root@master-node csi-driver-host-path]# kubectl apply -f csi-app_changeVolume.yaml
pod/my-csi-app-with-snapshot-2 created
[root@master-node csi-driver-host-path]# kubectl exec -it my-csi-app-with-snapshot-2 /bin/sh
/ # / # 
/ # 
/ # ls /da/ # ls /data/
hello-world
/ # / # ls /data/he/ # ls /data/hello-world 
/data/hello-world
/ # cat /data/hello-world
```


# 詳しく見る
## PVCとは
https://kubernetes.io/docs/concepts/storage/persistent-volumes/

> A PersistentVolumeClaim (PVC) is a request for storage by a user. It is similar to a Pod. Pods consume node resources and PVCs consume PV resources. Pods can request specific levels of resources (CPU and Memory). Claims can request specific size and access modes (e.g., they can be mounted once read/write or many times read-only).

すなわち、PVCとはどのようなボリュームが欲しいか、を記載した定義であって、実際に使用されるボリューム(PV)はこの定義に沿ってStorage Classから割り当てられる。

## Storage Classとは
https://kubernetes.io/docs/concepts/storage/storage-classes/
> A StorageClass provides a way for administrators to describe the “classes” of storage they offer. Different classes might map to quality-of-service levels, or to backup policies, or to arbitrary policies determined by the cluster administrators. Kubernetes itself is unopinionated about what classes represent. This concept is sometimes called “profiles” in other storage systems.

Storage Classとはどのようなストレージからボリュームを払い出すか、を記載した定義。
provisionerというパラメータを持ち、そこでどのプラグイン(バックエンド)からストレージの提供を受けるかが定義される。

## 実際どのように定義されているか

csi-appというpodは、my-csi-volumeというPVCを/dataにマウントしている。
my-csi-volume自体は同じyaml内で定義されていて、csi-pvcという名前のPersistent Volume Claim(PVC)に対してつけた名前(ラベル)。

```yaml: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
```

csi-pvcというPVCは以下のyamlで定義されている。サイズと読み書き権限について記載がある。また元になるStorage Classについて指定がある。

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

Storage Classについては以下で定義されている。provisionerとして、hostpathを使用している。
hostpath.csi.k8s.ioは、HostPathドライバのインストール時(deploy/kubernetes-1.17/deploy-hostpath.sh)に構成される。

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

HostPathドライバは以下のように設定されており、ボリュームの実体の置き場所も記載されている。

```shell-session
[root@master-node csi-driver-host-path]# kubectl describe pod csi-hostpathplugin-0

Volumes:
  csi-data-dir:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/csi-hostpath-data/
    HostPathType:  DirectoryOrCreate

[root@master-node csi-driver-host-path]# ls /var/lib/csi-hostpath-data/
af6400b6-391a-11ea-9916-669818be6e8d  dd805657-3919-11ea-9916-669818be6e8d.tgz  e2df0611-3854-11ea-9916-669818be6e8d
[root@master-node csi-driver-host-path]# ls /var/lib/csi-hostpath-data/af6400b6-391a-11ea-9916-669818be6e8d/
hello-world
[root@master-node csi-driver-host-path]# ls /var/lib/csi-hostpath-data/e2df0611-3854-11ea-9916-669818be6e8d/
```