AWSでJMeterのMaster/Slave環境を構築（CentOS 7）

AWSで動作するJMeterのMaster/Slave環境を構築した際の手順メモです。
OSはCentOS 7です。
※2019/5/19: JMeter（5.1.1）に合わせて内容を更新しました。

# 環境構築時の各バージョン

* OS: CentOS Linux release 7.6.1810 (Core)
* JMeter: apache-jmeter-5.1.1
* JMeterプラグイン: 
    * JMeter Plugins Manager 1.3
    * jpgc - Standard Set 2.0
* JDK: java-1.8.0_212-openjdk
* 作業PC: Mac OS X 10.14.3

# 前提

* JMeterのテスト計画は、作業PC上に作成済みのjmxファイルがある前提とします。
* AWS上のJMeter(Master/Slave)はコマンドラインで実行するものとします。（今回はGUI環境の構築はしませんでした）

# 手順

## AWSでJMeter用のセキュリティグループを作成
EC2ダッシュボードのセキュリティグループメニューを開き、「セキュリティの作成」を押す。
セキュリティグループ名と説明の欄に適当な名前（例：jmeter）を入力し、「作成」を押す。
作成したセキュリティグループを選択し、インバウンドルールに以下を追加する。

| タイプ           | プロトコル | ポート範囲      | 送信元                                    |
|:---------------:|:---------:|:-------------:|:----------------------------------------:|
| SSH             | TCP       | 22            | マイIP                                    |
| カスタムTCPルール | TCP       | 1099          | カスタムIP - JMeter用のセキュリティグループID |
| カスタムTCPルール | TCP       | 30000 - 65535 | カスタムIP - JMeter用のセキュリティグループID |

## AWSでJMeter用のEC2インスタンスを作成

### ステップ 1: Amazon マシンイメージ（AMI）
EC2ダッシュボードでインスタンスメニューを開き、「インスタンスの作成」を押す。
AWS Marketplaceを選択し、「CentOS 7」で検索する。
「CentOS 7 (x86_64) with Updates HVM」を選択する。

### ステップ 2: インスタンスタイプの選択
任意のインスタンスタイプを選択し、「次の手順: インスタンスの詳細の設定」を押す。

※JMeterで数万リクエスト／秒を超えるような高負荷をかける場合には、それなりの性能や帯域が必要になるので、インスタンスタイプは「m4.xlarge」以上が適当と思われる。

### ステップ 3: インスタンスの詳細の設定
設定画面で以下を選択し、「次の手順: ストレージの追加」を押す。

* インスタンス数: 1
* 購入のオプション: 選択なし
* ネットワーク: デフォルト
* サブネット: 優先順位なし
* 自動割り当てパブリック IP: サブネット設定を使用（有効）
* 配置グループ以下の設定は任意

### ステップ 4: ストレージの追加
ボリュームタイプに「汎用（SSD）」を選択し、「次の手順: インスタンスのタグ付け」を押す。

### ステップ 5: インスタンスのタグ付け
「タグの追加」を押し、追加された行に適当なタグ名（例：キーの欄に「Name」、値の欄に「jmeter」）を入れ、「次の手順: セキュリティグループの設定」を押す。

### ステップ 6: セキュリティグループの設定
設定画面で以下を選択し、「確認と作成」を押す。

* セキュリティグループの割り当て: 既存のセキュリティグループを選択する
* 最初の手順で作成したJMeter用のセキュリティグループを選択

### ステップ 7: インスタンス作成の確認
内容に間違いがないことを確かめて、「起動」を押す。

### 既存のキーペアを選択または新しいキーペアを作成
既存のキーペアがある場合はそれを選択する。
キーペアが無い場合や新たに作成したい場合は、「新しいキーペアの作成」を選択し、キーペア名を入力してキーペアをダウンロードする。

「インスタンスの作成」を押す。

※ダウンロードしたキーペアをSSHログインで使用する際は、事前にパーミッションの変更が必要。

```bash:コマンド
$ chmod 400 keypair.pem
```

## CentOSの設定

### 起動したEC2インスタンスにSSHでログイン

```bash:コマンド
$ ssh -i keypair.pem centos@xxx.xxx.xxx.xxx
```

