MySQL 8.0.22 DNS SRV レコードサポート
MySQL 8.0.22 の新機能で DNS SRV レコードのサポートというのがあったので試してみた。
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
を先頭につければいいのに…。
Discussion