[Kubernetes]Resource Quotaの動作を確認する

#はじめに
今回はResource Quotaによるリソース制御の動作を確認したいと思います。
Resource Quotaは「namespace単位」でリソースを制御することができます。

制御できる種類としては、以下の3つがあります。

* Compute Resource Quota
* Storage Resource Quota
* Object Count Quota

#Compute Resource Quota
Compute Resource Quotaは、CPU/メモリ/GPUに対して上限（limits）/下限（requests）を設定します。

##Resource Quotaの作成
以下のマニフェストのResource Quotaを作成します。ここでは「namespace: dev」に対して制限をかけます。
namespaceはあらかじめ作成済みです。

```computeResourceQuota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-resource-quota
  namespace: dev
spec:
  hard:
    limits.cpu: 0.5
    limits.memory: 500Mi
    requests.cpu: 0.2
    requests.memory: 200Mi
```
```
$ kubectl apply -f computeResourceQuota.yaml
resourcequota/compute-resource-quota created
$ kubectl describe -n dev resourcequotas compute-resource-quota
Name:            compute-resource-quota
Namespace:       dev
Resource         Used  Hard
--------         ----  ----
limits.cpu       0     500m
limits.memory    0     500Mi
requests.cpu     0     200m
requests.memory  0     200Mi
```

##動作確認
Pod（Deployment）をデプロイします。まずはlimits/requestsを設定しないでapplyしてみます。

```
$ kubectl apply -f nginx_redis.yaml
deployment.apps/resource-quota created
$ kubectl -n dev get deployments.apps
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
resource-quota   0/2     0            0           108s
$ kubectl -n dev describe replicasets.apps resource-quota-77d577889b
Name:           resource-quota-77d577889b
Namespace:      dev
・・・
Events:
  Type     Reason        Age                 From                   Message
  ----     ------        ----                ----                   -------
  Warning  FailedCreate  2m1s                replicaset-controller  Error creating: pods "resource-quota-77d577889b-6xn5d" is forbidden: failed quota: compute-resource-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memory
  Warning  FailedCreate  2m1s                replicaset-controller  Error creating: pods "resource-quota-77d577889b-5dhb6" is forbidden: failed quota: compute-resource-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memory
  Warning  FailedCreate  2m1s                replicaset-controller  Error creating: pods "resource-quota-77d577889b-bv84g" is forbidden: failed quota: compute-resource-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memory
  Warning  FailedCreate  2m1s                replicaset-controller  Error creating: pods "resource-quota-77d577889b-hbn26" is forbidden: failed quota: compute-resource-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memory
  Warning  FailedCreate  2m1s                replicaset-controller  Error creating: pods "resource-quota-77d577889b-h6vdw" is forbidden: failed quota: compute-resource-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memory
  Warning  FailedCreate  2m1s                replicaset-controller  Error creating: pods "resource-quota-77d577889b-9brcv" is forbidden: failed quota: compute-resource-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memory
  Warning  FailedCreate  2m1s                replicaset-controller  Error creating: pods "resource-quota-77d577889b-tmh2h" is forbidden: failed quota: compute-resource-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memory
  Warning  FailedCreate  2m1s                replicaset-controller  Error creating: pods "resource-quota-77d577889b-7ll7x" is forbidden: failed quota: compute-resource-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memory
  Warning  FailedCreate  2m                  replicaset-controller  Error creating: pods "resource-quota-77d577889b-bqtjx" is forbidden: failed quota: compute-resource-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memory
  Warning  FailedCreate  39s (x6 over 119s)  replicaset-controller  (combined from similar events): Error creating: pods "resource-quota-77d577889b-brs24" is forbidden: failed quota: compute-resource-quota: must specify limits.cpu,limits.memory,requests.cpu,requests.memor
```
limits.cpuなどを設定するのは必須だとエラーになっていますね。

今度は各コンテナにリソース制御を設定します。1つのPodに2つのコンテナが構成されていて、各コンテナのCPU/メモリのlimits/requestsの合計がResourceQuotaの設定値になるようにしています。

```nginx_redis.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: resource-quota
  namespace: dev
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app1
  template:
    metadata:
      labels:
        app: app1
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          resources:
            requests:
              memory: 100Mi
              cpu: 100m
            limits:
              memory: 200Mi
              cpu: 200m
        - name: redis
          image: redis:latest
          resources:
            requests:
              memory: 100Mi
              cpu: 100m
            limits:
              memory: 300Mi
              cpu: 300m
```