ログインユーザー名は`centos`になる。
xxx.xxx.xxx.xxxの部分には、インスタンスのIPv4 パブリック IPを入力する。

### SELinuxを無効化

```bash:コマンド
$ sudo sed -i -e 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
$ sudo setenforce 0
```

### タイムゾーンを日本に設定

```bash:コマンド
$ sudo cp -p /usr/share/zoneinfo/Japan /etc/localtime
```

### ファイアウォールを無効化
ファイアウォールが動作しているか確認する。

```bash:コマンド
$ sudo systemctl status firewalld
```

AWSのCentOS 7ではデフォルトで無効のようだが、もし有効だったら以下のコマンドを実行する。

```bash:コマンド
$ sudo systemctl disable firewalld
```

### 必要なパッケージをインストール

```bash:コマンド
$ sudo yum -y update
$ sudo yum -y install wget
$ sudo yum -y install java-1.8.0-openjdk.x86_64 java-1.8.0-openjdk-devel.x86_64
$ hash -r
```

### Javaが動作することを確認

```bash:コマンド
$ java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-b04)
OpenJDK 64-Bit Server VM (build 25.212-b04, mixed mode)
```

## JMeterのインストール

### JMeterをダウンロードして展開

```bash:コマンド
$ wget https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-5.1.1.tgz
$ tar xvzf apache-jmeter-5.1.1.tgz
$ mv apache-jmeter-5.1.1 ~/jmeter
```

### 必要に応じてプラグインをインストール
（例）JMeter Plugins Managerを利用してjpgc - Standard Setを入れる場合

```bash:コマンド
# JMeter Plugins Managerの準備
$ wget http://search.maven.org/remotecontent?filepath=kg/apc/cmdrunner/2.2/cmdrunner-2.2.jar -O ~/jmeter/lib/cmdrunner-2.2.jar
$ wget http://search.maven.org/remotecontent?filepath=kg/apc/jmeter-plugins-manager/1.3/jmeter-plugins-manager-1.3.jar -O ~/jmeter/lib/ext/jmeter-plugins-manager-1.3.jar
$ java -cp ~/jmeter/lib/ext/jmeter-plugins-manager-1.3.jar org.jmeterplugins.repository.PluginManagerCMDInstaller

# jpgc - Standard Setのインストール
$ ~/jmeter/bin/PluginsManagerCMD.sh install jpgc-standard
```

## JMeterの設定

### ヒープメモリのサイズを変更

```bash:コマンド
$ vi ~/jmeter/bin/jmeter
```

159G あたりにある
`: "${HEAP:="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"}"`
の数値を環境に合わせて増やす。

### タイムスタンプのフォーマットを変更

```bash:コマンド
$ vi ~/jmeter/bin/jmeter.properties
```

531G あたりにある
`#jmeter.save.saveservice.timestamp_format=yyyy/MM/dd HH:mm:ss.SSS`
のコメントアウトを外す。

### keystoreを作成

JMeter 4.0よりjmeter-serverとの通信がSSLで行われるようになり、SSL通信で使用するキーおよび証明書（keystore）の作成が必要になった。

```bash:コマンド
$ LANG=C ~/jmeter/bin/create-rmi-keystore.sh
What is your first and last name?
  [Unknown]:  # rmi と入力
What is the name of your organizational unit?
  [Unknown]:  # My unit name と入力
What is the name of your organization?
  [Unknown]:  # My organisation name と入力
What is the name of your City or Locality?
  [Unknown]:  # Your City と入力
What is the name of your State or Province?
  [Unknown]:  # Your State と入力
What is the two-letter country code for this unit?
  [Unknown]:  # XY と入力
Is CN=rmi, OU=My unit name, O=My organisation name, L=Your City, ST=Your State, C=XY correct?
  [no]:  # yes と入力

Enter key password for <rmi>
	(RETURN if same as keystore password): # 未入力のままEnter

Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore rmi_keystore.jks -destkeystore rmi_keystore.jks -deststoretype pkcs12".
Copy the generated rmi_keystore.jks to jmeter/bin folder or reference it in property 'server.rmi.ssl.keystore.file'
```

