无需重启,使用 Shell Operator 对 Pod 进行垂直扩缩容
通常情况下,要修改 Pod 的资源定义,是需要重启 Pod 的。在 Kubernetes 1.27 中,有一个 Alpha 状态的 InPlacePodVerticalScaling
开关,开启这一特性,就能在不重启 Pod 的情况下,修改 Pod 的资源定义。
要使用这个功能,需要在 kube-apiserver
的 featureGates
中显式地设置启用,启用这一特性之后,就可以进行测试了。
例如 Kind 集群,需要在配置中加入:
featureGates:
"InPlacePodVerticalScaling": true
测试一下
假设下面的 Pod 定义:
apiVersion: v1
kind: Pod
metadata:
name: stress
spec:
containers:
- name: stress
image: colinianking/stress-ng:latest
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired
- resourceName: memory
restartPolicy: RestartContainer
command: ["sleep", "3600"]
resources:
limits:
cpu: 200m
memory: 200M
requests:
cpu: 200m
memory: 200M
可以看到,spec 中加入了 resizePolicy 字段,用来指定对 CPU 和内存的扩缩容策略。内容很直白:
- CPU 的扩缩容策略是
NotRequired
,即不重启 Pod; - 内存的扩缩容策略是
RestartContainer
,即重启 Pod。
将上述内容提交到 Kubernetes 中运行。启动之后,如果运行 kubectl get po stress -o yaml
,会发现状态字段中加入了如下内容:
- allocatedResources:
cpu: 200m
memory: 200Mi
说明此时分配给容器的资源。如果这时候对 CPU 进行修改,例如修改为:
resources:
limits:
cpu: 800m
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
修改后查看 Pod 列表,会发现 Pod 没有重启:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
stress 1/1 Running 0 4m14s
重新获取 YAML,会看到状态字段的一些变化:
resize: InProgress
:表示正在扩缩容;当前分配的资源也发生了变化:
- allocatedResources: cpu: 100m memory: 100Mi
自动纵向扩缩容
到目前为止,VPA 还没有支持这一特性。我们可以简地使用 Prometheus 对 Pod 资源压力进行监控,然后使用 Shell Operator 来实现自动扩缩容。总体思路就是,定期读取 Prometheus,获取指定 Pod 的 CPU 和使用情况,如果 CPU 使用率超过 80%,则将其 CPU 上限扩容一倍。
Prometheus 监控指标
Awesome Prometheus alerts 提供了如下的告警定义,用于表达 CPU 用量和其 Limit 的关系:
- alert: ContainerHighCpuUtilization
expr: (sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (pod, container) / sum(container_spec_cpu_quota{container!=""}/container_spec_cpu_period{container!=""}) by (pod, container) * 100) > 80
for: 2m
labels:
severity: warning
annotations:
summary: Container High CPU utilization (instance {{ $labels.instance }})
description: "Container CPU utilization is above 80%\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
我们把它写入 Python 代码:
CPU_USAGE_QUERY = '''(sum(rate(container_cpu_usage_seconds_total{{namespace="{0}", pod="{1}", container="{2}"}}[5m])) by (pod, container)
/ sum(container_spec_cpu_quota{{namespace="{0}", pod="{1}",container="{2}"}}
/container_spec_cpu_period{{namespace="{0}", pod="{1}", container="{2}"}}) by (pod, container) * 100)'''
定期运行
要设置 Shell Operator 的定期运行,需要使用 Schedule 类型的配置,下面的 Configmap 设置每两分钟运行一次:
apiVersion: v1
data:
config.yaml: |+
configVersion: v1
schedule:
- crontab: "*/2 * * * *"
allowFailure: true
kind: ConfigMap
metadata:
creationTimestamp: null
name: so-config
我们会将这个 Configmap 加载到 Pod 定义中,
...
volumeMounts:
- mountPath: /conf/
name: operator-config
...
volumes:
- configMap:
name: so-config
name: operator-config
...
在 Hook 代码执行参数中带有 --config
参数时,读取该配置进行返回:
if len(sys.argv) > 1 and sys.argv[1] == "--config":
with open("/conf/config.yaml", "r") as f:
print(f.read())
exit(0)
RBAC
Shell Operator 需要对 Pod 资源进行扩容,所以需要如下授权:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "patch"]
构建 Docker 镜像
FROM flant/shell-operator
RUN apk update && \
apk add --no-cache py3-requests
COPY main.py /hooks
测试
Workload 中,我们设置的资源是 100M 内存+100m CPU 的配置,使用 kubectl exec -it stress -- sh
进入 Pod 之后,执行 stress-ng --cpu 1 --fork 2
制造一点压力,触发 Shell Operator 中的脚本对 Pod 进行纵向扩容,在 Prometheus 会看到如下曲线:
随着每次运行和扩容,CPU 水位不断下降,直到稳定。打开 Pod 定义,会看到扩容的痕迹:
containerStatuses:
- allocatedResources:
cpu: 6400m
memory: 100M
containerID: containerd://10c55739a6a63f3464184f5384a2f2b091a235b7b6689bcdb58526e3eb8bdb19
image: docker.io/colinianking/stress-ng:latest
imageID: docker.io/colinianking/stress-ng@sha256:1b10c09968ea3460196596398f7811c7a604489a8311b3dbf477f552ac5ea972
lastState:
terminated:
containerID: containerd://e3f1fe628086e291830b47247c88403d3ce0f4fd5db38b18afcca444659011d3
exitCode: 0
finishedAt: "2024-09-08T08:07:49Z"
reason: Completed
startedAt: "2024-09-08T07:07:49Z"
name: stress
ready: true
resources:
limits:
cpu: 6400m
memory: 100M
结论
全部代码被上传到了 https://github.com/fleeto/vscale-by-shelloperator
。内容当然还是非常简陋,例如缺乏缩容手段、没有对上限进行限制,防抖动措施也是缺乏的。另外该特性还处在 Alpha 阶段,因此不推荐在生产环境中使用。