# [Resource](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) 需求與限制
Control plane 的 scheduler 會負責將 Pod 安排到合適的 Node 上。Scheduler 考量的點有以下幾個:
+ Resource Requirements and Limits(本篇內容)
+ Taints and Tolerations
+ Node Selectors/Affinity
## Resource Requests and Limits for Containers in Pod
其中一個考慮的因素是這個 Pod 需要多少資源 、Node 還有沒有足夠的資源能運行這個 Pod。如果沒有任何一個 Node 有足夠資源能運行這個 Pod,這個 Pod 就會處於 Pending 的狀態。那 Pod 需要多少資源? 可以在 **`spec`** 中的 **`resource`** 定義:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
- name: app
image: images.my-company.example/app:v4
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
- name: log-aggregator
image: images.my-company.example/log-aggregator:v6
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
```
在這邊的資源是指什麼呢?在這裡最常指定的資源是 CPU & Memory,Resource 的使用是以 Container 為單位。
其中 Resources 又分成 Requests & Limits:
+ Requests 是 Container 運行時要求的最少 cpu 或 memory。
+ Limits 則是 Container 最多可以使用的 cpu 或 memory。而一個 Container 是有可能會使用超過它一開始要求的資源,但不能超過 Resource Limits。
## Kubernetes 中的資源單位
### CPU 資源單位
CPU 資源的限制和請求以 *cpu* 單位來衡量。在 Kubernetes 中,1 個 CPU 單元相當於 **1 個實體 CPU 核心**,或 **1 個虛擬核心**,取決於 Node 是實體主機還是在實體機內運行的虛擬機器。
可以定義小於 1 的數字。當您定義 Container 時,**`spec.containers[].resources.requests.cpu`** 設定為 **`0.5`**,與請求 **`1.0`** CPU 相比,您要求的 CPU 時間是一半。對於 CPU 資源單位,數量表達式 **`0.1`** 相當於表達式 **`100m`**,m 在這邊是 milliCPU。
CPU 資源總是指定為資源的**絕對量**,而不是相對量。例如,無論 Container 運行在單核心、雙核心或 48 核心機器上,**`500m`** CPU 都表示大致相同的運算能力。
:::success
:notebook: **Note:**
Kubernetes 不允許您指定精度小於 **`1m`** 或 **`0.001`** CPU 的 CPU 資源。為了避免意外使用無效的 CPU 數量,當使用少於 1 個 CPU 單元時,使用 milliCPU 形式而不是十進位形式來指定 CPU 單元非常有用。
例如,您有一個使用 **`5m`** 或 **`0.005`** CPU 的 Pod,並且希望減少其 CPU 資源。透過使用十進位形式,更難發現 **`0.0005`** CPU 是無效值,而透過使用 milliCPU 形式,更容易發現 **`0.5m`** 是無效值。
:::
### Memory 資源單位
Memory 單位為 **`bytes`**。可以用十進位制跟二進位制的方式表示。
| 1 Gi | 1 Gibibytes | 1,073,741,824 bytes |
| -------- | -------- | --------: |
| 1 Mi | 1 Mebibyte | 1,048,576 bytes |
| 1 Ki | 1 Kibibyte | 1,024 bytes |
| 1 G | 1 Gigabyte | 1,000,000,000 bytes |
| 1 M | 1 Megabyte | 1,000,000 bytes |
| 1 k | 1 Kilobyte | 1,000 bytes |
以下表示大致相同的值:
```
128974848, 129e6, 129M, 128974848000m, 123Mi
```
:::success
:notebook: **Note:**
注意後綴的大小寫。如果請求 **`400m`** 內存,這就是請求 **`0.4`** bytes。有人打字可能意味著要求 400 mebibytes (**`400Mi`**) 或 400 megabytes (**`400M`**)。
:::
利用 **`kubectl top pod <pod name>`** 可以看到現在 pod 資源使用的情況:
```bash
kubectl -n chest-ct top pod
# NAME CPU(cores) MEMORY(bytes)
# chest-ct-api-deployment-94886d595-4rlfw 2m 218Mi
# chest-ct-api-deployment-94886d595-f5w4g 2m 114Mi
# chest-ct-frontend-deployment-547cb977f-k25rz 4m 93Mi
```
#### 如果沒有設置 requests,但有設置 limits 會怎麼樣呢?
:arrow_right: CPU & Memory 的 requests 就會等於 limits。
#### 如果沒有設置 limits 會怎麼樣呢?
+ CPU:Container 在使用上就沒有限制,Container 能夠使用 Node 上所有的 CPU 資源。
+ Memory:Container 能使用 Node 的所有記憶體。這時有可能會發生 OOM - Out of Memory,會啟動 OOM Killer 殺掉 Process 來釋放空間,沒有資源使用上限的 Container 會有較高的機會被殺掉。
:::success
:notebook: **Note:**
Container 有較高的機會被殺掉但不一定會殺掉這個 Container,這與 OOM Killer - Linux kernel 的機制相關,會有某種規則來算 OOM score。如果 OOK Killer 殺掉的 Process 不是 Container 中 **`PID=1`** 的 Process,這樣 Container 本身通常仍然會繼續運行。
:::
通常 limits 會設置大於 requests 的數字,這代表有需要的時候 Pod 可使用較多的 CPU / Memory,例如某段時間有較大的流量。
## [Pod Scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-scheduling-readiness/)
CPU/Memory 的 Requests & Limits 是 Container level,對 Pod 來說這些資源的 Requests & Limits 就是所有 Container 的 Requests & Limits 的加總。而 Scheduler 在分配 Pod 到 Node 上的時候是看 Requests 的值,如果 Node 上的資源足夠提供 Pod 需要的 CPU & Memory,Pod 才會被調度到這個 Node 運行。
不過每次寫 deployment、寫 pod spec 都要寫 resource limits & requests,是不是有點煩呢?Kubernetes 提供了另一種 object - **LimitRange**,可以讓建立的 Pod 都套用預先定義好的 requests & limits。
## [LimitRange](https://kubernetes.io/docs/concepts/policy/limit-range/)
LimitRange 是 Namespaced level object。可以對 Namespace 中的 Pod 或是 PersistentVolumeClaim 做資源限制。
+ 限制 Pod 或 Container 的最小和最大計算資源使用量(CPU & Memory)。
+ 限制 PersistentVolumeClaim 的最小和最大 Storage Request。
+ 限制 Request & Limit 之間的比例。
+ 設置預設的計算資源給 Container。
PersistentVolumeClaim 是跟 Storage 相關,後面會再介紹。
這邊來看 LimitRange 的範例:
```yaml
apiVersion: v1
kind: LimitRange
metadata:
namespace: chest-ct
name: chest-ct-limit
spec:
limits:
- type: Container
max:
cpu: 2000m
memory: 1024Mi
min:
cpu: 250m
memory: 128Mi
default:
cpu: 1000m
memory: 512Mi
defaultRequest:
cpu: 500m
memory: 256Mi
maxLimitRequestRatio:
cpu: 2
memory: 2
```
+ **`type: Container`**:表示對 Pod 中的 Container 做限制。
+ **`max`**:Pod 中 Container 的 Limits 上限 :arrow_right: limits 如果大於 **`max`** 會無法建立。
+ **`min`**:Pod 中 Container 的 Requests 下限 :arrow_right: requests 如果小於 **`min`** 會無法建立。
+ **`default`**:Pod 中 Container 沒有指定 Limits 時,limits = **`default`**。
+ **`defaultRequest`**:Pod 中 Container 沒有指定 Requests 時,requests = **`defaultRequest`**。
+ **`maxLimitRequestRatio`**:規定 Pod 中的 Container 設置 Limits & Requests 的比例值不能超過 **`maxLimitRequestRatio`** 的值 ⇒ **`Limits / Requests ≤ maxLimitRequestRatio`**;例如 **`maxLimitRequestRatio = 2`**,表示 Limits 最多只能是 Requests 的兩倍。
和 Limits & Requests 數值相關的參數之間的大小關係如下:
**`min`** — **`defaultRequest`** — **`default`** — **`max`**
官網上有個例子,request value 介於 max & min 之間是被允許的,但要注意 request 還是不能大於 limit。
定義 LimitRange 如下:
```yaml
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-resource-constraint
spec:
limits:
- default: # this section defines default limits
cpu: 500m
defaultRequest: # this section defines default requests
cpu: 500m
max: # max and min define the limit range
cpu: "1"
min:
cpu: 100m
type: Container
```
還有一個 Pod 聲明 CPU 資源 request 為 **`700m`**,但沒有 limit:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: example-conflict-with-limitrange-cpu
spec:
containers:
- name: demo
image: registry.k8s.io/pause:2.0
resources:
requests:
cpu: 700m
```
那麼該 Pod 將不會被調度,失敗並出現類似以下內容的錯誤:
```
Pod "example-conflict-with-limitrange-cpu" is invalid: spec.containers[0].resources.req
uests: Invalid value: "700m": must be less than or equal to cpu limit
```
request **`700m`** 比 max **`1`** cpu 還小,但此時的 limit 為預設的 **`500m`**,request 必須小於或等於 cpu limit。
這個情況下必須也定義好 limit 至少等於 request 才能成功建立這個 Pod。
```yaml
apiVersion: v1
kind: Pod
metadata:
name: example-no-conflict-with-limitrange-cpu
spec:
containers:
- name: demo
image: registry.k8s.io/pause:2.0
resources:
requests:
cpu: 700m
limits:
cpu: 700m
```
## ResourceQuota
ResourceQuota 這個 object 則是對整個 Namespace 的資源做限制。Cluster administrator 可以利用 ResourceQuota 來管理各 Namespace 中資源的配額。這邊的資源除了 compute resource,也可以限制儲存空間(storage request)、Object 數量(e.g. deployments 數量、secrets 數量)。
例如:
```yaml
apiVersion: v1
kind: ResourceQuota
metadata:
namespace: chest-ct
name: chest-ct-quota
spec:
hard:
requests.cpu: 1 # 對這個 namespace 的 Pod,total request 的 cpu 不能超過 1
requests.memory: 1Gi
limits.cpu: 2 # 對這個 namespace 的 Pod,total limit 的 cpu 不能超過 2
limits.memory: 2Gi
```
上面的 ResourceQuota 指的是整個 Namespace 中運行的 Pod,加總的 requests memory 不能超過 1Gi;加總的 limits memory 不能超過 2Gi。
:::success
:notebook: **Note:**
如果一個 Namespace 中利用 ResourceQuota 定義了 cpu request,則後續建立的 Pod(Container)就必須要寫明 cpu request 是多少,不然會無法建立。同樣的 **`requests.memory`**、**`limits.cpu`**、**`limits.memory`** 也是。這時就可以搭配 **`LimitRange`** 使用。
:::
假設 Pod 沒有定義好 ResourceQuota 限制的項目,錯誤訊息會長這樣:
```
Error creating: pods "chest-ct-api-deployment-94886d595-dfp7g" is forbidden: failed
quota: chest-ct-quota: must specify limits.cpu for: chest-ct-api; limits.memory for:
chest-ct-api; requests.cpu for: chest-ct-api; requests.memory for: chest-ct-api
```
執行 **`kubectl -n <namespace_name> get resourcequotas`** 可以看到現在這個 namespace 中 resource 的使用狀況。
```bash
kubectl -n chest-ct get resourcequotas
# NAME AGE REQUEST LIMIT
# chest-ct-quota 23m requests.cpu: 1/1, requests.memory: 512Mi/1Gi limits.cpu: 2/2, limits.memory: 1Gi/2Gi
```