【ELB】負荷分散とEC2へのリクエスト数との関係について調べてみた

2013.04.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは植木和樹です。今回はAWSで提供されているロードバランシングサービス ELBについて調べてみました。

ELBとは

ELB(Elastic Load Balancing)サービスはその名の通りロードバランサを提供するサービスです。ロードバランサとは、Webサーバやアプリケーションサーバが複数あった場合に通信負荷が均一になるように分散してくれる装置です。商用製品としてはF5社のBIG-IPなどが有名です。

ELBについては弊社ブログ「Elastic Load Balancingで複数のゾーンにわたって振り分けるときに気をつけること」にまとまっていますので、ぜひそちらをご覧ください。

同一AZ内でどのように負荷は分散されるのか?

さて今回は同一AZ(アベイラビリティ・ゾーン)内に2つのEC2インスタンスがあった場合、それらへの負荷がどのように分散されるか、について調べてみました。AWSの公式ドキュメントや、ネット上のブログなどを読んでみましたが単純なラウンド・ロビン(2台交互にリクエストを振り分ける)方式ではないようです。

それでは早速同一AZ内にEC2インスタンスを作成して、負荷分散がどのように行われるか調べてみましょう。

今回の構成

同一AZ(ap-northeast-1a)にEC2インスタンスを2台(サーバAとサーバB)用意します。今回VPCは使用しません。 また同じAZにロードバランサを作成し、クライアントからのHTTPリクエストを2台のEC2インスタンスに分散するようにします。


20130423_aws_ebl_002

セキュリティグループの作成

EC2に関連づけるセキュリティグループ(ファイアウォール) "my-sg-http" を作成し、SSH(tcp/22)とHTTP(tcp/80)の通信を許可します。

$ ec2-create-group my-sg-http -d "SG for http (port 80 and 22)"
$ ec2-authorize my-sg-http -P tcp -p 22
$ ec2-authorize my-sg-http -P tcp -p 80

EC2インスタンスの起動

Amazon Linux 2013.03 64bitのAMI(ami-173fbf16)を使用してEC2インスタンスを2つ起動します。

$ ec2-run-instances \
  --group my-sg-http \
  --instance-type t1.micro \
  --instance-count 2 \
  --key <MY_KEYNAME> \
  ami-173fbf16

上記コマンドでEC2インスタンスが2つ起動します。今回それぞれインスタンスIDは "i-8d01cb8f" と "i-9b22e899" でした。インスタンスIDはこの後ELBを作成し、EC2インスタンスを紐付ける際に使用しますのでメモしておきます。

ELBの作成

ロードバランサー "my-lb" を作成します。このロードバランサはHTTP(tcp/80)を受け付けて、配下のEC2にリクエストをそのまま分散する単純なものです。ELBが作成できたら先ほど作成したEC2インスタンス2つを紐付けます。

$ elb-create-lb my-lb \
  --listener "lb-port=80,instance-port=80,protocol=http" \
  --availability-zones ap-northeast-1a \
  --region ap-northeast-1
$ elb-register-instances-with-lb my-lb --instances i-8d01cb8f, i-9b22e899

Apacheの起動

作成したEC2インスタンスにsshでログインしApacheをインストールしてサービスを起動します。この後いろいろ試してみたいのでPHPもインストールしておきます。アクセスしたページでは、どちらのEC2インスタンスと通信しているか分かるようにPHPページ(index.php)を作成しておきます。

$ sudo su -
# yum -y update
# yum -y install httpd php
# service httpd start
# vi /var/www/html/index.php

/var/www/html/index.php の内容

<?php
$header_keys = array( 'HTTP_X_FORWARDED_FOR', 'SERVER_NAME', 'SERVER_ADDR', 'REMOTE_ADDR', );
foreach($header_keys as $key) {
  printf("%s = %s\n", $key, $_SERVER[$key]);
}
?>

実験1 (まずはラウンドロビンに分散することを確認)

クライアントからロードバランサにHTTPリクエストを送信します。今回はApacheに付属しているApache Bench(ab)というツールを使いました。同時10接続(-c 10)で計10回のリクエスト(-n 10)を行います。

$ ab -n 10 -c 10 -v 1 http://my-lb-1486914138.ap-northeast-1.elb.amazonaws.com/?001

