2016年7月23日土曜日

[AWS] ELB+EC2(Apache/Tomcat) を Gatling で負荷テスト


ELB+EC2(1台)の構成で、Gatling で負荷テストしてみます。


1.WEBサーバの準備


テスト対象のWEBサーバを用意します。
今回は、AmazonLinux のAMIでEC2を作成し、インスタンスタイプは、t2.nano にしました。

1.1.パッケージのインストール


このEC2インスタンスに Apache、Tomcat をインストールします。また、Tomcatのサンプルアプリケーションもインストールします。
[ec2-user@ip-10-0-11-160 ~]$ sudo yum install httpd tomcat8 tomcat8-webapps

1.2.Apacheの設定


Apache を worker で動かします。
/etc/sysconfig/httpd を修正します。
下記がコメントアウトされているので1桁目の "#" を削除して、有効にします。
HTTPD=/usr/sbin/httpd.worker
/etc/httpd/conf/httpd.conf を修正します。
ELB向けに Timeout と Keepalive の設定を変更します。
KeepAliveTimeout は、ELBのアイドルタイムアウトより長くします。
KeepAlive On
MaxKeepAliveRequests 50
KeepAliveTimeout 120
MPMは 以下のようにしました。 MaxClient をデフォルトより増やしています。
<IfModule worker.c>
StartServers         4
MaxClients         512
MinSpareThreads     75
MaxSpareThreads    250
ThreadsPerChild     64
MaxRequestsPerChild  0
</IfModule>

1.3.ApacheとTomcat連携の設定


Apache へのリクエストを Tomcat に連携します。
/etc/httpd/conf.d/proxy_ajp.conf を作成して、内容を以下のようにします。
Tomcat のサンプルアプリけしょんが動くようにします。
<Location /sample/>
    ProxyPass ajp://localhost:8009/sample/ keepalive=on
    ProxyPassReverse ajp://localhost:8009/sample/
</Location>

1.4.Tomcat の設定


Tomcat はデフォルト設定で使用します。

1.5.ヘルスチェックページの用意


ELBが使用するヘルスチェック用のページを用意します。
[ec2-user@ip-10-0-11-160 html]$ cat /var/www/html/alive
alive

1.6.ApacheとTomcat の起動


サービスを起動します
[ec2-user@ip-10-0-11-160 ~]$ service httpd start
[ec2-user@ip-10-0-11-160 ~]$ service tomcat start

1.7.動作確認


curl を使用して、Apache経由でTomcat のサンプルアプリケーションにアクセスします
[ec2-user@ip-10-0-11-160 ~]$ curl http://localhost/sample/hello
<html>
<head>
<title>Sample Application Servlet Page</title>
</head>
<body bgcolor=white>
<table border="0">
<tr>
<td>
<img src="images/tomcat.gif">
</td>
<td>
<h1>Sample Application Servlet</h1>
This is the output of a servlet that is part of
the Hello, World application.
</td>
</tr>
</table>
</body>
</html>

バージョンは以下のとおり
[ec2-user@ip-10-0-11-160 ~]$ apachectl -V
Server version: Apache/2.2.31 (Unix)
Server built:   Jul 19 2016 00:11:53
Server's Module Magic Number: 20051115:40
Server loaded:  APR 1.5.1, APR-Util 1.4.1
Compiled using: APR 1.5.1, APR-Util 1.4.1
Architecture:   64-bit
Server MPM:     Worker
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
Server compiled with....
 -D APACHE_MPM_DIR="server/mpm/worker"
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=128
 -D HTTPD_ROOT="/etc/httpd"
 -D SUEXEC_BIN="/usr/sbin/suexec"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"
[ec2-user@ip-10-0-11-160 ~]$ rpm -aq | grep tomcat8
tomcat8-servlet-3.1-api-8.0.35-1.61.amzn1.noarch
tomcat8-lib-8.0.35-1.61.amzn1.noarch
tomcat8-el-3.0-api-8.0.35-1.61.amzn1.noarch
tomcat8-jsp-2.3-api-8.0.35-1.61.amzn1.noarch
tomcat8-8.0.35-1.61.amzn1.noarch
tomcat8-webapps-8.0.35-1.61.amzn1.noarch
ApacheとTomcatの制限値はデフォルトです。
[ec2-user@ip-10-0-11-160 ~]$ cat /proc/`pgrep java|head -1`/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             1898                 1898                 processes
Max open files            4096                 4096                 files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       1898                 1898                 signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us
[ec2-user@ip-10-0-11-160 ~]$ cat /proc/`pgrep httpd|head -1`/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             1898                 1898                 processes
Max open files            1024                 4096                 files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       1898                 1898                 signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us


