HAProxyを使用してUbuntuVPSでHTTP負荷分散を設定する方法
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が同じ接続を使用して後続の要求を行ったことを示しています。