本文へ移動

これはセクションの複数ページ印刷用ビューです。 .

このページの通常のビューに戻る.

HAProxy 3.4.4 ドキュメント

HAProxy 3.4 の入門・設定・管理マニュアルの日本語訳。未翻訳のページは英語版へ切り替わります。

HAProxy は、高可用性、TCP および HTTP の負荷分散、アプリケーショントラフィック管理を実現する、無料で高速かつ信頼性の高いリバースプロキシです。このサイトの英語版には、HAProxy 3.4 の主要な 3 つのマニュアルの全文を収録し、OINK 向けに階層のない一連の Markdown ページとして再構成しています。日本語版では翻訳済みのページを公開し、未翻訳のページへのリンクは英語版へ切り替わります。

マニュアルの選択

  • 入門ガイド — 負荷分散の概念、HAProxy のアーキテクチャ、機能、サイジング、リリース、エコシステムに関する 9 つのトピック。
  • 設定マニュアル — プロキシ、ACL、サンプル、ログ、フィルター、すべてのオプション群を網羅した 12 章。
  • 管理ガイド — 起動、リロード、リソース、ログ、統計、ランタイム CLI、デバッグ、セキュリティに関する 13 章。

収録範囲

マニュアルアップストリームの原文このサイトでの構成
入門ガイド1,695 行同一階層の 9 トピックページ
設定マニュアル33,148 行同一階層の 12 章ページ
管理ガイド5,285 行同一階層の 13 章ページ

英語の完全版では、34 の本文ページをすべて HAProxy コンポーネントのルート直下に配置しています。リンクを持たない 3 つの区切り用プレースホルダーにより、ディレクトリ階層やページ送りの経由先を増やすことなく、サイドバーで各マニュアルを区分しています。

固定バージョンの完全な入力データと SHA-256 チェックサムは sources/haproxy/ に保持しています。アップストリームのライセンス通知 と GPLv2 の本文 も、マニュアルとともに公開しています。各ページには、レビュー済みの簡体字中国語版も用意されています。

1 - サイジングと性能

容量計画の原則、性能の目安、実用的な経験則

一般的な CPU 使用率を見ると、TCP モードまたは HTTP close モードでは、処理時間の 15% を HAProxy が、85% をカーネルが使用します。HTTP keep-alive モードでは、HAProxy が約 30%、カーネルが約 70% を使用します。つまり、オペレーティングシステムとそのチューニングは、全体の性能に大きく影響します。

利用形態はユーザーによって大きく異なります。帯域幅を重視する場合もあれば、リクエストレート、同時接続数、SSL 性能を重視する場合もあります。このセクションでは、サイジングの判断に役立つ要素をいくつか紹介します。

どの処理にもコストがかかることを忘れないでください。個々の処理のオーバーヘッドは他の処理に上乗せされます。ある条件では無視できても、別の条件では支配的になることがあります。

接続からのリクエストを処理する際には、次の関係が成り立ちます。

  • データの転送は、リクエストヘッダーやレスポンスヘッダーの解析より低コストです。

  • リクエストヘッダーやレスポンスヘッダーの解析は、サーバーへの接続を確立して切断するより低コストです。

  • 接続の確立と切断は、TLS の再開より低コストです。

  • TLS の再開は、鍵計算を伴う完全な TLS ハンドシェイクより低コストです。

  • アイドル状態の接続は、バッファーにデータを保持している接続より CPU コストが小さくなります。

  • TLS コンテキストは、データを保持している接続よりさらに多くのメモリを消費します。

実際には、ペイロードのバイト列を処理するほうが、ヘッダーのバイト列を処理するより低コストです。そのため、単位データ量あたりのリクエスト数が多い小さなオブジェクトよりも、リクエスト数が少ない大きなオブジェクトのほうが、高いネットワーク帯域幅を実現しやすくなります。最大帯域幅を常に大きなオブジェクトで測定し、リクエストレートや接続レートを小さなオブジェクトで測定するのはこのためです。

複数の CPU に分散した複数のプロセスでよくスケールする処理もあれば、そうでない処理もあります。大きなオブジェクトでは CPU がボトルネックになることはまれで、主にネットワーク帯域幅とネットワークインターフェースに至るデータバスが制約となるため、ネットワーク帯域幅はあまりスケールしません。接続レートは、ローカルポートテーブルを扱う際のシステム内のロックの影響で、複数プロセッサーではうまくスケールしません。持続的接続上のリクエストレートは、メモリやネットワーク帯域幅をあまり使用せず、ロックされた構造へのアクセスも不要なため、非常によくスケールします。TLS の鍵計算は完全に CPU に制約されるため、非常によくスケールします。TLS の再開もある程度スケールしますが、共有テーブルへのアクセスのオーバーヘッドが処理能力の増加による小さな利得を相殺するため、約 4 プロセスで限界に達します。

十分にチューニングしたシステムで期待できる性能は、次に示す範囲です。これらは桁の目安として捉えてください。プロセッサー、IRQ 設定、メモリの種類、ネットワークインターフェースの種類、オペレーティングシステムのチューニングなどにより、上下どちらにも大きく変動します。

次の数値は、デュアルポートの 10 Gbps NIC を備えた 3.7 GHz の Core i7 上で、Linux カーネル 3.10、HAProxy 1.6、OpenSSL 1.0.2 を実行して測定したものです。HAProxy は専用の単一 CPU コア上で単一プロセスとして動作し、さらに二つのコアをネットワーク割り込み専用に割り当てました。

  • 平文での最大ネットワーク帯域幅は、256 kB 以上のオブジェクトで 20 Gbps、41kB 以上で 10 Gbps。

  • 大きなオブジェクトと AES256-GCM 暗号を使用した TLS トラフィックは 4.6 Gbps。

  • クライアントからサーバーへの TCP 接続は毎秒 83000。

  • クライアントからサーバーへの HTTP 接続は毎秒 82000。

  • server-close モードでは毎秒 97000 の HTTP リクエスト。クライアント側は keep-alive、サーバー側は close とします。

  • エンドツーエンドの keep-alive モードでは毎秒 243000 の HTTP リクエスト。

  • フィルタリングされる TCP 接続は毎秒 300000(DDoS 対策)。

  • 持続的な TLS 接続の keep-alive モードでは毎秒 160000 の HTTPS リクエスト。

  • 再開された TLS 接続では毎秒 13100 の HTTPS リクエスト。

  • RSA2048 で再ネゴシエーションされる TLS 接続では毎秒 1300 の HTTPS 接続。

  • RAM の GB あたり、帯域を使い切る同時接続を 20000 処理できます。システムバッファーに必要なメモリも含みます。入念にチューニングすればさらに改善できますが、この結果は容易に達成できます。

  • RAM の GB あたり、約 8000 の同時 TLS 接続(クライアント側のみ)。システムバッファーに必要なメモリも含みます。

  • RAM の GB あたり、約 5000 の同時エンドツーエンド TLS 接続(両側)。システムバッファーに必要なメモリも含みます。

