# 资源预留

kubelet中有几个参数，通过这几个参数可以为系统进程预留资源，不至于pod把计算资源耗尽，而导致系统操作都无法正常进行。

```
--enforce-node-allocatable
--system-reserved
--system-reserved-cgroup
--kube-reserved
--kube-reserved-cgroup
--eviction-hard
```

## Allocatable

在kubernetes 1.6版本后，引入了Node的Allocatable特性，通过该特性我们可以控制每个节点可分配的资源。

借用官网的图如下：

```
       capacity
------------------------
|     kube-reserved    |
------------------------
|     system-reserved  |
------------------------
|     eviction-threshhold    |
------------------------
|     allocatable      |
| (available for pods) |
----------------------
```

Capacity是指Node的容量，allocatable的值为

```
allocatable = capacity - kube_reserved - system_reserved - eviction_threshhold
```

当kubelet启动后，Node的allocatable就是固定的，不会因为pod的创建与销毁而改变。

* allocatable vs requests vs limits

在pod的yaml文件中，我们可以为pod设置requests与limits。其中limits与allocatable没有什么关系。但requests与allocatable关系紧密。

**调度到某个节点上的Pod的requests总和不能超过该节点的allocatable。limits的总和没有上限。**

比如某个节点的内存的allocatable为10Gi，有三个Pod（requests.memory=3Gi）已经调度到该节点上，那么第4个Pod就无法调度到该节点上，即使该Node上的空闲内存大于3Gi。

## 资源预留 - 不设cgroup

假设我们现在需要为系统预留一定的资源，那么我们可以配置如下的kubelet参数（在这里我们不设置对应的cgroup参数）：

```
--enforce-node-allocatable=pods
--kube-reserved=memory=...
--system-reserved=memory=...
--eviction-hard=...
```

在上面提到，节点上Pod的requests总和不能超过allocatable。

**当我们设置了以上的四个参数时，节点上所有Pod实际使用的资源总和不会超过capacity - kube\_reserved - system\_reserved**

我们可以通过实验进行验证。

1、参数设置

kubelet的启动参数如下：

> /usr/bin/kubelet --address=0.0.0.0 --allow-privileged=true --cluster-dns=10.254.0.10 --cluster-domain=kube.local --fail-swap-on=true --hostname-override=192.168.1.101 --kubeconfig=/etc/kubernetes/kubeconfig --pod-infra-container-image=10.142.232.115:8021/library/pause:latest --port=10250 --enforce-node-allocatable=pods --kube-reserved=memory=1Gi --system-reserved=memory=1Gi --cgroup-driver=cgroupfs --eviction-hard=memory.available<100Mi

2、查看capacity及allocatable

查看到Node的capacity及allocatable的值如下：

```
Capacity:
 cpu:     2
 memory:  4016436Ki (约3.83Gi)
 pods:    110
Allocatable:
 cpu:     2
 memory:  1816884Ki (约1.73Gi)
 pods:    110
```

我们可以计算出allocatable的值，刚好与上面的一致：

```
allocatale = capacity - kube_reserved - system_reserved - eviction_hard
1816884Ki = 4016436Ki - 1*1024*1024Ki - 1*1024*1024Ki - 100*1024Ki
```

我们可以通过free命令来查看Node的total值，与capacity一致：

```
$ free -k
              total        used        free      shared  buff/cache   available
Mem:        4016436     1224372     2234872       17100      557192     2453156
Swap:             0           0           0
```

3、查看kubepods控制组

查看kubepods控制组中对内存的限制，该值决定了Node上所有的Pod能使用的资源上限：

```
$ cat /sys/fs/cgroup/memory/kubepods/memory.limit_in_bytes 
1965346816
```

1965346816 Bytes = 1919284Ki = allocatable + 100Mi

根据上面的计算可知，Node上Pod能实际使用的资源上限值为：

```
kubepods/memory.limit_in_bytes = capacity - kube_reserved - system_reserved
```

**注意：根据上面的公式，我们可以知道，一个节点上所有Pod能使用的内存总和，与eviction-hard无关**

4、查看内存的空闲情况

查看内存的使用情况，发现空闲内存为 2.3Gi

```
$ free -h
              total        used        free      shared  buff/cache   available
Mem:           3.8G        1.2G        2.1G         16M        544M        2.3G
Swap:            0B          0B          0B
```

5、创建pod

此时内存的空闲值为2.3Gi，allocatable为1.73Gi，kubepod.limit为1.83Gi。

我们创建一个Pod，pod.request为0.1Gi，pod.limit为20Gi，Pod实际消耗内存1Gi。理论上该Pod能创建成功，实际也成功了，如下：

备注：yaml文件消耗内存的脚本见本文附录

```
$ kubectl get pod
NAME                      READY     STATUS    RESTARTS   AGE
centos-659755bf78-jdlrc   1/1       Running   0          44s
```

查看Node的内存使用情况：

