k8s, はじめてのデプロイ

# はじめに
前の記事の続き。構築したk8s クラスタに NGINX をデプロイし、適当なコンテンツにアクセスすることで動作を確かめる。

https://qiita.com/Shakapon/items/6beb6c027997438cfcd8

コンテンツの配備手法に分け、構築パターンは大きく3種類となる。NFSの利用方法はPV/PVCを利用するパターンとCSIを使うパターンで2つあり、今回は両者とも試す。
1. Worker ノードのローカルディスクに静的なHTMLファイルを配置する
2. NFSサーバを構築してコンテンツをNFS上に配備する
2-1. k8sからはPV/PVCを経由でアクセスするパターン
2-2. k8sからCSIを利用するパターン
3. CephFSサーバを構築してコンテンツはCephFS上に配備し、CephFSをWorker ノードにマウントして使用する。

今回はCSI利用までとし、CephFSは別にする。

# 1. Hello k8s! - ローカルディスクに配備
作成順序は以下。
1. 各 Workerノードに ```/mnt/data/index.html``` を作成
2. ```hostPath``` ボリュームでこのファイルをPodにマウント
3. ```NodePort``` を使って外部公開し、curl やブラウザから確認

:::note info
`hostPath`?
k8s-workerノード上のディレクトリやファイルを、Pod のコンテナにマウントする仕組み。
言い換えると、コンテナ内で見えるファイルパスを、実際にはホスト側のファイルシステムに接続するというもの。
:::

## 1-1. 各 Worker ノードでファイル作成
Worker1/2両者で以下を作成する。

```bash
mkdir -p /mnt/data
echo "<h1>Hello from $(hostname)</h1>" | sudo tee /mnt/data/index.html
```

## 1-2. NGINX Deployment
k8s を実際に利用する上ではあまり使わない設定ではあるが、
```hostPath```指定＋```nodeSelector```でk8s-Worker1を明示的に指定する。

```yaml nginx-hostpath-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  # Deploymentの名前。NameSpace内ではユニークである必要がある
  name: nginx-hostpath
spec:
  # レプリカ数の指定
  replicas: 1
  selector:
    matchLabels:
      # 管理対象のPodの名前 template.metadata.labels.appとは原則一致させること
      app: nginx-hostpath
  template:
    metadata:
      labels:
        # 実際にデプロイするPodの名前 spec.selector.matchLabelsとは原則一致させること
        app: nginx-hostpath
    spec:
      nodeSelector:
        kubernetes.io/hostname: <worker1のノード名>  # `kubectl get nodes -o wide`で確認
      containers:
      - name: nginx
        image: nginx:stable
        ports:
        - containerPort: 80
        volumeMounts:
        # Pod内コンテナから見えるパス。実態はvolumes.hostpath.pathで指定した/mnt/dataになる
        - mountPath: /usr/share/nginx/html
          name: html-volume
      volumes:
      - name: html-volume
        hostPath:
          # Worker ノード上の絶対パス
          path: /mnt/data
          type: Directory
```

:::note info
```spec.selector.matchLabels```と```spec.template.metadata.labels```
この二つは原則一致させる。ただし、Job（1回限りの処理）や CronJob（定期処理）、StatefulSet（State を持つ Pod）との共存する際に使わないことがないわけではない模様。
:::

:::note info
**Pod**と**コンテナ**
厳密にはPod ⊃ コンテナとなり、イコールではない。今回の例では 1 Pod = 1コンテナになっているが、1 Podに複数のコンテナを持たせるケースもある。
:::

:::note info
```containerPort: 80```
Pod内でどのポートが開いているかを**宣言する**ためのもの。あくまでも情報提供用のフィードのため、これがなくともコンテナは動作するし、このPort番号でもって外部からアクセスできるわけではない。ただし、`Service`や`kubectl describe pod`などの表示上、Podがどのポートで通信しているかを把握するうえで重要なヒントになるため、宣言しておくことが推奨される。
:::

:::note info
 ```image: nginx:stable```
 **Docker Hubの公式NGINXイメージ** をとってくる。実際には ```docker.io/library/nginx:stable``` からDownloadすることになる。
:::

## 1-3.NodePort Service
Podを外部公開するため、Serviceを作成する。
```nginx-hostpath-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-hostpath-service
spec:
  type: NodePort
  selector:
    app: nginx-hostpath
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
```
## 1-4. Port ???
`Port`, `targetPort`, `NodePort`, また、NGINX Deploymentで使った`containerPort` といろいろと出てきて混乱する。まず、対応関係全体図は以下のようになっている。

