以前にnginxを使ってTCPのロードバランシングを行いましたが、今度はHAProxyを使って
TCPのロードバランシングを行ってみたいと思います。
nginxのTCPロードバランシングを試す - CLOVER
今回も、お題的にはMySQLのロードバランシングをしてみたいと思います。
HAProxyでMySQLのロードバランシング
nginxの時と同様、今回もMySQLサーバーが次のように3台稼働しているものとします。
- server1 (172.17.0.2)
- server2 (172.17.0.3)
- server3 (172.17.0.4)
特にレプリケーションを組んでいるわけではなく、単純にそれぞれ別個で構築します。
で、別々のサーバーにアクセスしていることがわかるように、以下のようにテーブルを作成してデータを登録します。
-- server1 mysql> CREATE TABLE server(name VARCHAR(10), PRIMARY KEY(name)); mysql> INSERT INTO server VALUES('server1'); -- server2 mysql> CREATE TABLE server(name VARCHAR(10), PRIMARY KEY(name)); mysql> INSERT INTO server VALUES('server2'); -- server3 mysql> CREATE TABLE server(name VARCHAR(10), PRIMARY KEY(name)); mysql> INSERT INTO server VALUES('server3');
MySQL側の準備は以上です。
HAProxyでMySQL向けにロードバランスの設定を行う
今回のHAProxyは、Ubuntu Linuxにapt-getでインストールしたものを使用するのですが、デフォルトの設定ファイルは
以下のような状態になっています。
/etc/haproxy/haproxy.cfg
global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin stats timeout 30s user haproxy group haproxy daemon # Default SSL material locations ca-base /etc/ssl/certs crt-base /etc/ssl/private # Default ciphers to use on SSL-enabled listening sockets. # For more information, see ciphers(1SSL). This list is from: # https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS ssl-default-bind-options no-sslv3 defaults log global mode http option httplog option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http
で、MySQLに対する…要はTCPでのバランシングを行う際にはlistenディレクティブを用いるそうなのですが、
ここにいきなりmode tcpのlistenディレクティブを追記すると警告されます。
listen mysql bind 0.0.0.0:3306 mode tcp balance roundrobin server mysql1 172.17.0.2:3306 server mysql2 172.17.0.3:3306 server mysql3 172.17.0.4:3306
$ sudo service haproxy start * Starting haproxy haproxy [WARNING] 347/175142 (52) : parsing [/etc/haproxy/haproxy.cfg:24] : 'option httplog' not usable with proxy 'mysql' (needs 'mode http'). Falling back to 'option tcplog'. [WARNING] 347/175142 (53) : parsing [/etc/haproxy/haproxy.cfg:24] : 'option httplog' not usable with proxy 'mysql' (needs 'mode http'). Falling back to 'option tcplog'.
直前のdefautsディレクティブがHTTP向けの設定となっているところが、気に入らないようですね。
defaultsディレクティブが再度登場すると、内容がリセットされるそうなので、ここはdefaultsディレクティブごと
再定義してみました。
Proxies
defaults log global mode http option httplog option dontlognull ## 〜省略〜 defaults log global mode tcp option tcplog timeout connect 5000 timeout client 50000 timeout server 50000 listen mysql bind 0.0.0.0:3306 mode tcp balance roundrobin server mysql1 172.17.0.2:3306 server mysql2 172.17.0.3:3306 server mysql3 172.17.0.4:3306
これで、HAProxyを再起動すると、今度は警告されません。
設定の意味ですが、bindでバインドするアドレスとポートの指定、modeでTCP向けに指定し、serverでバックエンドのサーバーを指定します。
バランシングはラウンドロビンです。
serverの直後には名前を設定しますが、今回は「mysql1〜3」としました。
動作確認
それでは、動作確認してみます。確認には、次のようなGroovyスクリプトを使用しました。
current-server.groovy
@Grab('mysql:mysql-connector-java:6.0.5') @GrabConfig(systemClassLoader = true) import groovy.sql.Sql Sql.withInstance('jdbc:mysql://localhost:3306/practice?useUnicode=true&characterEncoding=utf-8&characterSetResults=utf-8&useServerPrepStmts=true&useLocalSessionState=true&elideSetAutoCommits=true&alwaysSendSetIsolation=false&useSSL=false', 'kazuhira', 'password') { sql -> sql.eachRow('SELECT name FROM server') { row -> println(row.name) } }
確認。
$ groovy current-server.groovy server1 $ groovy current-server.groovy server2 $ groovy current-server.groovy server3 $ groovy current-server.groovy server1 $ groovy current-server.groovy server2 $ groovy current-server.groovy server3
ラウンドロビンで振り分けられていそうな感じですね?
ところで、今回の設定ではヘルスチェックが入っていないので、背後にいるMySQLサーバーを落とした後で
アクセスされると、エラーになってしまいます。
$ groovy current-server.groovy Caught: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:590) at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:57) at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:1606) at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:633) at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:347) at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:219) at current-server.run(current-server.groovy:5) Caused by: com.mysql.cj.core.exceptions.CJCommunicationsException: Communications link failure
これでは困りますね、ヘルスチェックを設定してみましょう。
MySQL向けのヘルスチェックを設定する
MySQL向けのヘルスチェックを設定するには、「mysql-check」を使用するとよいみたいです。
listen mysql bind 0.0.0.0:3306 mode tcp option mysql-check user haproxy balance roundrobin server mysql1 172.17.0.2:3306 check port 3306 inter 3000 fall 3 server mysql2 172.17.0.3:3306 check port 3306 inter 3000 fall 3 server mysql3 172.17.0.4:3306 check port 3306 inter 3000 fall 3
「mysql-check」では、接続確認に使用するユーザーを指定します。ここでは、「haproxy」としました。
option mysql-check user haproxy
このユーザーは、MySQL側にも作成しておきます。
mysql> create user haproxy@localhost; Query OK, 0 rows affected (0.00 sec) mysql> create user haproxy@'%'; Query OK, 0 rows affected (0.00 sec)
また、次の設定で各サーバーに3秒ごとにヘルスチェックを行い、3回失敗すると切り離されます。
server mysql1 172.17.0.2:3306 check port 3306 inter 3000 fall 3 server mysql2 172.17.0.3:3306 check port 3306 inter 3000 fall 3 server mysql3 172.17.0.4:3306 check port 3306 inter 3000 fall 3
ここで、1台MySQLを停止すると、切り離されます。
$ groovy current-server.groovy server2 $ groovy current-server.groovy server3 $ groovy current-server.groovy server2 $ groovy current-server.groovy server3
停止していたサーバーを、復帰させるとアクセスできるようになります。
$ groovy current-server.groovy server1 $ groovy current-server.groovy server2 $ groovy current-server.groovy server3
他のサーバーがダウンしないとアクセスしないサーバーを作り出す
今回、せっかく3台MySQLがあるので、2台をレプリケーションのスレーブに見立てて、
1台をマスター、通常はスレーブにのみアクセスしスレーブがすべて停止するとマスターへ
アクセスするような設定を行ってみます。
変更した結果は、こちら。
listen mysql bind 0.0.0.0:3306 mode tcp option mysql-check user haproxy balance roundrobin server mysql1 172.17.0.2:3306 check port 3306 inter 3000 fall 3 backup server mysql2 172.17.0.3:3306 check port 3306 inter 3000 fall 3 server mysql3 172.17.0.4:3306 check port 3306 inter 3000 fall 3
今回は、「mysql1」をマスターと見立てました。なにが変わったかというと、最後に「backup」の設定が
追加されています。
※他の記事では、最後の行にbackupを置いていることが多いみたいですが
server mysql1 172.17.0.2:3306 check port 3306 inter 3000 fall 3 backup
この状態でアクセスすると、「mysql1」にはアクセスが振り分けられなくなります。
$ groovy current-server.groovy server2 $ groovy current-server.groovy server3 $ groovy current-server.groovy server2 $ groovy current-server.groovy server3
その後、「mysql2」と「mysql3」を落としてアクセスすると、「mysql1」にアクセスが
振り分けられるようになります。
$ groovy current-server.groovy server1 $ groovy current-server.groovy server1
OKそうですね。
まとめ
HAProxyで、MySQLに対するTCPロードバランシングを行ってみました。
ヘルスチェックの設定が、ちょっとMySQL寄りのものになってしまいましたが、TCPでもバランシング
できることは確認できたので、良しとしましょう。
参考)
HAProxyを使ってMySQLの負荷分散をする時はmysql-checkのuserオプションを使う – サーバーワークスエンジニアブログ
HAProxyでMySQL slaveの通信を分散する - インフラブログ