2.ELBを作成


内部用ELBを作成して、上記で作成してWEBサーバを登録します。
各種パラメータはデフォルト値にします。
ヘルスチェック用のURLは "/alive" にします。


3.Gatling用サーバの準備


Gatlingを動かすサーバを用意します。
今回は、AmazonLinux のAMIでEC2を作成し、インスタンスタイプは、t2.nano にしました。

3.1.Javaのインストール


OpenJDKを使います
[ec2-user@ip-10-0-11-193 ~]$ sudo yum install java-1.8.0-openjdk
バージョンを確認します。
[ec2-user@ip-10-0-11-193 ~]$ java -version
openjdk version "1.8.0_101"
OpenJDK Runtime Environment (build 1.8.0_101-b13)
OpenJDK 64-Bit Server VM (build 25.101-b13, mixed mode)
このとき、別のバージョンが表示されるようであれば、以下のようにして切り替えます。
[ec2-user@ip-10-0-11-193 ~]$ sudo alternatives --config java

There are 2 programs which provide 'java'.

  Selection    Command
-----------------------------------------------
* +1           /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
   2           /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Enter to keep the current selection[+], or type selection number: 2

3.2.Gatling のインストール


Gatling のサイトから zip ファイルをダウンロードして、workspace ディレクトリで解凍します。
解凍後のディレクトリ名が長すぎるので、短い名称に変更します。
[ec2-user@ip-10-0-11-193 ~]$ mkdir ~/workspace
[ec2-user@ip-10-0-11-193 ~]$ cd ~/workspace
[ec2-user@ip-10-0-11-193 workspace]$ curl -OL https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/2.2.2/gatling-charts-highcharts-bundle-2.2.2-bundle.zip
[ec2-user@ip-10-0-11-193 workspace]$ unzip ./gatling-charts-highcharts-bundle-2.2.2-bundle.zip
[ec2-user@ip-10-0-11-193 workspace]$ mv ./gatling-charts-highcharts-bundle-2.2.2-bundle gatling

3.3.Gatlingのテストシナリオ作成


テストシナリオは、以下のようにしたいと思います。
 ・1ユーザ1アクセスにする
 ・/sample/hello にアクセスする
 ・レスポンスのステータスが 200 であることをチェックする
 ・レスポンスに "Sample Application Servler" が含まれることをチェックする
 ・2分間の合計で10000ユーザになるまで段階的にユーザを増減させる。

Gatoling のテストケースは、以下のとおり。
後述しますが、Gatling 実行時に メニューの class 名が表示されます。
[ec2-user@ip-10-0-11-193 workspace]$ cat ./gatling/user-files/simulations/test.scala
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class TestMySimulation extends Simulation {

  val httpConf = http
      .baseURL("http://internal-in-elb-1279773330.us-east-1.elb.amazonaws.com")
      .acceptCharsetHeader("ISO-8859-1,utf-8;q=0.7,*;q=0.7")
      .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
      .acceptEncodingHeader("gzip, deflate")
      .acceptLanguageHeader("fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3")
      .disableFollowRedirect

  val scn = scenario("Test Scenario")
      .exec(http("request")
          .get("/sample/hello")
          .check(status.is(200))
          .check(regex( """Sample Application Servlet"""))
      )

  setUp(scn.inject(
     //atOnceUsers(1)
     //rampUsersPerSec(1).to(60).during(30),
     //constantUsersPerSec(60) during(300),
     //rampUsersPerSec(60).to(1).during(30)
     heavisideUsers(10000) over(120 seconds)
  ).protocols(httpConf))

}

3.4.Gatling実行時にDNSキャッシュを無効にする


JavaはデフォルトでDNSキャッシュが有効になっています。
ELBはDNSラウンドロビンを使用しているので、アクセスに偏りでできないように、JavaのDNSキャッシュを無効にします。
下記の赤字部分を追加します。
[ec2-user@ip-10-0-11-193 gatling]$ cat ./bin/gatling.sh
~省略~
JAVA_OPTS="${JAVA_OPTS} -XX:+HeapDumpOnOutOfMemoryError"
JAVA_OPTS="${JAVA_OPTS} -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false"
JAVA_OPTS="${JAVA_OPTS} -Dsun.net.inetaddr.ttl=0 -Dsun.net.inetaddr.negative.ttl=0"
COMPILER_OPTS="$JAVA_OPTS -Xss100M"

# Setup classpaths
~省略~

3.5.Gatlingのレポートをブラウザでみれるようにする