作成したkeystoreファイルをルートフォルダへコピーする。

```bash:コマンド
$ sudo cp ~/rmi_keystore.jks /
```

### JMeterが動作することを確認

```bash:コマンド
$ ~/jmeter/bin/jmeter -v
    _    ____   _    ____ _   _ _____       _ __  __ _____ _____ _____ ____
   / \  |  _ \ / \  / ___| | | | ____|     | |  \/  | ____|_   _| ____|  _ \
  / _ \ | |_) / _ \| |   | |_| |  _|    _  | | |\/| |  _|   | | |  _| | |_) |
 / ___ \|  __/ ___ \ |___|  _  | |___  | |_| | |  | | |___  | | | |___|  _ <
/_/   \_\_| /_/   \_\____|_| |_|_____|  \___/|_|  |_|_____| |_| |_____|_| \_\ 5.1.1 r1855137

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

## Slave用のCentOSの設定

### OS起動時にjmeter-serverが自動的に起動するよう設定

```bash:コマンド
$ sudo vi /etc/rc.d/rc.local
```

ファイルの末尾に
`/home/centos/jmeter/bin/jmeter-server &`
を追加する。

### rc.localファイルに実行権限を付与

```bash:コマンド
$ sudo chmod +x /etc/rc.d/rc.local
```

### ファイルディスクリプタの設定を変更

```bash:コマンド
$ sudo vi /usr/lib/systemd/system/rc-local.service
```

ファイル内の `[Service]` の配下に
`LimitNOFILE=20480`
を追加する。
※数値は環境に合わせて変更する。

__補足__
この設定によって、高負荷をかけた際に「java.net.SocketException: Too many open files」のエラーが発生することを防ぐ。
jmeter-serverをデーモンプロセスとして起動するのではなく、直接コマンドを打って起動する使い方をしたい場合は、上記の代わりに以下の設定を行う。

```bash:コマンド
$ sudo vi /etc/security/limits.conf
```

ファイル内に
`* soft nofile 20480`
`* hard nofile 20480`
の2行を追加する。
ファイルを保存後、再ログインする。

### カーネルパラメータを変更

```bash:コマンド
$ sudo vi /etc/sysctl.conf
```

ファイル内に
`net.ipv4.tcp_tw_reuse=1`
`net.ipv4.tcp_fin_timeout=5`
の2行を追加する。
ファイルを保存後、以下のコマンドを実行して設定を反映させる。

```bash:コマンド
$ sudo sysctl -p
```

__補足__
この設定によって、高負荷をかけた際に「java.net.NoRouteToHostException: 要求アドレスに割り当てられません」のエラーが発生することを防ぐ。

### OS再起動後にjmeter-serverが自動的に起動することを確認

```bash:コマンド
$ sudo shutdown -r now

$ ssh -i keypair.pem centos@xxx.xxx.xxx.xxx

$ ps -ax | grep jmeter
 4071 ?        S      0:00 /bin/sh /home/centos/jmeter/bin/jmeter-server
 4074 ?        S      0:00 /bin/sh /home/centos/jmeter/bin/jmeter -Dserver_port=1099 -s -j jmeter-server.log
 4166 ?        Sl     0:02 /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 /home/centos/jmeter/bin/ApacheJMeter.jar -Dserver_port=1099 -s -j jmeter-server.log

