Kubernetes 控制平面性能基准测试实践
随着容器化应用的日益普及,Kubernetes 已成为企业级容器编排的事实标准。然而,当业务规模不断增长,集群中的节点和 Pod 数量激增时,Kubernetes 控制面的性能和稳定性便面临严峻挑战。为确保集群在高负载下依然能够稳定运行,并提前评估其承载能力,进行系统的性能基准测试变得至关重要。
服务等级指标 (SLI) 与目标 (SLO)
在对 Kubernetes 控制面进行性能测试时,我们通常会参照服务等级指标(SLI)和服务等级目标(SLO)来衡量系统表现。SLI 是衡量服务稳定性的量化指标,而 SLO 则是基于 SLI 设定的具体目标值。以下是 Kubernetes 官方推荐的关键性能指标及其对应的目标:
| 性能指标 (SLI) | 目标值 (SLO) | 测试方法 |
|---|---|---|
| 单对象修改类 API 调用(例如:创建、更新、删除)的响应延迟,在过去 5 分钟内取第 99 百分位 | 99 百分位 <= 1 秒 | Kubemark + Clusterloader2 |
| 非流式只读 API 调用(例如:获取、列举)的响应延迟,在过去 5 分钟内取第 99 百分位 | (a) 单资源范围(scope=resource)<= 1 秒 (b) 命名空间或集群范围(scope=namespace 或 scope=cluster)<= 30 秒 |
Kubemark + Clusterloader2 |
| 可调度无状态 Pod 的启动延迟(不含镜像拉取和 Init 容器执行时间),从 Pod 创建到所有容器启动并通过 watch 观察到的时间,在过去 5 分钟内取第 99 百分位 | 99 百分位 <= 5 秒 | Kubemark + Clusterloader2 |
Kubernetes 性能测试工具
了解了这些关键的性能指标和目标后,下一步是选择合适的工具进行测试。Kubernetes 社区提供了强大的性能测试框架 Clusterloader2,它能够协助我们自动化测试流程并收集结果。此外,考虑到实际环境中搭建大规模物理集群的困难,Kubernetes 还提供了 Kubemark 项目,用于模拟大量虚拟节点,从而在资源有限的环境下模拟大规模集群场景。
Clusterloader2 主要提供两种测试场景:
-
密度测试 (Density Test):此测试主要评估集群在高密度节点和 Pod 负载下的性能表现。其核心逻辑是在一个包含 N 个节点的集群中,持续创建大约 30 * N 个 Pod,然后删除它们,并在此过程中监控上述 SLI 是否满足 SLO。
-
负载测试 (Load Test):此测试旨在模拟控制面接收大量不同类型的资源创建、删除、查询(LIST)及其他操作时的性能。测试过程中,同样会持续跟踪并判断 SLI 是否达到预设的 SLO。
使用 Kubemark 模拟 100 个虚拟节点
模拟大量 Kubernetes 节点以进行性能测试是常见的需求。Kubemark 项目允许我们在现有 Kubernetes 集群中创建"虚拟"节点。以下是配置和部署 100 个模拟节点的步骤。
环境准备:
- 工作集群 (Work Cluster):用于运行性能测试的 Kubernetes 集群。
- 测试集群 (Test Cluster):作为"空心节点(hollow-node)"提供方,即 Kubemark 虚拟节点运行所在的集群。
1. Kubemark 工具编译与镜像构建
首先,需要从 Kubernetes 源码构建 Kubemark 二进制文件,并制作 Docker 镜像。
# 克隆指定版本的 Kubernetes 源码仓库
git clone -b v1.18.10 https://github.com/kubernetes/kubernetes.git
cd kubernetes/
# 编译 Kubemark 二进制文件
./hack/build-go.sh cmd/kubemark/
# 将编译好的二进制文件复制到镜像构建目录
cp _output/bin/kubemark cluster/images/kubemark/
cd cluster/images/kubemark/
# make build 命令会根据 Dockerfile 构建镜像,如果需要可以先修改 Dockerfile 的基础镜像
make build
# 重新标记镜像并推送到自定义仓库,以便其他节点可以访问
docker tag staging-k8s.gcr.io/kubemark:latest your-registry/kubemark:v0.1.0
docker push your-registry/kubemark:v0.1.0
2. 配置虚拟节点运行环境(在 测试集群 上操作)
在测试集群中创建必要的命名空间、ConfigMap 和 Secret。kubeconfig 文件应从工作集群的 master 节点获取,确保它具有足够的权限访问工作集群的 API 服务器。
# 创建 Kubemark 专用的命名空间
kubectl create ns kubemark
# 创建一个 ConfigMap,用于传递节点配置类型
kubectl create configmap node-configmap -n kubemark --from-literal=content.type="test-cluster"
# 将工作集群 Master 节点的 .kube/config 文件复制到此处,并创建 Secret
# 假设文件名为 config,内容应包含对工作集群的访问凭证
kubectl create secret generic kubeconfig --type=Opaque --namespace=kubemark \
--from-file=kubelet.kubeconfig=config --from-file=kubeproxy.kubeconfig=config
# 为运行 Kubemark 虚拟节点的物理节点打标签。
# 这里的 $NodeName 应该替换为实际运行 Kubemark Pod 的节点名称。
kubectl label node $NodeName kubemark-node=true
3. 部署 Kubemark 虚拟节点
接下来,通过 Deployment 在测试集群中启动 100 个模拟节点。将 replicas 字段设置为所需的虚拟节点数量。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hollow-node-deployment
namespace: kubemark
labels:
app: hollow-node
spec:
replicas: 100 # 启动的虚拟节点数量
selector:
matchLabels:
app: hollow-node
template:
metadata:
labels:
app: hollow-node
spec:
# 将虚拟节点调度到带有 'kubemark-node=true' 标签的物理节点上
nodeSelector:
kubemark-node: "true"
initContainers:
- name: setup-inotify-limit
image: busybox
imagePullPolicy: IfNotPresent
command: ['sysctl', '-w', 'fs.inotify.max_user_instances=524288']
securityContext:
privileged: true
volumes:
- name: kubeconfig-volume
secret:
secretName: kubeconfig
containers:
- name: hollow-kubelet
image: your-registry/kubemark:v0.1.0 # 使用你构建的 Kubemark 镜像
imagePullPolicy: IfNotPresent
ports:
- containerPort: 4194
- containerPort: 10250
- containerPort: 10255
env:
- name: CONTENT_TYPE
valueFrom:
configMapKeyRef:
name: node-configmap
key: content.type
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
command:
- /bin/sh
- -c
# 启动 Kubemark 的 kubelet 模拟模式
- /kubemark --morph=kubelet --name=$(NODE_NAME) --kubeconfig=/kubeconfig/kubelet.kubeconfig $(CONTENT_TYPE) --alsologtostderr --v=2
volumeMounts:
- name: kubeconfig-volume
mountPath: /kubeconfig
readOnly: true
securityContext:
privileged: true
- name: hollow-proxy
image: your-registry/kubemark:v0.1.0 # 使用你构建的 Kubemark 镜像
imagePullPolicy: IfNotPresent
env:
- name: CONTENT_TYPE
valueFrom:
configMapKeyRef:
name: node-configmap
key: content.type
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
command:
- /bin/sh
- -c
# 启动 Kubemark 的 kube-proxy 模拟模式
- /kubemark --morph=proxy --name=$(NODE_NAME) --use-real-proxier=false --kubeconfig=/kubeconfig/kubeproxy.kubeconfig $(CONTENT_TYPE) --alsologtostderr --v=2
volumeMounts:
- name: kubeconfig-volume
mountPath: /kubeconfig
readOnly: true
tolerations:
- operator: "Exists"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubemark-node
operator: In
values:
- "true"
# 应用部署文件以启动虚拟节点
kubectl apply -f deploy-hollow-nodes.yaml # 假设上述 YAML 保存为此文件
使用 Clusterloader2 进行性能测试
虚拟节点部署完成后,我们可以在工作集群上运行 Clusterloader2 进行性能测试。此过程将配置测试场景、集成 Prometheus 进行指标收集,并执行实际的性能测试。
# 进入 GOPATH 目录或自定义的合适路径
mkdir -p ~/go/src/k8s.io
cd ~/go/src/k8s.io
# 克隆 Clusterloader2 性能测试仓库
git clone -b release-1.18 https://github.com/kubernetes/perf-tests.git --depth 1
cd perf-tests/clusterloader2/
# 构建 Clusterloader2 工具
go env -w GO111MODULE=off # 确保模块支持被关闭,如果 Go 版本较旧
go build -o clusterloader ./cmd/
# 定制密度测试配置
# 切换到密度测试用例目录
cd testing/density/
cp config.yaml custom-density-config.yaml
# 修改定制的 config.yaml 文件,调整测试参数
# 例如,将每个命名空间的节点数 (NODES_PER_NAMESPACE) 修改为 10
# 同时,检查并更新 pkg/execservice/manifest/exec_deployment.yaml 和
# testing/density/deployment.yaml 中使用的镜像地址,确保它们可用。
# vim custom-density-config.yaml
# ... (进行修改) ...
# 设置 Prometheus 监控系统(可选但推荐)
# 返回 perf-tests 根目录
cd ../../
git clone https://github.com/prometheus-operator/kube-prometheus.git --depth 1
# 部署 Prometheus Operator 相关组件
kubectl create -f kube-prometheus/manifests/setup/
kubectl create -f kube-prometheus/manifests/
# 根据实际网络环境和测试需求,移除或修改 Prometheus 的默认配置
# 例如,删除可能导致网络不通或与测试冲突的网络策略和服务监控
kubectl -n monitoring delete networkpolicies.networking.k8s.io prometheus-k8s grafana || true
kubectl -n monitoring delete servicemonitors.monitoring.coreos.com kubelet node-exporter || true
kubectl -n monitoring delete daemonsets.apps node-exporter || true
# 导入 Kubernetes SLI dashboard 并应用性能测试相关的 Prometheus 规则
# 这些规则通常定义了如何计算和记录 SLI 指标
kubectl apply -f pkg/prometheus/manifests/prometheus-rules.yaml
# 定义 Clusterloader2 运行参数
KUBE_CONFIG="${HOME}/.kube/config" # 工作集群 kubeconfig 路径
PROVIDER='kubemark' # 指定提供者为 kubemark
MASTER_SSH_IP='your_master_ssh_ip' # 替换为工作集群 Master 节点的 SSH IP
KUBE_SSH_KEY_PATH="${HOME}/.ssh/id_rsa" # SSH 私钥路径
MASTER_SSH_USER_NAME='root' # SSH 用户名
TEST_CONFIG='./testing/density/custom-density-config.yaml' # 使用定制的测试配置文件
# 执行 Clusterloader2 性能测试
# --enable-prometheus-server=true 启用 Prometheus 收集指标
# --tear-down-prometheus-server=false 测试结束后不删除 Prometheus 部署
# --kubemark-root-kubeconfig=$KUBE_CONFIG 指定 Kubemark 所需的 kubeconfig
./clusterloader --kubeconfig="${KUBE_CONFIG}" \
--provider="${PROVIDER}" \
--masterip="${MASTER_SSH_IP}" \
--testconfig="${TEST_CONFIG}" \
--report-dir="./performance-reports" \
--alsologtostderr \
--enable-prometheus-server=true \
--tear-down-prometheus-server=false \
--kubemark-root-kubeconfig="${KUBE_CONFIG}" \
2>&1 | tee clusterloader_test_output.log
分析性能报告
测试执行完成后,Clusterloader2 会在 --report-dir 指定的目录下生成详细的性能报告。我们可以通过分析这些 JSON 格式的报告来评估控制面的表现。
1. API 响应延迟分析 (APIResponsivenessPrometheus_*.json)
该报告文件记录了各类 API 请求的响应延迟数据。重点关注 Perc99(99th percentile)值,它代表了绝大多数请求(99%)的响应时间上限,是衡量服务稳定性的关键指标。
# 过滤并查看 API 响应延迟报告中 99 百分位超过 0 的数据
cat performance-reports/APIResponsivenessPrometheus_density_*.json | grep Perc99 | grep -v '"Perc99": 0'
示例输出片段(注意 Perc99 值及对应的资源和操作):
{
"data": {
"Perc50": 34.48,
"Perc90": 95.66,
"Perc99": 14323
},
"unit": "ms",
"labels": {
"Count": "1493",
"Resource": "pods",
"Scope": "cluster",
"SlowCount": "1490",
"Subresource": "",
"Verb": "LIST"
}
},
{
"data": {
"Perc50": 35.49,
"Perc90": 1844.54,
"Perc99": 4349
},
"unit": "ms",
"labels": {
"Count": "4634",
"Resource": "nodes",
"Scope": "cluster",
"SlowCount": "4629",
"Subresource": "",
"Verb": "LIST"
}
}
从上述示例中,我们可以发现 pods 和 nodes 的 LIST 操作的 99 百分位延迟较高(分别为 14323 ms 和 4349 ms),这可能意味着在大规模集群下,列举这些资源会成为性能瓶颈,超过了官方的 SLO 目标(集群范围 LIST <= 30 秒)。
2. Pod 启动延迟分析 (PodStartupLatency_*.json)
该报告详细分解了 Pod 从创建到完全运行各个阶段的延迟。
# 查看 Pod 启动延迟报告
cat performance-reports/PodStartupLatency_SaturationPodStartupLatency_density_*.json
示例输出片段(重点关注 Metric 字段及其 Perc99 值):
{
"version": "1.0",
"dataItems": [
{
"data": {
"Perc50": 0, "Perc90": 0, "Perc99": 1000
},
"unit": "ms",
"labels": { "Metric": "create_to_schedule" }
},
{
"data": {
"Perc50": 0, "Perc90": 1000, "Perc99": 3000
},
"unit": "ms",
"labels": { "Metric": "schedule_to_run" }
},
{
"data>: {
"Perc50": 212966.46, "Perc90": 396976.21, "Perc99": 445981.78
},
"unit": "ms",
"labels": { "Metric": "run_to_watch" }
},
{
"data": {
"Perc50": 212973.35, "Perc90": 396985.06, "Perc99": 446977.29
},
"unit": "ms",
"labels": { "Metric": "schedule_to_watch" }
},
{
"data": {
"Perc50": 212975.33, "Perc90": 396985.23, "Perc99": 446979.92
},
"unit": "ms",
"labels": { "Metric": "pod_startup" }
}
]
}
从报告中可以看出:
create_to_schedule(Pod 创建到调度器处理) 和schedule_to_run(调度到 kubelet 运行) 的延迟相对较低,说明调度器和 kubelet 接收调度的性能良好。run_to_watch(Pod 容器启动到状态通过 API Server 被观察到) 的延迟高达约 445 秒 (445981.78 ms),这显著高于官方 SLO 的 5 秒目标。这通常意味着 API Server 处理 Watch 事件或汇报 Pod 状态的效率低下,或者 kubelet 本身汇报状态有延迟。pod_startup是整个 Pod 启动过程的总延迟,其高值与run_to_watch的高延迟密切相关。
通过对这些指标的详细分析,可以识别 Kubernetes 控制面的潜在性能瓶颈,为后续的优化提供依据。