はじめに
今回は軽量・高性能で現在でも人気のあるプロキシソフトウェア HAProxy に関する話題です。
HAProxy – The Reliable, High Performance TCP/HTTP Load Balancer
データベースを運用していると、システムメンテナンスや問題・障害が発生した時に、特定のデータベースへのアクセスを行わないように制御する必要性が出てくる場合があると思います。
この時、プロキシソフトウェアを使用しているのであれば、当然ながらアプリケーションやデータベースを変更せずにプロキシソフトウェアのみでルーティングの切替え(アクセス制御)を行うことが期待されます。
その際の HAProxy での制御方法について、簡単に紹介したいと思います。
今回確認に用いた環境やバージョンなど
HAProxy サーバ
- Rocky Linux release 8.6 (Green Obsidian)
- HAProxy version 2.6.6-274d1a4 2022/09/22 – https://haproxy.org/
- ソースビルド
MySQL
- Rocky Linux release 8.6 (Green Obsidian)
- MySQL Ver 8.0.30 for Linux on x86_64 (MySQL Community Server – GPL)
- 公式 Yum リポジトリからインストール
バックエンドの変更可能な3つの状態と設定コマンド
HAProxy では、待ち受けおよび接続先グループの設定を frontend
で、接続先サーバの設定を backend
というディレクティブで設定できます。
MySQL レプリケーション(ソース:1台,レプリカ:2台)構成の例となりますが、下記の設定の場合、
- HAProxy 導入サーバの 3306 ポート宛通信はソースサーバへルーティング
- HAProxy 導入サーバの 3307 ポート宛通信は全サーバへルーティング
という制御となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
###################### # FRONTEND ###################### ## For Read/Write frontend mysql-80-r-front-rw bind *:3306 mode tcp default_backend mysql-80-r-back-rw ## For Read Only frontend mysql-80-r-front-ro bind *:3307 mode tcp default_backend mysql-80-r-back-ro ###################### # BACKEND ###################### ## For Read/Write backend mysql-80-r-back-rw mode tcp balance leastconn option mysql-check user haproxy default-server inter 5000 rise 3 fall 3 on-marked-down shutdown-sessions server mysql-80-r1 10.233.135.109:3306 check maxconn 100 ## For Read Only backend mysql-80-r-back-ro mode tcp balance leastconn option mysql-check user haproxy default-server inter 5000 rise 3 fall 3 on-marked-down shutdown-sessions server mysql-80-r1 10.233.135.109:3306 check maxconn 100 weight 1 server mysql-80-r2 10.233.135.62:3306 check maxconn 100 weight 10 server mysql-80-r3 10.233.135.18:3306 check maxconn 100 weight 10 |
実際に HAProxy を稼働させた状態で統計情報からバックエンドの状態を確認してみます。
1 2 3 4 5 6 7 |
# echo "show stat" | socat stdio /var/run/haproxy.sock | cut -d, -f1,2,18,23,37 | \ grep -v '[FRONT,BACK]END' | column -s, -t # pxname svname status chkdown check_status mysql-80-r-back-rw mysql-80-r1 UP 0 L7OK mysql-80-r-back-ro mysql-80-r1 UP 0 L7OK mysql-80-r-back-ro mysql-80-r2 UP 0 L7OK mysql-80-r-back-ro mysql-80-r3 UP 0 L7OK |
正常稼働中ですので、status:UP
になっていますね。
- ※
show stat
コマンドの出力結果を適宜抽出していますが、出力フィールドの詳細については以下の公式ドキュメントのチャプターを参照願います。
HAProxy version 2.6.6-35 – Management Guide | 9.1. CSV format - ※ HAProxy の 管理コマンド(CLI) 実行については、以下の公式ドキュメントのチャプターを参照願います。
HAProxy version 2.6.6-35 – Management Guide | 9.3. Unix Socket commands
HAProxy で変更可能なバックエンドステータスは DOWN
を除くと3つ存在します。
公式ドキュメントの設定コマンドの説明に各ステータスについて記載ありますので引用します。
HAProxy version 2.6.6-35 – Management Guide | 9.3. Unix Socket commands | set server
set server <backend>/<server> state [ ready | drain | maint ]
Force a server’s administrative state to a new state. This can be useful to disable load balancing and/or any traffic to a server.
Setting the state to "ready" puts the server in normal mode, and the command is the equivalent of the "enable server" command.
Setting the state to "maint" disables any traffic to the server as well as any health checks.
This is the equivalent of the "disable server" command.
Setting the mode to "drain" only removes the server from load balancing but still allows it to be checked and to accept new persistent connections.
Changes are propagated to tracking servers if any.
ready
は UP ステータスにすることができ、enable server
と同義です。maint
はすべてのトラフィックとヘルスチェックを無効にし、disable server
と同義です。- 意図的な
DOWN
状態となります。
- 意図的な
drain
はルーティング対象から外されますが、ヘルスチェックや既存のTCP持続的接続はそのまま許可します。
バックエンドの特定のサーバをルーティング対象外に変更するには、ステータスを maint
または drain
に変更することで実現可能となります。
どちらを使うのかは、目的や状況次第で使い分けることになります。
それぞれの挙動を確認してみましょう。
maint(メンテナンス)状態に移行する場合
レプリカサーバ mysql-80-r3 へのルーティングを行わないようにしたいと思います。
(表示が前後しますが) 読み取り専用のトランザクション実行中に MAINT ステータスへの変更を発行しました。
1 2 3 4 5 6 7 8 9 10 11 12 |
[root@haproxy-1 ~]# date '+%F %T';echo "disable server mysql-80-r-back-ro/mysql-80-r3" | \ socat /var/run/haproxy.sock stdio 2022-10-13 17:58:20 [root@haproxy-1 ~]# date '+%F %T';echo "show stat" | socat stdio /var/run/haproxy.sock | \ cut -d, -f1,2,18,23,37 | grep -v '[FRONT,BACK]END' | column -s, -t 2022-10-13 17:58:30 # pxname svname status chkdown check_status mysql-80-r-back-rw mysql-80-r1 UP 0 L7OK mysql-80-r-back-ro mysql-80-r1 UP 0 L7OK mysql-80-r-back-ro mysql-80-r2 UP 0 L7OK mysql-80-r-back-ro mysql-80-r3 MAINT 1 |
SELECT を実行中の処理では、HAProxy 側で MAINT ステータスに変更した直後にコネクション切断エラーが発生しました。
1 2 3 4 5 6 7 8 9 |
# mysql --login-path=app_user_ro -sNe " START TRANSACTION READ ONLY; SELECT NOW(), @@hostname; SELECT NOW(), @@hostname, SLEEP(10); select now(), @@hostname; COMMIT; " 2022-10-13 17:58:15 mysql-80-r3 ERROR 2013 (HY000) at line 4: Lost connection to MySQL server during query |
MAINT ステータスへ変更時、HAProxy ログには以下のように記録されます。
1 |
Oct 13 17:58:20 localhost haproxy[2737]: Server mysql-80-r-back-ro/mysql-80-r3 is going DOWN for maintenance. 2 active and 0 backup servers left. 1 sessions active, 0 requeued, 0 remaining in queue. |
元の通常状態に復帰させるには、set server ... state ready
または enable server
(以下例)を実行します。
1 2 3 |
[root@haproxy-1 ~]# date '+%F %T';echo "enable server mysql-80-r-back-ro/mysql-80-r3" | \ socat /var/run/haproxy.sock stdio 2022-10-13 18:00:14 |
復帰時は HAProxy ログには以下のように記録されます。
1 |
Oct 13 18:00:14 localhost haproxy[2737]: Server mysql-80-r-back-ro/mysql-80-r3 is UP/READY (leaving forced maintenance). |
メンテナンス作業開始前に、既存接続は強制的に切断してもよい、実行中のトランザクションは中断して問題ないという場合は maint
を使用するのがよいでしょう。
drain(ドレイン)状態に移行する場合
今度は mysql-80-r2 をルーティング除外対象としてみます。
前述と同じ読み取り専用トランザクションを実行中に DRAIN ステータスに変更しました。
1 2 3 4 5 6 7 8 9 10 11 12 |
[root@haproxy-1 ~]# date '+%F %T';echo "set server mysql-80-r-back-ro/mysql-80-r2 state drain" | \ socat /var/run/haproxy.sock stdio 2022-10-13 18:00:58 [root@haproxy-1 ~]# date '+%F %T';echo "show stat" | socat stdio /var/run/haproxy.sock | \ cut -d, -f1,2,18,23,37 | grep -v '[FRONT,BACK]END' | column -s, -t 2022-10-13 18:01:02 # pxname svname status chkdown check_status mysql-80-r-back-rw mysql-80-r1 UP 0 L7OK mysql-80-r-back-ro mysql-80-r1 UP 0 L7OK mysql-80-r-back-ro mysql-80-r2 DRAIN 0 L7OK mysql-80-r-back-ro mysql-80-r3 UP 1 L7OK |
DRAIN ステータス変更後も接続断となることなく、結果既存接続のトランザクション処理は正常に完了しました。
1 2 3 4 5 6 7 8 9 10 |
# mysql --login-path=app_user_ro -sNe " START TRANSACTION READ ONLY; SELECT NOW(), @@hostname; SELECT NOW(), @@hostname, SLEEP(10); select now(), @@hostname; COMMIT; " 2022-10-13 18:00:55 mysql-80-r2 2022-10-13 18:00:55 mysql-80-r2 0 2022-10-13 18:01:05 mysql-80-r2 |
このとき新規接続を行ってみると、想定通り以下のエラーで接続失敗します。(アクセス拒否で制御しているのですね)
1 2 |
# mysql --login-path=app_user_ro -sNe "SELECT NOW(),@@hostname" ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 0 |
DRAIN ステータス変更時、HAProxy ログには以下のメッセージが記録されます。
1 |
Oct 13 18:00:58 localhost haproxy[2737]: Server mysql-80-r-back-ro/mysql-80-r2 enters drain state. 2 active and 0 backup servers online. 0 sessions requeued, 0 total in queue. |
ステータスの戻しは maint
の時と同一です。
1 2 3 |
[root@haproxy-1 ~]# date '+%F %T';echo "enable server mysql-80-r-back-ro/mysql-80-r3" | \ socat /var/run/haproxy.sock stdio 2022-10-13 18:00:14 |
復帰すると HAProxy ログには以下のメッセージが記録されます。
1 |
Oct 13 18:04:30 localhost haproxy[2737]: Server mysql-80-r-back-ro/mysql-80-r2 is UP (leaving forced drain). |
今回の例では標準のレプリケーション構成におけるレプリカサーバのルーティングが対象でしたが、
グループレプリケーションのマルチプライマリーモードや Galera Cluster/Percona XtraDB Cluster、NDB Cluster といった、更新処理を複数ノードで分散実行している環境だったり、実行中のトランザクションの完了を待ってから新規接続を受け付けないようにする際に使用するステータスと言えます。
なお、DRAIN のあとに MAINT に移行することも可能です。
注意点としては、手動でステータスを変更している状態で、HAProxy のコンフィグリロードを行ってしまうと元の UP の状態に戻ってしまうということです。
一時的な設定変更をジョブや構成管理ツールなどで自動設定するようにしている場合、ルーティングされないはずのサーバへ接続が行われてしまい一大事になりかねませんので、そのような処理が裏で行われないように気を付けてください。
weight を 0 にする方法
その他の方法として、バックエンドへの接続ロードバランシングの重み(weight
)設定を 0 にすると、DRAIN ステータスと同じ効果を得ることができます。
HAProxy version 2.6.6-35 – Configuration Manual | 5.2. Server and default-server options | weight
(…)
The default weight is 1, and the maximal value is 256. A value of 0 means the server will not participate in load-balancing but will still accept persistent connections.
(…)
weight
は CLI コマンド(set server ... weight 0
または set weight ... 0
)で動的に設定変更できますが、
設定ファイル内の backend
ディレクティブで、特定のサーバのオプションとしても静的に設定することができます。
HAProxy 1.8 以降は シームレスリロード、つまり接続断やダウンタイムを発生させずに設定の更新を反映させることができます。
systemd ベースで起動させる(USE_SYSTEMD=1でビルドしている)場合、-Ws
オプション付与で起動されるのでデフォルトで有効になっています。
1 2 3 |
[root@haproxy-1 ~]# ps -ef | grep haproxy root 746 1 0 10:40 ? 00:00:00 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock haproxy 752 746 0 10:40 ? 00:00:00 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock |
シームレスリロードの詳細については以下の公式ブログ記事をご参照ください。
Announcing HAProxy 2.6 – HAProxy Technologies
Hitless Reloads with HAProxy – HOWTO – HAProxy Technologies
つまり任意の接続先サーバに対してweight 0
と設定ファイルを更新し HAProxy をリロードすることで、DRAIN ステータス同等のルーティング制御を行い、かつ、前述の注意点の回避策として不慮のコンフィグリロードによるステータスのクリアを防ぐことができます。
-
設定ファイルを更新します。(例:mysql-80-r2 の weight を 0 に変更)
12345backend mysql-80-r-back-ro(...)server mysql-80-r1 10.233.135.109:3306 check maxconn 100 weight 1server mysql-80-r2 10.233.135.62:3306 check maxconn 100 weight 0server mysql-80-r3 10.233.135.18:3306 check maxconn 100 weight 10 -
HAProxy のリロードを実行します。
1[root@haproxy-1 ~]# systemctl reload haproxy
まとめ
HAProxy のバックエンド接続のステータスを手動変更することでルーティング制御(遮断)を行う方法を紹介しました。
maint
(メンテナンス)またはdrain
(ドレイン)、それぞれのステータスで既存接続の扱いが異なりますので、メンテナンスや切替え作業時の状況に応じて使い分けていただければと思います。