```plaintext
[外部PC]
  ↓ http://<NodeIP>:30080     ← nodePort（Serviceが開ける外部ポート）
[Node (kube-proxy)]
  ↓ ルーティング
Service (type: NodePort)
  ↓ port: 80（Service内部ポート）
  ↓ targetPort: 80（PodのPortに転送）
[Pod]
  ↓
containerPort: 80（実際にコンテナ内でアプリがListenしているPort）
```

一覧にまとめるとこんな感じ。

| フィールド名| 設定ポイント | 意味 | 例  |
|:-:|:-:|:-:|:-:|
| `containerPort` | **Deployment** のコンテナ定義内| Pod内コンテナがListenしているポート| 80|
| `targetPort`    | **Service**| Service がトラフィックを **Podのどのポートに転送するか** | 80（`name` 指定も可能）  |
| `port`          | **Service**| Service 自体のポート番号（クラスタ内部で使用）| 80                    |
| `nodePort`      | **Service** （type: NodePort時） | 外部IP + このポートでアクセスできる| 30080（範囲：30000〜32767） |

## 1-5. 適用
```bash
kubectl apply -f nginx-hostpath-deployment.yaml
kubectl apply -f nginx-hostpath-service.yaml
```

## 1-6. 正常性確認
### 1-6-1. Podが正しく起動したか？
```console
ubuntu@k8s-master:~$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
nginx-hostpath-7b4445db8d-bsz7l   1/1     Running   1 (145m ago)   6h32m
```
StatusがRunning になっていればOK.

### 1-6-2. Serviceが正しく設定されているか？
```console
ubuntu@k8s-master:~$ kubectl get service
NAME                     TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes               ClusterIP   172.16.0.1       <none>        443/TCP        3d3h
nginx-hostpath-service   NodePort    172.16.246.180   <none>        80:30080/TCP   3d
```
contaierPort:80とNodePort 30080が紐づいていればOK.

### 1-6-3. アクセス確認
```console
ubuntu@k8s-master:~$ curl http://192.168.11.122:30080
<h1>Hello from k8s-worker1</h1>
```
ここで`192.168.11.122`はk8s-worker1の外部IP。ブラウザでもよいが、最小限の確認として`curl`でとれていることを確認する

### 1-6-4. Podに入ってみる
実際に動き出した Pod に入り状態を確認してみる。
構文はこんな感じ。
`kubectl exec -it コンテナ名 -- コンテナ内で実行するコマンド`

```sh
ubuntu@k8s-master:~$ kubectl exec -it nginx-hostpath-6f6f977f68-tx8zx -- /bin/sh
# ls 
# ls
bin   dev                  docker-entrypoint.sh  home  lib64  mnt  proc  run   srv  tmp  var
boot  docker-entrypoint.d  etc                   lib   media  opt  root  sbin  sys  usr
# exit
```
ここで`-it` は対話モード（インタラクティブ）、　`--`は UNIX系のオプションでk8s以外にも使われる特殊な記号。具体的にはPod名などのオプションと、Pod内で実行したいコマンドを区切るために使用する。`exec -it` を利用する際は少なくとも一つのPod 内で実行するコマンドを書く必要がある。`/bin/sh`であって`/bin/bash`ではないのは、コンテナに含まれるパッケージは最低限に絞られており、`/bin/bash`も含まれていないため（`ps`や`vi`も入っていない）。`sh`から抜けるには単に`exit`でよい。

### 1-6-5. ダメな例
コンテナ内で何を実行するのかを指定していないと怒られる。
```console
ubuntu@k8s-master:~$ kubectl exec -it nginx-hostpath-7b4445db8d-bsz7l
error: you must specify at least one command for the container
```

### 1-6-6. 対話モードでのpod操作の限界
状態確認はできる。設定変更はできない。コンテナにviが含まれていないとかもあるが、それ以前に設定変更したところで Podが再構築されると設定は初期化される。 Podの設定を変更したいのならyamlファイルで行うしかない。

## 1-7. hostPath の限界
hostPathは検証目的でPodを構築するだけであれば十分だが、
特定のノードに依存するためスケールしない（e.g. worker1/2それぞれのローカルディスクを見ることになる）、セキュリティ的にホストのファイルシステムへアクセスさせるのはリスクがあり、実環境に適用するには心もとない。