より新しいベンチマークでは、AWS の 64 コア ARM Graviton2 プロセッサー上でマルチスレッドを有効にした HAProxy 2.4 が、ミリ秒未満のレスポンスタイムで毎秒 2 million の HTTPS リクエストと、100 Gbps のトラフィックを達成しました。

https://www.haproxy.com/blog/haproxy-forwards-over-2-million-http-requests-per-second-on-a-single-aws-arm-instance/

覚えておくと便利な目安は、TLS keep-alive から TLS 再開へ、また TLS 再開から TLS 再ネゴシエーションへ切り替えると、リクエストレートがそれぞれ 10 分の一になることです。一方、HTTP keep-alive から HTTP close への切り替えでは 3 分の一になるだけです。また、AES 命令を備えた高クロックのコアでは、コアあたり約 20 Gbps の AES-GCM 処理が可能です。

同じサーバーを使う場合、HAProxy はおおむね次の台数を飽和させられると考えるのも、有用な目安です。

  • 約 5-10 台の静的ファイルサーバーまたはキャッシュプロキシ。

  • 約 100 台のアンチウイルスプロキシ。

  • 使用する技術に応じて、約 100-1000 台のアプリケーションサーバー。

2 - 補完製品と代替製品

Apache、NGINX、Varnish、LVS、Envoy、その他のロードバランサーと HAProxy の関係

HAProxy は、以下に挙げる製品の一部と非常にうまく連携します。そのため、HAProxy に直接関連しないものもここで紹介します。

4.1. Apache HTTP server

Apache は、事実上の標準となっている HTTP サーバーです。非常に充実したモジュール構成のプロジェクトで、ファイル配信と動的コンテンツの両方をサポートします。一部のアプリケーションサーバーのフロントエンドとしても使用でき、リクエストのプロキシやレスポンスのキャッシュも行えます。いずれの用途でも、通常は前段にロードバランサーが必要です。Apache にはさまざまな動作モードがあり、モードによって負荷が異なります。一部のモジュールでは、依然として負荷の大きい prefork モデルが必要なため、接続数が増えるとうまくスケールできません。この場合、HAProxy でサーバーごとの同時接続数を安全な値に制限すると、大きな効果があります。サーバーを大幅に高速化し、そのリソースを確保してアプリケーションで有効に利用できます。

Apache は “mod_rpaf” 拡張を使い、X-Forwarded-For ヘッダーからクライアントのアドレスを取得できます。HAProxy の設定に “option forwardfor” を指定すると、このヘッダーを自動的に付加します。HAProxy はさまざまな DoS 攻撃への耐性が高いため、インターネットに公開する Apache の保護にも役立ちます。

4.2. NGINX

NGINX は、もう一つの事実上の標準 HTTP サーバーです。Apache と同様、幅広い機能を備えています。HAProxy に似たモデルで構築されており、数万の同時接続を問題なく処理できます。付属の PHP FPM を使用する場合など、アプリケーションへのゲートウェイとして使用するときは、前段で接続数を制限して PHP アプリケーションの負荷を軽減すると効果的なことがよくあります。この場合、HAProxy は通常のロードバランサーとしてだけでなく、トラフィックを調整して PHP の混雑を解消し、高速化する役割でも有用です。また、どちらもイベント駆動型のアーキテクチャにより CPU 使用量が非常に小さいため、同じシステムに両方を配置することも容易です。NGINX は HAProxy の PROXY プロトコルを実装しているため、HAProxy から NGINX へクライアントの接続情報を簡単に渡し、アプリケーションに必要な情報をすべて提供できます。大きな静的ファイルの配信では、NGINX の前段にある HAProxy でコンシステントハッシュを使用すると、OS のキャッシュヒット率が改善することを示すベンチマークもあります。実質的に、サーバーノード数に応じた倍率で改善します。

4.3. Varnish

Varnish は、高度なキャッシュ機能を持つリバースプロキシです。Web アプリケーションアクセラレーターと呼ぶのが最も適切でしょう。SSL/TLS は実装せず、自身が最も得意とする処理にすべての CPU サイクルを使うことを目指しています。Varnish も HAProxy の PROXY プロトコルを実装しているため、HAProxy を Varnish の前段に簡単に配置し、SSL オフロードと負荷分散を行いながら、必要なクライアント情報をすべて渡せます。また、Varnish はサーバーから圧縮済みのオブジェクトを受け取った場合、キャッシュからの配信時に展開できますが、圧縮は行いません。バックエンドサーバーが圧縮に対応していなければ、HAProxy で送信データを圧縮できます。ただし、トラフィックが少ない場合を除き、ロードバランサーでの圧縮はあまり勧められません。

複数ノードで大規模なキャッシュ群を構築する場合、HAProxy は URL のコンシステントハッシュでキャッシュノードに負荷を適切に分散し、キャッシュの重複を避けられます。結果として、全キャッシュノードの合計に相当するキャッシュ容量を得られます。また、処理の不要な非常に小さいオブジェクトを HAProxy で短時間キャッシュすると、ネットワークの往復を減らし、HAProxy と Varnish の両ノードの CPU 負荷を軽減できることがあります。これは Varnish がそれらのオブジェクトに処理を加えない場合に限られます。一般に「favicon キャッシュ」と呼ばれる考え方で、不要な下流へのリクエストを相当な割合で減らせることがあります。ただし、他のキャッシュの前段で HAProxy のキャッシュを長時間、具体的には数秒を超えて有効にしないでください。大きな節約効果を得られないまま、トラブルシューティングが大幅に複雑になります。

