2020/09/30

MySQL 8.0.21のDockerイメージがRESTARTステートメントに対応したって話と、そもそもRESTARTステートメントがどうやってmysqldを再起動するのかとって話と

TL;DR

  • RESTART ステートメントの本質は「終了コード16で mysqld を停止」するだけ
    • 親プロセス( mysqld_safe, systemd, dockerd とかとか) が「終了コード16の時はもう一度 mysqld を起動する」という実装をしなければならない
  • RESTART ステートメントの処理の中で、「 SHUTDOWN 権限があるか」「 MYSQLD_PARENT_PID 環境変数がセットされているか」を判定してから、 SIGUSR2 シグナルを使って終了コード16での終了に持っていく
    • 8.0.20までのDockerイメージの entrypoint.shMYSQLD_PARENT_PID を渡していなかったので、その評価のところでfalseになって RESTART はエラーを返していた
    • 8.0.21からの entrypoint.sh はこれを設定するようになったので RESTART ステートメントから「終了コード16で停止」まではわたる。それを起動するには docker run --restart=on-failure にすること(でないと、 RESTART ステートメントが mysqld を停止したところでコンテナが止まって再起動にはならない)
    • 「上位プロセスが mysqld を起動しなおす」ということを押さえておけば、 MYSQLD_PARENT_PID を渡すだけなので汎用性は高そう

RESTART ステートメントを叩き込むと、 mysql_execute_command, Sql_cmd_restart_server::execute, signal_restart_server を通って __pthread_kill でsignel 12(= SIGUSR2 )が呼ばれる。

(gdb) b pthread_kill
+b pthread_kill
Breakpoint 1 at 0x7fcb07702a70: file ../nptl/sysdeps/unix/sysv/linux/pthread_kill.c, line 40.
(gdb) c
+c
(gdb) bt
+bt
#0  __pthread_kill (threadid=140509764310784, signo=signo@entry=12) at ../nptl/sysdeps/unix/sysv/linux/pthread_kill.c:40
#1  0x0000000000dadf0a in signal_restart_server () at /home/yoku0825/mysql-8.0.21/sql/mysqld.cc:2330
#2  0x0000000000efa1bd in Sql_cmd_restart_server::execute(THD*) () at /home/yoku0825/mysql-8.0.21/sql/sql_restart_server.cc:78
#3  0x0000000000ead21f in mysql_execute_command(THD*, bool) () at /home/yoku0825/mysql-8.0.21/sql/sql_parse.cc:4573
#4  0x0000000000eb217f in mysql_parse (thd=thd@entry=0x7fcaac0d0390, parser_state=parser_state@entry=0x7fcafa924510)
    at /home/yoku0825/mysql-8.0.21/sql/sql_parse.cc:5393
#5  0x0000000000eb42c0 in dispatch_command(THD*, COM_DATA const*, enum_server_command) ()
    at /home/yoku0825/mysql-8.0.21/sql/sql_parse.cc:1810
#6  0x0000000000eb5034 in do_command (thd=thd@entry=0x7fcaac0d0390) at /home/yoku0825/mysql-8.0.21/sql/sql_parse.cc:1294
#7  0x0000000000fcd330 in handle_connection (arg=arg@entry=0x62ab490)
    at /home/yoku0825/mysql-8.0.21/sql/conn_handler/connection_handler_per_thread.cc:302
#8  0x000000000260c2be in pfs_spawn_thread (arg=0x62e7040) at /home/yoku0825/mysql-8.0.21/storage/perfschema/pfs.cc:2880
#9  0x00007fcb076fdea5 in start_thread (arg=0x7fcafa925700) at pthread_create.c:307
#10 0x00007fcb05ae18dd in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111

mysqldSIGUSR2 を受け取ると signal_hand_thr_exit_code = MYSQLD_RESTART_EXIT をセットして ( MYSQLD_RESTART_EXITは16 ) 終了する ( case の中で break しないのでそのまま SIGTERM, SIGQUIT と同じ処理に流れる)

たとえば mysqld_safe はこんな風に終了コードを見て、再起動するかそのまま正常終了するかを決めていたりする。

このへんの仕組みを知っていれば、標準以外の環境でも RESTART ステートメントを使えるようにするにできる気がする。

2020/09/24