マルチノード環境での永続的なストレージを確保するためには、NFSやクラウドプロバイダーのストレージサービスを利用したPersistentVolume（PV）とPersistentVolumeClaim（PVC）の使用が推奨される。これにによって、Podがどのノードで起動しても一貫したストレージアクセスが可能になる。

hostPath 実環境での利用は、**物理ハードウェアやUSB機器など、ホストにしか存在しない特殊デバイスとの連携** などにとどめることが推奨される。


# 2. PV/PVCを使ってみる。
NGINXが参照するファイルをWorker Pod上に直接配備する例では当然ながらスケールしない。Podを増やしていったとしても、すべてのPodが共通のコンテンツにアクセスできる（RWX = Read Write Many）よう、共通のディスク領域を用意する必要がある。
k8s ではこれを実現するためにPV（Persistent Volume）という仕組みが用意されている。

## 2-1. NFS を構築する
ストレージをどのように作るかの議論は非常に奥深いものがあるが、ここでは最も簡単な`NFS`を用いる手法を試す。

### 2-1-1. NFSの用意（サーバサイド）
今回のラボ構成ではk8s-master ノード上でNFSを動作させる。
```console
# NFSインストールし、/srv/nfs/htdocを共有ディレクトリにする。
sudo apt update
sudo apt install -y nfs-kernel-server
sudo mkdir -p /srv/nfs/htdocs
sudo chmod -R 777 /srv/nfs/htdocs

# exports設定（例）
echo "/srv/nfs/htdocs *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
sudo exportfs -rav # /exc/exports の内容を再度読み込み
sudo systemctl restart nfs-server
```

#### 2-1-1-1. Export 設定のオプション
| オプション | 2 |
|:-:|:-:|
|rw|読み書きを許可（Read/Write）|
|sync|書き込み操作はすぐにディスクへ同期（安全だが遅くなる）|
|no_subtree_check|サブディレクトリのチェックを行わない（高速化）|
|no_root_squash   |クライアント側の root ユーザを、そのままサーバ上でも root として扱う|

:::note info
no_root_squash
通常は安全性を考えて`root_squash`（= root を nobody に変換）が推奨される。今回の NFS 検証ではアクセス権周りの問題回避のため `no_root_squash`を使用するとした。
:::

### 2-1-2. NFSの用意（Workerノード側）
用意としてはNFS-commonをインストールするだけ。実際にどのNFS領域をマウントするかはk8s側から行うため、**ノード側での永続マウントは不要**。
```console
sudo apt update
sudo apt install -y nfs-common
```

## 2-2. k8s マニフェスト作成
### 2-2-1. そも、PV/PVC とは？
- PV とは、**k8s管理者やCSIドライバによってあらかじめ提供される** ストレージリソース
- PVC とは、**ユーザ/Podからのストレージ要求** 

PV（実際に利用できるストレージ量）/PVC（クライアントが利用したい容量）を、k8sがバインドさせる。ユーザサイドでPVCを出すと、デフォルトではk8sが最適なPVを選んでくれる。しかし、PVが複数ある本番環境においては、事故防止のため明示的に紐づけを行うことが推奨される。この例では**自動でマッチングする**手法を採用。

### 2-2-2. PersistentVolume/PersistentVolumeClaim を作成

```nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /srv/nfs/htdocs
    server: <MASTER_NODE_IP>
  persistentVolumeReclaimPolicy: Retain
```

| フィールド                                   | 意味                                                     |
| --------------------------------------- | ------------------------------------------------------ |
| `capacity.storage`                      | 確保したストレージサイズをk8sに宣言 |
| `accessModes`                           | `ReadWriteMany` は同一クラスタの複数Podからなら同時に読み書き可能          |
| `nfs.path` / `server`                   | 実際のNFSマウント先（K8sノードから見たパス）                              |
| `persistentVolumeReclaimPolicy: Retain` | PVCが削除されても実体は保持（上書きや消去防止）                            |

:::note info
`capacity.storage` 
この例では1Gi 確保したとしているが、実際にNFS側がこの容量に制限されるわけではない。あくまでも宣言。この辺りを厳密にやろうとすると、NFSでは無理が出てくる。
:::

```nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
```

| フィールド                        | 意味                                |
| ---------------------------- | --------------------------------- |
| `accessModes`                | `ReadWriteMany` を要求。PVと一致する必要あり   |
| `resources.requests.storage` | このPVCが最低限必要とする容量（例：1Gi） |

### 2-2-3. サンプルスクリプト
```touch /srv/nfs/htdocs/index.html```でindex.htmlを空でよいので作成。