4.4. 代替製品

Linux Virtual Server(LVS または IPVS)は、Linux カーネルに含まれるレイヤー 4 のロードバランサーです。パケット単位で動作し、TCP と UDP を処理します。レイヤー 7 の知識をまったく持たないため、多くの場合、代替というより補完する製品です。

Pound もよく知られたロードバランサーです。HAProxy より大幅に簡素で機能も少ないものの、非常に基本的な構成であればどちらも利用できます。作者は常にコードの監査しやすさを最優先し、機能数を少なく保つ方針をとっています。スレッドベースのアーキテクチャのため、多数の接続ではスケーラビリティに劣りますが、優れた製品です。

Pen は比較的軽量なロードバランサーです。SSL をサポートし、クライアントの IP アドレスを格納する固定サイズのテーブルで接続先を固定します。パケット指向のモードも備え、Direct Server Return や UDP にもある程度対応します。接続先固定用のテーブルは 2048 エントリーしかなく、小さな負荷を想定しています。

NGINX にもある程度の負荷分散機能がありますが、明らかに主要な機能ではありません。実際のトラフィックを使ってサーバー障害を検出し、負荷分散アルゴリズムも限られ、接続先固定の機能も非常に限定的です。それでも、すでに NGINX を使用している単純な構成では、有用な場合があります。HAProxy と非常によく連携するため、限界に達してから HAProxy を追加しても問題ありません。

Varnish もバックエンドサーバーの負荷分散を行い、実際のヘルスチェックにも対応します。ただし、接続先固定は実装していません。そのため、NGINX と同様、接続先固定が不要であれば、最初はそれだけで十分な場合があります。HAProxy と Varnish は非常によく連携するので、後から HAProxy を追加して機能を補うのも容易です。

3 - ドキュメントとコミュニティ

アップストリームのマニュアル一式、ソースの場所、サポート チャネル、連絡先、ライセンス

1. 利用可能なドキュメント

完全な HAProxy ドキュメントは、次のドキュメントに含まれています。時間を節約し、ニーズに最も正確に応えるために、必ず関連ドキュメントを参照してください。また、これらの文書に回答が記載されているメーリング リストに質問を送信することはご遠慮ください。

  • intro.txt (このドキュメント): 負荷分散の基本、製品としての HAProxy、機能、機能、回避すべき既知のトラップ、OS 固有の制限、入手方法、進化の仕方、すべての既知の修正を確実に実行する方法、更新方法、補完と代替手段について説明します。

  • management.txt : HAProxy の開始方法、実行時の管理方法、複数のノードでの管理方法、およびシームレスなアップグレードの続行方法について説明します。

  • configuration.txt : リファレンス マニュアルには、すべての構成キーワードとそのオプションが詳しく説明されています。構成変更が必要な場合に使用されます。

  • coding-style.txt: これは、プロジェクトにコードを提案したい開発者向けです。コードに採用するスタイルについて説明します。これはそれほど厳密ではなく、すべてのコード ベースがこれを完全に尊重しているわけではありませんが、それからあまりにも逸脱したコントリビュートは拒否されます。

  • proxy-protocol.txt: これは、HAProxy および多くのサードパーティ製品によって実装される PROXY プロトコルの事実上の仕様です。

  • security.txt: セキュリティ問題を報告する方法、および何が脆弱性として認められ、何が脆弱性として認められないのか。

  • README: ソースから HAProxy を構築する方法

5. 連絡先

何かについて開発者またはコミュニティ メンバーに連絡したい場合、通常、メーリング リスト経由で haproxy@formilux.org にメッセージを送信するのが最善の方法です。このリストは公開されており、そのアーカイブも公開されているため、機密情報の開示は避けてください。そこにはさまざまな経験レベルの何千人ものユーザーが存在しており、最も複雑な質問であっても、通常は比較的迅速に最適な回答が見つかります。提案も歓迎です。電子メールに問題があるユーザーのために、http://discourse.haproxy.org/ で Discourse プラットフォームを利用できます。ただし、そこで質問を読む人は少なく、ほとんどの質問は非常に小さなチームによって処理されることに注意してください。いずれにせよ、他の人を助けるために余暇を費やしている人に対しては、辛抱強く敬意を持って接してください。

バグを見つけたと信じていますが、確信が持てない場合は、メーリング リストで報告するのが最善です。バグを見つけたこと、そのブランチでバージョンが最新であること、およびすでに GitHub アカウントを持っていることを確信している場合は、自由に https://github.com/haproxy/haproxy/ に直接アクセスし、入手可能なすべての詳細を記載して問題を提出してください。繰り返しになりますが、これは公開されるものであるため、後で後悔する可能性のある情報を投稿しないように注意してください。 Issue Tracker は非常に長いスレッドとして表示されるため、非常に長いダンプ (数百行以上) を貼り付けることは避け、代わりにダンプを添付してください。

セキュリティ問題が見つかった可能性があると思われる場合は、doc/security.txt. ファイルを参照してください。このファイルでは、HAProxy の脆弱性として何が該当するのか、該当しないのか、および本物の脆弱性を非公開で報告する方法が説明されています。疑わしい問題のほとんどは通常のバグであることが判明し、上記のように報告したほうがよいでしょう。

完全なローカルマニュアルセット

版の由来

4 - 6. キャッシュ

キャッシュの制約と cache および proxy セクションでの設定

HAProxy は、favicon や css などの小さなオブジェクトを対象とするキャッシュを提供します。RAM 上で動作する、最小限の保守で済む簡素なキャッシュです。

キャッシュには全スレッドで共有するメモリ領域を使用し、1kB のブロックに分割します。

オブジェクトが使われなくなると、有効期限にかかわらず、新しいオブジェクトを保存するために削除できます。新しいオブジェクトを割り当てる際は、最も古いオブジェクトから削除します。