次に2台あるEC2インスタンスのApacheアクセスログ(/var/log/httpd/access_log)を確認します。ラウンドロビンであればサーバA、サーバBとも5回ずつアクセスが記録されているはずですがどうなったでしょうか?

(サーバA)
# grep "?001" /var/log/httpd/access_log | wc -l
5
(サーバB)
# grep "?001" /var/log/httpd/access_log | wc -l
5

予想通り、どちらのサーバにも5回ずつアクセスがありました。

実験2 (サーバBのレスポンスを悪くして分散の変化をみる)

次はサーバBのPHPコードにsleepを入れて、サーバAよりも処理に時間がかかるようにしてみましょう。今回は2秒のsleepを入れてみました。

/var/www/html/index.php の内容(変更後:サーバBのみ)

<?php
$header_keys = array( 'HTTP_X_FORWARDED_FOR', 'SERVER_NAME', 'SERVER_ADDR', 'REMOTE_ADDR', );
foreach($header_keys as $key) {
  printf("%s = %s\n", $key, $_SERVER[$key]);
}
sleep(2); // ←追加
?>

それでは先ほどと同じくabを使ってリクエストを行ってみます。

$ ab -n 10 -c 10 -v 1 http://my-lb-1486914138.ap-northeast-1.elb.amazonaws.com/?002
(サーバA)
# grep "?002" /var/log/httpd/access_log | wc -l
7
(サーバB)
# grep "?002" /var/log/httpd/access_log | wc -l
3

サーバAへのリクエストが「7回」、サーバBへは「3回」になりました。どうやらELBはリクエストを振り分ける際に各EC2インスタンスにどれくらい「処理中」のリクエストがあるかを分散の判断基準としているようです。

実験3 (同時接続数を1にして分散の変化をみる)

それでは同時接続数を1にしてみましょう(-c 1)。こうすると一つ一つのリクエストに対してレスポンスが返ってから次のリクエストが投げられるので、(ELBによる分散時) EC2の「処理中」リクエスト数は常にゼロになっています。

サーバAもサーバBも処理中リクエスト数がゼロなわけですから、実験1の時と同様リクエストは均一に分散するはずですが、どうでしょう?

$ ab -n 10 -c 1 -v 1 http://my-lb-1486914138.ap-northeast-1.elb.amazonaws.com/?003
(サーバA) 
# grep "?003" /var/log/httpd/access_log | wc -l
5
(サーバB) 
# grep "?003" /var/log/httpd/access_log | wc -l
5

予想通りサーバA、サーバBとも同じ数だけリクエストが分散されています。

実験4 (同時接続数を2にして分散の変化をみる)

さらに同時接続数を2(-c 2)にしてみます。

まず最初に2つのリクエストが同時に投げられ、1つはサーバAへ、もう1つはサーバBへ分散されます。しかしサーバBでは処理に2秒以上かかるため、残りのリクエストはすべてサーバAに振り分けられるはずですがどうでしょう?

$ ab -n 10 -c 2 -v 1 http://my-lb-1486914138.ap-northeast-1.elb.amazonaws.com/?004
(サーバA)
# grep "?004" /var/log/httpd/access_log | wc -l
9
(サーバB)
# grep "?004" /var/log/httpd/access_log | wc -l
1

予想通り、10回中9回のリクエストがサーバAに偏る結果になりました。

まとめ

  • ELBの負荷分散アルゴリズムは単純なラウンドロビン方式ではない
  • ELBは各EC2インスタンスの「処理中」リクエストの数を内部で保持している
  • 新たなリクエストは処理中リクエストが少ないEC2インスタンスに分散する

一部のEC2インスタンスのWebアプリケーションからRDS(AWSのデータベースサービス)に時間のかかるSQLクエリが実行されている場合など、そのEC2インスタンス自体のCPUやメモリ使用率は低いのにリクエストが割り当てられないというケースがあるかもしれません。その場合はLoadAverageの値を調べるか、vmstatのブロックされたプロセス数を調べるなどして判断することになります。場合によってはアプリケーション側で処理時間を計測して閾値監視してあげる必要があるかもしれません。これら監視については今後の課題としたいと思います。