dockerコンテナからmysqlに接続出来なくて困った話

mysqlの接続でどハマリした話をお届けします
2024.02.29

MySQLに接続するだけの作業に4時間かけた男がいた

エンジニアのくせにMySQLの接続ごときで四苦八苦する奴おりゅ?



です。。

週に一度はなにかしらでドツボにハマッて泣きながらトラブルシューティングしている私ですが、今回はMySQLの接続でどハマリしました。

プライベートサブネットのRDSにローカル開発端末から直接接続したい

プライベートサブネットにあるAmazon RDS for MySQL(以後RDS)にローカルの開発端末からインターネット越しに直接接続したいというご要望がありました。
AWS Systems Manager Session Manager(以後Session Manager)で接続できるEC2がある環境でしたので、Session Managerを介したポートフォワーディングにより接続する方法を試していました。 構成図としては以下のようになります。

なお、Session Managerを介したポートフォワーディングの方法については以下の記事でも詳しく紹介されています。

上記構成図の構築はサクッとでき、Session Managerによるポートフォワーディングセッションも確立したので、「さあ、MySQLでつないでみよう」としたときそれは発生しました。

当時の自分は、開発端末上で以下のようなコマンドを実行しました。

#Session Managerによるポートフォワーディングセッションの確立
$ aws ssm start-session \
--target i-123 \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["testdb.ap-northeast-1.rds.amazonaws.com"],"portNumber":["3306"], "localPortNumber":["13306"]}'&

#mysqlコンテナを使って、コンテナからmysqlクライアントで接続
$ docker run --rm -it --net=host mysql:latest mysql -h localhost --port 13306 -u admin -p
Enter password: 
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

mysqlコンテナを使っているのは、いちいちmysqlクライアントのインストール、アンインストールをせずとも、mysqlクライアントのバージョンを自由に選べるからです。

ポートフォワーディングは正常にできていたので、これでつなげるはず!と思っていたので、エラーになったのは予想外でした。

接続できなかった原因

1つめの間違い

エラーメッセージの「Can't connect to local MySQL server through socket」の文言が気になったので調べていると、どうやらmysqlコマンドの「-h」オプションの指定が怪しいことがわかりました。 MySQL 8.0 リファレンスマニュアルには以下の様に書いてあります。

ホストが指定されていないか、localhost である場合、ローカルホストへの接続が発生します

Unix では、MySQL プログラムは、他のネットワークベースのプログラムと比較して予想とは異なる方法で、ホスト名 localhost を特別に処理: クライアントは Unix ソケットファイルを使用して接続します。

つまり「-h」オプションの引数にlocalhostを指定するとUnixソケット接続を行うということです。
MySQLにはソケット接続とTCP/IP接続の2種類の接続方法がありますが、ソケット接続は同じコンピュータ上のMySQLサーバに接続するときのみ使用できる方法です。

今回はポートフォワーディングを介して別の場所にあるMySQLサーバ(RDS)に接続するため、以下の様に指定する必要がありました。

$ docker run --rm -it --net=host mysql:latest mysql -h 127.0.0.1 --port 13306 -u admin -p
Enter password: 
ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1:13306' (111)

さてこれで接続できるはず、、と思ったのですが、またしてもエラーです。

2つめの間違い

aws ssm start-session コマンドでポートフォワーディングセッションを確立させると以下のようなメッセージが表示されます。

Starting session with SessionId: xxxx-0bb0df6ecca2867f8
Port 13306 opened for sessionId xxxx-0bb0df6ecca2867f8.
Waiting for connections...

さらにポートフォワーディングセッションを使って対象のリモートサーバに接続されると以下のメッセージが追加で出力されます。

Connection accepted for session [xxxx-0bb0df6ecca2867f8]

上記のmysqlコマンドを実行した際に Connection accepted for session のメッセージが出ないことが気になりました。
そこでよくよく考えてみると、今回はmysqlコンテナ経由で実行していることに思い当たりました。つまり、開発端末の127.0.0.1を指定しているつもりが、mysqlコンテナ上の127.0.0.1につなぎにいっていたわけです。 mysqlコンテナ上ではポートフォワーディングセッション用のListenポートは当然存在していないので、「Can't connect」となるわけです。

解決方法

そうとわかれば話は単純です。mysqlコンテナの使用をやめて、開発端末にmysqlクライアントを直接インストールして接続すれば良いのです。
開発端末はmacなので以下のコマンドでmysqlクライアントをインストールしました。

brew install mysql-client

インストールしたあとは以下のコマンドで接続します。

#MySQLはインストールしただけだとpathへの追加がされないので、pathを追加するかフルパスで実行する必要がある
$ /opt/homebrew/opt/mysql-client/bin/mysql -h 127.0.0.1 --port 13306 -u admin -p
Enter password:

Connection accepted for session [xxx-0bb0df6ecca2867f8]
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 37
Server version: 8.0.35 Source distribution

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

これでようやくMySQLへの接続ができました。

解決方法その2

上記の方法で解決出来たので満足していたのですが、この「mysql接続ができなくて4時間溶かした話」を同僚の某天才エンジニアに話したところ、

「コンテナでも接続できたよ」

と言うではありませんか!

そこで、私もmysqlコンテナ経由で接続する方法を調べてみました。

host.docker.internal

上でも記載したように、コンテナ内で、127.0.0.1を指定しても自身のコンテナに接続されてしまいます。そのため、コンテナからローカル開発端末(ホスト)に接続したいなら、ホストを示すIPアドレスに対して通信を行えばよいのです。
Docker Desktopでは host.docker.internal という特殊なホスト名が用意されており、このホスト名はホストのIPアドレスに名前解決されます。

参考:ネットワーク構築機能 — Docker-docs-ja 19.03 ドキュメント

私は開発端末にRancher Desktopを入れてDockerを動かしているのですが、Rancher Desktopでも同じように host.docker.internal が用意されているので以下のコマンドでホストのポートフォワーディングセッションに接続することができます。

$ docker run --rm -it --net=host mysql:latest mysql -h host.docker.internal --port 13306 -u admin -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 40
Server version: 8.0.35 Source distribution

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

まとめ

以上となります。
MySQLへの接続に苦しまされた悔しさから勢いで記事を書きましたが、色々と勉強になりました。

この記事が誰かのお役に立てばなによりです。
以上、とーちでした。

参考