少し前に、nginxでHTTPロードバランシングをやってみましたが、今度はHAProxyでやってみます。
nginxのHTTPロードバランシングを試す - CLOVER
HAProxyの設定をするには、このあたりを参考にしました。
第1章 ロードバランサーの概要 - Red Hat Customer Portal
HAProxy のバックエンド Web サーバーヘルスチェックでコンテンツの中身をチェックする
HAProxyをDNS名で指定したバックエンドのIPアドレスが変わったらリロードする
使うHAProxyは、1.6.3です。
では、書いていきましょう。
バックエンドサーバーを書く
まずは、ロードバランサの背後にいるバックエンドのサーバーを用意します。nginxの時も、簡単にSpring Boot CLIで用意しました。以下の3つのサーバー名で、サーバーを用意します。
- server1 (172.17.0.2)
- server2 (172.17.0.3)
- server3 (172.17.0.4)
server.groovy
import javax.servlet.http.HttpServletRequest @RestController class HelloController { def logger = org.slf4j.LoggerFactory.getLogger(getClass()) @GetMapping('hello') def hello(HttpServletRequest request) { logger.info(java.time.LocalDateTime.now().toString() + ": access " + request.requestURI) 'Hello ' + InetAddress.localHost.hostName + "!!" + System.lineSeparator() } }
アクセスしたら「Hello [ホスト名]!!」を返却します。
サーバーを起動。
$ spring run server.groovy
ロードバランサを設定する
それでは、この3つのサーバーの前段にHAProxyをバランサーとして配置します。
なお、HAProxyは
- 172.17.0.5
で稼働しているものとします。
haproxy.cfgの設定を、デフォルトの設定から以下のように追記。
/etc/haproxy/haproxy.cfg
frontend app-http bind *:8080 default_backend app-servers backend app-servers balance roundrobin mode http option forwardfor http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } server server1 172.17.0.2:8080 server server2 172.17.0.3:8080 server server3 172.17.0.4:8080
HAProxyはfrontendでクライアント側からのリクエストの受け付けの設定、backendで実際のサーバーへの設定を行うようです。
今回は、8080ポートでリッスン、バックエンドには「app-servers」という名前のものを指定。「app-servers」はこのあと定義します。
frontend app-http bind *:8080 default_backend app-servers
バックエンドの設定。バランシングアルゴリズムはラウンドロビン、serverで背後にいるサーバーを指定します。あとはX-Forwarded-Forの設定を入れています。
backend app-servers balance roundrobin mode http option forwardfor http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } server server1 172.17.0.2:8080 server server2 172.17.0.3:8080 server server3 172.17.0.4:8080
で、HAProxyを再起動。
確認。
$ curl http://172.17.0.5:8080/hello Hello server1!! $ curl http://172.17.0.5:8080/hello Hello server2!! $ curl http://172.17.0.5:8080/hello Hello server3!! $ curl http://172.17.0.5:8080/hello Hello server1!! $ curl http://172.17.0.5:8080/hello Hello server2!! $ curl http://172.17.0.5:8080/hello Hello server3!!
1台(server3)をダウンさせてアクセスしてみます。
$ curl http://172.17.0.5:8080/hello Hello server1!! $ curl http://172.17.0.5:8080/hello Hello server2!! $ curl http://172.17.0.5:8080/hello <html><body><h1>503 Service Unavailable</h1> No server is available to handle this request. </body></html>
あら…。
※このあと繰り返しても、同じです。
ヘルスチェックを設定する
で、こういうのだと困るので、ヘルスチェックを設定してみます。server3は、起動状態に戻します。
ヘルスチェックを行うには、serverのオプションに「check」を入れます。
server server1 172.17.0.2:8080 check server server2 172.17.0.3:8080 check server server3 172.17.0.4:8080 check
HAProxyを再起動。
確認。
$ curl http://172.17.0.5:8080/hello Hello server1!! $ curl http://172.17.0.5:8080/hello Hello server2!! $ curl http://172.17.0.5:8080/hello <html><body><h1>503 Service Unavailable</h1> No server is available to handle this request. </body></html> $ curl http://172.17.0.5:8080/hello Hello server1!! $ curl http://172.17.0.5:8080/hello Hello server2!! $ curl http://172.17.0.5:8080/hello Hello server1!!
1回失敗してますが、そのあとはOKそうですね。
もう少し、細かくヘルスチェックの設定をしてみましょう。
ヘルスチェック対象のURLや間隔を指定してみます。
ヘルスチェック用のURLを、バックエンドサーバーに追加します。「/health-check」でアクセスするものとしましょう。
server.groovy
import javax.servlet.http.HttpServletRequest @RestController class HelloController { def logger = org.slf4j.LoggerFactory.getLogger(getClass()) @GetMapping('hello') def hello(HttpServletRequest request) { logger.info(java.time.LocalDateTime.now().toString() + ": access " + request.requestURI) 'Hello ' + InetAddress.localHost.hostName + "!!" + System.lineSeparator() } @GetMapping('health-check') def healthCheck(HttpServletRequest request) { logger.info(java.time.LocalDateTime.now().toString() + ": access " + request.requestURI) 'OK ' + InetAddress.localHost.hostName + "!!" + System.lineSeparator() } }
HAProxy側の設定は、このようにします。
backend app-servers balance roundrobin mode http option forwardfor option httpchk GET /health-check http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } server server1 172.17.0.2:8080 inter 3000 check server server2 172.17.0.3:8080 inter 3000 check server server3 172.17.0.4:8080 inter 3000 check
「option httpchk」でヘルスチェック先の設定をして
option httpchk GET /health-check
バックエンドサーバーには、間隔3秒でヘルスチェックを行う、と。
server server1 172.17.0.2:8080 inter 3000 check server server2 172.17.0.3:8080 inter 3000 check server server3 172.17.0.4:8080 inter 3000 check
この状態でHAProxyを起動すると、バックエンドサーバーには3秒に1度アクセスが来ることが確認できます。
2016-11-26 06:33:34.921 INFO 147 --- [nio-8080-exec-1] HelloController : 2016-11-26T06:33:34.905: access /health-check 2016-11-26 06:33:37.958 INFO 147 --- [nio-8080-exec-2] HelloController : 2016-11-26T06:33:37.958: access /health-check 2016-11-26 06:33:40.991 INFO 147 --- [nio-8080-exec-3] HelloController : 2016-11-26T06:33:40.991: access /health-check
結果は、最初にヘルスチェックを入れた時とそう変わらないので割愛。
ボディの内容のチェックもできるみたい?
セッションパーシステンスする
最後に、セッションパーシステンスしてみましょう。
セッションを使って確認するので、サーバー側のコードを以下のように変更します。けっこう適当ですが、ご愛嬌。
server.groovy
import javax.servlet.http.HttpServletRequest @RestController class HelloController { def logger = org.slf4j.LoggerFactory.getLogger(getClass()) @GetMapping('hello') def hello(HttpServletRequest request) { logger.info(java.time.LocalDateTime.now().toString() + ": access " + request.requestURI) def session = request.session def now = session.getAttribute('now') if (!now) { now = java.time.LocalDateTime.now().toString() session.setAttribute('now', now) } '[' + now + '] Hello ' + InetAddress.localHost.hostName + "!!" + System.lineSeparator() } @GetMapping('health-check') def healthCheck(HttpServletRequest request) { logger.info(java.time.LocalDateTime.now().toString() + ": access " + request.requestURI) 'OK ' + InetAddress.localHost.hostName + "!!" + System.lineSeparator() } }
今のままだと、こんな感じにアクセスしても都度違うサーバーに振り分けられます。当然、セッションも維持されません。
$ curl -b cookie.txt -c cookie.txt http://172.17.0.5:8080/hello [2016-11-26T06:52:16.530] Hello server1!!
で、HAProxy側の設定は、このように変更。
backend app-servers balance roundrobin appsession JSESSIONID len 32 timeout 2h request-learn
参考)
demo/HAProxy-の各種設定方法.md at master · worksap-ate/demo · GitHub
としたら、うまくいきませんでした。HAProxyがappsessionを理解できないらしく、コケてしまいます…。
$ sudo service haproxy start * Starting haproxy haproxy [ALERT] 330/065527 (648) : parsing [/etc/haproxy/haproxy.cfg:42] : 'appsession' is not supported anymore, please check the documentation. [ALERT] 330/065527 (648) : Error(s) found in configuration file : /etc/haproxy/haproxy.cfg [ALERT] 330/065527 (648) : Fatal errors found in configuration. [fail]
困りました。ドキュメントにもappsessionは書いてあるのに…。
で、ちょっと調べたらこんなエントリを見つけまして。
Load Balancing, Affinity, Persistence, Sticky Sessions: What You Need to Know - HAProxy Technologies
これを見ると、appsessionではなくてcookieを使え、と。
http://cbonte.github.io/haproxy-dconv/1.6/configuration.html#4-cookie:cookie
HAProxyの設定は、このように変更。
backend app-servers balance roundrobin cookie JSESSIONID prefix nocache mode http option forwardfor option httpchk GET /health-check http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } server server1 172.17.0.2:8080 inter 3000 check cookie server1 server server2 172.17.0.3:8080 inter 3000 check cookie server2 server server3 172.17.0.4:8080 inter 3000 check cookie server3
Cookie名は、「JSESSIONID」として、prefixを指定します。
cookie JSESSIONID prefix nocache
prefixでは、Cookieの先頭に指定した接頭辞が入ることになるので、バックエンドサーバーにもその設定を入れます。
server server1 172.17.0.2:8080 inter 3000 check cookie server1 server server2 172.17.0.3:8080 inter 3000 check cookie server2 server server3 172.17.0.4:8080 inter 3000 check cookie server3
cookieのあとに続いているのが、接頭辞になります。
試してみましょう。
スティックされるようになりました。
$ curl -b cookie.txt -c cookie.txt http://172.17.0.5:8080/hello [2016-11-26T07:07:06.833] Hello server1!! $ curl -b cookie.txt -c cookie.txt http://172.17.0.5:8080/hello [2016-11-26T07:07:06.833] Hello server1!! $ curl -b cookie.txt -c cookie.txt http://172.17.0.5:8080/hello [2016-11-26T07:07:06.833] Hello server1!!
この時、Cookieの値を見ると、Cookieの値に指定したprefixが入っています。
$ cat cookie.txt # Netscape HTTP Cookie File # http://curl.haxx.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. #HttpOnly_172.17.0.5 FALSE / FALSE 0 JSESSIONID server1~D338ED4E06048EEAABE43E26BA1EB2E4
振り分け直すと、こんな感じに。
#HttpOnly_172.17.0.5 FALSE / FALSE 0 JSESSIONID server2~1CA5FDBD2807B239AAFFA1E555C0B9D0
とりあえず、やりたいことはできた感じです。
まとめ
というわけで、HAProxyを使ってHTTPロードバランシングを試してみました。
最後にセッションパーシステンスもやってみましたけど、appsessionを1.6のHAProxyが使えないのがバグなのか、それとも機能として落とされたのかがよくわかりませんでした…。
'appsession' is not supported anymore ... · Issue #105 · docker/dockercloud-haproxy · GitHub
[SOLVED] HAProxy 1.6 Ubuntu 16.04 fails to start with 1.5 config file [Archive] - Ubuntu Forums