<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>jmeter | 伪架构师</title>
    <link>/tags/jmeter/</link>
      <atom:link href="/tags/jmeter/index.xml" rel="self" type="application/rss+xml" />
    <description>jmeter</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Thu, 16 Nov 2017 23:31:55 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>jmeter</title>
      <link>/tags/jmeter/</link>
    </image>
    
    <item>
      <title>在 Kubernetes 上使用 Jmeter 运行压力测试</title>
      <link>/post/jmeter-on-k8s/</link>
      <pubDate>Thu, 16 Nov 2017 23:31:55 +0800</pubDate>
      <guid>/post/jmeter-on-k8s/</guid>
      <description>

&lt;p&gt;Kubernetes 的资源和任务调度能力，能给自动化测试提供相当大力的支持，这里以 Jmeter 为例，讲讲如何在 Kubernetes 中使用 Jmeter 进行简单的性能测试。&lt;/p&gt;

&lt;h2 id=&#34;开始之前&#34;&gt;开始之前&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;录制任务：本文所用镜像为 Jmeter 3.x 版本，建议提前录制一个简单的测试任务进行下面的操作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;支持 Jobs 的 Kubernetes 集群，以及缺省的 StorageClass 支持，能够实现 PVC 的动态供应。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;互联网连接。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;试验内容&#34;&gt;试验内容&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;搭建一个 Web DAV 服务，用于提供给 Jmeter 输入输出场所，也便于日后 CI/CD 工具的案例输入或结果输出。&lt;/li&gt;
&lt;li&gt;运行单实例的 Jmeter 测试任务。&lt;/li&gt;
&lt;li&gt;运行集群形式的 Jmeter 测试任务。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;预备存储&#34;&gt;预备存储&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;这一步骤并非强制，完全可以通过 scp 或者 mount 等其他方式来实现&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这里我们做一个 Web DAV 服务，挂载一个 PVC，在其中分为 input 和 output 两个目录，实际使用过程中，可以进一步按照任务或者 Job 对目录进行更详尽的规划。&lt;/p&gt;

&lt;p&gt;首先创建名为&lt;code&gt;jmeter-task&lt;/code&gt;的存储卷：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: jmeter-task
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;存储卷创建之后，可以使用 &lt;code&gt;cadaver&lt;/code&gt; 或者 &lt;code&gt;WinSCP&lt;/code&gt; 等工具去建立目录。&lt;/p&gt;

&lt;p&gt;接下来上传 &lt;code&gt;*.jmx&lt;/code&gt; 文件，到 &lt;code&gt;input&lt;/code&gt; 目录之中，这里我录制的一个持续访问京东首页的任务，命名为 &lt;code&gt;jd.jmx&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;单实例测试&#34;&gt;单实例测试&lt;/h2&gt;

&lt;p&gt;单实例测试很容易，使用 Kubernetes 的 Job 方式即可：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: batch/v1
kind: Job
metadata:
  name: jmeter
spec:
  template:
    metadata:
      name: jmeter
    spec:
      restartPolicy: Never
      containers:
      - name: jmeter
        image: dustise/jmeter-server
        command:
          - &amp;quot;/jmeter/bin/jmeter&amp;quot;
          - &amp;quot;-n&amp;quot;
          - &amp;quot;-t&amp;quot;
          - &amp;quot;/jmeter/input/jd.jmx&amp;quot;
          - &amp;quot;-l&amp;quot;
          - &amp;quot;/jmeter/output/log&amp;quot;
          - &amp;quot;-j&amp;quot;
          - &amp;quot;/jmeter/output/joker&amp;quot;
        volumeMounts:
        - name: data
          mountPath: /jmeter/input
          subPath: input
        - name: data
          mountPath: /jmeter/output
          subPath: output
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: jmeter-task
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的定义中：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;任务 Pod 加载了存储卷 &lt;code&gt;jmeter-task&lt;/code&gt;。使用 &lt;code&gt;subPath&lt;/code&gt; 指令，分别挂载了输入和输出目录。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使用 &lt;code&gt;-n -t&lt;/code&gt; 的方式运行测试任务，并把输出文件定位到 &lt;code&gt;output&lt;/code&gt; 目录中。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;接下来就可以使用 &lt;code&gt;kubectl create -f jobs1.yaml&lt;/code&gt; 来运行这一任务。&lt;/p&gt;