Debian / UbuntuあたりでMySQLのrootのパスワードが変更できないように見える件

TL;DR

  • mysql-community-serverをインストールした時に rootのパスワードどうする? パスワードなしでUNIX socketベースの認証をするなら空っぽにしておいて と聞かれませんでしたか?

    • これを空っぽにしておくと、 root@localhost が auth_socketプラグインを使うようになってパスワードを一切合切受け付けなくなる

    lqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu Configuring mysql-community-server tqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk

    x Please provide a strong password that will be set for the root account of your MySQL database. Leave it blank to enable   x

    x password less login using UNIX socket based authentication.                                                               x

    x                                                                                                                           x

    x Enter root password:                                                                                                      x

    x                                                                                                                           x

    x _________________________________________________________________________________________________________________________ x

    x                                                                                                                           x

    x                                                          <Ok>                                                             x

    x                                                                                                                           x

    mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj

書きたいことは全部書いてしまった気がするけれど、↑のプロンプトでパスワードを入力しないとauth_socketプラグインを使うように root@localhost が作られるので、「パスワードを変更しても変更後のパスワードでログインできない」ように見えたりする。

現実には、auth_socketプラグインは「クライアントから渡されたパスワードは見ないしサーバー側でもパスワードハッシュを保管していない、 getsockopt でSO_PEERCREDオプションを使うだけ」なので、 ALTER USERSET PASSWORD をどれだけ叩き込んでもそれらは認証には一切使われない。

mysql> SELECT user, host, plugin, authentication_string FROM mysql.user WHERE user = 'root';
+------+-----------+-------------+-----------------------+
| user | host      | plugin      | authentication_string |
+------+-----------+-------------+-----------------------+
| root | localhost | auth_socket |                       |
+------+-----------+-------------+-----------------------+
1 row in set (0.01 sec)

mysql> ALTER USER USER() IDENTIFIED BY 'MySQL8.0';
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT user, host, plugin, authentication_string FROM mysql.user WHERE user = 'root';
+------+-----------+-------------+-----------------------+
| user | host      | plugin      | authentication_string |
+------+-----------+-------------+-----------------------+
| root | localhost | auth_socket |                       |
+------+-----------+-------------+-----------------------+
1 row in set (0.00 sec)

切り捨てるならワーニングくらい出してくれても良いと思うんだけども。。

飽くまで OS上のrootユーザーであるか だけしか判定しないので、 sudo mysql するしかなく、一般ユーザーから mysql -uroot -p ではダメ。そしてOSのrootアカウントならパスワードは見ないのでどんなデタラメなパスワードを打ち込んでも通る。

$ sudo mysql -uroot -phogehogefugafuga -e "SELECT CURRENT_USER()"
mysql: [Warning] Using a password on the command line interface can be insecure.
+----------------+
| CURRENT_USER() |
+----------------+
| root@localhost |
+----------------+

$ sudo mysql -uroot -ppiyopiyo -e "SELECT CURRENT_USER()" ### さっきと違うパスワードでも文句は言われずログインできる
mysql: [Warning] Using a password on the command line interface can be insecure.
+----------------+
| CURRENT_USER() |
+----------------+
| root@localhost |
+----------------+