```/srv/nfs/htdocs/start.sh
#!/bin/sh
HOSTNAME=$(hostname)
echo "<html><body><h1>Hello from $HOSTNAME</h1></body></html>" > /usr/share/nginx/html/index.html
nginx -g 'daemon off;'
```

```bash
chmod +x /srv/nfs/htdocs/start.sh
```

## 2-3. NGINXのデプロイ
```nginx-nfs-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-nfs
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-nfs
  template:
    metadata:
      labels:
        app: nginx-nfs
    spec:
      securityContext:
        appArmorProfile:
          type: Unconfined    
      containers:
      - name: nginx
        image: nginx:stable
        command: ["/bin/sh", "-c", "/mnt/start.sh"]
        volumeMounts:
        - mountPath: /mnt
          name: nfs-volume
      volumes:
      - name: nfs-volume
        persistentVolumeClaim:
          claimName: nfs-pvc
```

特徴的なのが（当然だが）`volumes.persistentVolumeClaim`で`nfs-pvc`を指定しているところ。

:::note info
Excuse
`spec.securityContext.appArmorProfile.type` は正直なところおまじない。今回の検証ではAppArmorについてはデフォルトからは変更していないため、本来は不要のはず。ただ、ラボ環境のAppArmor周りがどこかおかしくなってしまった様子で、W/Aとして入れている。
:::

```nginx-nfs-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-nfs-service
spec:
  type: NodePort
  selector:
    app: nginx-nfs
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080
```

### 2-3-1. デプロイしてみる
```console
kubectl apply -f nfs-pv.yaml
kubectl apply -f nfs-pvc.yaml
kubectl apply -f nginx-nfs-deployment.yaml
kubectl apply -f nginx-nfs-service.yaml
```

### 2-3-2. 正常性確認
```console
ubuntu@k8s-master:~$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
nginx-hostpath-7b4445db8d-bsz7l   1/1     Running   0          3h28m
nginx-nfs-77bbfbb487-mqwvq        1/1     Running   0          2m32s
nginx-nfs-77bbfbb487-wgmnk        1/1     Running   0          2m24s
```
`nginx-nfs-***`が running であること

```console
ubuntu@k8s-master:~$ kubectl get pvc
NAME      STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
nfs-pvc   Bound    nfs-pv   1Gi        RWX                           <unset>                 99m
```
PVC が Bound になっていること。

```console
ubuntu@k8s-master:~$ curl http://192.168.11.122:30180
<html><body><h1>Hello from nginx-nfs-c8544878d-jnpwb</h1></body></html>
ubuntu@k8s-master:~$ curl http://192.168.11.122:30180
<html><body><h1>Hello from nginx-nfs-c8544878d-vm976</h1></body></html>
ubuntu@k8s-master:~$ curl http://192.168.11.122:30180
<html><body><h1>Hello from nginx-nfs-c8544878d-vm976</h1></body></html>
ubuntu@k8s-master:~$ curl http://192.168.11.122:30180
<html><body><h1>Hello from nginx-nfs-c8544878d-jnpwb</h1></body></html>
```
実際にアクセスして別々の Pod から応答が得られること。

## 2-4. PV/PVC の限界
PV/PVCの仕組みによって宣言的なリソース管理が可能になる。しかし、PVは実態としてのストレージデバイスごとの違い、またオンプレ、クラウドの違いもすべて手動で吸収する必要がある。CSIを導入することでこの辺りを抽象化、Storage Classとして共通な Interfaceを使用できるようになる。

# 3. CSI を使ってみる

## 3-1. CSIとStorageClass?
**CSI（Container Storage Interface）**は、Kubernetesが外部のストレージシステムと連携するための標準インターフェースのこと。k8s v1.13 から利用できるようになった。
CSIを利用することで、統一的な仕組みで Storage へアクセスできるようになった。ストレージベンダーから見ると、k8s のコアコードを変更することなく、自社のストレージをk8sに統合できるようになった。

CSIを利用することで、PVCの作成時に必要な Storageが自動的にプロビジョニングされるようになり、手動でのPV作成が不要になった。このほか、スナップショットなどの高度なStorage機能の利用、Security やAccess制御の高度化が可能になる。

StorageClassは、k8sで異なるストレージの Class を定義するリソースのこと。各StorageClassは、特定のCSIドライバー（provisioner）を指定し、そのドライバーに渡すパラメータを含められる。

