
1. 项目概述为什么我们需要分布式压测与K8s做性能测试的朋友尤其是经历过单机压测瓶颈的对“分布式”这个词应该深有感触。当你面对一个日活百万、千万级别的系统想模拟真实用户的高并发场景单台机器跑Locust脚本可能还没把服务器压垮自己的压测机先因为网络、CPU或内存限制而“罢工”了。这就是分布式压测要解决的核心问题突破单机性能瓶颈生成足够真实且巨大的负载。Locust本身的设计就考虑到了这一点其原生的Master-Worker架构就是为了分布式而生。Master负责协调、收集数据、提供Web UIWorker才是真正的“蝗虫”孵化器负责执行测试脚本模拟用户行为。这个模式简单清晰但当你手头有十几台甚至几十台物理机或云主机时如何快速部署、统一管理、动态伸缩就成了新的挑战。这时候Kubernetes后面简称K8s的价值就凸显出来了。它本质上是一个容器编排的“操作系统”能把一堆服务器抽象成一个巨大的资源池。用K8s来部署Locust分布式压测集群好处是显而易见的一键部署与销毁再也不用一台台机器去SSH执行命令了一个配置文件几分钟就能拉起一个包含指定数量Worker的压测集群。测试结束一键清理资源立即释放特别适合云上按需使用的场景。弹性伸缩发现并发量不够直接在K8s上修改Worker的副本数Replicas新的Worker Pod会自动创建并加入集群。压测峰值过去可以缩容节省成本。环境标准化通过容器镜像保证所有Worker节点的运行环境Python版本、依赖库、测试脚本完全一致避免了“在我机器上好好的”这类问题。集中管理通过K8s的Dashboard或命令行可以直观地看到所有Pod的状态、日志和资源消耗运维监控变得非常方便。所以“Locust分布式压测Master-Worker模式和Kubernetes集群部署”这个主题就是把Locust原生的分布式能力和K8s现代化的运维部署能力结合起来打造一个高可用、易扩展、好管理的现代化压测平台解决方案。接下来我会从设计思路开始带你一步步实现它。2. 整体架构设计与核心组件解析在动手敲命令之前我们必须把架构想清楚。一个基于K8s的Locust集群不是简单地把Master和Worker扔进容器就跑起来了里面涉及到服务发现、网络通信、配置管理等多个环节。2.1 架构拓扑图概念我们设计的核心架构如下[ Locust Web UI (Master Service) ] | | (HTTP/8089) v [ Locust Master Pod (1个) ] ----- [ Locust Worker Pods (N个) ] | | | (K8s Service) | (K8s Service/DNS) v v [ K8s Master Node ] [ K8s Worker Nodes ]关键角色说明Locust Master Pod运行Locust的master进程。它需要暴露两个端口8089: 用于提供Web UI让我们可以通过浏览器访问启动测试、查看报告。5557: 用于与Worker通信接收Worker的心跳和注册。Locust Worker Pods运行Locust的worker进程。它们需要知道Master的地址以便连接并接收任务。Kubernetes Service这是K8s的核心抽象之一它为Pod提供一个稳定的访问端点域名和IP。对于Master我们需要创建一个Service这样Worker Pods无论在哪里启动都能通过这个Service的域名例如locust-master找到Master。Worker通常不需要对外暴露Service。ConfigMap / Secret用于管理配置。比如我们可以把Locust的测试脚本locustfile.py通过ConfigMap挂载到每个Pod中或者把一些环境变量如目标测试主机地址通过ConfigMap注入这样无需每次修改都重新构建镜像。2.2 镜像选择与定制策略官方提供了Locust的Docker镜像locustio/locust这为我们省去了很多基础环境搭建的麻烦。但在生产级使用中直接使用官方镜像往往不够。1. 基础镜像选择locustio/locust:latest最省事但可能包含你不需要的依赖且版本不可控。locustio/locust:2.xx-py3.xx指定主版本和Python版本更稳定。推荐做法以官方镜像为基础镜像Base Image在其之上构建我们自己的镜像。这样既能继承官方优化又能加入自定义内容。2. 为什么需要自定义镜像预装依赖你的测试脚本可能需要额外的Python库如requests,pymongo,redis,pandas用于数据处理等。在Dockerfile里一次性安装好。固化测试脚本虽然可以通过ConfigMap动态挂载但对于稳定、核心的测试脚本直接打包进镜像可以保证一致性避免挂载失败导致脚本丢失。自定义启动命令可以封装一些初始化逻辑。3. 一个实用的Dockerfile示例# 使用官方指定版本的镜像作为基础 FROM locustio/locust:2.20.0-py3.11 # 切换到root用户安装系统依赖如果需要 USER root # 例如安装一些工具或库非必须 # RUN apt-get update apt-get install -y vim curl rm -rf /var/lib/apt/lists/* # 安装额外的Python包 # 将requirements.txt复制到镜像中 COPY requirements.txt /home/locust/requirements.txt RUN pip install --no-cache-dir -r /home/locust/requirements.txt # 将本地测试脚本目录复制到镜像中 COPY locustfiles/ /home/locust/locustfiles/ # 切换回locust用户官方镜像使用locust用户运行更安全 USER locust # 可以设置默认的工作目录 WORKDIR /home/locust # 官方镜像已有默认CMD这里可以覆盖但通常不需要 # CMD [“locust”]注意USER locust这行很重要。官方镜像出于安全考虑默认使用非root的locust用户运行。如果你以root身份复制文件可能会导致locust用户没有读取权限。要么在复制后修改文件权限RUN chown -R locust:locust /home/locust要么确保最终运行时的用户有权限。构建并推送镜像到你的私有仓库如Harbor或公有仓库如Docker Hubdocker build -t your-registry/your-project/locust-custom:2.20.0 . docker push your-registry/your-project/locust-custom:2.20.03. 基于Kubernetes的详细部署实操理论清晰后我们进入实战环节。假设你已经有一个正常运行的Kubernetes集群可以是Minikube、K3s、云厂商的托管集群等。3.1 部署Locust MasterMaster是单点但通过K8s的Deployment和Service我们可以实现故障恢复和稳定访问。1. 创建Master的Deployment (locust-master-deployment.yaml):apiVersion: apps/v1 kind: Deployment metadata: name: locust-master labels: app: locust component: master spec: replicas: 1 # Master通常只需一个实例 selector: matchLabels: app: locust component: master template: metadata: labels: app: locust component: master spec: containers: - name: locust-master image: your-registry/your-project/locust-custom:2.20.0 # 使用你的自定义镜像 ports: - containerPort: 8089 # Web UI端口 name: webui - containerPort: 5557 # Worker通信端口 name: comm env: - name: LOCUST_MODE value: master - name: TARGET_HOST value: http://your-target-service.com # 待测系统的地址 command: [locust] args: - --master - --host$(TARGET_HOST) - --web-host0.0.0.0 # 监听所有地址方便Service暴露 resources: requests: memory: 512Mi cpu: 250m limits: memory: 1Gi cpu: 500m关键参数解析replicas: 1: Master部署一个副本即可保证唯一性。env: 这里设置了两个环境变量。LOCUST_MODE是我们自定义的用于后续区分Pod角色也可以用不同的镜像或命令区分。TARGET_HOST是所有Locust请求发往的目标地址这个非常重要必须正确设置。command和args: 覆盖了容器的启动命令。--master指定以master模式运行。--web-host0.0.0.0让Web UI监听所有网络接口这是让K8s Service能够转发流量到Pod内部的关键。resources: 为容器申请和限制资源。根据你的压测规模预估Master所需资源通常不需要太多。2. 创建Master的Service (locust-master-service.yaml):apiVersion: v1 kind: Service metadata: name: locust-master labels: app: locust component: master spec: type: NodePort # 或 LoadBalancer (云环境下) ClusterIP (仅集群内访问) ports: - port: 8089 # Service端口 targetPort: 8089 # 容器端口 name: webui nodePort: 30009 # 如果type是NodePort可指定或随机 - port: 5557 targetPort: 5557 name: comm selector: app: locust component: masterService类型选择ClusterIP默认类型只能在K8s集群内部通过Service名locust-master访问。Worker连接Master用这个就够了。NodePort在每个Node上开放一个静态端口如30009这样你可以通过任意Node的IP:30009从集群外部访问Web UI。适合本地测试或没有Ingress/LoadBalancer的环境。LoadBalancer云服务商提供会自动创建一个外部负载均衡器分配一个公网IP。生产环境常用。3. 应用配置kubectl apply -f locust-master-deployment.yaml kubectl apply -f locust-master-service.yaml检查状态kubectl get pods -l applocust,componentmaster kubectl get svc locust-master如果Service类型是NodePort记下8089端口对应的NodePort例如30009然后用浏览器访问http://你的K8s节点IP:30009应该能看到Locust的Web UI虽然还没有Worker界面可能提示等待。3.2 部署Locust WorkersWorker需要能够发现并连接到Master。在K8s中最简单的方式就是通过Master的Service名称。1. 创建Worker的Deployment (locust-worker-deployment.yaml):apiVersion: apps/v1 kind: Deployment metadata: name: locust-worker labels: app: locust component: worker spec: replicas: 5 # 初始Worker数量可根据需要调整 selector: matchLabels: app: locust component: worker template: metadata: labels: app: locust component: worker spec: containers: - name: locust-worker image: your-registry/your-project/locust-custom:2.20.0 # 使用和Master相同的镜像 env: - name: LOCUST_MODE value: worker - name: LOCUST_MASTER_HOST value: locust-master # 关键通过K8s Service名访问Master - name: TARGET_HOST value: http://your-target-service.com command: [locust] args: - --worker - --master-host$(LOCUST_MASTER_HOST) - --host$(TARGET_HOST) resources: requests: memory: 1Gi # Worker内存需求可能更高取决于脚本复杂度 cpu: 500m limits: memory: 2Gi cpu: 1000m核心解析replicas: 5 一开始启动5个Worker Pod。这是可以随时通过kubectl scale命令或修改yaml文件来动态调整的。LOCUST_MASTER_HOSTlocust-master 这是整个配置的灵魂。在K8s集群内Pod之间可以通过Service名称进行DNS解析。locust-master这个名称会自动解析到前面创建的locust-masterService的ClusterIP上。这样无论Master Pod被调度到哪个节点无论IP如何变化Worker都能找到它。--master-host Locust Worker通过这个参数指定Master的地址。2. 应用配置并检查kubectl apply -f locust-worker-deployment.yaml稍等片刻查看Pod状态kubectl get pods -l applocust,componentworker再回到Locust的Web UI你应该能看到“Workers”部分显示已连接的Worker数量例如5/5并且每个Worker都有其ID。3.3 进阶使用ConfigMap管理测试脚本把测试脚本打包进镜像虽然一致性好但每次修改脚本都要重新构建和推送镜像太繁琐。更灵活的方式是使用K8s的ConfigMap。1. 创建包含locustfile的ConfigMap假设你的测试脚本文件名为locustfile.py。# 从本地文件创建ConfigMap kubectl create configmap locust-script --from-filelocustfile.py或者通过yaml文件定义# locust-script-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: locust-script data: locustfile.py: | from locust import HttpUser, task, between class QuickstartUser(HttpUser): wait_time between(1, 5) task def hello_world(self): self.client.get(/)kubectl apply -f locust-script-configmap.yaml2. 修改Master和Worker的Deployment将ConfigMap挂载为卷在Pod模板的spec部分添加# 在spec.template.spec下添加 volumes: - name: locust-script-volume configMap: name: locust-script在容器定义部分添加volumeMounts# 在容器定义下添加 volumeMounts: - name: locust-script-volume mountPath: /home/locust/locustfile.py # 挂载路径 subPath: locustfile.py # 只挂载ConfigMap中的这个键这样/home/locust/locustfile.py文件的内容就来自ConfigMap。当你需要修改脚本时只需更新ConfigMapkubectl create configmap locust-script --from-filelocustfile.py -o yaml --dry-runclient | kubectl replace -f -注意更新ConfigMap后默认情况下已经运行的Pod不会自动加载新内容。你需要重启Pod例如通过kubectl rollout restart deployment locust-master locust-worker才能使更改生效。有一些第三方工具可以监听ConfigMap变化自动重启但在压测场景下重启Pod意味着短暂中断通常是可接受的。4. 压测执行、监控与弹性伸缩实战集群部署完毕真正的压测才刚刚开始。如何在K8s上高效、安全地执行一次压测并观察系统状态4.1 启动与执行压测流程访问Web UI通过Master Service暴露的地址NodePort或LoadBalancer IP访问Locust Web界面。填写参数Number of users (peak concurrency): 总用户数。Locust会逐步孵化到这个数量。Spawn rate (users started/second): 孵化速率每秒启动多少个用户。Host: 这里应该已经自动填充了我们在环境变量TARGET_HOST里设置的值检查确认。Start swarming点击按钮压测开始。Master会通过5557端口将任务指令分发给所有已注册的Worker。实时监控在Web UI上可以实时查看RPS每秒请求数、响应时间、失败率等关键指标。4.2 集群资源监控与日志查看1. 查看Pod状态与资源使用# 查看所有Locust相关Pod kubectl get pods -l applocust -o wide # 查看Pod的资源使用情况需要Metrics Server kubectl top pods -l applocust # 查看具体Pod的详细信息 kubectl describe pod pod-name2. 查看日志至关重要用于调试脚本和发现问题# 查看Master日志 kubectl logs -f deployment/locust-master # 查看某个Worker的日志 kubectl logs -f deployment/locust-worker --prefix -c locust-worker # 或者查看指定Pod的日志 kubectl logs -f worker-pod-name在日志中你可以看到Worker成功连接到Master、用户孵化、请求执行和可能出现的异常信息。3. 监控目标系统同时务必使用你的APM工具如SkyWalking、Pinpoint、监控系统如PrometheusGrafana或云监控密切关注被压测应用的各项指标CPU、内存、数据库连接数、慢查询等。4.3 动态伸缩Worker规模这是K8s部署方案最强大的功能之一。1. 手动伸缩# 将Worker扩展到10个实例 kubectl scale deployment locust-worker --replicas10执行命令后K8s会立即开始创建新的Pod。新的Worker Pod启动后会自动连接到Master并在Web UI上显示出来。你可以在压测运行中动态执行此操作实现并发量的平滑增加。2. 自动伸缩HPA - Horizontal Pod Autoscaler对于更自动化的场景可以基于CPU或自定义指标来伸缩Worker。但压测Worker的负载主要取决于我们设定的虚拟用户数而非Pod自身的CPU。因此为Locust Worker设置基于CPU的HPA意义不大。更常见的做法是基于压测任务队列长度或自定义业务指标来伸缩这需要更复杂的自定义Metrics Adapter。对于大多数场景手动伸缩已经足够灵活。缩容同样简单# 压测结束缩容到0释放资源 kubectl scale deployment locust-worker --replicas05. 常见问题、故障排查与优化经验在实际操作中你肯定会遇到各种问题。这里我总结了一些典型坑点和解决方案。5.1 Worker无法连接Master这是最常见的问题Web UI上Worker数量为0或少于预期。排查步骤检查Master Service和Podkubectl get svc locust-master # 确保Service存在且有ClusterIP kubectl get pods -l componentmaster # 确保Master Pod是Running状态 kubectl logs deployment/locust-master | grep -i listening # 查看Master日志是否在5557端口监听检查Worker Pod的环境变量和日志kubectl describe pod worker-pod-name | grep -A2 -B2 LOCUST_MASTER_HOST # 确认环境变量设置正确 kubectl logs worker-pod-name | head -50 # 查看Worker启动日志通常会有连接Master的错误信息常见错误1Connection refused。说明Worker无法连接到LOCUST_MASTER_HOST:5557。确认Master Service的Selector标签是否正确匹配了Master Pod的标签。可以在集群内临时启动一个busyboxPod进行网络测试kubectl run -it --rm debug --imagebusybox --restartNever -- sh # 进入容器后执行 nslookup locust-master telnet locust-master 5557常见错误2Master日志显示Worker已连接但Web UI不显示。检查Master和Worker的Locust版本是否严格一致。不同大版本的Locust通信协议可能不兼容。5.2 压测数据不准确或RPS上不去单Worker性能瓶颈每个Worker都是一个Python进程受限于GIL单核CPU是瓶颈。如果虚拟用户数很多且任务很复杂单个Worker可能无法产生足够压力。解决方案增加Worker Pod的数量横向扩展而不是增加单个Pod的资源限制。这是分布式压测的核心思想。网络带宽瓶颈压测机Worker Pod所在的K8s节点网络带宽可能成为瓶颈特别是模拟大量用户下载/上传数据时。解决方案将Worker Deployment的Pod分散到不同的K8s节点上利用K8s调度或者使用网络带宽更高的节点规格。目标系统瓶颈提前出现可能还没等到Locust集群发挥全力被测系统已经崩溃或达到瓶颈。需要结合目标系统的监控来判断。Locust配置参数检查--run-time、--step-load等参数是否设置合理。确保没有设置过短的测试时间。5.3 资源管理与优化建议合理设置资源请求和限制requests是调度依据设置过低可能导致Pod无法调度节点资源不足设置过高则浪费资源。limits是硬限制防止单个Pod异常吃掉所有资源。对于Worker建议根据你的locustfile.py复杂度和单Pod计划模拟的用户数来设定。可以通过几次测试观察kubectl top pod的结果来调整。使用亲和性/反亲和性spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - locust topologyKey: kubernetes.io/hostname这段配置会尽量将Locust的Pod调度到不同的节点上避免所有Worker挤在一台机器上充分利用集群资源也避免单节点资源争抢。压测后的清理# 删除所有相关资源 kubectl delete deployment,service,configmap -l applocust # 或者删除整个namespace如果压测资源在独立namespace中 kubectl delete namespace locust-test养成好习惯压测完成后及时清理资源尤其是按需付费的云环境。5.4 关于测试脚本的注意事项连接池与Session管理在HttpUser中self.client默认对每个用户实例是独立的。如果你需要更精细的连接池管理可能需要自定义。小心全局变量Locust会为每个虚拟用户复制一份locustfile模块。修改全局变量通常不会影响其他用户。如果需要在Worker间共享状态需要使用其他机制如Redis但这会增加复杂度通常不推荐。任务权重与思考时间合理利用task(权重)和wait_time来模拟真实用户行为避免产生不切实际的、持续不断的请求洪流。将Locust与Kubernetes结合你获得的不仅仅是一个分布式压测工具而是一个可编程、可扩展、云原生的性能测试平台。它允许你将性能测试像其他微服务一样进行版本控制、CI/CD集成和按需调度。当你需要验证一次大促活动的系统容量时快速拉起一个数百Worker的集群测试完成后立即销毁这种效率和灵活性是传统方式难以比拟的。