&lt;p&gt;任务启动之后，可以：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;使用 &lt;code&gt;kubectl get jobs&lt;/code&gt; 来查看任务运行状况。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;kubectl get pods --show-all&lt;/code&gt; 查看任务 Pod。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;kubectl logs -f [pod name&lt;/code&gt; 查看任务输出。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后任务会变成完成状态，就可以在 Web DAV 中查看任务报告了。&lt;/p&gt;

&lt;h2 id=&#34;集群测试&#34;&gt;集群测试&lt;/h2&gt;

&lt;p&gt;Jmeter 可以使用控制台+负载机的形式，使用多个节点进行压力测试，这里需要解决的一个最重要问题就是，在指派任务给负载机时，Jmeter 需要使用 &lt;code&gt;-R host:port&lt;/code&gt; 的参数，来指定任务要调用的负载机。这一通信是无法通过 Kubernetes 方式的 Service 来完成的。必须建立 Pod 之间的通信，而 Pod 的主机名地址是很飘逸的，同时，我们还是希望负载节点的数量能够实现较为自由的伸缩，因此解决方法就只有 StatefulSet 了。&lt;/p&gt;

&lt;p&gt;这个 YAML 很长，所以放在最后了，说说其中的要点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;注解中的 &lt;code&gt;security.alpha.kubernetes.io/sysctls&lt;/code&gt;：实际运行中，jmeter 负载机是需要对内核参数进行一点调整的，Pod 中可以用这一方式进行调整，&lt;code&gt;https://kubernetes.io/docs/concepts/cluster-administration/sysctl-cluster/&lt;/code&gt; 中有更详细的关于这方面的内容讲解。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;spec.affinity&lt;/code&gt;：这里设置 Jmeter Pod 尽量分布在不同节点上。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;RMI_HOST&lt;/code&gt;环境变量：使用每个 Pod 的 IP 为这一变量赋值。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Service：利用这个 Headless 服务，为每个 Pod 提供主机名支持。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;启动这个 Statefulset 之后，会看到规律创建的 Pod 名称：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;jnode-0                   1/1       Running   0          1h
jnode-1                   1/1       Running   0          1h
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对应的主机名称就应该是 jnode-0.jfarm，jnode-1.jfarm。所以上面的 &lt;code&gt;job.yaml&lt;/code&gt; 可以新增 &lt;code&gt;-R jnode-0.jfarm:1099,jnode-1.jfarm:1099&lt;/code&gt; 即可。&lt;/p&gt;

&lt;p&gt;使用 &lt;code&gt;kubectl create&lt;/code&gt; 启动任务之后，查看该任务 Pod 的日志，会出现大致这样的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Creating summariser &amp;lt;summary&amp;gt;
Created the tree successfully using /jmeter/input/jd.jmx
Configuring remote engine: jnode-0.jfarm:1099
Configuring remote engine: jnode-1.jfarm:1099
Starting remote engines
Starting the test @ Thu Nov 16 07:24:14 GMT 2017 (1510817054558)
Remote engines have been started
Waiting for possible Shutdown/StopTestNow/Heapdump message on port 4445
summary +    302 in 00:01:16 =    4.0/s Avg:  2967 Min:  2627 Max:  5457 Err:     0 (0.00%) Active: 0 Started: 20 Finished: 20
summary +     98 in 00:00:00 = 3062.5/s Avg:  3270 Min:  2635 Max:  7192 Err:     0 (0.00%) Active: 0 Started: 20 Finished: 20
summary =    400 in 00:01:16 =    5.3/s Avg:  3041 Min:  2627 Max:  7192 Err:     0 (0.00%)
Tidying up remote @ Thu Nov 16 07:25:31 GMT 2017 (1510817131966)
... end of run
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，成功配置远程负载服务器之后，测试开始，最后成功完成。&lt;/p&gt;

&lt;h2 id=&#34;statefulset-源码&#34;&gt;Statefulset 源码&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: jnode
  labels:
    app: jmeter
    component: node
spec:
  serviceName: jfarm
  replicas: 2
  selector:
    matchLabels:
      app: jmeter
      component: node
  template:
    metadata:
      labels:
        app: jmeter
        component: node
      annotations:
        security.alpha.kubernetes.io/sysctls: net.ipv4.ip_local_port_range=10000 65000,net.ipv4.tcp_syncookies=1
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              topologyKey: kubernetes.io/hostname
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - jmeter
                - key: component
                  operator: In
                  values:
                  - node
      restartPolicy: Always
      containers:
      - name: jmeter
        image: dustise/jmeter-server
        ports:
        - name: server
          containerPort: 1099
        - name: rmi
          containerPort: 20000
        env:
        - name: RMI_HOST
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
---
apiVersion: v1
kind: Service
metadata:
  name: jfarm
  labels:
    app: jmeter
spec:
  clusterIP: None
  ports:
  - port: 1099
    name: server
  selector:
    app: jmeter
    component: node
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>CI/CD 工具链的分分合合</title>
      <link>/post/cicd-toolchain-or-toolset/</link>
      <pubDate>Fri, 03 Nov 2017 04:49:29 +0800</pubDate>
      <guid>/post/cicd-toolchain-or-toolset/</guid>
      <description>

&lt;h2 id=&#34;作案动机&#34;&gt;作案动机&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;一种对 ci/cd 工具的轻量化和解耦的尝试？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jenkins 的传统集群方式，是使用不同环境的服务器构成不同能力的 Jenkins 节点，由主节点根据任务
环节的需要，调度不同能力的子节点来完成构建或部署任务。&lt;/p&gt;

&lt;p&gt;进入容器云时代，情况发生了变化，我们可以使用不同能力的 Jenkins 镜像，使用 Kubernetes 插件来
完成这种任务的拆分和调度，为此，我构建了一个包含所有我们平时用到的工具的 Jenkins 镜像，简化了
节点的扩展和选择过程。&lt;/p&gt;

&lt;p&gt;然而随着学习和应用的深入，我意识到这种做法有几个问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DevOps 中隐含着发挥个人能力的愿景，工具链的所谓大而全，只不过是在画一个比较大的圈，使用这样的一套 Jenkins，还是要被其中所包含的仅有的工具中进行选择，对身陷其中的技术人员绝不能说是友好，也绝不是鼓励各展所长的态度。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;现有的功能测试、接口测试、压力测试等工具，越来越专业化，往往会有各自的工作集群调度甚至是托管方案，例如 selenium grid、JMeter 集群等。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;同样的测试工作，可能有多种工具都可以完成，例如一个 Restful 的接口测试，不管是 JMeter 还是Postman，或者 SoapUI 以及五花八门的自有工具，都可以完成这样的工作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;各种 DevOps 以及微服务管控和治理平台会有各自的工具链构成以及扩展方案。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;让各种工具自成镜像，无疑对镜像尺寸和更新速度都会有更好的支持。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这样就让我产生一个新的思路：能不能让 Jenkins 回归到一个原始状态，只负责问题的定义、任务的分发和结果的归集呢？于是就有了这样的一点尝试。&lt;/p&gt;

&lt;p&gt;这一尝试的思路是，Jenkins 镜像/容器只使用插件和一些 Shell 脚本，同外部的调度能力（例如
Docker 的容器或者 Kubernetes 的任务等）进行交互，利用网络和共享存储，来实现任务的分发和协调
以及最后的结果汇聚。&lt;/p&gt;

&lt;p&gt;下面以 JMeter 为例，进行一个简单的压力测试，测试环境为了节省起见，使用的是 Docker 加本地目录
共享的方式，这种方式也可以很方便的扩展为 Kubernetes 的 Job + PV/PVC 方式。&lt;/p&gt;

&lt;h2 id=&#34;镜像&#34;&gt;镜像&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Jenkins: dustise/jenkins&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;JMeter: hauptmedia/jmeter&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;注意此处随意的选择了一个 2.x 版本的 JMeter。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;jenkins-插件&#34;&gt;Jenkins 插件&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker_build_step&lt;/code&gt;：用于执行 Docker 指令，类似功能的 Docker Common 插件，其参数不支持环境变量，因此淘汰。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Performance Plugin：一个用于搜集和展示多种测试结果报告的插件。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;存储&#34;&gt;存储&lt;/h2&gt;

&lt;p&gt;这里我们需要为 Jenkins 和各种工具（这里是 JMeter）提供一个可以共享访问的文件交换区域：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;单机 Docker 下，可以是共享的主机目录。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Kubernetes 环境可以使用共享的分布式存储卷。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如我们创建名为&lt;code&gt;/var/cicd/exchange/jmeter&lt;/code&gt;的目录，进行文件交换。&lt;/p&gt;

&lt;h2 id=&#34;输入&#34;&gt;输入&lt;/h2&gt;

&lt;p&gt;使用 JMeter GUI 录制 jmx 文件，为任务生成汇总日志（保存到/root/summary.log）。
将 jmx 上传到上面所说的目录之中，这样就保证了 Docker 和 Jenkins 都能通过 &lt;code&gt;-v&lt;/code&gt; 来进行加载。&lt;/p&gt;

&lt;p&gt;这里我们启动 Jenkins 的时候，使用 &lt;code&gt;-v /var/cicd/exchange/jmeter:/exchange/jmeter&lt;/code&gt;
参数让 Jenkins 加载这一目录。&lt;/p&gt;

&lt;h2 id=&#34;jenkins-任务&#34;&gt;Jenkins 任务&lt;/h2&gt;

&lt;h3 id=&#34;准备工作&#34;&gt;准备工作&lt;/h3&gt;

&lt;p&gt;首先的环节是，为后面要开工的 JMeter 准备工作环境。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 创建当前 Build 目录
mkdir -p /exchange/jmeter/$BUID_TAG
# 复制 jmx 到当前 Build 目录
cp /exchange/jmeter/jd.jmx /exchange/jmeter/$BUID_TAG
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;设置容器&#34;&gt;设置容器&lt;/h3&gt;

&lt;p&gt;准备好文件之后，我们需要添加下一个环节就是设置一个容器：&lt;/p&gt;

&lt;p&gt;这里添加的环节是&lt;code&gt;docker_build_step&lt;/code&gt;的&lt;code&gt;Execute Docker Command&lt;/code&gt;环节。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Image name: hauptmedia/jmeter&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Command：bin/jmeter -n -t /root/jd2.jmx -l /root/result.log -j /root/process.log&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Bind Mounts：&lt;code&gt;/var/cicd/kube/$BUILD_TAG /root&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;注意绑定卷这里就使用了 $BUILD_TAG 变量&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这样就完成了 Jenkins 和 JMeter 的文件共享：&lt;/p&gt;

&lt;p&gt;Jenkins 的 &lt;code&gt;/exchange/jmeter/$BUILD_TAG&lt;/code&gt;，对应的是新创建容器的&lt;code&gt;/root&lt;/code&gt;目录。&lt;/p&gt;

&lt;h3 id=&#34;启动容器&#34;&gt;启动容器&lt;/h3&gt;

&lt;p&gt;使用&lt;code&gt;Execute Docker Command&lt;/code&gt;的&lt;code&gt;start container(s)&lt;/code&gt;环节，&lt;code&gt;Container ID(s)&lt;/code&gt;填写变量
&lt;code&gt;$DOCKER_CONTAINER_IDS&lt;/code&gt;，代表启动我们刚才创建的容器。&lt;/p&gt;

&lt;h3 id=&#34;等待任务&#34;&gt;等待任务&lt;/h3&gt;

&lt;p&gt;如果我们需要使用 Jenkins 进行结果的汇聚，那么这里就需要进行阻塞——等到 JMeter 执行完毕后，
才能进行下面的搜集结果、清理现场等操作。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;RESULT=&amp;quot;/exchange/jmeter/$BUILD_TAG/summary.log&amp;quot;
while [ ! -f $RESULT ]
do
  sleep 30
done


while [[ `lsof | grep summary.log` ]]
do
    sleep 10
done
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的脚本利用 lsof 来检测 summary.log 的占用情况，一旦该文件关闭，说明压力测试已经结束。&lt;/p&gt;

&lt;h3 id=&#34;清理容器&#34;&gt;清理容器&lt;/h3&gt;

&lt;p&gt;使用 &lt;code&gt;Execute Docker Command&lt;/code&gt; 的 &lt;code&gt;remove container(s)&lt;/code&gt; 环节，&lt;code&gt;Container ID(s)&lt;/code&gt; 填写变量
&lt;code&gt;$DOCKER_CONTAINER_IDS&lt;/code&gt;，代表清理我们刚才创建的容器。&lt;/p&gt;

&lt;h3 id=&#34;结果展示&#34;&gt;结果展示&lt;/h3&gt;

&lt;p&gt;Performance Plugin 能够识别 JMeter、SoapUI 以及 Parrot 生成的报告文件，这里我们只设置一
个选项，就是 &lt;code&gt;Source data files (autodetects format):&lt;/code&gt;，这里填写
&lt;code&gt;/exchange/jmeter/$BUILD_TAG/summary.log&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;插件会把这一文件拷贝到 Workspace，进行解析和显示。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-job&#34;&gt;Kubernetes Job&lt;/h2&gt;

&lt;p&gt;上述过程可以很方便的改造成为 Kubernetes Job：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker 相关内容，改为使用 kubectl 进行的 job 文件的生成和操作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;阻塞过程可以查询任务节点的状态&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;文件存储可以使用 PVC 来共享&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
