# JMeter – Performing Distributed Load Testing with Docker

**日期: 2023/11/06**

JMeter 是一個強大的開源工具，用於對網絡應用進行性能和負載測試。在單個 JMeter 實例生成的負載不足的情況下，可以使用主從架構將負載分發給多個 JMeter 實例。文章內容將指導您如何使用 Docker 容器設置 JMeter 主從架構。

### 架構圖

<figure><img src="https://1934262382-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRmnDr9RXeSmhkUmaUsX7%2Fuploads%2F3ScYsjyDs7vzKye9RDCw%2Fjm-master-slave-host-docker.png?alt=media&#x26;token=291d4a44-7581-4996-939e-879df198411b" alt=""><figcaption></figcaption></figure>

### 先決條件

在開始之前，請確保您的系統上已安裝 Docker 和 Docker Compose。

## 建置容器

### JMeter Base Dockerfile

在分散式測試中，確保所有環境都擁有相同版本的 Java、JMeter 和插件是至關重要的。主節點和從節點之間的唯一區別是所公開的埠和正在運行的進程。為了實現這個目標，我們創建了一個通用的 Dockerfile，我們將其稱為 `jmbase` 映像，其中包含主節點和從節點都通用的步驟。以下是構建基礎映像的步驟：

1. **選擇 Java 11 版本：** 使用 `openjdk-11-jre-slim` 精簡版，以減小映像的大小。
2. **安裝實用工具：** 我額外增加安裝了一些實用工具，如 `wget`、`unzip` 和 `telnet`等。
3. **安裝 JMeter：** 下載並安裝了指定版本的 Apache JMeter（這裡使用的是版本 5.6）。
4. **使用版本變數：** 我創建了一個版本變數 `JMETER_VERSION`，以便未來維護更加方便。
5. **添加插件文件夾：** 將所有自定義插件添加到 JMeter 的 `lib` 和 `lib/ext` 文件夾中。
6. **添加示例測試：** 添加了一個示例測試的文件夾。

```docker
# Use Java 11 slim JRE
FROM openjdk:11.0.11-jre-slim
MAINTAINER CasterHsu

# JMeter version
ARG JMETER_VERSION=5.6

# Install few utilities
RUN apt-get clean && \
    apt-get update && \
    apt-get -qy install \
                wget \
                telnet \
                iputils-ping \
                unzip \
                vim

# Install JMeter
RUN   mkdir /jmeter \
      && cd /jmeter/ \
      && wget https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-$JMETER_VERSION.tgz \
      && tar -xzf apache-jmeter-$JMETER_VERSION.tgz \
      && rm apache-jmeter-$JMETER_VERSION.tgz

# ADD all the plugins
ADD plugins-lib /jmeter/apache-jmeter-$JMETER_VERSION/lib

## replace customer properties
ADD properties /jmeter/apache-jmeter-$JMETER_VERSION/bin

# ADD the sample test
ADD jmx jmx

# Set JMeter Home
ENV JMETER_HOME /jmeter/apache-jmeter-$JMETER_VERSION/

# Add JMeter to the Path
ENV PATH $JMETER_HOME/bin:$PATH
```

Build base jmeter image

```bash
docker build -t caster/jmeter-base:latest -f ./base.Dockerfile .
```

這樣，我們已經成功創建了通用的 `jmbase` Docker 映像，並且可以在主節點和從節點中使用它，確保它們都具有相同的環境設置，以順利進行 JMeter 測試。

### JMeter Client/Master Dockerfile：&#x20;

主節點的Docker文件應該繼承自基礎映像，並應該公開端口60000

```docker
# file name:jmmaster_.Dockerfile
# Use caster base image
FROM caster/jmeter
MAINTAINER CasterHsu

# Ports to be exposed from the container for JMeter Master
EXPOSE 60000

# keep container alive
CMD ["tail", "-f", "/dev/null"]
```

### JMeter Server/Slave Dockerfile：

服務端的Docker文件應繼承自基礎映像，並應該公開端口1099和50000。jmeter-servert常駐運行

```docker
# file name:jmslave_.Dockerfile
# Use caster base image
FROM caster/jmeter

MAINTAINER CasterHsu

# Ports to be exposed from the container for JMeter Slaves/Server
EXPOSE 1099 50000

# Application to run on starting the container
ENTRYPOINT $JMETER_HOME/bin/jmeter-server

# keep container alive
CMD ["tail", "-f", "/dev/null"]
```

