HAProxyについて
HAProxy(High Availability Proxy)は、任意のTCPサービスの負荷を分散できるオープンソースのロードバランサーです。 セッションの永続性とレイヤー7処理をサポートするため、HTTP負荷分散に特に適しています。
DigitalOcean Private Networking を使用すると、HAProxyをフロントエンドとして構成して、プライベートネットワーク接続を介して2つのVPSの負荷を分散できます。
プレリュード
ここでは3つのVPS(ドロップレット)を使用します。
ドロップレット1-ロードバランサーホスト名:haproxy OS:UbuntuパブリックIP:1.1.1.1プライベートIP:10.0.0.100
ドロップレット2-ノード1ホスト名:lamp1 OS:UbuntuプライベートIP上のLAMP:10.0.0.1
ドロップレット2-ノード2ホスト名:lamp2 OS:UbuntuプライベートIP上のLAMP:10.0.0.2
HAProxyのインストール
apt-getコマンドを使用してHAProxyをインストールします。
apt-get install haproxy
initスクリプトでHAProxyを起動できるようにする必要があります。
nano /etc/default/haproxy
ENABLEDオプションを1に設定します
ENABLED=1
この変更が適切に行われたかどうかを確認するには、パラメータなしでHAProxyのinitスクリプトを実行します。 次のように表示されます。
root@haproxy:~# service haproxy
Usage: /etc/init.d/haproxy {start|stop|reload|restart|status}
HAProxyの設定
デフォルトの構成ファイルを移動して、独自の構成ファイルを作成します。
mv /etc/haproxy/haproxy.cfg{,.original}
新しい構成ファイルを作成および編集します。
nano /etc/haproxy/haproxy.cfg
このファイルにブロックごとに構成を追加することから始めましょう。
global
log 127.0.0.1 local0 notice
maxconn 2000
user haproxy
group haproxy
logディレクティブは、ログメッセージの送信先となるsyslogサーバーについて言及しています。 Ubuntuでは、rsyslogはすでにインストールされ実行されていますが、IPアドレスをリッスンしません。 rsyslogの設定ファイルは後で変更します。
maxconnディレクティブは、フロントエンドでの同時接続の数を指定します。 デフォルト値は2000であり、VPSの構成に応じて調整する必要があります。
userおよびgroupディレクティブは、HAProxyプロセスを指定されたユーザー/グループに変更します。 これらは変更しないでください。
defaults
log global
mode http
option httplog
option dontlognull
retries 3
option redispatch
timeout connect 5000
timeout client 10000
timeout server 10000
このセクションではデフォルト値を指定しています。 変更する値は、さまざまなtimeoutディレクティブです。 connectオプションは、VPSへの接続試行が成功するのを待機する最大時間を指定します。
clientおよびserverタイムアウトは、クライアントまたはサーバーがTCPプロセス中にデータを確認または送信することが期待される場合に適用されます。 HAProxyは、clientとserverのタイムアウトを同じ値に設定することをお勧めします。
retriesディレクティブは、接続障害後にVPSで実行する再試行回数を設定します。
option redispatchは、接続障害が発生した場合のセッションの再配布を可能にします。 したがって、VPSがダウンすると、セッションのスティッキネスが上書きされます。
listen appname 0.0.0.0:80
mode http
stats enable
stats uri /haproxy?stats
stats realm Strictly\ Private
stats auth A_Username:YourPassword
stats auth Another_User:passwd
balance roundrobin
option httpclose
option forwardfor
server lamp1 10.0.0.1:80 check
server lamp2 10.0.0.2:80 check
これには、フロントエンドとバックエンドの両方の構成が含まれます。 アプリケーションを識別するための単なる名前であるappnameをポート80でリッスンするようにHAProxyを構成しています。 statsディレクティブは、接続統計ページを有効にし、stats authディレクティブで指定された資格情報を使用してHTTP基本認証で保護します。
このページはstats uriに記載されているURLで表示できるため、この場合はhttp://1.1.1.1/haproxy?statsです。 このページのデモはここで見ることができます。
balanceディレクティブは、使用する負荷分散アルゴリズムを指定します。 使用可能なオプションは、ラウンドロビン(roundrobin)、静的ラウンドロビン(static-rr)、最小接続(leastconn)、ソース(source)、URI([ X135X] )およびURLパラメーター(url_param)。
各アルゴリズムに関する情報は、公式ドキュメントから入手できます。
serverディレクティブは、バックエンドサーバーを宣言します。構文は次のとおりです。
server <name> <address>[:port] [param*]
ここで言及する名前は、ログとアラートに表示されます。 このディレクティブでサポートされている多くのパラメーターがあり、この記事ではcheckおよびcookieパラメーターを使用します。 checkオプションは、VPSのヘルスチェックを有効にします。それ以外の場合、VPSは常に使用可能であると見なされます。
設定が完了したら、HAProxyサービスを開始します。
service haproxy start
負荷分散とフェイルオーバーのテスト
この設定をテストするには、すべてのWebサーバー(バックエンドサーバー-ここではLAMP1とLAMP2)でPHPスクリプトを作成します。
/var/www/file.php
<?php
header('Content-Type: text/plain');
echo "Server IP: ".$_SERVER['SERVER_ADDR'];
echo "\nClient IP: ".$_SERVER['REMOTE_ADDR'];
echo "\nX-Forwarded-for: ".$_SERVER['HTTP_X_FORWARDED_FOR'];
?>
次に、curlを使用して、このファイルを複数回要求します。
> curl http://1.1.1.1/file.php Server IP: 10.0.0.1 Client IP: 10.0.0.100 X-Forwarded-for: 117.213.X.X > curl http://1.1.1.1/file.php Server IP: 10.0.0.2 Client IP: 10.0.0.100 X-Forwarded-for: 117.213.X.X > curl http://1.1.1.1/file.php Server IP: 10.0.0.1 Client IP: 10.0.0.100 X-Forwarded-for: 117.213.X.X
ここで、HAProxyがLAMP1とLAMP2の間の接続を交互に切り替えた方法に注目してください。これは、ラウンドロビンの仕組みです。 ここに表示されるクライアントIPはロードバランサーのプライベートIPアドレスであり、X-Forwarded-ForヘッダーはIPです。
フェイルオーバーがどのように機能するかを確認するには、Webサーバーにアクセスしてサービスを停止します。
lamp1@haproxy:~#service apache2 stop
curlを使用してリクエストを再度送信し、動作を確認してください。
セッションの粘着性
Webアプリケーションがユーザーのログインセッションに基づいて動的コンテンツを提供する場合(アプリケーションは提供しません)、訪問者はVPSを継続的に切り替えるために奇妙なことを経験します。 セッションのスティッキネスにより、訪問者は最初のリクエストを処理したVPSに固執することができます。 これは、各バックエンドサーバーにCookieをタグ付けすることで可能になります。
次のPHPコードを使用して、セッションのスティッキネスがどのように機能するかを示します。
/var/www/session.php
<?php
header('Content-Type: text/plain');
session_start();
if(!isset($_SESSION['visit']))
{
echo "This is the first time you're visiting this server";
$_SESSION['visit'] = 0;
}
else
echo "Your number of visits: ".$_SESSION['visit'];
$_SESSION['visit']++;
echo "\nServer IP: ".$_SERVER['SERVER_ADDR'];
echo "\nClient IP: ".$_SERVER['REMOTE_ADDR'];
echo "\nX-Forwarded-for: ".$_SERVER['HTTP_X_FORWARDED_FOR']."\n";
print_r($_COOKIE);
?>
このコードは、 PHPセッションを作成し、単一セッションのページビュー数を表示します。
クッキー挿入方法
この方法では、HAProxyからクライアントへのすべての応答に、Cookie値としてバックエンドサーバーの名前が付いたSet-Cookie:ヘッダーが含まれます。 したがって、今後、クライアント(Webブラウザー)はこのCookieをすべての要求に含め、HAProxyはCookieの値に基づいて要求を適切なバックエンドサーバーに転送します。
この方法では、cookieディレクティブを追加し、listenの下のserverディレクティブを変更する必要があります。
cookie SRVNAME insert server lamp1 10.0.0.1:80 cookie S1 check server lamp2 10.0.0.2:80 cookie S2 check
これにより、HAProxyは、S1またはS2という値を持つSRVNAMEという名前のCookieを含むSet-Cookie:ヘッダーを追加します。これは、バックエンドがリクエストに応答したことに基づいています。 これが追加されたら、サービスを再起動します。
service haproxy restart
curlを使用して、これがどのように機能するかを確認します。
> curl -i http://1.1.1.1/session.php HTTP/1.1 200 OK Date: Tue, 24 Sep 2013 13:11:22 GMT Server: Apache/2.2.22 (Ubuntu) X-Powered-By: PHP/5.3.10-1ubuntu3.8 Set-Cookie: PHPSESSID=l9haakejnvnat7jtju64hmuab5; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Vary: Accept-Encoding Content-Length: 143 Connection: close Content-Type: text/plain Set-Cookie: SRVNAME=S1; path=/ This is the first time you're visiting this server Server IP: 10.0.0.1 Client IP: 10.0.0.100 X-Forwarded-for: 117.213.X.X Array ( )
これは私たちが行った最初のリクエストであり、Set-Cookie: SRVNAME=S1; path=/からわかるようにLAMP1によって応答されました。 ここで、Webブラウザーが次の要求に対して行うことをエミュレートするために、curlの--cookieパラメーターを使用してこれらのCookieを要求に追加します。
> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=l9haakejnvnat7jtju64hmuab5;SRVNAME=S1;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:11:45 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 183
Connection: close
Content-Type: text/plain
Your number of visits: 1
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.87.127
Array
(
[PHPSESSID] => l9haakejnvnat7jtju64hmuab5
[SRVNAME] => S1
)
> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=l9haakejnvnat7jtju64hmuab5;SRVNAME=S1;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:11:45 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 183
Connection: close
Content-Type: text/plain
Your number of visits: 2
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.87.127
Array
(
[PHPSESSID] => l9haakejnvnat7jtju64hmuab5
[SRVNAME] => S1
)
これらの要求は両方ともLAMP1によって処理され、セッションは適切に維持されました。 この方法は、Webサーバー上のすべてのファイルに粘着性が必要な場合に役立ちます。
クッキープレフィックス方式
一方、特定のCookieに対してのみスティッキーが必要な場合、またはセッションのスティッキーに対して個別のCookieが必要ない場合は、prefixオプションが適しています。
この方法を使用するには、次のcookieディレクティブを使用します。
cookie PHPSESSID prefix
PHPSESSIDは独自のCookie名に置き換えることができます。 serverディレクティブは、前の構成と同じままです。
それでは、これがどのように機能するかを見てみましょう。
> curl -i http://1.1.1.1/session.php HTTP/1.1 200 OK Date: Tue, 24 Sep 2013 13:36:27 GMT Server: Apache/2.2.22 (Ubuntu) X-Powered-By: PHP/5.3.10-1ubuntu3.8 Set-Cookie: PHPSESSID=S1~6l2pou1iqea4mnhenhkm787o56; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Vary: Accept-Encoding Content-Length: 143 Content-Type: text/plain This is the first time you're visiting this server Server IP: 10.0.0.1 Client IP: 10.0.0.100 X-Forwarded-for: 117.213.X.X Array ( )
serverクッキーS1がセッションクッキーの前に付けられていることに注目してください。 それでは、このCookieを使用してさらに2つのリクエストを送信しましょう。
> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=S1~6l2pou1iqea4mnhenhkm787o56;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:36:45 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 163
Content-Type: text/plain
Your number of visits: 1
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X
Array
(
[PHPSESSID] => 6l2pou1iqea4mnhenhkm787o56
)
> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=S1~6l2pou1iqea4mnhenhkm787o56;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:36:54 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 163
Content-Type: text/plain
Your number of visits: 2
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X
Array
(
[PHPSESSID] => 6l2pou1iqea4mnhenhkm787o56
)
両方のリクエストがLAMP1によって処理され、セッションが完全に機能していることがはっきりとわかります。
HAProxyのロギングを設定する
HAProxyの設定を開始したときに、log 127.0.0.1 local0 noticeという行を追加しました。これはsyslogメッセージをローカルホストのIPアドレスに送信します。 ただし、デフォルトでは、Ubuntuのrsyslogはどのアドレスもリッスンしません。 だから私たちはそうさせなければなりません。
rsyslogの設定ファイルを編集します。
nano /etc/rsyslog.conf
次の行を追加/編集/コメント解除します。
$ModLoad imudp $UDPServerAddress 127.0.0.1 $UDPServerRun 514
これで、rsyslogはアドレス127.0.0.1のUDPポート514で機能しますが、すべてのHAProxyメッセージは/var/log/syslogに送信されるため、それらを分離する必要があります。
HAProxyログのルールを作成します。
nano /etc/rsyslog.d/haproxy.conf
次の行を追加します。
if ($programname == 'haproxy') then -/var/log/haproxy.log
次に、rsyslogサービスを再起動します。
service rsyslog restart
これにより、すべてのHAProxyメッセージとアクセスログが/var/log/haproxy.logに書き込まれます。
HAProxyのキープアライブ
listenディレクティブでは、Connection: closeヘッダーを追加するoption httpcloseを使用しました。 これは、応答を受信した後に接続を閉じるようにクライアント(Webブラウザー)に指示します。
HAProxyでキープアライブを有効にする場合は、option httpclose行を次のように置き換えます。
option http-server-close timeout http-keep-alive 3000
いくつかの接続がロードバランサーのすべてのリソースを消費しないように、キープアライブタイムアウトを適切に設定します。
キープアライブのテスト
キープアライブは、curlを使用して、同時に複数のリクエストを送信することでテストできます。 次の例では、不要な出力は省略されます。
> curl -v http://1.1.1.1/index.html http://1.1.1.1/index.html * About to connect() to 1.1.1.1 port 80 (#0) * Trying 1.1.1.1... connected > GET /index.html HTTP/1.1 > User-Agent: curl/7.23.1 (x86_64-pc-win32) libcurl/7.23.1 OpenSSL/0.9.8r zlib/1.2.5 > Host: 1.1.1.1 > Accept: */* > ......[Output omitted]......... * Connection #0 to host 1.1.1.1 left intact * Re-using existing connection! (#0) with host 1.1.1.1 * Connected to 1.1.1.1 (1.1.1.1) port 80 (#0) > GET /index.html HTTP/1.1 > User-Agent: curl/7.23.1 (x86_64-pc-win32) libcurl/7.23.1 OpenSSL/0.9.8r zlib/1.2.5 > Host: 1.1.1.1 > Accept: */* > .......[Output Omitted]......... * Connection #0 to host 1.1.1.1 left intact * Closing connection #0
この出力では、Re-using existing connection! (#0) with host 1.1.1.1という行を探す必要があります。これは、curlが同じ接続を使用して後続の要求を行ったことを示しています。