キャッシュのキーには、host ヘッダーと URI のハッシュを使用します。

Unix ソケットの “show cache” コマンドでキャッシュの状態を確認できます。詳細は管理ガイドの第 9.3 節 「Unix ソケットコマンド」を参照してください。

キャッシュからオブジェクトを配信すると、ログのサーバー名は “<CACHE>” に置き換えられます。

6.1. 制約

次の場合、キャッシュはオブジェクトを保存せず、配信もしません。

  • レスポンスが 200 ではない場合。

  • レスポンスに Vary ヘッダーがあり、process-vary オプションが無効になっているか、Vary の値に現在未対応のヘッダーが指定されている場合。現時点で対応しているのは accept-encoding、referer、origin のみです。

  • Content-Length とヘッダーサイズの合計が “max-object-size” を超える場合。

  • レスポンスをキャッシュできない場合。

  • レスポンスに明示的な有効期限(Cache-Control の s-maxage または max-age ディレクティブ、または Expires ヘッダー)も、検証子(ETag または Last-Modified ヘッダー)もない場合。

  • process-vary オプションが有効で、現在のレスポンスと同じプライマリーキーのエントリーがすでに max-secondary-entries 個存在する場合。

  • process-vary オプションが有効で、クライアントの accept-encoding ヘッダーによってレスポンスが変化する一方、そのエンコーディングが未知の場合。https://www.iana.org/assignments/http-parameters/http-parameters.xhtml に記載されていないエンコーディングが該当します。

  • リクエストが GET ではない場合。

  • リクエストの HTTP バージョンが 1.1 未満の場合。

  • リクエストに Authorization ヘッダーが含まれる場合。

6.2. 設定

キャッシュを設定するには、cache セクションを定義し、対応する http-request アクションと http-response アクションを使ってプロキシから利用する必要があります。

6.2.1. cache セクション

cache <name>

cache <name>

cache セクションを宣言し、<name> という名前の共有キャッシュメモリを割り当てます。キャッシュサイズの指定は必須です。後述の “total-max-size” キーワードを参照してください。

max-age <seconds>

max-age <seconds>

有効期間の上限を定義します。有効期限には、レスポンスの Cache-Control ヘッダーにある s-maxage、なければ max-age ディレクティブの値と、この設定値のうち小さいほうを使用します。デフォルト値は 60 秒なので、デフォルトではオブジェクトを 60 秒より長くキャッシュできません。

max-object-size <bytes>

max-object-size <bytes>

キャッシュするオブジェクトの最大サイズを定義します。“total-max-size” の半分を超えてはいけません。未設定の場合はキャッシュサイズの 256 分の一になります。“max-object-size” より大きいオブジェクトは、すべてキャッシュされません。

max-secondary-entries <number>

max-secondary-entries <number>

キャッシュ内で同じプライマリーキーを持つセカンダリーエントリーの同時存在数の上限を定義します。Vary のサポートを有効にする必要があります。デフォルト値は 10 で、正の整数を指定してください。

process-vary <on/off>

process-vary <on/off>