個別啟動 Master / Slava

```bash
docker build -t caster/jmmaster:latest -f ./master.Dockerfile .
docker build -t caster/jmslave:latest -f ./slave.Dockerfile .
```

## JMeter 分散式測試

啟動 JMeter Server 後，您可以知道 Server 在容器內部的 IP 地址。以這個範例為例，Slave 的 IP 是 '192.168.80.4'。

### 獲取容器 IP方式 一

您可以在日誌（log）內容中查找 IP 地址，通常位於 WARN 訊息後。以下是示例內容：

```log
WARN StatusConsoleListener The use of package scanning to locate plugins is deprecated and will be removed in a future release
WARN StatusConsoleListener The use of package scanning to locate plugins is deprecated and will be removed in a future release
WARN StatusConsoleListener The use of package scanning to locate plugins is deprecated and will be removed in a future release
WARN StatusConsoleListener The use of package scanning to locate plugins is deprecated and will be removed in a future release
Created remote object: UnicastServerRef2 [liveRef: [endpoint:[192.168.80.4:46521](local),objID:[-21c30768:18bacbd9455:-7fff, 76839754569490693]]]
```

### 查找容器的 IP 地址二

要使用主從架構，您需要知道從容器的 IP 地址。您可以使用以下命令查找 IP 地址：

```bash
docker inspect --format '{{ .NetworkSettings.Networks.jmeternet.IPAddress }}' jmeter-master
docker inspect --format '{{ .NetworkSettings.Networks.jmeternet.IPAddress }}' jmeter-slave01
```

如果無法使用上述命令找到 IP 地址，您可以以 JSON 格式查看容器的所有信息：

```bash
docker inspect [容器名稱或 ID]
```

### 執行 JMeter 測試

* **單個 JMeter 執行:** 使用以下命令運行單個 JMeter 測試：

  ```bash
  jmeter -n -t sample-test.jmx
  ```

  執行結果：

  ```log
  Creating summariser <summary>
  Created the tree successfully using sample-test.jmx
  Starting standalone test @ 2023 Nov 8 02:39:16 UTC (1699411156838)
  Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
  Warning: Nashorn engine is planned to be removed from a future JDK release
  summary +    143 in 00:00:13 =   11.1/s Avg:    57 Min:    45 Max:   172 Err:     0 (0.00%) Active: 5 Started: 5 Finished: 0
  summary +    238 in 00:00:17 =   14.1/s Avg:    56 Min:    43 Max:   160 Err:     0 (0.00%) Active: 0 Started: 5 Finished: 5
  summary =    381 in 00:00:30 =   12.8/s Avg:    56 Min:    43 Max:   172 Err:     0 (0.00%)
  Tidying up ...    @ 2023 Nov 8 02:39:46 UTC (1699411186924)
  ... end of run
  ```
* **分散模式執行:** 若要在分散式模式下執行測試，您需要指定 Docker Slave 容器的內部 IP 地址，可以使用 `-R` 選項指定多個 IP 地址，以逗號分隔。

  ```bash
  jmeter -n -t sample-test.jmx -R 192.168.80.3,192.168.80.4
  ```

  執行結果：

  ```log
  Creating summariser <summary>
  Created the tree successfully using sample-test.jmx
  Configuring remote engine: 192.168.80.3
  Configuring remote engine: 192.168.80.4
  Starting distributed test with remote engines: [192.168.80.3, 192.168.80.4] @ 2023 Nov 8 02:41:03 UTC (1699411263848)
  Warning: Nashorn engine is planned to be removed from a future JDK release
  Remote engines have been started:[192.168.80.3, 192.168.80.4]
  Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
  summary +      1 in 00:00:00 =    2.1/s Avg:    91 Min:    91 Max:    91 Err:     0 (0.00%) Active: 2 Started: 2 Finished: 0
  summary +    312 in 00:00:34 =    9.2/s Avg:   702 Min:    45 Max:  5547 Err:    10 (3.21%) Active: 0 Started: 10 Finished: 10
  summary =    313 in 00:00:34 =    9.1/s Avg:   700 Min:    45 Max:  5547 Err:    10 (3.19%)
  Tidying up remote @ 2023 Nov 8 02:41:38 UTC (1699411298753)
  ... end of run
  ```

有錯誤應該是被google 阻擋，太頻繁訪問...如果是自己的server應該是沒有這問題，至少我有在測試一次自己的server.