```
$ free -h
              total        used        free      shared  buff/cache   available
Mem:           3.8G        2.2G        1.1G         16M        546M        1.3G
Swap:            0B          0B          0B
```

此时，空闲内存为1.3Gi，Node剩余的request为1.63Gi，Node的kubepods.limit还剩0.83Gi。

我们再创建一个同样的Pod，根据推测，Pod可以调度成功，但是由于要消耗1Gi的实际内存，超过了0.83Gi，那么该Pod会出现OOM。实验结果也的确如此：

```
$ kubectl get pod
NAME                      READY     STATUS      RESTARTS   AGE
centos-659755bf78-j8wjv   0/1       OOMKilled   0          5s
centos-659755bf78-jdlrc   1/1       Running     1          1m
```

## 资源预留 - 设置对应的cgroup

如果还设置了对应的 `--system-reserved-cgroup` 和 `--kube-reserved-cgroup`参数，Pod能实际使用的资源上限不会改变（即kubepods.limit\_in\_bytes不变），但系统进程与kube进程也会受到资源上限的限制。如果系统进程超过了预留资源，那么系统进程会被cgroup杀掉。

但是如果不设这两个参数，那么系统进程可以使用超过预留的资源上限。

## 配置建议

为kubelet设置以下四个参数即可：

```
--enforce-node-allocatable=pods
--kube-reserved=cpu=xx,memory=xx,ephemeral-storage=xx
--system-reserved=cpu=xx,memory=xx,ephemeral-storage=xx
--eviction-hard=memory.available<10%,nodefs.available<10%
```

一般来说，我们不希望资源的使用率超过70%，所以kube-reserved、system-reserved、eviction-hard都应该设为10%。但由于kube-reserved与system-reserved不能设置百分比，所以它们要设置为绝对值。

## 总结

* Node的allocatable在kubelet启动后是一个固定的值，不会因为pod的创建与删除而改变
* 当我们为Pod设置了resources.requests时，调度到Node上的Pod的resources.requests的总和不会超过Node的allocatable。但Pod的resources.limits总和可以超过Node的allocatable
* 一个Pod能否成功调度到某个Node，关键看该Pod的resources.request是否小于Node剩下的request，而不是看Node实际的资源空闲量。即使空闲资源小于Pod的requests，Pod也可以调度到该Node上
* 当Pod的资源实际使用量超过其limits时，docker（实际是cgroup）会把该Pod内超出限额的进程杀掉
* 当我们只设置如下四个参数时，可以达到为系统预留资源的效果，即Pod的资源实际使用量不会超过allocatable的值（因为`kubepods`控制组中memory.limit\_in\_bytes的值就为allocatable的值）。即使系统本身没有使用完预留的那部分资源，Pod也无法使用。当系统超出了预留的那部分资源时，系统进程可以抢占allocatable中的资源，即对系统使用的资源没有限制。

```
--enforce-node-allocatable=pods
--kube-reserved=memory=...
--system-reserved=memory=...
--eviction-hard=...
```

* 当我们除了设置了以上四个参数，还设置了对应的cgroup时（如下），那么除了Pod使用的资源上限不会超过allocatable外，系统使用的资源上限也不会超过预留资源。当系统进程超过预留资源时，系统进程也会被cgroup杀掉。所以推荐使用上面的设置方法

```
--enforce-node-allocatable=pods,kube-reserved,system-reserved
--kube-reserved=memory=...
--kube-reserved-cgroup=...
--system-reserved=memory=...
--system-reserved-cgroup=...
--eviction-hard=...
```

* allocatable与kubepods.limit的值不一样，它们之间相差一个 eviction\_hard

```
allocatable = capacity - kube_reserved - system_reserved - eviction_hard
kubepods.limit = capacity - kube_reserved - system_reserved
```

## 附录

* centos.yaml

```
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: centos
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      name: centos
  template:
    metadata:
      labels:
        name: centos
    spec:
      volumes:
      - name: volume1
        hostPath:
          path: /home/docker/yaml/mem.py
      containers:
      - name: centos
        image: 10.142.232.115:8021/library/centos:centos7
        command:
        - python
        - /mem.py
        - 1GB
        volumeMounts:
        - mountPath: /mem.py
          name: volume1
        resources:
          requests:
            memory: 0.1Gi
          limits:
            memory: 20Gi
```

* mem.py

```
import sys
import re
import time

def print_help():
    print 'Usage: '
    print '  python mem.py 100MB'
    print '  python mem.py 1GB'

if __name__ == "__main__":
    if len(sys.argv) == 2:
        pattern = re.compile('^(\d*)([M|G]B)$')
        match = pattern.match(sys.argv[1].upper())
        if match:
            num = int(match.group(1))
            unit = match.group(2)
            if unit == 'MB':
                s = ' ' * (num * 1024 * 1024)
            else:
                s = ' ' * (num * 1024 * 1024 * 1024)

            time.sleep(10000)
        else:
            print_help()
    else:
        print_help()
```
