HAProxyは1.6

HAProxyロゴ

断固として歓迎します。

1年半後(4つではなく)、興味深い機能を備えたHAProxy 1.6の安定したバージョンが生まれたという良いニュースを急いで伝えます。



これは、フォールトトレランスを保証し、TCPおよびHTTP要求のバランスとプロキシを提供する超高速ソリューションであることを思い出させてください。

何ができる
多くのクエリバランシングアルゴリズム

多くの基準によるリクエストのルーティングとフィルタリング

SSL終了、SNI / NPN / ALPNおよびOCSPステープルを含む

HTTPヘッダー操作とACLサポート

チェックによるHTTPおよびTCPバックエンドサーバーの監視

VRRPとの簡単な統合(キープアライブ)

圧縮(gzip、deflate)

syslogサポート、柔軟なログ形式

ほぼ無制限の数のサーバー、ファーム、サービス

セキュリティ(13年間で1つのハックではありません)

IPv6およびUNIXソケットのサポート

...および他の多くの可能性





LANで検出されたすべての不正確さとエラーについてご連絡ください。すみやかに修正します。



この記事で 、バージョン番号1.6のリリースがどれほど注目に値するか、どのイノベーションに注意を払うべきか、これらのイノベーションを試す方法を簡単に説明します。 この記事の例は、慣れるために提示されており、そのアプリケーションは、常に更新されるドキュメントのページを参照する必要性を免除されていません。





最後に引数に引用符を使用できます



これは本当に良いニュースです。 構成ファイルに貼り付けるときに、ヘッダーなどのバックスラッシュでスペースを挿入する必要がなくなりました。



reqirep "^Host: www.(.*)" "Host: foobar\1"
      
      





 option httpchk GET / "HTTP/1.1\r\nHost: www.domain.com\r\nConnection: close"
      
      





ルア



どうやら、エイプリルフールの開発者たちの冗談は、HAProxy全体をLUAに書き換えることを決めたということで、機能にプラスの効果がありました。 これは、1.5でSSLが使用されていたように、1.6で大きな変更となった可能性があります。

たとえば、「ミラー」Webサーバーの実装を見てみましょう。 応答本文のヘッダーを変更せずに返します。



 global lua-load ./webmirror.lua frontend fe_habrahabr bind :81 name frontend_name http-request lua mirror default_backend be_habrahabr backend be_habrahabr server main_nginx 127.0.0.1:82
      
      







