MySQL 8.0.22 の新機能で DNS SRV レコードのサポートというのがあったので試してみた。 https://dev.mysql.com/doc/refman/8.0/en/connecting-using-dns-srv.html
MySQLサーバー3台 (a.example.com, b.example.com, c.example.com)とそれに接続するためのクライアントの計4台を docker-compose で作成する。
Dockerfile
FROM ubuntu RUN apt update RUN apt install -y mysql-client libmysqlclient-dev gcc unbound bind9-dnsutils RUN rm -f /etc/unbound/unbound.conf.d/root-auto-trust-anchor-file.conf COPY entrypoint.sh / RUN chmod +x /entrypoint.sh ENTRYPOINT /entrypoint.sh
docker-compose.yml
services: client: build: . hostname: client volumes: - ./resolv.conf:/etc/resolv.conf - ./unbound-example.conf:/etc/unbound/unbound.conf.d/example.conf - .:/work networks: test: ipv4_address: 192.168.100.100 a: image: mysql:8.0.22 hostname: a networks: test: ipv4_address: 192.168.100.101 environment: - MYSQL_ALLOW_EMPTY_PASSWORD=1 entrypoint: bash -c '/entrypoint.sh mysqld & sleep infinity' b: image: mysql:8.0.22 hostname: b networks: test: ipv4_address: 192.168.100.102 environment: - MYSQL_ALLOW_EMPTY_PASSWORD=1 entrypoint: bash -c '/entrypoint.sh mysqld & sleep infinity' c: image: mysql:8.0.22 hostname: c networks: test: ipv4_address: 192.168.100.103 environment: - MYSQL_ALLOW_EMPTY_PASSWORD=1 entrypoint: bash -c '/entrypoint.sh mysqld & sleep infinity' networks: test: driver: bridge ipam: driver: default config: - subnet: 192.168.100.0/24
unbound-example.conf
server: interface: 127.0.0.1 local-zone: "example.com." static local-data: "a.example.com. IN A 192.168.100.101" local-data: "b.example.com. IN A 192.168.100.102" local-data: "c.example.com. IN A 192.168.100.103" local-data: "_mysql._tcp.example.com. IN SRV 1 0 3306 a.example.com" local-data: "_mysql._tcp.example.com. IN SRV 2 0 3306 b.example.com" local-data: "_mysql._tcp.example.com. IN SRV 3 0 3306 c.example.com"
resolv.conf
nameserver 127.0.0.1
entrypoint.sh
#!/bin/bash unbound sleep infinity
以上のファイルを同じディレクトリに置いて docker-compose
を実行する。
% docker-compose up -d Creating network "m_test" with driver "bridge" Creating m_c_1 ... done Creating m_a_1 ... done Creating m_client_1 ... done Creating m_b_1 ... done % docker-compose exec client bash
現状はこんな感じ。_mysql._tcp.examlpe.com
の SRV レコードの値は優先度が高い順に a.example.com
, b.example.com
, c.example.com
。
root@client:/# dig _mysql._tcp.example.com srv +short 1 0 3306 a.example.com. 2 0 3306 b.example.com. 3 0 3306 c.example.com.
何回実行しても最優先の a に繋がる。
root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' a root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' a root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' a
a の mysqld を落とすと b に繋がる。
root@client:/# mysql -h a.example.com -e shutdown root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' b
b を落とすと c に繋がり、c も落とすとエラーになる。
root@client:/# mysql -h b.example.com -e shutdown root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' c root@client:/# mysql -h c.example.com -e shutdown root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' ERROR 2003 (HY000): Can't connect to MySQL server on 'c.example.com' (111)
次に優先度は同一にして Weight(分散割合)を変更してみる。
unbound-example.conf を次のように変更する。
server: interface: 127.0.0.1 local-zone: "example.com." static local-data: "a.example.com. IN A 192.168.100.101" local-data: "b.example.com. IN A 192.168.100.102" local-data: "c.example.com. IN A 192.168.100.103" local-data: "_mysql._tcp.example.com. IN SRV 1 10 3306 a.example.com" local-data: "_mysql._tcp.example.com. IN SRV 1 20 3306 b.example.com" local-data: "_mysql._tcp.example.com. IN SRV 1 30 3306 c.example.com"
% docker-compose down Stopping m_client_1 ... done Stopping m_a_1 ... done Stopping m_b_1 ... done Stopping m_c_1 ... done Removing m_client_1 ... done Removing m_a_1 ... done Removing m_b_1 ... done Removing m_c_1 ... done Removing network m_test % docker-compose up -d Creating network "m_test" with driver "bridge" Creating m_client_1 ... done Creating m_b_1 ... done Creating m_c_1 ... done Creating m_a_1 ... done % docker-compose exec client bash root@client:/# dig _mysql._tcp.example.com srv +short 1 10 3306 a.example.com. 1 20 3306 b.example.com. 1 30 3306 c.example.com.
何回実行しても c に繋がる。
root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' c root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' c root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' c root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' c root@client:/# mysql -s -N --dns-srv-name _mysql._tcp.example.com -e 'select @@hostname' c
SRV レコードのどれに接続するのかはプログラムが制御するんだけど、mysql コマンドは実行する度にプロセスが異なるし、前回何に繋いだかなんて覚えてないので、毎回 Weight が一番高い c が選択されるということなんだろう。
というわけで C API で簡単なプログラムを書いてみた。
#include <stdio.h> #include <string.h> #include <mysql/mysql.h> int main(int argc, char *argv[]) { MYSQL my; mysql_init(&my); for (int i = 0; i < 30; i++) { if (mysql_real_connect_dns_srv(&my, "_mysql._tcp.example.com", "root", NULL, NULL, 0) == NULL) goto error; if (mysql_real_query(&my, "select @@hostname", 17) != 0) goto error; MYSQL_RES *res; if ((res = mysql_store_result(&my)) == NULL) goto error; MYSQL_ROW row; while ((row = mysql_fetch_row(res))) { unsigned long *lengths; lengths = mysql_fetch_lengths(res); printf("%.*s\n", (int)lengths[0], row[0]); } mysql_close(&my); } exit(0); error: puts(mysql_error(&my)); exit(1); }
同じプロセス内で30回接続している。
root@client:/work# gcc test.c -lmysqlclient root@client:/work# ./a.out c b c c c b b c b c b c b b c c c c a c a b a c a b a a c b root@client:/work# ./a.out | sort | uniq -c 6 a 10 b 14 c
なんとなく Weight に応じた割合で接続先が選ばれてる感じになった。
SRV レコードの話。
SRV レコードは MX レコードを汎用化したようなもので、レコード名の先頭はサービスとプロトコル(TCP, UDP)。
RFC6186では次のような例が示されてる。
_submission._tcp SRV 0 1 587 mail.example.com. _imap._tcp SRV 0 1 143 imap.example.com. _imaps._tcp SRV 0 1 993 imap.example.com. _pop3._tcp SRV 0 1 110 pop3.example.com. _pop3s._tcp SRV 0 1 995 pop3.example.com.
SRV レコードに対応したメールアプリは example.com
というドメイン名さえ知っていれば SMTP/POP/IMAP の接続先を DNS で得ることができる。
というような感じなんで、MySQL の場合はレコード名の先頭は _mysql._tcp
固定なのでわざわざ指定する必要はないはずなんだけど、なんで指定させてるんだろう。mysql_real_connect_dns_srv()
関数の中で _mysql._tcp
を先頭につければいいのに…。