這個示例的 `jmeter.properties` 文件已將 RMI SSL 連線關閉，如果您以後需要使用它，請自行調整屬性。現在，您已經具備了執行 JMeter 測試的基本知識，您可以在單台或分散式環境中開始執行測試。通過這些步驟，您可以使用 Docker 容器設置 JMeter 主從架構，從而更輕鬆地執行負載測試。

Slave 啟動錯誤回報RMI SSL&#x20;

```log
Error : java.io.FileNotFoundException: rmi_keystore.jks (No such file or directory)
```

RMI SSL 問題也可以參考官方文件操作: [前往](https://jmeter.apache.org/usermanual/remote-test.html#setup_ssl)

這不就上扣了嗎? 急什麼：[GitHub](https://github.com/MinchangHsu/JMeterLoadTest)

### 參考來源

* [jmeter-distributed-load-testing-using-docker](https://www.testautomationguru.com/jmeter-distributed-load-testing-using-docker/)
* [JMeter Blockly JMX](https://jmeter-plugins.org/editor/)

### 後續補充 - master 容器增加 ssh 設定

日期:2023/11/09

```docker
# Use caster base image
FROM caster/jmeter-base:latest
MAINTAINER CasterHsu

# 更新 apt 套件管理系統的存儲庫，然後安裝 openssh-server 套件
RUN apt-get update && apt-get install -y openssh-server

# 將自定義啟動腳本覆制到容器內
COPY start.sh /start.sh

# 創建 SSH 伺服器需要的目錄
RUN mkdir /var/run/sshd

# 設定 root 用戶的密碼為 "aa123456"
RUN echo 'root:aa123456' | chpasswd

# 修改 SSH 伺服器的設定以允許 root 用戶透過密碼進行登入
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# 修正 SSH 登入的問題，否則在登入後使用者會被踢出
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd

# 將 PATH 環境變數添加到 /etc/profile 中
RUN echo "export PATH=$PATH" >> /etc/profile

# Ports to be exposed from the container for JMeter Master
EXPOSE 60000 22

# keep container alive
CMD ["/start.sh"]
```

增加 SSH  設定實在不再守備範圍，花了一點時間才弄出來，而且還有點粗暴....

遇到的問題有：

1. sshd\_config 設定取代
2. ssh server 啟動失敗，主因容器尚未完成，就下指令結果導致，改用 start.sh 啟動
3. 連線成功後，user環境變數沒有吃到
4. ssh 連線指紋變更導致.know\_hosts  檔案要修改

#### 首次連接

```bash
The authenticity of host '[172.20.160.120]:2222 ([172.20.160.120]:2222)' can't be established.
ED25519 key fingerprint is SHA256:ez34LpGqQO5RGB+jzl0/3+l/W+UIshHcXTgALiZASC0.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? y

Please type 'yes', 'no' or the fingerprint: yes
Warning: Permanently added '[172.20.160.120]:2222' (ED25519) to the list of known hosts.
root@172.20.160.120's password:
Linux a8ad45f5bdd7 5.10.16.3-microsoft-standard-WSL2 #1 SMP Fri Apr 2 22:23:49 UTC 2021 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
```

這是 SSH 的一個安全性機制。當你第一次連接到一個 SSH 伺服器時，SSH 客戶端會提示你確認伺服器的身份。

該提示顯示了伺服器的公鑰指紋（在這個例子中是 ED25519 金鑰的 SHA256 指紋）。公鑰指紋是伺服器的一種唯一識別，通常用於確保你正在連接到正確的伺服器，而不是遭到中間人攻擊。

你可以選擇以下其中一種選項：

* **yes**: 確認並繼續連接，將伺服器的公鑰指紋保存在你的本地 `known_hosts` 文件中。
* **no**: 中斷連接，不保存伺服器的公鑰指紋。
* **\[fingerprint]**: 如果你事先知道伺服器的指紋，可以直接輸入該指紋確認連接。

通常，第一次連接時，你應該檢查伺服器的指紋，確保它是正確的伺服器。如果你確信伺服器是安全的，可以選擇 `yes` 繼續連接。之後，該伺服器的指紋將被保存在 `~/.ssh/known_hosts` 文件中，下次再次連接時就不會再提示。

#### 刪除容器後再次連接報錯

```bash
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
SHA256:ez34LpGqQO5RGB+jzl0/3+l/W+UIshHcXTgALiZASC0.
Please contact your system administrator.
Add correct host key in C:\\Users\\xxx/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in C:\\Users\\xxx/.ssh/known_hosts:45
Host key for [172.20.160.120]:2222 has changed and you have requested strict checking.
Host key verification failed.
```

這個警告表示你先前保存在你的 `known_hosts` 文件中的遠端主機金鑰已經發生了變化。這可能是正常的，例如當你重新安裝或更換遠端伺服器時，伺服器的金鑰會改變。

然而，這也可能是一個安全性問題的跡象，例如中間人攻擊。當你連接到一個伺服器時，SSH 會檢查伺服器的金鑰是否與先前保存的金鑰相符。如果不匹配，SSH 會發出警告，因為這可能是攻擊的跡象。

在這種情況下，你可以考慮以下步驟：

1. **確認伺服器身份**：確保伺服器確實是你預期的伺服器。你可以通過其他渠道（例如直接連絡伺服器管理員）來確認伺服器的指紋。
2. **更新 `known_hosts` 文件**：如果你確信伺服器的指紋已經更改並且是合法的，可以手動更新 `known_hosts` 文件。找到 `C:\\Users\\caster.hsu/.ssh/known_hosts` 文件，找到包含遠端伺服器指紋的行，將其刪除，然後再次嘗試連接。

   警告：請謹慎操作，確保你知道伺服器的指紋確實已經更改。
3. **確保安全連接**：如果你對伺服器的身份有任何疑慮，最好通過其他渠道驗證伺服器的指紋，以確保安全連接。

這種情況通常發生在伺服器重新安裝、更換金鑰或者其他可能改變伺服器身份的情況下。確保你確實知道伺服器的身份，然後決定是否要更新 `known_hosts` 文件。

#### 個人筆記 - 存紀錄使用....

```
jmeter docker
    https://github.com/guitarrapc/docker-jmeter-gui
    https://stackoverflow.com/questions/61324195/how-to-use-jmeter-with-docker-in-a-virtual-machine
    https://stackoverflow.com/questions/67419731/use-jmeter-desktop-application-as-web-app
    https://kaichu.io/posts/build-jmeter-docker-with-plugins/
    https://vepo.medium.com/dockerized-jmeter-84228733e306 ---> 還沒看懂.... 
    https://www.testautomationguru.com/jmeter-distributed-load-testing-using-docker/

excute jmx file in docker
    https://hub.docker.com/r/egaillardon/jmeter
    https://hub.docker.com/r/justb4/jmeter/

jmeter 主從架構
    2023/11/06 啟動指令.... 類
        docker-compose run -dit --name jmeter-master jmmaster /bin/bash
        docker-compose run -dit --name jmeter-slave01 jmslave /bin/bash

        看你要起幾台slave 只是後面觸發時要先找到 slave 的 ipaddress
            docker-compose run -dit --name jmeter-slave[XX] jmslave /bin/bash

    確認container ipAddress
        docker inspect --format '{{ .NetworkSettings.Networks.jmeternet.IPAddress }}' jmeter-master
        docker inspect --format '{{ .NetworkSettings.Networks.jmeternet.IPAddress }}' jmeter-slave01
    真的都找不到 就接下 docker inspect [container name or id] 看所有資訊 json 格式

    單個 jmeter 執行:
        jmeter -n -t LocalTest.jmx  
    主從一起執行: 
        jmeter -n -t LocalTest.jmx -R172.25.0.3
            -R 要填入 docker slave 的內部Ip
    ssh command line:
        ssh root@172.20.160.120 -p 2222

    docker 檔案指令傳入 or 導出
        導出:
            docker cp [containerId]:[containerPath] [localSourcePath]
            Ex. docker cp f0812086941b:/sample-test/LoaclTest.jmx LoaclTest.jmx
        傳入:
            docker cp [localSourcePath] [containerId]:[containerPath]
            Ex. docker cp ./LocalTest.jmx f0812086941b:/sample-test/LocalTest.jmx

        build base & master & slave:
            docker build -t caster/jmeter-base:latest -f ./base.Dockerfile .
            docker build -t caster/jmmaster:latest -f ./master.Dockerfile .
            docker build -t caster/jmslave:latest -f ./slave.Dockerfile .

        docker-compose build & run :
            docker-compose up -d jmmaster
            docker-compose up -d jmslave
```