-webmirror.lua

 function mirror(txn) local buffer = "" local response = "" local mydate = txn.sc:http_date(txn.f:date()) buffer = buffer .. "You sent the following headers/r/n" buffer = buffer .. "===============================================/r/n" buffer = buffer .. txn.req:dup() buffer = buffer .. "===============================================/r/n" response = response .. "HTTP/1.0 200 OK/r/n" response = response .. "Server: haproxy-lua/mirror/r/n" response = response .. "Content-Type: text/html/r/n" response = response .. "Date: " .. mydate .. "/r/n" response = response .. "Content-Length: " .. buffer:len() .. "/r/n" response = response .. "Connection: close/r/n" response = response .. "/r/n" response = response .. buffer txn.res:send(response) txn:close() end
      
      







 $ curl -v 127.0.0.1:82 HTTP/1.0 200 OK Server: haproxy-lua/mirror Content-Type: text/html Date: Fri, 12 Mar 2015 13:06:44 GMT Content-Length: 208 Connection: keep-alive You sent the following headers =============================================== GET / HTTP/1.1 User-Agent: curl/7.41.0 Host: 127.0.0.1:82 Accept: */* ===============================================
      
      







または、たとえば、tcpサーバー:



 global lua-load hello_world.lua listen proxy bind 127.0.0.1:10001 tcp-request content use-service lua.hello_world
      
      







-hello_world.lua

 core.register_service("hello_world", "tcp", function(applet) applet:send("hello world\n") end)
      
      







セクション間でヘッダーを渡す(コンテキスト)



以前は、各コンテキストは分離されていました。 つまり、要求ヘッダーを応答に使用できませんでした。 しかし、今では可能です。

 defaults mode http frontend fe_habr bind :9001 declare capture request len 32 # id=0 to store Host header declare capture request len 64 # id=1 to store User-Agent header http-request capture req.hdr(Host) id 0 http-request capture req.hdr(User-Agent) id 1 default_backend be_habr backend be_habr http-response set-header Your-Host %[capture.req.hdr(0)] http-response set-header Your-User-Agent %[capture.req.hdr(1)] server nginx1 10.0.0.3:4444 check
      
      







マルチプロセッシング、ピア、およびスティックテーブル



peerは別のhaproxyインスタンスです。 たとえば、別のVM、別のDCなど。

stick-table-たとえば、1つのIPアドレスからの1秒あたりのリクエスト数、同時セッション数、エラー率、CookieによるセッションIDなどに関する情報を格納するためのフラットデータベース



1.5には(1.6に) peersのようなパラメーターがありました。 バランサー間でスティックテーブルを同期するように設計されています。 また、残念ながら、haproxy(nbprocパラメーター)でマルチプロセッシングが有効になっていると、メモリ内の各プロセスの独自のテーブルが原因で、この機能が正しく動作しなくなりました。

ソリューションはbind-process



パラメーターの形で提供され、例ではその使用方法が明確に示されています。



 peers article peer itchy 127.0.0.1:1023 global pidfile /tmp/haproxy.pid nbproc 3 defaults mode http frontend f_scalessl bind-process 1,2 bind :9001 ssl crt /home/bassmann/haproxy/ssl/server.pem default_backend bk_lo backend bk_lo bind-process 1,2 server f_myapp unix@/tmp/f_myapp send-proxy-v2 frontend f_myapp bind-process 3 bind unix@/tmp/f_myapp accept-proxy default_backend b_myapp backend b_myapp bind-process 3 stick-table type ip size 10k peers article stick on src server s1 10.0.0.3:4444 check
      
      







ログ:syslogタグと新しい変数



これ以降、ログのフィルタリングの便宜上、フロントエンド、バックエンド、およびプロセスごとにさまざまなsyslogタグを適用できます。 パラメータが指定されていない場合、haproxyという単語が使用されます。

 frontend fe_habr_ssl log-tag SSL [...] frontend fe_habr log-tag CLEAR [...]
      
      







log-formatパラメーターで使用できる新しい変数:



 %HM: HTTP method (ex: POST) %HP: HTTP request URI without query string (path) %HQ: HTTP request URI query string (ex: ?bar=baz) %HU: HTTP request URI (ex: /foo?bar=baz) %HV: HTTP version (ex: HTTP/1.0)
      
      







DNSサーバー名



バージョン1.5以前では、DNS名がバックエンドとして指定された場合、HAProxyは起動時にIPアドレスを受け取り、glibc(/etc/resolv.conf)を使用しました



1.6では、HAProxyはその場でIPアドレスへの名前の関連性を非同期的にチェックし、明示的に指定されたDNSサーバーを使用します。 これにより、バックエンドのサーバーIPアドレスが変更された場合にバランサーを再起動する必要がなくなります(これは、DockerまたはAmazon Web Service環境でよく発生します)。



Dockerの構成例:

 resolvers docker nameserver dnsmasq 127.0.0.1:53 defaults mode http log global option httplog frontend fe_habr bind :80 default_backend be_habr backend be_habr server s1 nginx1:80 check resolvers docker resolve-prefer ipv4
      
      







ここで、「docker restart nginx1」コマンドを使用してnginxでコンテナを再起動すると、ログにこの機能の証拠が表示されます。

(...) haproxy[15]: b_myapp/nginx1 changed its IP from 172.16.0.4 to 172.16.0.6 by docker/dnsmasq.









HTTPリクエストを処理するためのルール





HTTP要求を処理するための新しいルールが登場しました。

http-request:キャプチャ、set-method、set-uri、set-map、set-var、track-scX、sc-in-gpc0、sc-inc-gpt0、silent-drop

http-response:キャプチャ、set-map、set-var、sc-inc-gpc0、sc-set-gpt0、サイレントドロップ、リダイレクト




DDoSを使用する戦闘機は、興味深いsilent-drop



パラメーターに注意を払う必要があります。 reqtarpit/reqitarpit



置き換えることができreqtarpit/reqitarpit





その結果、HAProxyにsilent-drop



を適用した後にクライアントが確立した接続(ESTABLISHED)がバランサーの接続リストから消え、リソースが解放されます。 したがって、バランサーの貴重なリソースを無駄にすることなく、はるかに強力な攻撃を撃退することができます。 ただし、この接続が通過したすべてのファイアウォール、プロキシ、バランサーは、この接続を保持し続け、防御のボトルネック(「ボトルネック」)になる可能性があることを覚えておく価値があります。



変数



以前にHTTPヘッダーを使用して、HAProxyに一時データを保存していました。 顕著な例は、 1秒あたりのリクエスト数が1.5に制限されていることです。

現在、変数があります。



ユーザーエージェントを小文字で記述します。

 http-request set-var(req.my_var) req.fhdr(user-agent),lower
      
      







変数を使用して書き換えられたコンテキストの例

 global # variables memory consumption, in bytes tune.vars.global-max-size 1048576 tune.vars.reqres-max-size 512 tune.vars.sess-max-size 2048 tune.vars.txn-max-size 256 defaults mode http frontend f_myapp bind :9001 http-request set-var(txn.host) req.hdr(Host) http-request set-var(txn.ua) req.hdr(User-Agent) default_backend b_myapp backend b_myapp http-response set-header Your-Host %[var(txn.host)] http-response set-header Your-User-Agent %[var(txn.ua)] server s1 10.0.0.3:4444 check
      
      







郵便



HAProxyは手紙を送ることを学びました。 たとえば、バックエンドが応答しなくなった。

以下の例は、おそらくこのイノベーションのすべての可能性をカバーしています。 承認サポートなし。



 mailers mymailers mailer smtp1 192.168.0.1:587 mailer smtp2 192.168.0.2:587 backend be_habr mode tcp balance roundrobin email-alert mailers mymailers email-alert from haproxy@habrahabr.ru email-alert to admin@habrahabr.ru server srv1 192.168.0.30:80 server srv2 192.168.0.31:80
      
      







HTTPリクエスト本文の処理



これで、HTTPヘッダーの処理に加えて、リクエスト本文を処理できるようになりました。

option http-buffer-request



フロントエンドまたはバックエンドセクションに含まれoption http-buffer-request







バージョン1.5では、slowlorisなどの攻撃に対処することが可能でした。この攻撃では、接続タイムアウトの直前に、攻撃者からのリクエストヘッダーが可能な限り低速で送信されます。

しかし、POST要求の本文をできるだけ遅く送信することを妨げるものはいませんでした。 バージョン1.6では、攻撃者からこの機会を奪うことができます。



ところで、 http-buffer-request



オプションを使用すると、req.body、req.body_param、req.body_len、req.body_sizeなどのメソッドを使用できるようになります。



POSTリクエストの本文にある「SELECT *」行の言及をブロックする方法の例を次に示します。



 defaults mode http frontend f_mywaf bind :9001 option http-buffer-request http-request deny if { req.body -m reg "SELECT \*" } default_backend b_myapp backend b_myapp server s1 10.0.0.3:4444 check
      
      







コンバーター





ACLで使用され、あらゆる方法で構成を簡素化しました。 たとえば、それらのないリクエストをルーティングする場合:

 frontend ft_allapps [...] use_backend bk_app1 if { hdr(Host) -i app1.domain1.com app1.domain2.com } use_backend bk_app2 if { hdr(Host) -i app2.domain1.com app2.domain2.com } default_backend bk_default
      
      







コンバーターの場合:

 frontend ft_allapps [...] use_backend %[req.hdr(host),lower,map(/etc/haproxy/domain2backend.map,bk_default)]
      
      







-domain2backend.map

 #domainname backendname app1.domain1.com bk_app1 app1.domain2.com bk_app1 app2.domain1.com bk_app2 app2.domain2.com bk_app2
      
      







便利ですね。

そのため、1.6ではさらに多くのものがあり、コメントにある誰かの例に感謝します。



クライアントデバイスの定義





予想外に、HAProxyはDeviceAtlasおよび51Degrees連携してデバイスのタイプを判別し、結果のバックエンドを送信する機会を得ました。



DeviceAtlasの構成例:




 global deviceatlas-json-file <path to json file> frontend www-only-ua bind *:8881 default_backend servers #   User-agent http-request set-header X-DeviceAtlas-Data %[req.fhdr(User-Agent),da-csv-conv(primaryHardwareType,osName,osVersion,browserName,browserVersion)] deviceatlas-json-file <path> frontend www-all-headers bind *:8882 default_backend servers #     http-request set-header X-DeviceAtlas-Data %[da-csv-fetch(primaryHardwareType,osName,osVersion,browserName,browserVersion)]
      
      







51Degreesの場合:




 global 51degrees-data-file '51D_REPO_PATH'/data/51Degrees-LiteV3.2.dat 51degrees-property-name-list IsTablet DeviceType IsMobile 51degrees-property-separator , 51degrees-cache-size 10000 frontend www-only-ua bind *:8082 default_backend servers #   User-agent http-request set-header X-51D-DeviceTypeMobileTablet %[req.fhdr(User-Agent),51d.single(DeviceType,IsMobile,IsTablet)] frontend www-all-headers bind *:8081 default_backend servers #      http-request set-header X-51D-DeviceTypeMobileTablet %[51d.all(DeviceType,IsMobile,IsTablet)] http-request set-header X-51D-Tablet %[51d.all(IsTablet)] # ,   51Degrees   http-request set-header X-51D-Stats %[51d.all(Method,Difference,Rank)]
      
      







注意! デフォルトでは、サポートは有効になっていません。 それを使用するには、次のものが必要です。



DeviceAtlasの場合:




DeviceAtlasからAPIソースコードをダウンロードする

次のパラメーターを使用してHAProxyをコンパイルします。

 $ make TARGET=<target> USE_PCRE=1 USE_DEVICEATLAS=1 DEVICEATLAS_SRC=<path to the API root folder>
      
      







51Degreesの場合:




 $ git clone https://github.com/51Degrees/Device-Detection
      
      





作業方法を選択します。

*パターン-メモリとプロセッサを均等に使用して動作する

  $ make TARGET=linux26 USE_51DEGREES=1 51DEGREES_SRC='51D_REPO_PATH'/src/pattern
      
      





* Trieは、パターンよりも大幅に多くのメモリを使用する高性能アルゴリズムです

  $ make TARGET=linux26 USE_51DEGREES=1 51DEGREES_SRC='51D_REPO_PATH'/src/trie
      
      







バックエンドサーバーの状態を保存する



1.5では、リロードまたは再起動コマンドを受信すると、HAProxyは最初のチェックの前にすべてのサーバーにUPステータスを割り当てました。 サービスのアップタイムの1秒ごとに費用がかかる場合、これは受け入れられません。 1.6では、再起動時にバックエンドに関する情報が保存されるファイルへのパスを指定できます。

 global stats socket /tmp/socket server-state-file /tmp/server_state backend bk load-server-state-from-file global server s1 10.0.0.3:4444 check weight 11 server s2 10.0.0.4:4444 check weight 12
      
      







再起動する前に、バックエンドの状態を保存します。



 socat /tmp/socket - <<< "show servers state" > /tmp/server_state
      
      







タスクが完了し、起動時にhaproxyがファイルを読み取り、すぐにメモします。



外部チェック



1.5では、指定されたポートに定期的に接続することにより、バックエンドサーバーのステータスを確認できます。

1.6では、これらの目的のために、サードパーティのスクリプトを追加で使用できます。

 global external-check backend b_myapp external-check path "/usr/bin:/bin" external-check command /bin/true server s1 10.0.0.3:4444 check
      
      







TLS / SSL





同じIPアドレスでECCとRSAをサポート



ECCはRSAと同様にコンテンツを保護しますが、キーサイズが小さいため、サーバーでリクエストを処理する時間が短くなると考えられています。 残念ながら、すべてのお客様がECCをサポートしているわけではありませんが、すべてのユーザーとの互換性が必要です。

実装には、ドメイン用のECCおよびRSA証明書、HAProxyバージョン1.6、および次の構成が必要です。

 frontend ssl-relay mode tcp bind 0.0.0.0:443 use_backend ssl-ecc if { req.ssl_ec_ext 1 } default_backend ssl-rsa backend ssl-ecc mode tcp server ecc unix@/var/run/haproxy_ssl_ecc.sock send-proxy-v2 backend ssl-rsa mode tcp server rsa unix@/var/run/haproxy_ssl_rsa.sock send-proxy-v2 listen all-ssl bind unix@/var/run/haproxy_ssl_ecc.sock accept-proxy ssl crt /usr/local/haproxy/ecc.www.foo.com.pem user nobody bind unix@/var/run/haproxy_ssl_rsa.sock accept-proxy ssl crt /usr/local/haproxy/www.foo.com.pem user nobody mode http server backend_1 192.168.1.1:8000 check
      
      







E5-2680v3 CPUおよびOpenSSL 1.0.2でベンチマーク結果があります。

 256bit ECDSA: sign verify sign/s verify/s 0.0000s 0.0001s 24453.3 9866.9 2048bit RSA: sign verify sign/s verify/s 0.000682s 0.000028s 1466.4 35225.1
      
      





応答の署名でほぼ15倍の増加。



その場でSSL証明書の偽造

これにより、企業でHAProxyを使用してリクエストのコンテンツを分析できます。



証明書の透明性のサポート(RFC6962)

.pemファイル(キーを持つ証明書チェーン)をロードするとき、HAProxyは同じ名前と接尾辞.sctlのファイルを同じ方法で見つけようとします。 検出されると、TLS証明書の透明性のサポートが有効になります。 OpenSSLバージョン1.0.2以降が必要です。 現在、Certificate Transparency拡張機能では、2015年に発行されたEV証明書にChromeが必要です。



SSLバックエンドへの接続時のSNIサポート



 backend b_myapp_ssl mode http server s1 10.0.0.3:4444 check ssl sni req.hdr(Host)
      
      







HTTP再利用



デフォルトでは、HAProxyとバックエンドサーバーの間に確立された接続は、それを開始したセッションに属します。 このアプローチの欠点は、リクエスト間でこの接続がアイドルになることです。 ほとんどの場合、これらの接続を他のセッションと再利用すると、バックエンドのパフォーマンスが向上します。

オプション
 http-reuse
      
      



4つの異なるモードでは、これらのアイドル接続を使用できます。



エラー408



このエラーは、インターネットでのサーフィンを高速化するために設計された、接続前の接続タイムアウトが原因でブラウザで発生しました。

1.5では、 defaults



セクションのerrorfile 408 /dev/null



で処理されていました。

1.6はhttp-ignore-probes



オプションを使用する必要があります








結論として、すべての新しいバージョンには古い構成ファイルとの完全な後方互換性があり、新しいバージョンに更新しても頭痛の種にはならないことを思い出してください。 また、上記の機会は、過去1年半にわたって開発者が行った作業のほんの一部にすぎません。



このレビューにご注意いただきありがとうございます。 コメントとPMで質問にお答えします。



All Articles