```
$ kubectl apply -f nginx_redis.yaml
deployment.apps/resource-quota created
$ kubectl -n dev get pod
NAME                              READY   STATUS    RESTARTS   AGE
resource-quota-57b9c6c54c-dsn7v   2/2     Running   0          46s
```

replica数が「2」なのに、Podが一つしか起動していないですね。
マニュアルをよく読むと、全てのPodの合計がResource Quotaで設定した値に収まらないといけないですね。

>[Compute Resource Quota](https://kubernetes.io/docs/concepts/policy/resource-quotas/#compute-resource-quota)
>Across all pods in a non-terminal state, the sum of CPU limits cannot exceed this value.

各コンテナのrequests/limitsの値を調整して収まるようにすると、デプロイに成功しました。

```
$ kubectl apply -f nginx_redis.yaml
deployment.apps/resource-quota created
$ kubectl -n dev get pod
NAME                             READY   STATUS    RESTARTS   AGE
resource-quota-7dcf5bc48-22fcr   2/2     Running   0          13s
resource-quota-7dcf5bc48-zvfkr   2/2     Running   0          13s
$ kubectl -n dev describe resourcequotas
Name:            compute-resource-quota
Namespace:       dev
Resource         Used   Hard
--------         ----   ----
limits.cpu       400m   500m
limits.memory    400Mi  500Mi
requests.cpu     200m   200m
requests.memory  200Mi  200Mi
```


#Storage Resource Quota
Storage Resource Quotaは、PVC（PersistentVolumeClaim）の容量とStorageClassに対して制限をかけることができます。

##Resource Quotaの作成
以下のマニフェストのResource Quotaを作成します。ここでは「namespace: dev」に対して制限をかけます。

```storageResourceQuota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: storage-resource-quota
  namespace: dev
spec:
  hard:
    requests.storage: 50Mi
    zfs-sc.storageclass.storage.k8s.io/requests.storage: 100Mi
```

```
$ kubectl apply -f storageResourceQuota.yaml
resourcequota/storage-resource-quota created
$ kubectl -n dev describe resourcequotas
Name:                                                storage-resource-quota
Namespace:                                           dev
Resource                                             Used  Hard
--------                                             ----  ----
requests.storage                                     0     50Mi
zfs-sc.storageclass.storage.k8s.io/requests.storage  0     100Mi
```
なお、StorageClassは以前作成したものを利用しています。
[参考：[Kubernetes]Dynamic Provisioningの動作を確認する](https://qiita.com/dingtianhongjie/items/6142bfdddbbf627da055)

```
$ kubectl get storageclasses.storage.k8s.io
NAME     PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
zfs-sc   gentics.com/zfs   Retain          Immediate           false                  5d23h
```

##動作確認
###PVC
まずはrequests.storageの設定に対して、PVCの動作を確認してみます。

以下のマニフェストのPVを作成します。

```pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0001
  namespace: dev
spec:
  capacity:
    storage: 50Mi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /share
    server: k8s-client
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0002
  namespace: dev
spec:
  capacity:
    storage: 100Mi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /share
    server: k8s-client
```

```
$ kubectl apply -f pv.yaml
persistentvolume/pv0001 created
persistentvolume/pv0002 created
$ kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv0001   50Mi       RWX            Recycle          Available           slow                    12s
pv0002   100Mi      RWX            Recycle          Available           slow                    12s
```

requests.storageは「50Mi」で設定しました。pv0002はその値を超えていますが、この時点ではエラーにはなってないですね。

次にPVCを作成します。

```pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim1
  namespace: dev
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 50Mi
  storageClassName: slow
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim2
  namespace: dev
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 100Mi
  storageClassName: slow
```
```
$ kubectl apply -f pvc.yaml
persistentvolumeclaim/myclaim1 created
Error from server (Forbidden): error when creating "pvc.yaml": persistentvolumeclaims "myclaim2" is forbidden: exceeded quota: storage-resource-quota, requested: requests.storage=100Mi, used: requests.storage=50Mi, limited: requests.storage=50Mi
```

1つ目のPVCはClaimできましたが、2つ目は制限を超えたためエラーになりました。
2つ目は100MiでClaimしましたので、仮に1つだけだったとしてもエラーになります。また、requests.storageの設定値は「合計値」ですので、2つ目のPVCが50Mi以下だったとしても、合計値で制限を超えてしまいます。

###StorageClass
今度はzfs-sc.storageclass.storage.k8s.io/requests.storageの設定値に対して動作を確認します。
以下のマニフェストでDynamic ProvisioningでPVCを作成します。

```pvc-sc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: zfs-pvc001
  namespace: dev
  annotations:
    volume.beta.kubernetes.io/storage-class: "zfs-sc"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 100Mi
```

```
$ kubectl apply -f pvc-sc.yaml
Error from server (Forbidden): error when creating "pvc-sc.yaml": persistentvolumeclaims "zfs-pvc001" is forbidden: exceeded quota: storage-resource-quota, requested: requests.storage=100Mi, used: requests.storage=50Mi, limited: requests.storage=50Mi
```

StorageClassに対しては「100Mi」で制限をかけていますが、同じnamespaceでPVCに対して50Miで制限をかけていますので、こちらの制限に引っかかってますね。
なので、PVCを削除して再度applyしてもエラーになります。

```
$ kubectl delete -f pvc.yaml
persistentvolumeclaim "myclaim1" deleted
Error from server (NotFound): error when deleting "pvc.yaml": persistentvolumeclaims "myclaim2" not found
$ kubectl describe -n dev resourcequotas
Name:                                                storage-resource-quota
Namespace:                                           dev
Resource                                             Used  Hard
--------                                             ----  ----
requests.storage                                     0     50Mi
zfs-sc.storageclass.storage.k8s.io/requests.storage  0     100Mi
$ kubectl apply -f pvc-sc.yaml
Error from server (Forbidden): error when creating "pvc-sc.yaml": persistentvolumeclaims "zfs-pvc001" is forbidden: exceeded quota: storage-resource-quota, requested: requests.storage=100Mi, used: requests.storage=0, limited: requests.storage=50Mi
```

storageのrequestsを「100Mi -> 30Mi」に変更して再度applyします。

```
$ kubectl apply -f pvc-sc.yaml
persistentvolumeclaim/zfs-pvc001 created
$ kubectl -n dev get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
zfs-pvc001   Bound    pvc-d4a48e0f-e70d-484a-a4ff-b760694daebb   30Mi       RWX            zfs-sc         11s
$ kubectl describe -n dev resourcequotas
Name:                                                storage-resource-quota
Namespace:                                           dev
Resource                                             Used  Hard
--------                                             ----  ----
requests.storage                                     30Mi  50Mi
zfs-sc.storageclass.storage.k8s.io/requests.storage  30Mi  100Mi
```

今度は成功しました。
Storage Resource Quotaは2つの設定が競合しますので、両方設定する際には注意が必要ですね。


#Object Count Quota
Compute/Storage Resource Quotaは「リソースの使用量」に対して制限をかけています。対してObject Count Quotaは、「Object（リソース）の数」に対して制限をかけます。
設定方法はKubernetesのバージョンによって以下の二通りがありますが、v1.9以降であればどちらの方法も可能です。

##Kubernetes v1.9以降
v1.9以降では、count/\<resource>.\<group>の形式で指定できるようになりました。
指定できるリソースは以下になります。

>https://kubernetes.io/docs/concepts/policy/resource-quotas/#object-count-quota
>
>* count/persistentvolumeclaims
>* count/services
>* count/secrets
>* count/configmaps
>* count/replicationcontrollers
>* count/deployments.apps
>* count/replicasets.apps
>* count/statefulsets.apps
>* count/jobs.batch
>* count/cronjobs.batch
>* count/deployments.extensions

###Resource Quotaの作成

以下のマニフェストのResource Quotaを作成します。ここでは「namespace: dev」に対して制限をかけます。

```objectCountQuota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-count-quota
  namespace: dev
spec:
  hard:
    count/deployments.apps: 3
    count/jobs.batch: 3
```

```
$ kubectl apply -f objectCountQuota.yaml
resourcequota/object-count-quota created
$ kubectl -n dev describe resourcequotas
Name:                   object-count-quota
Namespace:              dev
Resource                Used  Hard
--------                ----  ----
count/deployments.apps  0     3
count/jobs.batch        0     3
```

###動作確認

ここではDeploymentを作成して動作を確認します。

```
$ kubectl apply -f nginx-dev.yaml
deployment.apps/nginx1 created
deployment.apps/nginx2 created
deployment.apps/nginx3 created
Error from server (Forbidden): error when creating "nginx-dev.yaml": deployments.apps "nginx4" is forbidden: exceeded quota: object-count-quota, requested: count/deployments.apps=1, used: count/deployments.apps=3, limited: count/deployments.apps=3
```

4つ目のDeploymentで失敗しました。

DeploymentとResource Quotaの状態を確認します。

```
$ kubectl get -n dev deployments.apps
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
nginx1   2/2     2            2           24s
nginx2   2/2     2            2           24s
nginx3   2/2     2            2           24s
$ kubectl describe -n dev resourcequotas
Name:                   object-count-quota
Namespace:              dev
Resource                Used  Hard
--------                ----  ----
count/deployments.apps  3     3
count/jobs.batch        0     3
```

3つだけDeploymentが作成されていますね。

念のため「namespace: default」のDeploymentに制限がかかっていないことを確認します。

```
$ kubectl apply -f nginx-default.yaml
deployment.apps/nginx-default1 created
deployment.apps/nginx-default2 created
deployment.apps/nginx-default3 created
deployment.apps/nginx-default4 created
$ kubectl get deployments.apps -A
NAMESPACE        NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
default          nginx-default1            2/2     2            2           28s
default          nginx-default2            2/2     2            2           28s
default          nginx-default3            2/2     2            2           28s
default          nginx-default4            2/2     2            2           28s
dev              nginx1                    2/2     2            2           24m
dev              nginx2                    2/2     2            2           24m
dev              nginx3                    2/2     2            2           24m
```
「namespace: default」には制限がかかっていませんね。

##Kubernetes v1.9未満
v1.9未満の場合は、以下のリソースタイプを指定できます。

>* configmaps
>* persistentvolumeclaims
>* pods
>* replicationcontrollers
>* resourcequotas
>* services
>* services.loadbalancers
>* services.nodeports
>* secrets

###Resource Quotaの作成

以下のマニフェストのResource Quotaを作成します。ここでは「namespace: stg」に対して制限をかけます。

```objectCountQuota-previous.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-count-quota-previous
  namespace: stg
spec:
  hard:
    services.nodeports: 3
    pods: 3
```

```
$ kubectl apply -f objectCountQuota-previous.yaml
resourcequota/object-count-quota-previous created
$ kubectl -n stg describe resourcequotas
Name:               object-count-quota-previous
Namespace:          stg
Resource            Used  Hard
--------            ----  ----
pods                0     3
services.nodeports  0     3
```

###動作確認

ここでは、Deploymentのreplica数を「4」にして確認してみます。

```
$ kubectl apply -f nginx-stg.yaml
deployment.apps/nginx-stg created
$ kubectl -n stg get deployments.apps
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
nginx-stg   3/4     3            3           20s
$ kubectl -n stg get replicasets.apps
NAME                   DESIRED   CURRENT   READY   AGE
nginx-stg-7f54869d4d   4         3         3       33s
$ kubectl -n stg get pod
NAME                         READY   STATUS    RESTARTS   AGE
nginx-stg-7f54869d4d-9mtzs   1/1     Running   0          16s
nginx-stg-7f54869d4d-9v7l2   1/1     Running   0          16s
nginx-stg-7f54869d4d-qmww6   1/1     Running   0          16s
$ kubectl -n stg describe resourcequotas
Name:               object-count-quota-previous
Namespace:          stg
Resource            Used  Hard
--------            ----  ----
pods                3     3
services.nodeports  0     3
```

replica数が「4」の設定ですが、Podは3つしかデプロイされていませんね。replicasetの詳細を確認します。

```
$ kubectl describe -n stg replicaset nginx-stg-7f54869d4d
Name:           nginx-stg-7f54869d4d
Namespace:      stg
・・・
Events:
  Type     Reason            Age                     From                   Message
  ----     ------            ----                    ----                   -------
  Normal   SuccessfulCreate  3m57s                   replicaset-controller  Created pod: nginx-stg-7f54869d4d-9v7l2
  Normal   SuccessfulCreate  3m57s                   replicaset-controller  Created pod: nginx-stg-7f54869d4d-9mtzs
  Normal   SuccessfulCreate  3m57s                   replicaset-controller  Created pod: nginx-stg-7f54869d4d-qmww6
  Warning  FailedCreate      3m57s                   replicaset-controller  Error creating: pods "nginx-stg-7f54869d4d-wkxhl" is forbidden: exceeded quota: object-count-quota-previous, requested: pods=1, used: pods=3, limited: pods=3
  Warning  FailedCreate      3m57s                   replicaset-controller  Error creating: pods "nginx-stg-7f54869d4d-4x2zr" is forbidden: exceeded quota: object-count-quota-previous, requested: pods=1, used: pods=3, limited: pods=3
  Warning  FailedCreate      3m57s                   replicaset-controller  Error creating: pods "nginx-stg-7f54869d4d-wfr9t" is forbidden: exceeded quota: object-count-quota-previous, requested: pods=1, used: pods=3, limited: pods=3
  Warning  FailedCreate      3m57s                   replicaset-controller  Error creating: pods "nginx-stg-7f54869d4d-x8dtb" is forbidden: exceeded quota: object-count-quota-previous, requested: pods=1, used: pods=3, limited: pods=3
  Warning  FailedCreate      3m57s                   replicaset-controller  Error creating: pods "nginx-stg-7f54869d4d-hnvrq" is forbidden: exceeded quota: object-count-quota-previous, requested: pods=1, used: pods=3, limited: pods=3
  Warning  FailedCreate      3m57s                   replicaset-controller  Error creating: pods "nginx-stg-7f54869d4d-97tnw" is forbidden: exceeded quota: object-count-quota-previous, requested: pods=1, used: pods=3, limited: pods=3
  Warning  FailedCreate      3m57s                   replicaset-controller  Error creating: pods "nginx-stg-7f54869d4d-xkg2p" is forbidden: exceeded quota: object-count-quota-previous, requested: pods=1, used: pods=3, limited: pods=3
  Warning  FailedCreate      3m57s                   replicaset-controller  Error creating: pods "nginx-stg-7f54869d4d-rvkg5" is forbidden: exceeded quota: object-count-quota-previous, requested: pods=1, used: pods=3, limited: pods=3
  Warning  FailedCreate      3m56s                   replicaset-controller  Error creating: pods "nginx-stg-7f54869d4d-92jl8" is forbidden: exceeded quota: object-count-quota-previous, requested: pods=1, used: pods=3, limited: pods=3
  Warning  FailedCreate      3m34s (x11 over 3m56s)  replicaset-controller  (combined from similar events): Error creating: pods "nginx-stg-7f54869d4d-jghnm" is forbidden: exceeded quota: object-count-quota-previous, requested: pods=1, used: pods=3, limited: pods=3
```

4つ目のPodをデプロイするときに、Podの上限「3」を超えたため、エラーになっていますね。

---

なお、v1.9以降とv1.9未満の両方の形式を一つのマニフェストにまとめて記載することも可能です。

```objectCountQuota-consolidate.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-count-quota-consolidate
  namespace: prd
spec:
  hard:
    # v1.9以降の記載
    count/deployments.apps: 3
    count/jobs.batch: 3

    # v1.9未満の記載
    pods: 5
    services.loadbalancers: 5
```

```
$ kubectl apply -f objectCountQuota-consolidate.yaml
resourcequota/object-count-quota-consolidate created
$ kubectl -n prd describe resourcequotas
Name:                   object-count-quota-consolidate
Namespace:              prd
Resource                Used  Hard
--------                ----  ----
count/deployments.apps  0     3
count/jobs.batch        0     3
pods                    0     5
services.loadbalancers  0     5
```

#まとめ
今回のResource Quotaだけでなく、これまでKubernetesのリソース制御方法を確認してきました。
クラスタ管理者がLimitRangeやResource Quotaで各namespaceの上限/下限を設定して、各アプリの開発者がその範囲内でデプロイするPodのリソースを設定するのかなと検証しながら思っていました。
LimitRangeとResource Quotaで設定できる対象や値が微妙に異なりますので、システムやアプリの要件に応じて適切に設定しないといけないですね。ベストプラクティスを考えながらこれからも検証したいと思います。