負荷試験ツールJMeterを試しに触ってみた

## はじめに
ハンズラボ [Advent Calendar 2020](https://qiita.com/advent-calendar/2020/handslab"Advent Calendar 2020") 15日目の記事になります。

2020/12/01にハンズラボ株式会社に入社しました@anakagawa1114です！
入社後わずか2週間でこのような記事を書く機会をいただけて嬉しく思います。
今後も何かしら技術系の記事を定期期的にあげられればと思いますので、
宜しくお願い致します。

さて、今回の記事は負荷試験ツールJMeterについてとなります。
丁度現在タスクで負荷試験ツールの調査をやっており、それの備忘録も兼ねておりますので、
技術的に正しくなかったり稚拙な部分があるかもしれませんが、生暖かい目で見守っていただければ幸いです。

## JMeterとは

Java製のWebページ負荷テストツール
オススメの使い方はGUIでシナリオ作成してCUIで実施みたい
※ GUIはMac, WIndows両方対応

公式：https://jmeter.apache.org/
最新版DL:http://jmeter.apache.org/download_jmeter.cgi

## 構築環境

#### JMeter GUI環境 (テストシナリオ作成環境)

* MacOS 10.15.7
* JMeter 5.4

#### JMeter CUI環境 (テストシナリオ実行環境 Master/Slave)
* CentOS 7.9
* JMeter 5.4
* Java 1.8.0


#### Target Webサーバ (負荷をかけるWebサーバ)

* CentOS 7.9
* Apache 2.4

## アーキテクチャ図

* VirtualBox内のVMにDockerコンテナを設置し各環境を構築

![JMater (3).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/35270fcd-7712-1ea8-a0bd-a32b42b5bdd0.jpeg)

## GUI環境作成編

#### Javaインストール

* JMeterが対応しているJavaを入れる必要があります
  * JMeter 5.4なのでJava 8相当
  * JMeter 5.4 要件：https://jmeter.apache.org/download_jmeter.cgi
  * Java8 SDK https://www.oracle.com/jp/java/technologies/javase/javase-jdk8-downloads.html

```
anakagawa$ java -version
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
```

#### JMeter実行

* JMeterをダウンロードし実行する
  * 最新版:http://jmeter.apache.org/download_jmeter.cgi からダウンロード
  * 解凍しapache-jmeter-5.4/binの中にある【jmeter】をダブルクリック
  * 起動に成功すると下記画面が出る

![スクリーンショット 2020-12-09 18.01.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/93d9b4fb-17b7-9b84-1200-0f5f6689a48d.png)

* このままでも良いのですがついでに日本語化してみます
  * メニュー画面の【Options】→【Choose Language】→【Japanese】

![スクリーンショット 2020-12-10 10.19.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/e658bd14-1471-e8ea-cbdf-72d7a1fc17db.png)

#### シナリオ作成

* スレッドの設定作成&設定
  * スレッド数：同時に実行するテストの回数 今回は1回
  * Ramp-Up(秒)：スレッドグループが起動するまでの時間です 今回は1秒で1個のスレッドグループが起動します
  * ループ回数：各スレッドが一連の処理を実施する回数です 今回は100回アクセスする設定

![スクリーンショット 2020-12-10 12.13.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/36e629c2-659d-9c1a-f509-9559b436c37f.png)

![スクリーンショット 2020-12-10 14.31.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/0a4aae23-805a-d861-d2de-6a7c29c36f63.png)

* スレッドグループにHTTPリクエストを作成
  * 右クリックで【追加】→【サンプラー】→【HTTPリクエスト】

![スクリーンショット 2020-12-10 14.14.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/131a0e37-19e3-c3a4-1d48-f100c97cf817.png)

* HTTPリクエストを設定
  * 接続先は要件に合わせて設定
  * 今回はTOPページにアクセスが集中する想定で実験するのでGETで/index.htmlを指定
  * 必要な設定をしたら画面下部の【追加】を選択

**ターゲットWebサイト**

* VirtualBox上Dockerコンテナ内にあるWebサーバにテストを実施
  * 下記Webサーバコンテナに転送（構築方法は割愛）


```
[root@Tool-Test ~]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
ccd384fc68a4        centos:centos7      "/sbin/init"        4 days ago          Up 11 hours         0.0.0.0:8080->80/tcp   httpd
```

```
IP Address : 192.168.56.3:8080
ドキュメントルート：/index.html
リクエスト：GET
```

![スクリーンショット 2020-12-10 14.15.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/bd8a3cf0-01be-b483-19ae-7f9b4b9858f3.png)

* 統計レポートの追加(結果を集計するやつ)
  * HTTPリクエストの上で右クリックし【追加】→【リスナー】→【統計レポート】で作成

![スクリーンショット 2020-12-10 14.15.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/0897793f-5607-8342-6962-5bc4412ea700.png)

![スクリーンショット 2020-12-10 14.15.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/ee6a8368-147a-73b9-190b-48198cb8ddf1.png)

* ここまでできたらテストを一度保存します
  * 【ファイル】→【テスト計画を保存】を選択し保存します(後でCUIで動作させる時に使います)

![スクリーンショット 2020-12-10 15.18.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/161dea13-6642-e8c1-a6a9-86676c686e26.png)

#### GUIで動作確認

* テスト実施
  * 画面上の【▶】(開始)を押す
  * 押すと最初に設定したスレッドの設定の通り100回アクセスされている(画面上は)

**Before**
![スクリーンショット 2020-12-10 15.24.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/6eb950e4-c650-104f-39c1-abf55eb8b6ab.png)

**After**
![スクリーンショット 2020-12-10 15.26.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181414/fd5586c9-cbfa-5b73-eaaf-34b06a3676e8.png)

* Webサーバ側で確認
  * VirtualBoxのホストネットワークを経由するのでアクセスログの接続元が【192.168.56.1】になる

```
[root@ccd384fc68a4 /]# ionice -c2 -n7 nice -n19 egrep '10/Dec/2020:06' /var/log/httpd/access_log | awk '{print $4}'| cut -c 2-18 | sort  | uniq -c
    100 10/Dec/2020:06:25

[root@ccd384fc68a4 /]# cat /var/log/httpd/access_log | grep "10/Dec/2020:06" | head
192.168.56.1 - - [10/Dec/2020:06:25:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_271)"
192.168.56.1 - - [10/Dec/2020:06:25:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_271)"
192.168.56.1 - - [10/Dec/2020:06:25:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_271)"
192.168.56.1 - - [10/Dec/2020:06:25:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_271)"
192.168.56.1 - - [10/Dec/2020:06:25:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_271)"
192.168.56.1 - - [10/Dec/2020:06:25:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_271)"
192.168.56.1 - - [10/Dec/2020:06:25:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_271)"
192.168.56.1 - - [10/Dec/2020:06:25:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_271)"
192.168.56.1 - - [10/Dec/2020:06:25:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_271)"
192.168.56.1 - - [10/Dec/2020:06:25:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_271)"
```

* **無事サーバのアクセスログからもアクセスが確認できたので次はCUI編です**
  * 集計コマンドについては私の過去記事を参考にどうぞ
  * https://qiita.com/anakagawa1114/items/56ee60ea720b83f8142f

## CUI環境作成編

#### CentOS7

```
CentOS Linux release 7.9.2009
```

* Virtualbox&Dockerで作成(Virtualbox&Dockerの構築は割愛)

#### Javaインストール

```
yum install -y java

[root@24c7217afc90 opt]# java -version
openjdk version "1.8.0_272"
OpenJDK Runtime Environment (build 1.8.0_272-b10)
OpenJDK 64-Bit Server VM (build 25.272-b10, mixed mode)
```

#### JMeter ダウンロード&配置

* /opt配下に設置を想定

```
cd /opt
curl -O https://ftp.kddi-research.jp/infosystems/apache//jmeter/binaries/apache-jmeter-5.4.tgz
tar -xvzf apache-jmeter-5.4.tgz

```

* Path追加

```
vi ~/.bashrc

# apache jmeter
PATH=$PATH:$HOME/bin:/opt/apache-jmeter-5.4/bin
```

* Path反映&バージョン確認

```
source ~/.bashrc

[root@24c7217afc90 opt]# jmeter --version
Dec 09, 2020 6:55:24 AM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
    _    ____   _    ____ _   _ _____       _ __  __ _____ _____ _____ ____
   / \  |  _ \ / \  / ___| | | | ____|     | |  \/  | ____|_   _| ____|  _ \
  / _ \ | |_) / _ \| |   | |_| |  _|    _  | | |\/| |  _|   | | |  _| | |_) |
 / ___ \|  __/ ___ \ |___|  _  | |___  | |_| | |  | | |___  | | | |___|  _ <
/_/   \_\_| /_/   \_\____|_| |_|_____|  \___/|_|  |_|_____| |_| |_____|_| \_\ 5.4

Copyright (c) 1999-2020 The Apache Software Foundation
```

※ ロゴの横に小さく5.4って書いてあります(わかりにくい)

#### JMeter実行(CUI Master)

* SSL設定
  * 今回はHTTPであってHTTPSで実験しないので、SSLはOFFにする
  * \<JMeter Home Dir>/bin/jmeter.properties

```
server.rmi.ssl.disable=false → trueへ
```

* JMeterにシナリオを読み込ませて実行する
  * GUI編で作成したシナリオファイル(.jmx)をJMeterサーバにコピーする
  * JMeter実行時のオプションは下記の通り
  
```
    --?
        print command line options and exit
    -h, --help
        print usage information and exit
    -v, --version
        print the version information and exit
    -p, --propfile <argument>
        the jmeter property file to use
    -q, --addprop <argument>
        additional JMeter property file(s)
    -t, --testfile <argument>
        the jmeter test(.jmx) file to run
    -l, --logfile <argument>
        the file to log samples to
    -i, --jmeterlogconf <argument>
        jmeter logging configuration file (log4j2.xml)
    -j, --jmeterlogfile <argument>
        jmeter run log file (jmeter.log)
    -n, --nongui
        run JMeter in nongui mode
    -s, --server
        run the JMeter server
    -H, --proxyHost <argument>
        Set a proxy server for JMeter to use
    -P, --proxyPort <argument>
        Set proxy server port for JMeter to use
    -N, --nonProxyHosts <argument>
        Set nonproxy host list (e.g. *.apache.org|localhost)
    -u, --username <argument>
        Set username for proxy server that JMeter is to use
    -a, --password <argument>
        Set password for proxy server that JMeter is to use
    -J, --jmeterproperty <argument>=<value>
        Define additional JMeter properties
    -G, --globalproperty <argument>=<value>
        Define Global properties (sent to servers)
        e.g. -Gport=123
         or -Gglobal.properties
    -D, --systemproperty <argument>=<value>
        Define additional system properties
    -S, --systemPropertyFile <argument>
        additional system property file(s)
    -f, --forceDeleteResultFile
        force delete existing results files and web report folder if present before starting the test
    -L, --loglevel <argument>=<value>
        [category=]level e.g. jorphan=INFO, jmeter.util=DEBUG or com.example.foo=WARN
    -r, --runremote
        Start remote servers (as defined in remote_hosts)
    -R, --remotestart <argument>
        Start these remote servers (overrides remote_hosts)
    -d, --homedir <argument>
        the jmeter home directory to use
    -X, --remoteexit
        Exit the remote servers at end of test (CLI mode)
    -g, --reportonly <argument>
        generate report dashboard only, from a test results file
    -e, --reportatendofloadtests
        generate report dashboard after load test
    -o, --reportoutputfolder <argument>
        output folder for report dashboard
```

* 下記コマンドで実行（NoGUI&Test File指定）

```
[root@24c7217afc90 ~]# /opt/apache-jmeter-5.4/bin/jmeter -n -t /opt/apache-jmeter-5.4/test-case.jmx 
Creating summariser <summary>
Created the tree successfully using /opt/apache-jmeter-5.4/test-case.jmx
Starting standalone test @ Thu Dec 10 08:05:19 UTC 2020 (1607587519993)
Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
summary =    100 in 00:00:00 =  534.8/s Avg:     0 Min:     0 Max:    31 Err:     0 (0.00%)
Tidying up ...    @ Thu Dec 10 08:05:20 UTC 2020 (1607587520458)
... end of run
```

* Webサーバ側で確認

```
[root@ccd384fc68a4 /]# ionice -c2 -n7 nice -n19 egrep '10/Dec/2020:08:05' /var/log/httpd/access_log | awk '{print $4}'| cut -c 2-18 | sort  | uniq -c
    100 10/Dec/2020:08:05

[root@ccd384fc68a4 /]# cat /var/log/httpd/access_log | grep "10/Dec/2020:08:05" | head
172.17.0.2 - - [10/Dec/2020:08:05:20 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.2 - - [10/Dec/2020:08:05:20 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.2 - - [10/Dec/2020:08:05:20 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.2 - - [10/Dec/2020:08:05:20 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.2 - - [10/Dec/2020:08:05:20 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.2 - - [10/Dec/2020:08:05:20 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.2 - - [10/Dec/2020:08:05:20 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.2 - - [10/Dec/2020:08:05:20 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.2 - - [10/Dec/2020:08:05:20 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.2 - - [10/Dec/2020:08:05:20 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
```

**CUIでも無事アクセスができていることを確認できました！**

#### JMeter実行(CUI Slave経由)

* Slave環境の作成
  * 基本はMasterと同じ環境をコピーするだけで問題ない
  
* SSLの設定
  * Masterと同じで使用しない場合はMasterでやったSSL設定をやること
  * \<JMeter Home Dir>/bin/jmeter.properties

```
server.rmi.ssl.disable=false → trueへ
```
  
* jmeter-serverの自動起動設定の為下記の内容を追加
  * /etc/rc.d/rc.local

```
/opt/apache-jmeter-5.4/bin/jmeter-server &
```

* /etc/rc.d/rc.localの実行権限を付与

```
sudo chmod +x /etc/rc.d/rc.local
```

* ファイルディスクリプタ上限を変更
  

```
sudo vi /usr/lib/systemd/system/rc-local.service

[Service]
LimitNOFILE=20000
```

```
$ sudo vi /etc/security/limits.conf

* soft nofile 20000
* hard nofile 20000
```

* カーネルパラメータ設定
  * 下記を追加

```
sudo vi /etc/sysctl.conf

net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_fin_timeout=5
```

* 設定の反映

```
sudo systemctl -p
```

* サーバ再起動後Jeater起動を確認

```
[root@JMeter-Slave01 /]$ ps auxfww | grep "jmeter"
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        48  0.0  0.0  15124  1436 ?        S    10:03   0:00 /bin/sh /opt/apache-jmeter-5.4/bin/jmeter-server
root        52  0.0  0.0  15124  1524 ?        S    10:03   0:00  \_ /bin/sh /opt/apache-jmeter-5.4/bin/jmeter -Dserver_port=1099 -s -j jmeter-server.log
root        85  0.2  4.8 3529028 188668 ?      Sl   10:03   0:06      \_ /usr/bin/java -server -XX:+HeapDumpOnOutOfMemoryError -Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1ReservePercent=20 -Djava.security.egd=file:/dev/urandom -Duser.language=en -Duser.region=EN -jar /opt/apache-jmeter-5.4/bin/ApacheJMeter.jar -Dserver_port=1099 -s -j jmeter-server.log
```

* Slaveサーバを指定してテストを実行
  * $ jmeter -n -t \<シナリオファイル> -R \<IPアドレス>

```
[root@JMeter-Master /]$ /opt/apache-jmeter-5.4/bin/jmeter -n -t /opt/apache-jmeter-5.4/test-case.jmx -R 172.17.0.3
Creating summariser <summary>
Created the tree successfully using /opt/apache-jmeter-5.4/test-case.jmx
Configuring remote engine: 172.17.0.3
Starting distributed test with remote engines: [172.17.0.3] @ Mon Dec 14 10:36:25 UTC 2020 (1607942185991)
Remote engines have been started:[172.17.0.3]
Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
summary =    100 in 00:00:00 =  423.7/s Avg:     0 Min:     0 Max:    10 Err:     0 (0.00%)
Tidying up remote @ Mon Dec 14 10:36:26 UTC 2020 (1607942186912)
... end of run
```

* Webサーバ側で確認

```
[root@ccd384fc68a4 /]# ionice -c2 -n7 nice -n19 egrep '14/Dec/2020:10:36' /var/log/httpd/access_log | awk '{print $4}'| cut -c 2-18 | sort  | uniq -c
    100 14/Dec/2020:10:36

[root@ccd384fc68a4 /]# cat /var/log/httpd/access_log | grep "14/Dec/2020:10:36" | head
172.17.0.3 - - [14/Dec/2020:10:36:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.3 - - [14/Dec/2020:10:36:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.3 - - [14/Dec/2020:10:36:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.3 - - [14/Dec/2020:10:36:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.3 - - [14/Dec/2020:10:36:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.3 - - [14/Dec/2020:10:36:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.3 - - [14/Dec/2020:10:36:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.3 - - [14/Dec/2020:10:36:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.3 - - [14/Dec/2020:10:36:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
172.17.0.3 - - [14/Dec/2020:10:36:26 +0000] "GET /index.html HTTP/1.1" 200 10 "-" "Apache-HttpClient/4.5.12 (Java/1.8.0_272)"
```

**SlaveサーバのIPアドレス(172.17.0.3)から100回アクセスが来てることを確認できました！**

## 最後に

検証は以上となります！

今まで負荷試験ツールというとWeb上でできる簡単な物しか使ったことがなかったのですが、今回始めてアプリで実施するに辺りJMeterの設定やSlaveの設定など、
若干躓く所がありましたが、ツールとしては非常に構築しやすいと思いました！
まだ、いくつか負荷試験ツールを検証する予定なので、余力があればまた記事としてあげたいと思います！

最後まで読んでいただき有難うございました！
また次の記事でお会いしましょう！！