この関係により、ユーザーはStorageClassを通じて、必要なストレージの種類や特性を抽象的に指定し、実際のストレージのプロビジョニングや管理はCSIドライバーが担う構造となっている。

この規模ではあまりメリットはないが、NFSをPVの代わりにCSI を利用して領域を割り当てることにする。

## 3-2. CSIドライバの導入
原本はこちら。Helm からのインストールが推奨のようだが、今回はkubectl ベースでのインストール（Helmは検証前...）

https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/docs/install-csi-driver-v4.11.0.md

```console
ubuntu@k8s-master:~$ curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/v4.11.0/deploy/install-driver.sh | bash -s v4.11.0 --
Installing NFS CSI driver, version: v4.11.0 ...
serviceaccount/csi-nfs-controller-sa created
serviceaccount/csi-nfs-node-sa created
clusterrole.rbac.authorization.k8s.io/nfs-external-provisioner-role created
clusterrolebinding.rbac.authorization.k8s.io/nfs-csi-provisioner-binding created
clusterrole.rbac.authorization.k8s.io/nfs-external-resizer-role created
clusterrolebinding.rbac.authorization.k8s.io/nfs-csi-resizer-role created
csidriver.storage.k8s.io/nfs.csi.k8s.io created
deployment.apps/csi-nfs-controller created
daemonset.apps/csi-nfs-node created
NFS CSI driver installed successfully.
```

### 3-2-1. 正常性確認
CSIドライバをインストールすると`csi-nfs-controller`（NFS CSI ドライバのコントローラー側）と`csi-nfs-node`（各ノードに1つずつ常駐するドライバ）が`kube-system`にインストールされる。確認方法は以下。

```console
ubuntu@k8s-master:~$ kubectl -n kube-system get pod
NAME                                      READY   STATUS    RESTARTS         AGE
(snip)
csi-nfs-controller-5bf646f7cc-f7nkp       5/5     Running   0                24m
csi-nfs-node-8gwp2                        3/3     Running   0                24m
csi-nfs-node-c8f2k                        3/3     Running   3 (17m ago)      24m
csi-nfs-node-mvrc9                        3/3     Running   0                24m
(snip)
ubuntu@k8s-master:~$ kubectl -n kube-system get pod -o wide -l app=csi-nfs-controller
NAME                                  READY   STATUS    RESTARTS   AGE   IP               NODE          NOMINATED NODE   READINESS GATES
csi-nfs-controller-5bf646f7cc-f7nkp   5/5     Running   0          19m   192.168.11.123   k8s-worker2   <none>           <none>
ubuntu@k8s-master:~$ 
ubuntu@k8s-master:~$ kubectl -n kube-system get pod -o wide -l app=csi-nfs-node
NAME                 READY   STATUS    RESTARTS      AGE   IP               NODE          NOMINATED NODE   READINESS GATES
csi-nfs-node-8gwp2   3/3     Running   0             20m   192.168.11.123   k8s-worker2   <none>           <none>
csi-nfs-node-c8f2k   3/3     Running   3 (19m ago)   20m   192.168.11.122   k8s-worker1   <none>           <none>
csi-nfs-node-mvrc9   3/3     Running   0             20m   192.168.11.121   k8s-master    <none>           <none>
```

## 3-2. NFSをCSIから使ってみる。
### 3-2-1. StorageClass
CSI を利用する場合、PVを定義せず、代わりに StorageClassを利用する。StorageClassを利用すればPVを個別に定義することなく、新しいPodが作られるたびに自動でStorageが作られていく。

```nfs-sc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
  server: 192.168.11.121
  share: /exported/path
reclaimPolicy: Retain
volumeBindingMode: Immediate
```

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

`nfs-pvc.yaml`との差分は`storageClassName: nfs-csi`が追加されたことのみ。ストレージクラス名は`nfs-sc.yaml`で定義したものとそろえる

### 3-2-1. 正常性確認
`kubectl get pvc` で新しく定義したPVのStatusがBoundとなっていればOK
```console
ubuntu@k8s-master:~$ kubectl get storageClass
NAME      PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-csi   nfs.csi.k8s.io   Retain          Immediate           false                  3s
```
```console
ubuntu@k8s-master:~$ kubectl get pvc
NAME              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
nfs-dynamic-pvc   Bound    pvc-342392a4-28d7-4aa2-8d3f-56406398277b   1Gi        RWX            nfs-csi        <unset>                 7s
```
ここで`pvc-342392a4-28d7-4aa2-8d3f-56406398277b`ができているが、NFSサーバ上に同名のディレクトリが作成されていることが確認できる。