$ cat /proc/`pgrep java | head -1`/limits | grep 'open files'
Max open files            20480                20480                files
```

## テスト計画ファイルの配置

### jmxファイルを作業PCからCentOSに転送

```bash:コマンド
$ scp -i keypair.pem Test.jmx centos@xxx.xxx.xxx.xxx:/home/centos/
```

__ファイルの配置について__
jmxファイルはMaster側にのみ配置すればよい。
CSVファイルを読み込んで使用する場合は、Slave側にもCSVファイルを配置しておく。
その際、テスト計画にはCSVファイルを絶対パスで指定しておき、そのパスにCSVファイルを格納する方がわかりやすい。
相対パスで指定した場合は、Master側ではjmxファイルからの相対位置になるが、Slave側ではルートフォルダからの相対位置になる。

## AWSでJMeter用のAMIを保存
EC2ダッシュボードでインスタンスメニューを開く。
JMeter用インスタンスを右クリック→インスタンスの状態→停止を選択する。
確認画面で「停止する」を押す。
再度インスタンスを右クリック→イメージ→イメージの作成を選択する。
イメージ名とイメージの説明の欄に適当な名前（例：jmeter）を入力し、「イメージの作成」を押す。
AMIの保存が終わるまで待つ。

## AWSでAMIからSlave用のインスタンスを作成
EC2ダッシュボードでAMIメニューを開く。
作成したAMIを選択して右クリック→作成を選択する。
インスタンスの作成は、最初のインスタンス作成と同じ手順で実施する。

※Slaveを多数作成する場合は、インスタンスの詳細の設定でインスタンス数を増やしておく。
※インスタンスのタグ付けでは、Slave用であることがわかりやすいような値を入力するとよい。
※キーペアは既存のものを使用するとよい。

## Master側のJMeterにSlaveのリモートホストを設定
作成したSlave用のインスタンスのパブリックDNSを確認する。
Master用のインスタンスを起動し、SSHでログインする。
以下の設定ファイルを編集する。

```bash:コマンド
$ vi ~/jmeter/bin/jmeter.properties
```

258G-259G あたりにある`remote_hosts`を以下のように編集する。
`remote_hosts=ec2-AAA-AAA-AAA-AAA.compute-1.amazonaws.com:1099,ec2-BBB-BBB-BBB-BBB.compute-1.amazonaws.com:1099...`

※AAA-AAA-AAA-AAA などの部分には、Slave用のインスタンスのパブリックDNSの値を入力する。MasterとSlaveが同じサブネット内にあるなら、SlaveのプライベートIPを入力してもよい。

__補足__
設定ファイルを編集する代わりに、Master側でJMeterを実行する際のオプションにリモートホストの情報を加える方法もある。
（例）
`~/jmeter/bin/jmeter -n -t Test.jmx -R ec2-AAA-AAA-AAA-AAA.compute-1.amazonaws.com:1099,ec2-BBB-BBB-BBB-BBB.compute-1.amazonaws.com:1099...`

## Master側でテスト計画の実行を開始（リモート）

```bash:コマンド
# テストを開始するコマンド
$ ~/jmeter/bin/jmeter -n -t Test.jmx -r

# テストを途中で終了するコマンド
$ ~/jmeter/bin/shutdown.sh

# テストを途中で強制停止するコマンド
$ ~/jmeter/bin/stoptest.sh
```

以上で基本的な構築は完了です。

---
# 参考サイト
本環境構築にあたり、以下のサイトの記事を特に参考に致しました。
ありがとうございました。

* [SpotInstanceとJMeterを使って400万req/minの負荷試験を行う | Developers.IO](http://dev.classmethod.jp/cloud/apache-jmeter-master-slave-100mil-req-min/)
* [Linux サーバでの「Too many open files」対策について - akishin999の日記](http://d.hatena.ne.jp/akishin999/20130213/1360711554)
* [KB Odin: ドメイン数が 256 を超えると、Nginx が "Too many open files" エラーで失敗します](http://kb.odin.com/jp/115139)
* [CentOS7のインストールと設定（サービス、ファイアウォールの有効・無効切り替え等） - UbuntuによるEco Linuxサーバ構築記](http://eco.senritu.net/centos7_install_and_service_settings/)
* [CentOS７ ファイアウォール停止方法 | server-memo.net](http://www.server-memo.net/centos-settings/centos7/firewalld-stop.html)
* [【ELB】AWS ELBの負荷テストをJMeterServer群でやる【負荷テスト】](http://qiita.com/takudo/items/1e4dac976cfbd3c775d2)
* [ローカルポートを食いつぶしていた話](http://d.hatena.ne.jp/download_takeshi/20091013/1255443592)
* [Plugins Manager from Command-Line](https://jmeter-plugins.org/wiki/PluginsManagerAutomated/)
* [Apache JMeter - User's Manual: Remote (Distributed) Testing](http://jmeter.apache.org/usermanual/remote-test.html)