Vary ヘッダーの処理を有効または無効にします。無効の場合、このヘッダーを含むレスポンスはキャッシュされません。有効の場合は、すべての受信リクエストについて、リクエストヘッダーの一部から予備的なハッシュを計算し、それを使って各リクエストのセカンダリーキーを構成します(RFC 7234#4.1 参照)。これには CPU コストがかかる場合があります。現在、セカンダリーキーは ‘accept-encoding’、‘referer’、‘origin’ ヘッダーの内容から構成します。RFC では ‘origin’ と ‘referer’ ヘッダーは単一値とされているため、いずれかを複数含むリクエストは不正な形式とみなすべきです。そのようなリクエストではセカンダリーキーを構成せず、キャッシュからレスポンスを配信することもありません。対処方法をサーバーに判断させるためです。デフォルト値は off(無効)です。

total-max-size <megabytes>

total-max-size <megabytes>

RAM 上のキャッシュサイズをメガバイト単位で定義します。この領域は 1kB のブロックに分割され、キャッシュエントリーが使用します。最大値は 4095 です。

6.2.2. proxy セクション

キャッシュを利用する proxy セクションでは、要求されたオブジェクトをキャッシュから検索するため、“http-request” ルールセットに “cache-use” アクションを含める必要があります。また、取得したオブジェクトをキャッシュに保存または更新するため、“http-response” ルールセットに “cache-store” アクションを含める必要があります。いずれのアクションにも任意で条件を指定できます。たとえば、キャッシュできないことがわかっているサブディレクトリでは “cache-use” を省略したり、キャッシュする価値がない content-type では “cache-store” を省略したりできます。キャッシュのインデックスキーは “cache-use” アクションの実行時に計算されるため、このアクションを省略すると、レスポンス処理時にキャッシュを更新することもありません。

例:

backend bck1
  mode http

  http-request cache-use foobar
  http-response cache-store foobar
  server srv1 127.0.0.1:80

cache foobar
  total-max-size 4
  max-age 240

5 - 1. 前提条件

UNIX 管理およびトラブルシューティングに関する知識を想定しています

このドキュメントでは、HAProxy の起動、停止、管理、トラブルシューティングの方法について説明します。また、既知の制限や回避すべき落とし穴についても述べています。設定方法については記載していません(詳細は configuration.txt を参照してください)。

このドキュメントでは、読者がUNIX互換のオペレーティングシステムにおける十分な管理スキルを持ち、日常的にシェルを使用し、strace や tcpdump などのトラブルシューティングユーティリティに精通していることを前提としています。

6 - 2. HAProxy のアーキテクチャ

プロセス、スレッド、イベントループ、chroot、ログ、クロック、TCP プロキシモデル

HAProxy は、マルチスレッドで動作するイベント駆動型のノンブロッキングデーモンです。複数の処理の切り替えをシステムのスケジューリングに任せず、イベントの多重化によってすべての処理をスケジュールします。通常は単一プロセスとして動作するため、システム上で “ps aux” を実行すると “haproxy” プロセスは一つだけ表示されます。ただし、ソフトリロード中に、新しいプロセスと並行して古いプロセスが残りの処理を終えている場合は例外です。このため、strace ユーティリティで動作を簡単に追跡できます。利用可能なプロセッサー数に応じて性能を拡張するため、haproxy はデフォルトで、実行を許可されたプロセッサーごとにワーカースレッドを一つ起動します。明示的に別の設定をしない限り、受信トラフィックはこれらすべてのスレッドに分散され、各スレッドは同じイベントループを実行します。ほぼ線形のスケーラビリティを実現するため、スレッド間の依存関係を必要最小限に抑えるよう細心の注意を払っています。その影響の一つとして、各接続は単一のスレッドで処理されます。したがって、利用可能な処理能力をすべて使うには、少なくともスレッド数と同じだけの接続が必要です。この条件は、ほぼ常に満たされます。

HAProxy は起動時に chroot jail 内に自身を隔離し、その中ではファイルシステムに一切アクセスできないよう設計されています。これは、依存するライブラリ、たとえば libc や libssl などにも当てはまります。直接的な影響として、実行中のプロセスは設定ファイルをリロードして変更を適用できません。代わりに、更新済みの設定ファイルを使って新しいプロセスを起動します。わかりにくい影響としては、libc が実行時に参照しようとするタイムゾーンファイルやリゾルバーファイルが見つからなくなる場合があります。ただし、通常これらは起動後には不要なため、この問題は一般には発生しないはずです。この設計の利点は、HAProxy プロセスが完全にステートレスであり、強制終了後のクリーンアップが不要なことです。プロセスを終了できる方法であれば、どの方法でも適切に処理できます。

HAProxy はログファイルを書き込みません。標準の syslog プロトコルを使ってリモートサーバーにログを送信します。このサーバーは、同じシステム上に配置されていることもよくあります。

HAProxy は、システム時刻を基に予期しないずれを補正した内部クロックを使用し、タイムアウトを管理します。poll() でイベントを待つ時間を制限し、実際に経過した時間を測定することで補正します。実際には、一秒を超えて待つことはありません。そのため、完全にアイドル状態のプロセスを strace で追跡すると、二回の gettimeofday() 呼び出しの間に poll() またはその変種が定期的に呼ばれることがわかります。これは正常でまったく無害な動作です。コストも非常に小さく、システム全体では負荷を検出できないほどであり、異常ではありません。例:

16:35:40.002320 gettimeofday({1442759740, 2605}, NULL) = 0
16:35:40.002942 epoll_wait(0, {}, 200, 1000) = 0
16:35:41.007542 gettimeofday({1442759741, 7641}, NULL) = 0
16:35:41.007998 gettimeofday({1442759741, 8114}, NULL) = 0
16:35:41.008391 epoll_wait(0, {}, 200, 1000) = 0
16:35:42.011313 gettimeofday({1442759742, 11411}, NULL) = 0

HAProxy は TCP プロキシであり、ルーターではありません。カーネルが検証した確立済みの接続を扱い、パケットそのものや、他の状態のソケット、たとえば SYN_RECV や TIME_WAIT 状態のソケットを扱うことはありません。ただし、そのようなソケットの存在がポートへのバインドを妨げる場合はあります。受信接続の受け付けと送信接続の開始はシステムに依存します。その直接的な結果として、転送される接続の両側で観測されるパケットには対応関係がなく、サイズ、個数、さらにはアドレスファミリーまで異なる場合があります。接続は LISTEN 状態のソケットからしか受け付けられないため、HAProxy がリッスンしているソケットはすべて、“netstat” ユーティリティでリッスンソケットを表示すれば必ず確認できます。例:

# netstat -ltnp

Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1629/sshd tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2847/haproxy tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 2847/haproxy

7 - 6. メモリ管理

メモリ割り当て、制限、プール、バッファ、プロセスのサイジング

HAProxy は、シンプルかつ高速なプールベースのメモリ管理を使用します。少数の異なるオブジェクト タイプに依存するため、異なるサイズごとに malloc() を呼び出すよりも、適切なサイズのオブジェクトが既に含まれているプールから新しいオブジェクトを選択する方がはるかに効率的です。プールはスタックまたは LIFO として編成されるため、新しく割り当てられたオブジェクトは、CPU キャッシュ内でまだホットな最近リリースされたオブジェクトから取得されます。メモリの断片化を制限するために、同様のサイズのプールがマージされます。

デフォルトでは、パフォーマンスに重点が置かれているため、解放された各オブジェクトは元のプールに戻され、割り当てられたオブジェクトはすぐに再利用されることが予想されるため、解放されることはありません。

CLI では、「show pools」コマンドを使用して、プールでメモリがどのように使用されているかを確認できます。

> show pools
Dumping pools usage. Use SIGQUIT to flush them.
  - Pool cache_st (16 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9ccc40=03 [SHARED]
  - Pool pipe (32 bytes): 5 allocated (160 bytes), 5 used, 0 failures, 2 users, @0x9ccac0=00 [SHARED]
  - Pool comp_state (48 bytes): 3 allocated (144 bytes), 3 used, 0 failures, 5 users, @0x9cccc0=04 [SHARED]
  - Pool filter (64 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 3 users, @0x9ccbc0=02 [SHARED]
  - Pool vars (80 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 2 users, @0x9ccb40=01 [SHARED]
  - Pool uniqueid (128 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 2 users, @0x9cd240=15 [SHARED]
  - Pool task (144 bytes): 55 allocated (7920 bytes), 55 used, 0 failures, 1 users, @0x9cd040=11 [SHARED]
  - Pool session (160 bytes): 1 allocated (160 bytes), 1 used, 0 failures, 1 users, @0x9cd140=13 [SHARED]
  - Pool h2s (208 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 2 users, @0x9ccec0=08 [SHARED]
  - Pool h2c (288 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9cce40=07 [SHARED]
  - Pool spoe_ctx (304 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 2 users, @0x9ccf40=09 [SHARED]
  - Pool connection (400 bytes): 2 allocated (800 bytes), 2 used, 0 failures, 1 users, @0x9cd1c0=14 [SHARED]
  - Pool hdr_idx (416 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9cd340=17 [SHARED]
  - Pool dns_resolut (480 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9ccdc0=06 [SHARED]
  - Pool dns_answer_ (576 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9ccd40=05 [SHARED]
  - Pool stream (960 bytes): 1 allocated (960 bytes), 1 used, 0 failures, 1 users, @0x9cd0c0=12 [SHARED]
  - Pool requri (1024 bytes): 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9cd2c0=16 [SHARED]
  - Pool buffer (8030 bytes): 3 allocated (24090 bytes), 2 used, 0 failures, 1 users, @0x9cd3c0=18 [SHARED]
  - Pool trash (8062 bytes): 1 allocated (8062 bytes), 1 used, 0 failures, 1 users, @0x9cd440=19
Total: 19 pools, 42296 bytes allocated, 34266 used.

プール名は単なる例示であり、このプールを使用する最初のオブジェクト タイプの名前です。括弧内のサイズは、このプール内のオブジェクトのオブジェクト サイズです。オブジェクトのサイズは常に 16 バイトの最も近い倍数に切り上げられます。現在割り当てられているオブジェクトの数とそれに相当するバイト数がレポートされるため、どのプールが最も多くのメモリ使用量を発生させているかを簡単に知ることができます。現在使用中のオブジェクトの数も「used」フィールドにレポートされます。 “allocated” と “used” の差は、オブジェクトが解放され、すぐに使用できるかどうかに対応します。行の末尾のアドレスはプールのアドレスで、次の数字はプール インデックス (存在する場合) であり、インデックスが割り当てられていない場合は -1 として報告されます。

「-m」コマンド ライン オプションに続いてメガバイト数を使用すると、プロセスごとに割り当てられるメモリの量を制限できます。これは、プロセスのアドレス指定可能な空間すべてをカバーするため、スタックだけでなく一部のライブラリで使用されるメモリも含まれますが、リソースに制約のあるシステムを構築する場合には信頼できる制限となります。これがあるシステムでは「ulimit -v」、他のシステムでは「ulimit -d」と同じように機能します。

メモリ制限に達したため、またはシステムに十分なメモリがないためにメモリ割り当てが失敗した場合、HAProxy は、メモリの再割り当てを試みる前に、まずすべてのプールから利用可能なすべてのオブジェクトの解放を開始します。未使用のメモリを解放するこのメカニズムは、HAProxy プロセスに SIGQUIT シグナルを送信することでトリガーできます。

リロード操作中に、グレースフルシャットダウンの状態に切り替わったプロセスは、接続を解放した後に自動的にいくつかのフラッシュを実行するため、すべての可能なメモリが解放されて新しいプロセス用に保存されます。

8 - 8. ロギング

Syslog 統合、起動ログ、実行時ログ、ログのトラブルシューティング

HAProxy はファイル システム アクセスを実行しないため、ロギングに関しては常に syslog サーバーに依存します。標準的な使用方法は、UDP 経由でログ サーバー (デフォルトではポート 514) にログを送信することです。通常、これはローカル syslog デーモンが実行されている 127.0.0.1 に設定されますが、中央サーバーにログを記録するためにネットワーク経由でも使用されます。中央サーバーは、特にログを到着順にマージしておくことが望ましいアクティブ/アクティブ シナリオで追加の利点を提供します。 HAProxy は、UNIX ソケットを使用してログをローカル syslog デーモンに送信することもできますが、HAProxy の実行中に syslog サーバーが再起動されるとソケットが置き換えられ、新しいログが失われるため、これはまったく推奨されません。 HAProxy は chroot ジェイル内に隔離されるため、新しいソケットに再接続する機能はありません。また、UNIX ソケットで使用されているログ バッファが非常に小さいため、負荷が非常に軽い場合でもメッセージが失われる可能性があることが現場で観察されています。ただし、これはテストには問題ありません。

次のディレクティブを「global」セクションに追加して、ファシリティ “local0"を使用して HAProxy ログをローカル デーモンに記録することをお勧めします。

log 127.0.0.1:514 local0

次に、次の行を各"defaults” セクション、または各フロントエンドとバックエンドのセクションに追加します。

log global

このようにして、ログ サーバーの場所のグローバル定義を通じてすべてのログが一元化されます。

一部の syslog デーモンはデフォルトでは UDP トラフィックをリッスンしないため、使用するデーモンに応じて、これを有効にする構文は異なります。

  • sysklogd では、デーモンのコマンド ラインで引数「-r」を渡して、「リモート」ログの UDP ソケットをリッスンする必要があります。アドレス 127.0.0.1 に制限する方法はないので、リモート システムからもログを受信することになることに注意してください。

  • rsyslogd では、次の行を構成ファイルに追加する必要があります。

$ModLoad imudp
$UDPServerAddress *
$UDPServerRun 514
  • syslog-ng では、次の方法で新しいソースを作成できます。その後、「log」ディレクティブの 1 つに有効なソースとして追加する必要があります。
source s_udp {
  udp(ip(127.0.0.1) port(514));
};

詳細については、syslog デーモンのマニュアルを参照してください。システムのログ ファイルにログが見つからない場合は、次のテストを検討してください。

  • haproxy を再起動します。各フロントエンドとバックエンドは、開始を示す 1 行をログに記録します。これらのログが受信された場合は、ログが動作していることを意味します。

  • 「strace -tt -s100 -etrace=sendmsg -p <haproxy’s pid>」を実行し、ログに記録されると予想されるアクティビティを実行します。 sendmsg() を使用して送信されているログ メッセージが表示されるはずです。表示されない場合は、HAProxy 上で strace を使用して再起動します。それでもログが表示されない場合は、構成に何か問題があることを意味します。

  • tcpdump を実行して、ポート 514 を監視します。たとえば、トラフィックがローカルに送信されている場合は、ループバック インターフェイス上で「tcpdump -As0 -ni lo port 514」となります。パケットがそこに見られる場合、それはパケットが送信された証拠であるため、syslogd デーモンのトラブルシューティングが必要です。

トラフィック ログはフロントエンド (受信接続が受け入れられる場所) から送信されますが、ヘルスチェックに続いてサーバー状態の変化を報告するために、バックエンドもログを送信できる必要があります。考えられるすべてのログ設定の詳細については、HAProxy の構成マニュアルを参照してください。

他のデーモンが使用していないファシリティを選択すると便利です。 HAProxy の例では、トラフィック ログには「local0」、管理ログには「local1」が推奨されることがよくあります。これは、これらは実際には表示されないためです。ファシリティは一つでも十分です。個別のログがあるとログ分析には便利ですが、ログには機密情報が含まれる場合があるため、権限のない人に誤って渡される可能性のある他のログと混ぜてはいけないことにも留意することが重要です。

サーバーの容量に大きな影響を与えずに現場でトラブルシューティングを行うには、HAProxy で提供される「halog」ユーティリティを使用することをお勧めします。これは、非常に高速なデータ速度で HAProxy ログ ファイルを処理するように設計された grep に似たユーティリティです。一般的な数値は、1 秒あたり 1 ~ 2 GB のログの範囲です。特定のログのみを抽出し (例: HTTP ステータス コードの一部のクラスの検索、接続終了ステータス、応答時間の範囲による検索、エラーのみの検索)、行数のカウント、出力の行数の制限、および応答時間やエラー数によるサーバーの並べ替え、時間や回数による URL の並べ替え、アクセス数によるクライアント アドレスの並べ替えなどのより高度な統計の実行が可能です。サイト上でループするボットなどの異常をすぐに発見し、ブロックするのは非常に便利です。

9 - 10. より簡単な構成管理

大規模な HAProxy 構成の保守を容易にするためのテクニック

クラスターを構成する 2 つの HAProxy ノードが、少数のアドレスを除いてまったく同じ設定を共有することはよくあります。ノードごとに設定の複製を管理すると、その内容は必然的に食い違っていきます。設定に環境変数を組み込めば、システム全体の環境変数をいくつか変えるだけで、複数の設定でまったく同じファイルを共有できます。この機能はバージョン 1.5 で導入され、当初はアドレスにのみ環境変数を含めることができました。1.6 では対応範囲が広がり、どこでも環境変数を使用できます。構文は UNIX シェルと同じで、変数はドル記号 (’$’)、開き波括弧 (’{’)、変数名、閉じ波括弧 (’}’) の順に記述します。アドレスを除き、環境変数は二重引用符で囲まれた引数の中でのみ解釈されます。これは、ドル記号を含む正規表現を使用する既存の設定を壊さないために必要な制限です。

環境変数を使用すると、アドレスだけが異なる複数の拠点で動作する設定を簡単に記述できます。一部の設定からパスワードを取り除くためにも利用できます。次の例では、起動時に init スクリプトが “site1.env” ファイルを読み込みます。

$ cat site1.env
LISTEN=192.168.1.1
CACHE_PFX=192.168.11
SERVER_PFX=192.168.22
LOGGER=192.168.33.1
STATSLP=admin:pa$$w0rd
ABUSERS=/etc/haproxy/abuse.lst
TIMEOUT=10s

$ cat haproxy.cfg
global
    log "${LOGGER}:514" local0

defaults
    mode http
    timeout client "${TIMEOUT}"
    timeout server "${TIMEOUT}"
    timeout connect 5s

frontend public
    bind "${LISTEN}:80"
    http-request reject if { src -f "${ABUSERS}" }
    stats uri /stats
    stats auth "${STATSLP}"
    use_backend cache if { path_end .jpg .css .ico }
    default_backend server

backend cache
    server cache1 "${CACHE_PFX}.1:18080" check
    server cache2 "${CACHE_PFX}.2:18080" check

backend server
    server cache1 "${SERVER_PFX}.1:8080" check
    server cache2 "${SERVER_PFX}.2:8080" check

10 - 11. 回避すべき既知の落とし穴

回避すべき運用上の誤りと予想外の動作

システムの再起動後に haproxy サービスが起動せず、手動で起動すると動く、という報告をときどき受けます。多くの場合、keepalived などのクラスター IP アドレス管理機構を使い、マスターノードだけにサービスの IP アドレスを割り当てています。haproxy を 0.0.0.0 にバインドしていたときは動作していたものが、仮想 IP アドレスにバインドするように変更すると動かなくなります。これは、サービスの起動時点ではローカルノードが仮想 IP アドレスをまだ所有しておらず、HAProxy がそのアドレスにバインドしようとすると、ローカルの IP アドレスではないためシステムが拒否することが原因です。解決策は haproxy サービスの起動を遅らせることではありません。それでは再起動に対応できません。ローカルに存在しないアドレスへのバインドを許可するよう、システムを正しく設定します。Linux では、net.ipv4.ip_nonlocal_bind sysctl を 1 に設定するだけです。この設定は、特定の宛先アドレスに向けて HAProxy を通過する IP トラフィックを透過的に捕捉する場合にも必要です。

送信元ポートの範囲を使用するマルチプロセス構成は、一見動作しているようでも、高負荷時にランダムな障害を起こします。複数のプロセスが同じ送信元ポートを使って同じサーバーに接続しようとする可能性があり、それはできないためです。システムがエラーを返し、別のポートを選んで再試行します。“retries” パラメーターに大きな値を設定すれば影響をある程度隠せますが、CPU 使用量と処理時間も増加します。ログにも一定数の再試行が記録されます。このため、マルチプロセス構成ではポート範囲の使用を避けてください。

HAProxy は SO_REUSEPORT を使用し、複数の独立したプロセスを同じ IP:port にバインドできます。このため、トラブルシューティング中に古いプロセスを停止しないまま新しいプロセスを起動してしまうことがあります。その結果、設定の変更がすべて無視されているかのような、不可解なテスト結果が得られます。新しいプロセスを新しい設定で再起動しても、古いプロセスが受信接続の一部を処理し、想定外の結果を返すためです。疑わしい場合は新しいプロセスを停止し、もう一度試してください。それでも動作するなら、古いプロセスが残っていて停止する必要がある可能性が非常に高いと考えられます。Linux の “netstat -lntp” が調査に役立ちます。

コマンドラインから ACL にエントリーを追加する場合、たとえば送信元アドレスをブラックリストに登録する場合は、これらのエントリーがファイルに同期されないことに注意してください。誰かが設定をリロードすると、更新内容は失われます。ブラックリストへの登録ではこの動作が望ましいことも多いものの、問題を修正するための変更であれば、期待どおりとは限りません。CLI インターフェースの “add acl” アクションを参照してください。

11 - 13. セキュリティに関する考慮事項

権限の分離、攻撃対象領域、Linux capabilities、安全な運用

HAProxy は、非常に限られた権限で動作するよう設計されています。標準的な使用方法は、chroot jail に隔離したうえで、jail 内で何の権限も持たない非 root ユーザーに権限を落とすことです。これにより、将来脆弱性が発見されても、侵害がシステムの他の部分に影響しないようにします。

chroot を実行するには、最初に root ユーザーとして起動する必要があります。手作業で chroot 環境を構築し、その中でプロセスを起動するのは意味がありません。そのような環境の構築は面倒で、適切に保守されることがなく、メインのファイルシステムよりはるかに多くの問題を抱えがちです。侵害された場合、侵入者はその専用のファイルシステムを利用できます。残念ながら、多くの管理者が「root として起動する」ことと「root として動作する」ことを混同し、haproxy の起動前に uid を変更してしまうため、実際のセキュリティ制限が弱くなっています。

HAProxy は、次の処理を行うために root として起動する必要があります。

  • ファイル記述子の上限を調整する。
  • 特権ポート番号にバインドする。
  • 特定のネットワークインターフェースにバインドする。
  • 他のホストのアドレスで透過的にリッスンする。
  • chroot jail 内に自身を隔離する。
  • 別の非特権 UID に切り替える。

次の処理を行うには、HAProxy を root として動作させる必要がある場合があります。

  • 送信接続を特定のインターフェースにバインドする。
  • 送信接続を特権送信元ポートにバインドする。
  • 送信接続を他のホストのアドレスに透過的にバインドする。

ほとんどのユーザーは「root として動作する」必要はありません。一方、ほとんどの用途では「root として起動する」必要があります。

安全な設定には、次の要素を含めます。

  • アクセス権限が一切ない空の場所を指す chroot 文。UNIX のコマンドラインでは、次のように準備できます。
# mkdir /var/empty && chmod 0 /var/empty || echo "Failed"

HAProxy 設定の global セクションでは、次のように参照します。

chroot /var/empty
  • global セクション内の uid/user 文と gid/group 文の両方。
user haproxy
group haproxy
  • CLI へのアクセスを許可するユーザーまたはグループに合わせて mode、uid、gid を設定した stats ソケット。これにより、他のユーザーからのアクセスを防ぎます。
stats socket /var/run/haproxy.stat uid hatop gid hatop mode 600

13.1. Linux capabilities のサポート

バージョン v2.9 以降、haproxy は Linux capabilities をサポートしています。バイナリーが USE_LINUX_CAP=1 でコンパイルされている場合、root ユーザーから非 root ユーザーに切り替える際に、‘setcap’ キーワードで指定された capability を保持できます。

バージョン v3.1 以降では、‘setcap’ キーワードで指定された capability が、管理者によってバイナリーファイルの Permitted セットに設定されているかも確認します(capget システムコール)。設定されていれば、非 root ユーザーとして動作しながら、それらをプロセスの Effective セットに移します(capset システムコール)。

これは、透過プロキシモードや特権ポートへのバインドなど、haproxy を root として起動し、動作させる必要があるすべての用途をなくすための対応です。

‘setcap’ キーワードは、次のネットワーク capability をサポートします。

  • cap_net_admin: 透過プロキシ、特定のネットワークインターフェースへのソケットのバインド、set-mark アクションの使用。
  • cap_net_raw(cap_net_admin のサブセット): 透過プロキシ。
  • cap_net_bind_service: 特定のネットワークインターフェースへのソケットのバインド。
  • cap_sys_admin: 特定のネットワーク名前空間内でのソケットの作成。

HAProxy は、これらの capability が ‘setcap’ の引数に列挙されていない限り、Permitted セットから Effective セットへ移しません。‘setcap’ キーワードとサポートされる capability の詳細は、設定ガイドの第 3.1 章「プロセス管理とセキュリティ」を参照してください。

管理者は、次のコマンドで haproxy バイナリーファイルの Permitted セットに必要な capability を追加できます。

例:

# setcap cap_net_admin,cap_net_bind_service=p /usr/local/sbin/haproxy

追加した capability は、プロセスの起動後にその Permitted セットで確認できます。同じ capability を ‘setcap’ キーワードの引数に指定していれば、プロセスの Effective セットでも確認できる場合があります。次のコマンドで確認できます。

例:

# grep Cap /proc/<haproxy PID>/status
CapInh: 0000000000000000
CapPrm: 0000000000001400
CapEff: 0000000000001400
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000

setcap と capability のセットの詳細は、Linux のマニュアルページ capabilities(7) を参照してください。

透過プロキシや、特定のネットワーク名前空間内でのソケットの作成などの用途では、設定ファイルのパーサーが cap_net_raw、cap_sys_admin、その他のサポート対象 capability が必要であることを検出します。その後、初期化段階で haproxy プロセスが、それらを自身の Effective セットに設定できるか確認します。SELinux や Seccomp などのセキュリティモジュールによるシステムコール制限のため capget や capset が失敗するなど、設定できない場合は、診断用の警告を出力します(-dD を指定して起動します)。

システム設定の異なる多数のプラットフォームをサポートしているため、特権ポートへのバインドが行われるかどうかを、パーサーが設定ファイルだけから判断することはできません。そのため、非 root での動作など権限が不足する場合は、次のようなアラートメッセージを出してプロセスが終了するだけです。設定と haproxy バイナリーの capability セットは、ユーザー自身が再確認する必要があります。

例:

$ haproxy -dD -f haproxy.cfg
...
[ALERT]    (96797): Binding [haproxy.cfg:36] for frontend fe: cannot bind socket (Permission denied) for [0.0.0.0:80]
[ALERT]    (96797): [haproxy.main()] Some protocols failed to start their listeners! Exiting.