よく見ずにEnterを叩いたことを反省しながら、 ALTER USER root@localhost IDENTIFIED WITH caching_sha2_password BY 'MySQL 8.0' とかやると、フツーの(?)パスワードを使った認証に切り替えることができる(mysql_native_passwordでもいい

mysql> SELECT user, host, plugin, authentication_string FROM mysql.user WHERE user = 'root';
+------+-----------+-----------------------+------------------------------------------------------------------------+
| user | host      | plugin                | authentication_string                                                  |
+------+-----------+-----------------------+------------------------------------------------------------------------+
| root | localhost | caching_sha2_password | $A$005$s8U/pウ・・邏エ靉オ・籌ネ踵ヌツモネ・ヨキクレョヤ・カ・ヌⅳーク ・
ォュュュュュュォュュュュュュュュュュュォュュュュュュュュュュュュュュュュュュュュュュュォュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュュォ
ア ・鴃 ・ィーョーー 罠

( ´-`).oO(authentication_stringがバイナリだから化けた… print_identified_with_as_hexSHOW CREATE USER の時にしか効かないんだよなぁ…


ところでこれ、Ansibleとか使うとこの対話部分はどう扱われるんでしょうね…?


ちなみに

2020/09/03

mysqlrouterのdisconnect_on_metadata_unavailableはURIに書く

TL;DR

The metadata-cache URI options are:


disconnect_on_metadata_unavailable なるものの存在を知って mysqlrouter.conf に書いてみたけど反映されなくてよく読んだらURIオプションだった。

$ sudo vim /etc/mysqlrouter/mysqlrouter.conf
..
[routing:myRs_ro]
bind_address=0.0.0.0
bind_port=6447
destinations=metadata-cache://myRs/?role=SECONDARY
round-robin-with-fallback
protocol=classic
disconnect_on_promoted_to_primary=yes  ### 間違い!!!
..

↑のようにルーティングセクションにそのまま書くのではなくて、ルーティングセクションの destinationsのURIに 指定する。

$ sudo vim /etc/mysqlrouter/mysqlrouter.conf
..
[routing:myRs_ro]
bind_address=0.0.0.0
bind_port=6447
destinations=metadata-cache://myRs/?role=SECONDARY&disconnect_on_promoted_to_primary=yes  ### ここ!
round-robin-with-fallback
protocol=classic
..

間違った書き方をしてもUnknown variables的なエラーもワーニングも出してくれないのでしばらく悩んでいた。

3306, 3307, 3308がメンバーの実ポートでmysqlrouter経由で接続している場合に3306から3307に rs.setPrimaryInstance でスイッチオーバーした時の切断の有無は以下のとおり。

クライアントから見た接続先 実際の接続先 3306から3307へのsetPrimaryInstance
6446 3306 切断される
6447 3307 disconnect_on_promoted_to_primary依存
6447 3308 切断されない

read-onlyの6447経由で昇格も降格もしていない3308への接続が切断されないのは良いこと。read-writeの6446経由は常に切断される。

6447経由で3307への接続はdisconnect_on_metadata_unavailable=yesなら切断されるしデフォルトのnoなら切断はされない。

コネクションプールのことを考えるとyesが良い気がしますね。

2020/09/01

mysqlrouterに ERROR 2003 (HY000): Can't connect to remote MySQL server for client connected to '0.0.0.0:6446' と言われたら

TL;DR

  • ポートに対応する宛先(デフォルトでは6446はマスター、6447なら全てのスレーブとマスターも(デフォルトだとフォールバックするから))のmysqldが全滅していると、CR_CONN_HOST_ERROR(2003)の後ろのアドレスがmysqlrouterのLISTENポートになる

    • どこが落ちてるのかメッセージからわかりにくいと嘆かないで、「全滅した時だけ」だから
    • 切り分けの一助になれば幸い
  • ただしこの「全滅」は _hidden: true を含む。


フツーに「起動していないmysqld」のIPアドレスとポートを指定して接続できないと、エラーメッセージの後半は指定したIPアドレスが出る。括弧書きの中はOSエラーコード。たとえば111は ECONNREFUSED なのでIPアドレスかポート番号間違ってるよねというのがすぐにわかる。

$ mysql -h127.0.0.1 -P64080
ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (111)

それに対して、宛先を全滅させたmysqlrouterはちょっと違う感じのエラーを返す。エラー番号は同じ2003でもメッセージがちょっと違うしOSエラーコードも出ていない。mysqlコマンドラインクライアントから見た接続先である 127.0.0.1 もエラーメッセージには出てこない。

$ mysql -h127.0.0.1 -P6446
ERROR 2003 (HY000): Can't connect to remote MySQL server for client connected to '0.0.0.0:6446'

mysqlrouterプロセスそのものを止めてしまうと、いつも通り(?)のエラーメッセージになる。

$ sudo systemctl stop mysqlrouter
$ mysql -h127.0.0.1 -P6446
ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (111)

と、このようにエラーメッセージがちょっと違うことをおぼえておくと、mysqlrouterがおかしいのか、宛先のmysqldがおかしいのかの切り分けのチャンスになる鴨。

ちなみにルーティングそのものに失敗したのはmysqlrouter.logには出力されてなかった(今回は全滅させているので、延々(InnoDB RSの)メタデータ取得に失敗したログだけが吐かれている)

2020-09-01 18:49:10 metadata_cache WARNING [7fbc59665700] Failed connecting with Metadata Server 127.0.0.1:3306: Can't connect to MySQL server on '127.0.0.1' (111) (2003)
2020-09-01 18:49:10 metadata_cache WARNING [7fbc59665700] Could not connect to the instance: fd1c9072-ec24-11ea-b2bc-0201965f8d32 on 127.0.0.1:3306
2020-09-01 18:49:10 metadata_cache WARNING [7fbc59665700] Failed connecting with Metadata Server 127.0.0.1:3307: Can't connect to MySQL server on '127.0.0.1' (111) (2003)
2020-09-01 18:49:10 metadata_cache WARNING [7fbc59665700] Could not connect to the instance: 06d3bc79-ec25-11ea-8c8c-0201965f8d32 on 127.0.0.1:3307
2020-09-01 18:49:10 metadata_cache WARNING [7fbc59665700] Failed connecting with Metadata Server 127.0.0.1:3308: Can't connect to MySQL server on '127.0.0.1' (111) (2003)
2020-09-01 18:49:10 metadata_cache WARNING [7fbc59665700] Could not connect to the instance: 135d0639-ec25-11ea-8bbd-0201965f8d32 on 127.0.0.1:3308
2020-09-01 18:49:10 metadata_cache ERROR [7fbc59665700] Failed fetching metadata from any of the 3 metadata servers.

ところで、「クライアントサイドのエラーってクライアントライブラリにハードコーディングで可変部分そんなに多くなかったよね?」と思ったそこのアナタ。アナタは鋭い。


この2003、クライアントサイドエラーの番号を使ってはいるが「mysqlrouterがmysqlコマンドラインクライアントに対して『サーバーサイドエラーパケット』として送り込んでいる」

なのでクライアントの実装によらずに「クライアントライブラリまでエラーメッセージの本文が渡る」であろうことが期待できる。

エラーメッセージの本文を握りつぶすので有名(?)なConnector/Jはどう振る舞うんだろうってちょっと心配になってきた。

2020/08/31

MySQL InnoDB Cluster/ReplicaSet 8.0.21で「mysqlrouterから参照されないように」設定する

TL;DR


まずはフツーにMySQL Shellでサンドボックスを3つばかり作る。

$ mysqlsh -- dba deploySandboxInstance 3306 { --password="" }
$ mysqlsh -- dba deploySandboxInstance 3307 { --password="" }
$ mysqlsh -- dba deploySandboxInstance 3308 { --password="" }

3306のインスタンスで dba.createaReplicaSet('myRs')

$ mysqlsh --uri=root:""@localhost:3306 -- dba createReplicaSet myRs

--replicasetrs に受けてからの rs.addInstance
(このコマンドラインオプションの奇妙な書き方は 日々の覚書: CentOS 7のAMIでEC2を起動してGroup Replicationを組むところまでを何も考えずに をやってる時に見つけた)

$ mysqlsh --uri=root:""@localhost:3306 --replicaset -- rs addInstance root@localhost:3307 { --recoveryMethod=clone }
$ mysqlsh --uri=root:""@localhost:3306 --replicaset -- rs addInstance root@localhost:3308 { --recoveryMethod=clone }

以前にInnoDB Clusterとかを組んだことがあると mysqlrouter.conf に書かれていたりするので一度消してから mysqlrouter --bootstrap

$ sudo rm /etc/mysqlrouter/mysqlrouter.conf
$ sudo mysqlrouter --bootstrap=root@localhost:3306 --user=mysqlrouter
Please enter MySQL password for root:
$ sudo systemctl start mysqlrouter

この状態でRead-Onlyな6447は3307と3308にラウンドロビンされている。

$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3308
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3308
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3308

rs.setInstanceOption(‘127.0.0.1:3308’, ‘tag:_hidden’, true) とやると

$ mysqlsh --uri=root:""@localhost:3306 --replicaset -- rs setInstanceOption '127.0.0.1:3308' 'tag:_hidden' 'true'
WARNING: Using a password on the command line interface can be insecure.
You are connected to a member of replicaset 'myRs'.

ルーターを再起動とかしなくても読み取り分散から外れる。

$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307

これはうまく使えば便利そう。


ただし、_hidden: trueのままでもsetPrimaryInstanceできてしまうし、_hiddenは効いたままなのでその辺はどうなのか感ある。