```console
ubuntu@k8s-master:~$ ls /srv/nfs/htdocs/
index.html  pvc-342392a4-28d7-4aa2-8d3f-56406398277b  start.sh
```


## 3-3. コンテンツの準備
前の正常性確認で、nfs-dynamic-pvcが`/srv/nfs/htdocs`の配下に`pvc-342392a4-28d7-4aa2-8d3f-56406398277b`が作られたことが確認できた。このディレクトリに先ほど作った`start.sh`をコピーする。
NFS をexport する際、`no_root_squash`を指定して作成したため、PVCのアクセス権限はroot:root で作成されているのに注意。
```console
ubuntu@k8s-master:/srv/nfs/htdocs$ pwd
/srv/nfs/htdocs
ubuntu@k8s-master:/srv/nfs/htdocs$ sudo cp ./index.html ./pvc-342392a4-28d7-4aa2-8d3f-56406398277b/
ubuntu@k8s-master:/srv/nfs/htdocs$ sudo cp ./start.sh ./pvc-342392a4-28d7-4aa2-8d3f-56406398277b/
```

## 3-4. Deployment と Service 
差分が出てくるのはspec.template.spec.volumes.persistentVolumeClaim.claimNameが`nfs-sc-pvc.yaml`で指定した`nfs-dynamic-pvc`となっている点。ほかはpodの名称を変更したのみ。

`nginx-nfs-csi-service.yaml`もnodePort の番号を少しずらした。

```nginx-nfs-csi-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-nfs-csi
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-nfs-csi
  template:
    metadata:
      labels:
        app: nginx-nfs-csi
    spec:
      containers:
      - name: nginx
        image: nginx:stable
        command: ["/bin/sh", "-c", "/mnt/start.sh"]
        volumeMounts:
        - mountPath: /mnt
          name: nfs-volume
      volumes:
      - name: nfs-volume
        persistentVolumeClaim:
          claimName: nfs-dynamic-pvc
```

```/nginx-nfs-csi-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-nfs-csi-service
spec:
  type: NodePort
  selector:
    app: nginx-nfs-csi
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30280
```

## 3-5. 適用と正常性確認
```console
ubuntu@k8s-master:~$ kubectl apply -f ./nginx-nfs-csi-deployment.yaml
deployment/nginx-nfs-csi-deployment created
ubuntu@k8s-master:~$ kubectl apply -f ./nginx-nfs-csi-service.yaml
service/nginx-nfs-csi-service created
```

### 3-5-1. Service の確認
```console
ubuntu@k8s-master:~$ kubectl get service
NAME                     TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes               ClusterIP   172.16.0.1       <none>        443/TCP        3d3h
(snip)
nginx-nfs-csi-service    NodePort    172.16.9.152     <none>        80:30280/TCP   4s
```

### 3-5-2. Pod の確認
```console
ubuntu@k8s-master:~$ kubectl get pod
NAME                              READY   STATUS    RESTARTS       AGE
(snip
nginx-nfs-csi-6b564979c-ftw92     1/1     Running   0              12m
nginx-nfs-csi-6b564979c-n9ndv     1/1     Running   0              12m
```

### 3-5-3. NodePort での応答を確認する
正しく応答してくれる。
```console
ubuntu@k8s-master:~$ curl http://192.168.11.122:30280
<html><body><h1>Hello from nginx-nfs-csi-6b564979c-ftw92</h1></body></html>
ubuntu@k8s-master:~$ curl http://192.168.11.122:30280
<html><body><h1>Hello from nginx-nfs-csi-6b564979c-n9ndv</h1></body></html>
ubuntu@k8s-master:~$ curl http://192.168.11.122:30280
<html><body><h1>Hello from nginx-nfs-csi-6b564979c-ftw92</h1></body></html>
```

## 3-6. NFSの限界
PVで`spec.capacity.storage`で用意するディスクサイズを、PVCで`spec.resources.requests.storage`で必要なディスクサイズを宣言している。では、宣言した量を超えてpodがデータを使うとなるとどうなるかというと、実は監視されておらず、**k8sの範囲の中では利用できてしまう**。実際に絞ろうとすると、`xfs_quota`や`edquota`などを使ってNFS側で頑張るしかない。この辺り、k8sの宣言型で進めるという理想と、実際のStorage 実装のはざまに落ちる。

k8s側からCSIを通じて容量管理をもくろんだ場合、CephFS などといった別のソリューションを探ることになる。