Gatlingを実行するとHTML形式のレポートを作成してくれます。
このレポートをブラウザで見るために Apache をインストールして、レポートが格納されたディレクトリにリンクを作成します。
apache ユーザで参照できるように、/home/ec2-user のパーミションを変更しています。
[ec2-user@ip-10-0-11-193 ~]$ sudo yum install httpd
[ec2-user@ip-10-0-11-193 ~]$ chmod 755 /home/ec2-user
[ec2-user@ip-10-0-11-193 workspace]$ ln -s /home/ec2-user/workspace/gatling/results /var/www/html/gatling
[ec2-user@ip-10-0-11-193 ~]$ service httpd start


4.Gating の実行


準備ができたので、Gatling を実行します。
以下のようにして、gatling.sh を実行すると、上記で作成したテストシナリオが一覧に表示されるので、番号で選択してシナリオを実行します。
緑字部分で ENTER します。
[ec2-user@ip-10-0-11-193 workspace]$ ./gatling/bin/gatling.sh
GATLING_HOME is set to /home/ec2-user/workspace/gatling
Choose a simulation number:
     [0] TestMySimulation
     [1] computerdatabase.BasicSimulation
     [2] computerdatabase.advanced.AdvancedSimulationStep01
     [3] computerdatabase.advanced.AdvancedSimulationStep02
     [4] computerdatabase.advanced.AdvancedSimulationStep03
     [5] computerdatabase.advanced.AdvancedSimulationStep04
     [6] computerdatabase.advanced.AdvancedSimulationStep05
0 <ENTER>
Select simulation id (default is 'testmysimulation'). Accepted characters are a-z, A-Z, 0-9, - and _
<ENTER>
Select run description (optional)
<ENTER>
Simulation TestMySimulation started...

~省略~

================================================================================
2016-07-23 05:32:27                                         117s elapsed
---- Test Scenario -------------------------------------------------------------
[##########################################################################]100%
          waiting: 0      / active: 0      / done:10000
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=10000  KO=0     )
> request                                                  (OK=10000  KO=0     )
================================================================================

Simulation TestMySimulation completed in 117 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...

================================================================================
---- Global Information --------------------------------------------------------
> request count                                      10000 (OK=10000  KO=0     )
> min response time                                      2 (OK=2      KO=-     )
> max response time                                    176 (OK=176    KO=-     )
> mean response time                                     4 (OK=4      KO=-     )
> std deviation                                          7 (OK=7      KO=-     )
> response time 50th percentile                          3 (OK=3      KO=-     )
> response time 75th percentile                          4 (OK=4      KO=-     )
> response time 95th percentile                          7 (OK=7      KO=-     )
> response time 99th percentile                         12 (OK=12     KO=-     )
> mean requests/sec                                 84.746 (OK=84.746 KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                         10000 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Reports generated in 1s.
Please open the following file: /home/ec2-user/workspace/gatling/results/testmysimulation-1469251829209/index.html
テストが終わると、最後にサマリーが表示されます。
サマリーの一番下が、レポートの格納場所です。
ブラウザで http://<IPアドレス>/gatling/ にアクセスすると、レポートの一覧が表示されるので見たいレポートをクリックします。



上記実行結果のレポートは下図のとおり。


アクセス状況をグラフで見ると、徐々にアクセスが増えて、徐々にアクセスが減る山の形になっているのがわかります。



ちなみに、
テストシナリオ実行中のWEBサーバのアクセスログを見ると、以下のように表示されます。
ELBのIPアドレスが2つあるのがわかります。
[ec2-user@ip-10-0-11-160 ~]$ sudo tail /var/log/httpd/access_log
10.0.11.119 - - [23/Jul/2016:05:32:21 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.8 - - [23/Jul/2016:05:32:22 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.8 - - [23/Jul/2016:05:32:23 +0000] "GET /alive HTTP/1.1" 200 6 "-" "ELB-HealthChecker/1.0"
10.0.11.119 - - [23/Jul/2016:05:32:23 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.8 - - [23/Jul/2016:05:32:24 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.119 - - [23/Jul/2016:05:32:25 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.8 - - [23/Jul/2016:05:32:27 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.119 - - [23/Jul/2016:05:32:27 +0000] "GET /alive HTTP/1.1" 200 6 "-" "ELB-HealthChecker/1.0"
10.0.11.8 - - [23/Jul/2016:05:32:53 +0000] "GET /alive HTTP/1.1" 200 6 "-" "ELB-HealthChecker/1.0"
10.0.11.119 - - [23/Jul/2016:05:32:57 +0000] "GET /alive HTTP/1.1" 200 6 "-" "ELB-HealthChecker/1.0"