ども、cloudpack の かっぱ (@inokara) です。
はじめに
HAProxy で User-Agent やリクエストパス等でバックエンドに対する振り分けを行う際に利用する use_backend
で複数条件が利用出来るか試してみた。ドキュメントを改めてみると何でも出来そうな雰囲気が HAProxy の底力を魅せつけられたような気がする。
参考
解ったこと
とりあえず検証結果が長くなってしまったので結論から。
use_backend
で複数の条件を定義することが可能acl
で定義した条件をそのまま並べるとand
条件となるand
の代わりにor
を付けることも可能(どちらかにマッチしたらという条件に代わる)-
and
条件をor
で並記することが可能 - HAProxy とは関係ないけど Sinatra で複数のパス指定は
sinatra/multi_route
をrequire
すると良い
俺の条件
以下のような条件で試す。
パターン(1)(or)
- User-Agent が Firefox か、リクエストパスが /firefox であれば Firefox 用サイトに飛ばす
- User-Agent が Chrome か、リクエストパスが /chrome であれば Chrome 用サイトに飛ばす
- 上記以外はデフォルトのバックエンドに飛ばす
パターン(2)(and)
- User-Agent が Firefox で且つ、リクエストパスが /firefox であれば Firefox 用サイトに飛ばす
- User-Agent が Chrome で且つ、リクエストパスが /chorme であれば Chrome 用サイトに飛ばす
- 上記以外はデフォルトのバックエンドに飛ばす
パターン(3)(接続元 and User-Agent)
- User-Agent が Firefox で且つ、接続元が xxx.xxx.xxx.xxx/32 であれば Firefox 用サイトに飛ばす
- User-Agent が Chrome で且つ、接続元が yyy.yyy.yyy.yyy/32 であれば Chrome 用サイトに飛ばす
パターン(4)(接続元(X-Forwarded-for) and User-Agent)
- User-Agent が Firefox で且つ、接続元が xxx.xxx.xxx.xxx/32 であれば Firefox 用サイトに飛ばす
- User-Agent が Chrome で且つ、接続元が yyy.yyy.yyy.yyy/32 であれば Chrome 用サイトに飛ばす
Firefox 用サイト
require 'sinatra' require 'sinatra/reloader' require "sinatra/multi_route" get '/', '/firefox' do "firefox
""#{request.path_info}""n" end
Chrome 用サイト
require 'sinatra' require 'sinatra/reloader' require "sinatra/multi_route" get '/', '/firefox' do "chrome
""#{request.path_info}""n" end
HAProxy の設定
パターン(1)(or)
(snip) frontend balancer-test option forwardfor bind 0.0.0.0:8000 acl foo path_beg /firefox acl bar path_beg /chrome acl crm hdr_sub(User-Agent) -i Chrome acl fox hdr_sub(User-Agent) -i Firefox use_backend hoge_bk if foo or fox use_backend fuga_bk if bar or crm default_backend aa backend hoge_bk option forwardfor server web01 127.0.0.1:8081 check backend fuga_bk option forwardfor server web02 127.0.0.1:8082 check backend aa server web03 127.0.0.1:80
パターン(2)(and)
use_backend
にて acl
で定義した条件を並べると and
条件となる。
(snip) frontend balancer-test option forwardfor bind 0.0.0.0:8000 acl foo path_beg /firefox acl bar path_beg /chrome acl crm hdr_sub(User-Agent) -i Chrome acl fox hdr_sub(User-Agent) -i Firefox # condition foo and fox use_backend hoge_bk if foo fox # condition bar and crm use_backend fuga_bk if bar crm default_backend aa backend hoge_bk option forwardfor server web01 127.0.0.1:8081 check backend fuga_bk option forwardfor server web02 127.0.0.1:8082 check backend aa server web03 127.0.0.1:80
パターン(3)(接続元 and User-Agent)
use_backend
にて acl
で定義した条件を並べると and
条件となる。
(snip) frontend balancer-test option forwardfor bind 0.0.0.0:8000 # acl crm hdr_sub(User-Agent) -i Chrome acl fox hdr_sub(User-Agent) -i Firefox # IP アドレスは CIDR 表記が可能 acl hoge src xxx.xxx.xxx.xxx/32 acl fuga src yyy.yyy.yyy.yyy/32 # use_backend firefox_bk if fox hoge use_backend chrome_bk if crm fuga default_backend aa backend hoge_bk option forwardfor server web01 127.0.0.1:8081 check backend fuga_bk option forwardfor server web02 127.0.0.1:8082 check backend aa server web03 127.0.0.1:80
単純に接続元 IP を指定する場合には src
を利用するが、X-Forwarded-for
がヘッダにくっついてくる場合には以下のように acl
を設定する必要がある。
acl hoge_x hdr_ip(X-Forwarded-For) xxx.xxx.xxx.xxx/32
パターン(4)(接続元(X-Forwarded-for) and User-Agent)
HAProxy の前面に ELB を配置したのでクライアントからは ELB のエンドポイントにアクセスすることになる。
frontend balancer-test option forwardfor bind 0.0.0.0:8000 # acl foo path_beg /firefox acl bar path_beg /chrome acl crm hdr_sub(User-Agent) -i Chrome acl fox hdr_sub(User-Agent) -i Firefox acl hoge src xxx.xxx.xxx.xxx/32 acl fuga src yyy.yyy.yyy.yyy/32 acl hoge_x req.hdr_ip(X-Forwarded-For) xxx.xxx.xxx.xxx/32 acl fuga_x req.hdr_ip(X-Forwarded-For) yyy.yyy.yyy.yyy/32 # use_backend firefox_bk if fox hoge or fox hoge_x use_backend chrome_bk if crm fuga or crm fuga_x default_backend aa backend firefox_bk option forwardfor server web01 127.0.0.1:8001 check backend chrome_bk option forwardfor server web02 127.0.0.1:8002 check backend aa server web03 127.0.0.1:80
以下のような条件でバックエンドへの振分を行う。
use_backend firefox_bk if fox hoge or fox hoge_x
は以下の条件が含まれる。
- User-Agent に
Firefox
が含まれており且つ接続元がxxx.xxx.xxx.xxx/32
の場合にはfirefox_bk
に振り分ける - User-Agent に
Firefox
が含まれており且つX-Forwarded-For
がxxx.xxx.xxx.xxx/32
の場合にはfirefox_bk
に振り分ける
use_backend chrome_bk if crm fuga or crm fuga_x
は以下の条件が含まれる。
- User-Agent に
Chrome
が含まれており且つ接続元がyyy.yyy.yyy.yyy/32
の場合にはchrome_bk
に振り分ける - User-Agent に
Chrome
が含まれており且つX-Forwarded-For
がyyy.yyy.yyy.yyy/32
の場合にはchrome_bk
に振り分ける
また、バックエンドで利用するスクリプトを以下のように用意した。
firefox_bk
用。
chrome_bk 用。
default_backend
用。試した
パターン(1)(or)
Chrome で
/firefox
にアクセス(上)、そして Firefox で/chrome
にアクセス(下)。以下、メモ。
- Chrome で
/firefox
にアクセス出来るのはuse_backend hoge_bk if foo or fox
のor
条件となっているから - Firefox で
/chrome
にアクセスした場合にはuse_backend hoge_bk if foo or fox
の条件にマッチしているものの Firefox 用サイトに/chrome
というパスが定義されていない為に Sinatra の404
エラーとなっている
まとめると…
-
or
で複数の条件を並べることが可能 - 複数の条件がある場合は条件にマッチした時点で条件判断処理は終了
パターン(2)(and)
Chrome で /
にアクセス。and
条件となるので User-Agent
とリクエストパスが両方マッチする必要があるので default_backend
が利用される。
Chrome で /chrome
にアクセス。User-Agent
とリクエストパスの両方がマッチしたので表示される。
Firefox で /
にアクセス。and
条件となるので User-Agent
とリクエストパスが両方マッチする必要があるので default_backend
が利用される。
Firefox で /firefox
にアクセス。User-Agent
とリクエストパスの両方がマッチしたので表示される。
Chrome で /firefox
にアクセス(上)、そして Firefox で /chrome
にアクセス(下)。
以下、メモ。
どちらの条件にもマッチしないので default_backend
が利用されるが、default_backend
のパスに /chrome
や /firefox
が存在しない為にバックエンドの Web サーバーの 404 エラーが表示される。
まとめると…
and
は明示的に指定する必要はない
パターン(3)(接続元 and User-Agent)
すいません、以下、スクショが辛いのでコマンドラインから実行します。
接続元 xxx.xxx.xxx.xxx/32 から Chrome でアクセス。
% http http://zzz.zzz.zzz.zzz:8000/ User-Agent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36" HTTP/1.1 200 OK Accept-Ranges: bytes Content-Length: 8 Content-Type: text/html; charset=UTF-8 Date: Fri, 09 Jan 2015 03:11:37 GMT ETag: "40975-8-50c2bca4b0cf9" Last-Modified: Thu, 08 Jan 2015 22:45:00 GMT Server: Apache/2.2.29 (Amazon) default
条件にマッチしないので default_backend
にアクセスする。
接続元 xxx.xxx.xxx.xxx/32 から Firefox で /
にアクセス。
% http http://zzz.zzz.zzz.zzz:8000/ User-Agent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:34.0) Gecko/20100101 Firefox/34.0" HTTP/1.1 200 OK Content-Length: 14 Content-Type: text/html;charset=utf-8 Date: Fri, 09 Jan 2015 03:06:41 GMT Server: WEBrick/1.3.1 (Ruby/2.0.0/2014-11-13) X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-Xss-Protection: 1; mode=block firefox
/
接続元 xxx.xxx.xxx.xxx/32 から User-Agent を指定しない場合には以下のようになる。
% http http://54.65.162.168:8000/ HTTP/1.1 200 OK Accept-Ranges: bytes Content-Length: 8 Content-Type: text/html; charset=UTF-8 Date: Fri, 09 Jan 2015 03:08:05 GMT ETag: "40975-8-50c2bca4b0cf9" Last-Modified: Thu, 08 Jan 2015 22:45:00 GMT Server: Apache/2.2.29 (Amazon) default
条件にマッチしないので default_backend
にアクセスする。
接続元 yyy.yyy.yyy.yyy/32 から Chrome でアクセス。
$ http GET http://zzz.zzz.zzz.zzz:8000/ User-Agent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36" HTTP/1.1 200 OK Content-Type: text/html;charset=utf-8 Content-Length: 13 X-Xss-Protection: 1; mode=block X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Server: WEBrick/1.3.1 (Ruby/2.0.0/2014-11-13) Date: Fri, 09 Jan 2015 03:16:06 GMT chrome
/
接続元 yyy.yyy.yyy.yyy/32 から Firefox で /
にアクセス。
$ http GET http://zzz.zzz.zzz.zzz:8000/ User-Agent:User-Agent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:34.0) Gecko/20100101 Firefox/34.0" HTTP/1.1 200 OK Date: Fri, 09 Jan 2015 03:17:25 GMT Server: Apache/2.2.29 (Amazon) Last-Modified: Thu, 08 Jan 2015 22:45:00 GMT ETag: "40975-8-50c2bca4b0cf9" Accept-Ranges: bytes Content-Length: 8 Content-Type: text/html; charset=UTF-8 default
条件にマッチしないので default_backend
にアクセスする。
接続元 yyy.yyy.yyy.yyy/32 から User-Agent を指定しない場合には以下のようになる。
$ http GET http://zzz.zzz.zzz.zzz:8000/ HTTP/1.1 200 OK Date: Fri, 09 Jan 2015 03:18:17 GMT Server: Apache/2.2.29 (Amazon) Last-Modified: Thu, 08 Jan 2015 22:45:00 GMT ETag: "40975-8-50c2bca4b0cf9" Accept-Ranges: bytes Content-Length: 8 Content-Type: text/html; charset=UTF-8 default
条件にマッチしないので default_backend
にアクセスする。
パターン(4)(接続元(X-Forwarded-for) and User-Agent)
接続元 xxx.xxx.xxx.xxx/32 から Chrome で ELB にアクセス。
% http http://test-12345678.ap-northeast-1.elb.amazonaws.com/ User-Agent:Chrome HTTP/1.1 200 OK Connection: keep-alive Content-Length: 74 Content-Type: text/html; charset=UTF-8 Date: Fri, 09 Jan 2015 08:44:49 GMT Server: Apache X-Powered-By: PHP X-Forwarded-For: xxx.xxx Default X-Forwarded-For: xxx.xxx.xxx.xxx, 172.xx.xx.xx User-Agent: Chrome
条件にマッチしない為に default_backend
が利用される。尚、X-Forwarded-For
にはクライアントの IP である xxx.xxx.xxx.xxx
と ELB の IP アドレス 172.xx.xx.xx
が記録される。
また、以下の通り ELB 経由ではなく直接 HAProxy にアクセスしてみる。
% http http://zzz.zzz.zzz.zzz:8000/ User-Agent:Chrome HTTP/1.1 200 OK Content-Length: 60 Content-Type: text/html; charset=UTF-8 Date: Fri, 09 Jan 2015 08:48:34 GMT Server: Apache X-Powered-By: PHP Default X-Forwarded-For: xxx.xxx.xxx.xxx User-Agent: Chrome
こちらも条件にマッチしない為に default_backend
が利用される。
以下は接続元 xxx.xxx.xxx.xxx/32 から Firefox で ELB にアクセス。
% http http://test-12345678.ap-northeast-1.elb.amazonaws.com/ User-Agent:Firefox HTTP/1.1 200 OK Connection: keep-alive Content-Length: 75 Content-Type: text/html; charset=UTF-8 Date: Fri, 09 Jan 2015 08:51:10 GMT Server: Apache X-Powered-By: PHP Firefox X-Forwarded-For: xxx.xxx.xxx.xxx, 172.xx.xx.xx User-Agent: Firefox
use_backend
の条件にマッチしている為に Firefox ページにアクセスされる。
% http http://zzz.zzz.zzz.zzz:8000/ User-Agent:Firefox HTTP/1.1 200 OK Content-Length: 60 Content-Type: text/html; charset=UTF-8 Date: Fri, 09 Jan 2015 08:54:50 GMT Server: Apache X-Powered-By: PHP Default X-Forwarded-For: xxx.xxx.xxx.xxx User-Agent: Chrome
接続元 yyy.yyy.yyy.yyy/32 から Chrome で ELB にアクセス。
http GET http://test-12345678.ap-northeast-1.elb.amazonaws.com/ User-Agent:Chrome HTTP/1.1 200 OK Content-Type: text/html; charset=UTF-8 Date: Fri, 09 Jan 2015 08:56:39 GMT Server: Apache X-Powered-By: PHP Content-Length: 72 Connection: keep-alive Chrome X-Forwarded-For: yyy.yyy.yyy.yyy, 172.xx.xx.xx User-Agent: Chrome
use_backend
の条件にマッチしている為に Chrome ページにアクセスされる。
引続き、接続元 yyy.yyy.yyy.yyy/32 から HAProxy に直接 User-Agent を Firefox にしてアクセスしてみる。
$ http GET http://zzz.zzz.zzz.zzz:8000/ User-Agent:Firefox HTTP/1.1 200 OK Date: Fri, 09 Jan 2015 09:00:06 GMT Server: Apache X-Powered-By: PHP Content-Length: 60 Content-Type: text/html; charset=UTF-8 Default X-Forwarded-For: yyy.yyy.yyy.yyy User-Agent: Firefox
use_backend
何れの条件にもマッチしない為に default_backend
に指定されたバックエンドにアクセスされる。
最後に
わかった事
冒頭にも書いたけど。
use_backend
で複数の条件を定義することが可能acl
で定義した条件をそのまま並べるとand
条件となるand
の代わりにor
を付けることも可能(どちらかにマッチしたらという条件に代わる)and
条件をor
で並記することが可能- HAProxy とは関係ないけど Sinatra で複数のパス指定は
sinatra/multi_route
をrequire
すると良い
元記事はこちらです。
「HAProxy の acl によるバックエンドへの振り分けを色々と試す(1) (「User-Agent と接続元 IP」という条件でバックエンドへの振り分けをコントロールする)」