K8s 资源管理
高级调度
Kubernetes(k8s)是一个强大的容器编排平台,具备许多高级调度功能,以优化容器的资源利用和性能。以下是一些常见的 k8s 高级调度功能:
- 亲和性调度(Affinity Scheduling):通过亲和性调度规则,将容器调度到具有特定标签或节点上,以满足应用程序的需求。亲和性调度可以实现容器之间的亲和关系,如将相关的容器调度到相同的节点上,或将某个容器调度到特定类型的节点上。
- 反亲和性调度(Anti-Affinity Scheduling):与亲和性调度相反,反亲和性调度可以将容器调度到具有特定标签或节点上的不同节点,以提高容器的高可用性和容错性。
- 资源限制(Resource Limits):可以为容器设置资源限制,如 CPU 和内存限制,以确保容器在运行时不会超出预定义的资源限制,从而避免资源竞争和意外的崩溃。
- 资源配额(Resource Quotas):通过资源配额,可以限制命名空间中的容器使用的总资源量,包括 CPU、内存、存储等。资源配额可以帮助确保不同应用程序之间的资源隔离和公平分配。
- 亲和性互斥(Affinity Preemption):通过亲和性互斥规则,可以防止容器被调度到具有相同亲和性规则的其他容器所在的节点上。这可以避免容器之间的资源冲突和干扰。
- 水平 Pod 自动伸缩(Horizontal Pod Autoscaling):根据应用程序的负载和性能需求,可以自动调整 Pod 的副本数量。水平 Pod 自动伸缩功能可根据预定义的规则自动扩展或缩减 Pod 的数量,以满足应用程序的需求。
- 优先级和预选调度(Priority and Preemption Scheduling):可以为容器设置优先级和预选条件,以确保重要任务或紧急任务得到优先调度。如果节点资源不足以满足高优先级任务的需求,则可以通过预选调度功能,暂时剥夺低优先级任务的资源,以满足高优先级任务的需求。
- 自定义调度器(Custom Scheduler):Kubernetes 允许用户编写自定义调度器,根据特定的调度算法和策略,将容器调度到节点上。自定义调度器可以根据应用程序的特定需求进行优化和定制化。
禁止调度
有时候需要手动标记节点为不可调度状态来进行维护工作,可以使用 cordon
命令来标记节点不再接收新的 Pod 请求:
[root@server4-master ~]$ kubectl cordon server5-node1
node/server5-node1 cordoned
同时,可以使用 drain
命令在停止调度后将节点上的 Pod 转移到其他节点上:
[root@server4-master ~]$ kubectl drain server5-node1
node/server5-node1 already cordoned
DEPRECATED WARNING: Aborting the drain command in a list of nodes will be deprecated in v1.23.
The new behavior will make the drain command go through all nodes even if one or more nodes failed during the drain.
For now, users can try such experience via: --ignore-errors
error: unable to drain node "server5-node1", aborting command...
There are pending nodes to be drained:
server5-node1
cannot delete Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet (use --force to override): bar/test, default/host-pid, default/pod-host-network, default/pod-privileged, foo/test
cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): calico-system/calico-node-2cp86, kube-system/kube-proxy-fnvff
cannot delete Pods with local storage (use --delete-emptydir-data to override): default/pod-readonly
根据提示,可以添加一些参数,例如 --force
用于覆盖,--ignore-daemonsets
用于忽略守护进程集等,以忽略警告信息。
如果想解除不可调度状态,可以使用 uncordon
命令:
[root@server4-master ~]$ kubectl uncordon server5-node1
node/server5-node1 uncordoned
污点和容忍度
在使用 kubeadm 建立集群时,主节点会自动设置污点,不允许在其上部署 Pod。可以通过 describe 命令查看污点信息:
[root@server4-master ~]$ kubectl describe node server4-master
Taints: node-role.kubernetes.io/master=true:NoSchedule
其中 Taints 选项包含键、值和生效规则。主节点上的污点信息键为 node-role.kubernetes.io/master
,值为 true
,生效规则为 NoSchedule。这个污点将阻止 Pod 被调度到该节点上,除非有 Pod 能够容忍该污点。通常,容忍该污点的 Pod 是系统级别的 Pod,例如 kube-proxy:
[root@server4-master ~]$ kubectl describe po kube-proxy-wkgjf -n kube-system
Tolerations: op=Exists
node.kubernetes.io/disk-pressure:NoSchedule op=Exists
node.kubernetes.io/memory-pressure:NoSchedule op=Exists
node.kubernetes.io/network-unavailable:NoSchedule op=Exists
node.kubernetes.io/not-ready:NoExecute op=Exists
node.kubernetes.io/pid-pressure:NoSchedule op=Exists
node.kubernetes.io/unreachable:NoExecute op=Exists
node.kubernetes.io/unschedulable:NoSchedule op=Exists
污点效果
每个污点都关联一个生效规则,共有三种规则:
-
NoSchedule(不可调度)
如果 Pod 不能容忍这些污点,则 Pod 将无法被调度到包含这些污点的节点上。
-
PreferNoSchedule(优先不调度)
表示尽量不将 Pod 调度到具有该污点的节点上。只有在没有其他可用节点时,才会将 Pod 调度到该节点上。
-
NoExecute(不执行)
这个规则会影响节点上运行的 Pod。如果节点上添加了 NoExecute 污点,那些正在该节点上运行的 Pod 将会从该节点上移除。
添加污点
在生产环境的节点上,可以添加污点来拒绝部署:
[root@server4-master ~]$ kubectl taint node server4-master node-type=prod:NoSchedule
node/server4-master tainted
[root@server4-master ~]$ kubectl describe node server4-master
Taints: node-type=prod:NoSchedule
此命令会给节点添加一个污点,其中键为 node-type
,值为 prod
,效果为 NoSchedule。
污点容忍度
如果需要在有污点的节点上部署 Pod,可以在 Pod 的 spec.template.spec.tolerations
中添加污点容忍度:
[root@server4-master ~]$ vi dp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubia
spec:
replicas: 3
selector:
matchLabels:
app: kubia
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- name: nodejs
image: luksa/kubia
ports:
- name: http
containerPort: 8080
resources:
requests:
cpu: 100m
tolerations:
- key: node-type
operator: Equal
value: prod
effect: NoSchedule
可以根据需求灵活配置节点失效(unready 或 unreachable)状态下,Pod 重新调度之前的等待时间:
tolerations:
- key: node.alphakubernetes.io/notReady
operator: Exists
tolerationSeconds: 30
effect: NoExecute
- key: node.alphakubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
effect: NoExecute
如果没有定义这两个容忍度,它们会自动添加到 Pod 上,初始等待时间为 300 秒。可以缩短值以减少等待时间。
删除污点
如果需要删除添加的污点,只需在添加污点的命令后加上减号 -
即可:
[root@server4-master ~]$ kubectl taint node server5-node1 node-type=prod:NoSchedule-
node/server5-node1 untainted
亲缘性
在 Kubernetes(k8s)中,亲缘性(Affinity)是一种调度策略,用于指定容器与节点或其他容器之间的关系。亲缘性可通过标签选择器和节点选择器来定义。
节点亲缘性
节点亲缘性(Node Affinity)允许 Kubernetes 将 Pod 只调度到某个节点子集上。
节点亲缘性规则用于取代节点选择器,其写法如下:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: in
values:
- "true"
其中,requiredDuringSchedulingIgnoredDuringExecution
用于表示强制性规则,不影响已经在节点上运行的 Pod。规则为 Pod 只会调度到包含 gpu=true
标签的节点上。
可以通过 preferredDuringSchedulingIgnoredDuringExecution
设置软性规则,指定多个节点并调整优先级:
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: gpu
operator: In
values:
- "true"
- weight: 20
preference:
matchExpressions:
- key: zone
operator: In
values:
- "cn"
其中,节点亲缘性优先级设置了 Pod 被优先调度到标签为 gpu=true
和 zone=cn
的节点上,其次是拥有 gpu=true
但 zone
值不为 cn
的节点,再次是 zone=cn
的节点,最后是优先级最低的其他节点。
对 node1
打上 gpu=true
和 zone=cn
标签:
[root@server4-master ~]$ kubectl label node server5-node1 gpu=true
node/server5-node1 labeled
[root@server4-master ~]$ kubectl label node server5-node1 zone=cn
node/server5-node1 labeled
使用 Deployment 发布一个有 5 个副本的应用,并查看 Pod 分布:
[root@server4-master ~]$ kubectl create -f deploy.label.yaml
deployment.apps/kubia created
[root@server4-master ~]$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE kubia-6f84b588d-8b6dn 1/1 Running 0 9s 10.244.191.247 server6-node2
kubia-6f84b588d-kbrks 1/1 Running 0 9s 10.244.191.250 server5-node1
kubia-6f84b588d-kf6p2 1/1 Running 0 9s 10.244.191.251 server5-node1
kubia-6f84b588d-pbf7p 1/1 Running 0 9s 10.244.191.248 server5-node1
kubia-6f84b588d-sfqv4 1/1 Running 0 9s 10.244.191.249 server5-node1
可以看到,5 个 Pod 中有 4 个被部署到了 node1
,另外 1 个部署到了 node2
。这是因为使用了 Selector SpreadPriority
函数,它确保属于同一个 ReplicaSet 或 Service 的 Pod 分布到不同节点,避免单节点故障导致服务不可用。
Pod 亲缘性
如果是协同工作的两个 Pod,可以设置 Pod 亲缘性,将它们尽可能部署到同一节点:
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: backend
此部署将创建一个包含强制性要求的 Pod,其中要求该 Pod 尽可能调度到与其他包含标签 app=backend
的 Pod 相同的节点上(通过 topologyKey
字段指定)。
反亲缘性
除了能让调度器对 Pod 进行协同部署,还可以让 Pod 之间远离彼此。通过 podAntiAffinity
字段来配置,这将导致调度器永远不会选择那些具有与 podAntiAffinity
匹配标签的 Pod 所在节点。
通常用于分离同样消耗大量系统资源的 Pod,或者将 Pod 分布在不同的可用区,以提高服务的可用性。以下是一个反亲缘性的示例:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- frontend
topologyKey: "kubernetes.io/hostname"
资源请求和限制
资源请求和限制是在 Kubernetes 中用于管理 Pod 资源分配和控制的重要概念。通过设置资源请求和限制,可以确保集群中的容器能够按预期方式运行,并有效地利用可用资源。
使用资源请求
在创建 Pod 时,可以指定容器对 CPU 和内存资源的请求量(requests)和资源限制量(limits)。下面创建一个 Pod,并指定容器对 CPU 和内存资源的请求量:
[root@server4-master ~]$ vi pod-requests.yaml
apiVersion: v1
kind: Pod
metadata:
name: requests-pod
spec:
containers:
- image: busybox
command: ["dd", "if=/dev/zero", "of=/dev/null"]
name: main
resources:
requests:
cpu: 200m
memory: 100Mi
[root@server4-master ~]$ kubectl create -f pod-requests.yaml
pod/requests-pod created
在上述配置中,声明容器需要 200 毫核(1 核 = 1000m)的 CPU 才能正常运行。在没有设置 CPU 资源请求的情况下,该进程可能无法分配到足够的 CPU 资源(当其他进程对 CPU 的需求很大时)。
另外,还配置了容器请求内存为 100MB,表示期望容器内的进程最多使用 100MB 的内存,实际占用可能会小于这个值。
运行容器后,可以使用 top
命令查看 CPU 使用情况:
[root@server4-master ~]$ kubectl exec -it requests-pod -- top
Mem: 7796064K used, 193976K free, 62492K shrd, 4240K buff, 6155012K cached
CPU: 1.2% usr 5.6% sys 0.0% nic 93.1% idle 0.0% io 0.0% irq 0.0% sirq
Load average: 0.83 0.39 0.20 3/895 19
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
1 0 root R 1304 0.0 0 6.8 dd if /dev/zero of /dev/null
14 0 root R 1312 0.0 2 0.0 top
dd
命令会消耗所有 CPU,但由于它是单线程运行,所以最多只能使用一个核心的资源。进程对 CPU 使用率远远超过了 Pod 定义申请的 1/5 核,因为 requests
不会限制容器可以使用的 CPU 总量。
资源请求对调度影响
通过设置 requests
指定 Pod 对资源需求的最小值,调度器在将 Pod 调度到节点的过程中会使用该信息。
每个节点可分配资源是一定的,调度器只考虑那些未分配资源量满足 Pod 需求量的节点,而不会将 Pod 调度到无法满足需求的节点。此外,调度器计算的资源量是节点上所有 Pod 的资源请求量之和,与实际资源使用量无关。
调度器基于资源请求量的优先级排序有两个函数:LeastRequestedPriority 和 MostRequestedPriority。前者优先将 Pod 调度到资源请求量较少的节点上,后者则相反。通常在可动态调整节点的云基础设施上使用 MostRequestedPriority,以确保 Kubernetes 尽可能少使用节点。
通过查看节点描述,可以了解节点的资源总量、可分配资源量以及已使用资源量等信息:
[root@server4-master ~]$ kubectl describe nodes
Addresses:
InternalIP: 192.168.2.206
Hostname: server6-node2
Capacity:
cpu: 16
ephemeral-storage: 66678Mi
hugepages-2Mi: 0
memory: 7990040Ki
pods: 110
Allocatable:
cpu: 16
ephemeral-storage: 62925255372
hugepages-2Mi: 0
memory: 7887640Ki
pods: 110
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 200m (1%) 200m (1%)
memory 100Mi (1%) 100Mi (1%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
可以创建一个请求 CPU 核数大于节点提供的 CPU 核数的 Pod:
[root@server4-master ~]$ vi pod-requests.yaml
resources:
requests:
cpu: 20
由于目前没有服务器可以提供 20 核 CPU 资源,因此该 Pod 无法被创建,会一直处于 Pending 状态。
[root@server4-master ~]$ kubectl get po
NAME READY STATUS RESTARTS AGE
requests-pod 0/1 Pending 0 63s
[root@server4-master ~]$ kubectl describe po requests-pod
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 17s default-scheduler 0/3 nodes are available: 3 Insufficient cpu.
只有在释放足够的 CPU 资源后,新的 Pod 才能被正常调度。
CPU 实际分配原则
CPU 请求不仅在调度时起作用,还决定了剩余的 CPU 时间如何在 Pod 之间分配。
假设有两个 Pod,一个请求 20% 的 CPU,另一个请求 80% 的 CPU,并且两个 Pod 都能够使用满额的 CPU。在没有限制 CPU 使用量的情况下,这两个 Pod 最终会分别占用 20% 和 80% 的 CPU 使用率。
当第一个 Pod 停止消耗 CPU 时,第二个 Pod 会占满 CPU,直到第一个 Pod 重新开始运行。这样,CPU 分配率又会回到 20% 和 80% 的比例。
通过设置 CPU 请求量,可以在调度时控制 Pod 对 CPU 的分配情况。
使用自定义资源
除了内存和处理器,Kubernetes 还可以添加自定义资源(例如 GPU 单元数)进行分配。需要将自定义资源添加到节点的 API 对象的 capacity 属性中,可以通过执行 HTTP 的 PATCH 请求来完成。
自定义资源的名称可以是任意值,不必以 kubernetes.io
域名开头,例如 example.org/myresource
。数量必须是整数。这个值会自动从 capacity 字段复制到 allocatable 字段。
之后,在创建 Pod 时,可以通过设置 spec.resources.requests
字段来指定自定义资源的名称和申请量。
设置资源限制
请求量只是保证每个容器能够获得所需资源的最小量,而限制值则是容器能够消耗资源的最大量。
CPU 是一种可压缩的资源,可以在不对进程产生不利影响的情况下对使用量进行限制。但内存不能被压缩,一旦系统为进程分配了一块内存,该内存在进程主动释放之前将无法回收。因此,需要限制容器的最大内存分配量。
创建一个带有资源限制的 Pod 与设置资源请求基本相同:
[root@server4-master ~]$ vi pod-requests2.yaml
apiVersion: v1
kind: Pod
metadata:
name: requests-pod2
spec:
containers:
- image: busybox
command: ["dd", "if=/dev/zero", "of=/dev/null"]
name: main
resources:
limits:
cpu: 200m
memory: 100Mi
[root@server4-master ~]$ kubectl create -f pod-requests2.yaml
pod/requests-pod2 created
由于没有指定 requests,它将被设置为与 limits 相同的值。通过 top
命令查看是否生效:
[root@server4-master ~]$ kubectl exec -it requests-pod2 -- top
Mem: 7802620K used, 187420K free, 62492K shrd, 4240K buff, 6155304K cached
CPU: 0.6% usr 1.8% sys 0.0% nic 97.5% idle 0.0% io 0.0% irq 0.0% sirq
Load average: 0.11 0.14 0.28 3/896 24
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1743839 root 20 0 1304 4 0 R 19.9 0.0 0:38.53 dd
现在,CPU 使用量被限制在了 20.4%。
超过资源限制
与资源请求不同,资源限制不受可分配资源量的约束。所有 limits 的总和允许超过节点资源总量的 100%。如果节点资源使用量超过 100%,某些容器将被终止。
对于 CPU 限额,进程只会被限制在规定数值以下。然而,内存限额的表现有所不同。如果进程尝试申请超过限额的内存,容器将因为内存不足而被终止(OOMKilled)。如果重启策略为 OnFailure 或 Always,进程会立即重启。如果在重启后再次超过限额,系统将增加下次重启的间隔时间,间隔时间最多增加到 300 秒。这将导致 pod 的状态显示为 CrashLoopBackOff,系统将以每 5 分钟重启一次的频率进行重启,直到容器运行成功或被删除。
[root@server4-master ~]$ vi pod-requests2.yaml
resources:
limits:
cpu: 200m
memory: 2Mi
[root@server4-master ~]$ kubectl create -f pod-requests2.yaml
pod/requests-pod2 created
[root@server4-master ~]$ kubectl get po --watch
NAME READY STATUS RESTARTS AGE
requests-pod2 0/1 ContainerCreating 0 3s
[root@server4-master ~]$ kubectl describe po requests-pod2
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedCreatePodSandBox 20s (x12 over 33s) kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to start sandbox container for pod "requests-pod2": Error response from daemon: failed to create shim: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:722: waiting for init preliminary setup caused: read init-p: connection reset by peer: unknown
Normal SandboxChanged 20s (x12 over 32s) kubelet Pod sandbox changed, it will be killed and re-created.
在容器中使用 top
命令可以查看到整个节点的资源使用量。
LimitRange
可以通过创建一个 LimitRange
资源来为命名空间中的容器配置最大和最小限额,同时给没有指定限制的容器设置默认值。
LimitRange
资源被 LimitRanger
准入控制插件控制。当 API 服务器接收带有 Pod 描述信息的 POST 请求时,会检查设置的限制值是否在范围内,以决定是否创建资源,避免调度失败。
LimitRange
作用于同一命名空间中的每个独立 Pod、容器或其他类型对象。它不会限制命名空间中所有 Pod 可用资源的总量,总量是通过 ResourceQuota
对象指定。
创建 LimitRange 资源
下面的配置展示了一个 LimitRange
资源的完整定义:
[root@server4-master ~]$ vi LR-example.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: example
spec:
limits:
- type: Pod ##指定了资源类型为Pod
min: ##Pod中所有容器的CPU和内存请求量之和的最小值
cpu: 50m
memory: 5Mi
max: ##Pod中所有容器的CPU和内存请求量之和的最大值
cpu: 1
memory: 1Gi
- type: Container ##指定了资源类型为Container
defaultRequest: ##容器没有指定请求量时设置的默认值
cpu: 100m
memory: 10Mi
default: ##容器没有指定限制量时设置的默认值
cpu: 200m
memory: 100Mi
min: ##容器requests和limits的最小值
cpu: 50m
memory: 5Mi
max: ##容器requests和limits的最大值
cpu: 1
memory: 1Gi
maxLimitRequestRatio: ##每种资源requests与limits的最大比值
cpu: 4
memory: 10
- type: PersistentVolumeClaim ##指定请求PVC储存容量的最小最大值
min:
storage: 1Gi
max:
storage: 10Gi
[root@server4-master ~]$ kubectl create -f LR-example.yaml
limitrange/example created
在上面的示例中,defaultRequest
和 default
字段指定了容器的默认请求和限制。type
字段指定了限制类型为容器。maxLimitRequestRatio
字段指定了 CPU 和内存的最大限制请求比例。min
字段和 max
字段分别指定了 CPU 和内存的最小和最大限制。设置 CPU 为 4,表示容器的 CPU limits 不能超过 requests 的 4 倍,内存同理。
上面新建的 LimitRange
对象包含了对所有资源的限制,也可以将其分割为多个对象。多个 LimitRange
对象的限制会在校验 Pod 或 PVC 合法性时进行合并。
对 LimitRange
进行修改时,已经存在的 Pod 和 PVC 不会受到影响。
测试限制值
尝试创建一个 CPU 申请量大于 LimitRange
允许值的 Pod:
[root@server4-master ~]$ vi pod-2.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-2
spec:
containers:
- image: busybox
command: ["dd", "if=/dev/zero", "of=/dev/null"]
name: main
resources:
limits:
cpu: 2
[root@server4-master ~]$ kubectl create -f pod-2.yaml
Error from server (Forbidden): error when creating "pod-2.yaml": pods "pod-2" is forbidden: [maximum cpu usage per Pod is 1, but limit is 2, maximum cpu usage per Container is 1, but limit is 2]
服务器将返回拒绝创建 Pod 的原因,因为每个 Pod 和容器的最大 CPU 请求量限制为 1 核,但此处容器申请了 2 核。
应用限制默认值
测试资源没有指定 requests
和 limits
的情况下分配到的默认值:
[root@server4-master ~]$ kubectl create -f pod-drop.yaml
pod/pod-drop created
[root@server4-master ~]$ kubectl describe po pod-drop
Limits:
cpu: 200m
memory: 100Mi
Requests:
cpu: 100m
memory: 10Mi
容器的 requests
和 limits
与 LimitRange
对象中设置的一致。
可以在另一个命名空间中指定不同的 LimitRange
,以适应不同的使用场景。
ResourceQuota
可以通过 ResourceQuota
对象来限制命名空间中可用资源的总量。
ResourceQuota
的准入控制插件会检查将要创建的 pod 是否会导致总资源量超出限制。如果超出限制,请求会被拒绝。
资源配额不仅限制了命名空间中 pod 和 PVC(持久化卷声明)可以使用的最大资源总量,还可以限制用户在该命名空间中创建 pod、PVC 以及其他 API 对象的数量。
创建配额
创建一个 ResourceQuota
资源来限制命名空间中所有 pod 允许使用的 CPU 和内存总量:
[root@server4-master ~]$ vi quota-1.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: cpu-and-mem
spec:
hard:
requests.cpu: 440m
requests.memory: 200Mi
limits.cpu: 2
limits.memory: 500Mi
[root@server4-master ~]$ kubectl create -f quota-1.yaml
resourcequota/cpu-and-mem created
创建后,命名空间内所有 pod 合计可申请的 CPU 数量为 440m,限制总量为 2 核;可申请的内存大小为 440MB,限制内存总量为 500MB。
测试配额
新建一个 pod 进行测试:
[root@server4-master ~]$ kubectl create -f pod-readonly.yaml
Error from server (Forbidden): error when creating "pod-readonly.yaml": pods "pod-readonly" is forbidden: failed quota: cpu-and-mem: must specify limits.cpu,limits.memory,requests.cpu,requests.memory
API 服务器提示必须为 pod 设置 limits
和 requests
属性才能建立。设置 limits
后再次尝试建立:
[root@server4-master ~]$ kubectl create -f pod-requests2.yaml
Error from server (Forbidden): error when creating "pod-requests2.yaml": pods "requests-pod2" is forbidden: exceeded quota: cpu-and-mem, requested: requests.memory=400Mi, used: requests.memory=10Mi, limited: requests.memory=200Mi
这次提示配额不足。查看命名空间 default
下的配额使用量和限制:
[root@server4-master ~]$ kubectl describe quota -n default
Name: cpu-and-mem
Namespace: default
Resource Used Hard
-------- ---- ----
limits.cpu 200m 2
limits.memory 100Mi 500Mi
requests.cpu 100m 440m
requests.memory 10Mi 200Mi
储存配额
ResourceQuota
对象同样可以限制命名空间中最多可以声明的持久化储存总量:
[root@server4-master ~]$ vi quota-2.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: storage
spec:
hard:
requests.storage: 500Gi
ssd.storageclass.storage.k8s.io/requests.storage: 300Gi
standard.storageclass.storage.k8s.io/requests.storage: 1Ti
[root@server4-master ~]$ kubectl create -f quota-2.yaml
resourcequota/storage created
上述设置中,对命名空间内所有可申请的 PVC 总量限制为 500GB,名为 ssd
的存储类总量限制为 300GB,名为 standard
的标准存储类总量限制为 1TB。
限制对象数量
可以限制单个命名空间中 pod、RC(副本控制器)、SVC(服务)等对象的个数:
[root@server4-master ~]$ vi quota-3.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: objects
spec:
hard:
pods: 10
services: 5
services.nodeports: 2
[root@server4-master ~]$ kubectl create -f quota-3.yaml
resourcequota/objects created
上述声明限制了命名空间中最多可以运行 10 个 pod,5 个 Service。其中,最多可以有两个类型为 NodePort
的 Service。
分配配额
通过 spec.scopes
属性,可以限制配额的作用范围,有四种范围:
-
BestEffort
和NotBestEffort
范围决定配额作用于 QoS 的最低优先级与其他优先级; -
Terminating
和NotTerminating
范围作用于配置了activeDeadlineSeconds
或未配置的 pod。
BestEffort
范围只允许限制 pod 的个数,其他范围可以限制所有资源。
[root@server4-master ~]$ vi quota-4.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: besteffort-pods
spec:
scopes:
- BestEffort
- NotTerminating
hard:
pods: 10
[root@server4-master ~]$ kubectl create -f quota-4.yaml
resourcequota/besteffort-pods created
这个配额只会应用于最低优先级以及没有设置有效期的 pod 上,这样的 pod 最多只允许存在 10 个。
QoS 等级
假设节点中的两个 Pod 已经完全消耗了系统资源,当 Pod 申请更多资源时,必须终止一个 Pod。可以通过 QoS 等级确定优先级较低的 Pod 进行退让。优先级从低到高有三种(BestEffort、Burstable、Guaranteed)。
QoS 等级源自 Pod 包含容器的资源请求和限制配置。
BestEffort 等级
分配给未设置任何资源请求和限制的 Pod,它们可能无法分配到任何 CPU 资源,并且在需要为其他 Pod 释放内存时,这些容器将被优先终止。
但是由于没有配置内存限制,在资源充足的情况下可以分配任意数量的内存。
Guaranteed 等级
分配给所有资源请求和限制相等的 Pod。以下条件决定了 Guaranteed 等级:
- CPU 和内存都必须设置请求和限制。
- 每个容器都需要设置资源量。
- 请求和限制必须相等。
可以简单地只指定限制字段,这样默认请求与限制相同。但是这些 Pod 容器无法消耗超过限制的资源。
Burstable 等级
所有其他 Pod 都属于这个等级,包括只定义了请求或限制的单容器 Pod,以及多个容器没有完全指定请求或限制的 Pod。
可以通过使用 describe 命令查看 Pod 中的 QoS Class 字段来获取 QoS 等级:
[root@server4-master ~]$ kubectl describe po pod-drop
QoS Class: BestEffort
处理相同 QoS 等级容器
在相同的 QoS 等级下,每个运行中的进程都有一个 OOM(OutOfMemory)分数的值,当需要释放内存时,分数最高的进程将被终止。
OOM 分数由进程已消耗内存百分比和容器内存请求量固定的 OOM 分数调节因子计算。
例如,Pod A 使用了 1GB 内存,Pod B 使用了 2GB 内存,但是 Pod A 的限制是 2GB,Pod B 的限制是 5GB,根据内存占比,Pod A 大于 Pod B,因此 Pod A 会被优先终止。
自动伸缩
Horizontal Pod Autoscaling (HPA) 是一种管理由控制器管理的 Pod 副本数量的横向自动伸缩方法,根据当前的负载情况自动触发水平扩展或缩容的行为。
自动伸缩的过程可以分为三个步骤:
-
获取被伸缩资源对象所管理的所有 Pod 的度量值。
Autoscaler 本身不收集 Pod 的度量数据,而是从 cAdvisor agent 获取数据,并由 Heapster 进行聚合,供 HPA 请求调用。
-
计算使度量值达到(或接近)目标值所需的 Pod 数量。
一旦 Autoscaler 获取了它所调整的资源所管理的所有 Pod 的度量值,它将利用这些度量值来计算所需的副本数量。
当只有一个度量值时,计算副本数量只需将所有 Pod 的度量值求和后除以目标值,再向上取整即可。
当有多个度量值时,将分别计算每个度量值所需的副本数量,并取最大值。
-
更新被伸缩资源的 replicas 字段。
Autoscaler 控制器通过 Scale 子资源来修改被伸缩资源的 replicas 字段,从而启动更多的 Pod 或删除多余的 Pod。
HPA 不允许将最小副本数设置为 0。
基于 CPU 使用率
对于 Autoscaler 来说,CPU 使用率是通过比较 Pod 的 CPU 请求量和实际使用量来确定的,因此必须设置 CPU 的请求量:
[root@server4-master ~]$ vi dp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubia
spec:
replicas: 3
selector:
matchLabels:
app: kubia
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- name: nodejs
image: luksa/kubia
ports:
- name: http
containerPort: 8080
resources:
requests:
cpu: 100m
[root@server4-master ~]$ kubectl create -f dp.yaml
deployment.apps/kubia created
创建一个 HPA 对象,将其指向该 Deployment。在 HPA 中设置目标 CPU 使用率为 30%,并指定副本的最小和最大数量。Autoscaler 将持续调整 Deployment 上的副本数量,以使 CPU 使用率接近 30%:
[root@server4-master ~]$ kubectl autoscale deployment kubia --cpu-percent=30 --min=1 --max=5
horizontalpodautoscaler.autoscaling/kubia autoscaled
[root@server4-master ~]$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
kubia Deployment/kubia <unknown>/30% 1 5 3 63s
[root@server4-master ~]$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
kubia 3/3 3 3 2m13s
由于三个 Pod 没有活动,CPU 使用率接近于 0。等待 5 分钟后,Autoscaler 将其缩减到 1 个副本。自动扩容操作的等待时间为 3 分钟。
基于内存使用
基于内存的自动伸缩比基于 CPU 的困难得多,主要原因是扩容后原有的 Pod 需要有一种方式来释放内存。这只能由应用程序完成,系统无法代劳。
系统只能终止并重新启动应用程序,希望它在重新启动后占用较少的内存。在 Autoscaler 上,将持续进行扩容操作,直到达到配置的最大 Pod 数量。
基于其他度量
定义 HPA 需要三个要素的配合:定义度量类型、被监控的资源类型以及资源的目标使用量。
可以在 HPA 对象中使用三种度量:
-
Resource 度量类型
Resource 类型基于资源度量来做出自动伸缩决策。
例如,基于 CPU 使用量作为度量:
spec: maxReplicas: 5 metrics: - resource: name: cpu targetAverageUtilization: 30
-
Pods 度量类型
Pods 类型用于引用与 Pod 直接相关的任何其他类型(包括自定义的度量),例如每秒查询次数(QPS)或消息队列中的消息数量。
例如,使 HPA 控制的 ReplicaSet 控制器下所有 Pod 的平均 QPS 维持在 100 的水平:
spec: metrics: - type: Pods resource: name: qps targetAverageValue: 100
-
Object 度量类型
Object 度量类型用于指定与 Pod 非直接关联的度量,例如 Ingress 对象。在使用 Object 度量类型时,Autoscaler 只会从该单个对象中获取单个度量数据。
例如,使用 Ingress 对象 frontend 的 latencyMillis 作为度量,如果度量超过目标值太多,则会对 Deployment 资源进行扩容:
spec: metrics: - type: Object resource: metricName: latencyMillis target: apiVersion: extensions/v1beta1 kind: Ingress name: frontend targetValue: 20 scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: hpans
并非所有的度量都适合作为自动伸缩的基础。在决定基于自定义度量进行伸缩时,需要考虑 Pod 数量变化对度量值的影响。
纵向自动伸缩
并非所有应用都可以进行横向伸缩。例如,某些 Pod 运行在特定的单节点上,这时需要进行纵向伸缩。
纵向伸缩通过更改 Pod 的配置文件字段来配置 Pod 的资源请求。可以从官方的 GitHub 上下载部署文件。以下是一个 VPA 的示例:
apiVersion: autoscaling.k8s.io/v1beta2
kind: VerticalPodAutoscaler
metadata:
name: nginx-vpa
namespace: vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: nginx
updatePolicy:
updateMode: "Off"
resourcePolicy:
containerPolicies:
- containerName: "nginx"
minAllowed:
cpu: "250m"
memory: "100Mi"
maxAllowed:
cpu: "2000m"
memory: "2048Mi"
VPA 还需要与 Metrics Server 配合使用,不能与 HPA 同时使用。
节点自动伸缩
集群自动伸缩器(Cluster Autoscaler)负责在节点资源不足时自动部署新节点,并在节点长时间使用率低下时下线节点。目前节点自动伸缩支持 GKE、GCE、AWS 和 AZURE。
显然,并非任意一个节点都能满足 Pod 的需求,因此云服务提供商通常将具有相同特性的节点聚合成组。当请求新节点时,需要指明节点的类型。
节点的下线是通过监视所有节点上的 CPU 和内存请求来实现的。如果系统中的 Pod 在该节点上运行(不包括 DaemonSet 部署的服务),则该节点不会被释放。也就是说,只有集群自动伸缩器知道在该节点上运行的 Pod 能够调度到其他节点时,该节点才能被释放。在释放节点之前,该节点会被标记为不可调度,并将其中的 Pod 疏散到其他节点。
如果在下线节点的过程中需要指定最少需要保持运行的 Pod 数量,可以使用 PodDisruptionBudget 资源来进行控制。