ytooyamaのブログ

サーバ構築とか、仕事で発見したこととか、趣味のこととかを書いています。

久しぶりにNode.jsを触ってMySQLに繋ごうとしたらえらい時間かかった話

要約

  • これは2021年12月28日時点の情報です。
  • Node.jsのMySQLモジュールはMySQL 8以降のcaching_sha2_passwordに非対応(2.18.1で確認)
  • Node.jsのMySQL2モジュールはMySQL 8以降のcaching_sha2_passwordに対応(しているらしいが手元では動かなかった。2.3.3で確認)
  • Node.jsのMariaDBモジュールは最新のMariaDBでは接続すらできなかった(2.5.5で確認)
  • 以下の設定を行い、Node.jsのユーザーを書き換えることで、MySQL Serverと接続できた
ログイン
$ mysql -uroot -p
ユーザー作成
> CREATE USER 'nodejsuser'@'localhost' IDENTIFIED BY 'my-secure-password';
ユーザーに全権限追加
> GRANT all ON *.* TO 'nodejsuser'@'localhost';
ネイティブパスワードに変更
> ALTER USER 'nodejsuser'@'localhost' IDENTIFIED WITH mysql_native_password BY 'my-secure-password';
権限の変更をデータベースに反映
> FLUSH PRIVILEGES;
> quit
$ node mysql.js
[
  RowDataPacket { id: 1, name: 'Yamada' },
  RowDataPacket { id: 2, name: 'Tanaka' },
  RowDataPacket { id: 3, name: 'Suzuki' }
]

本題

とある検証のため、Node.jsを使うことにしました。 Macで動かすため、Node.jsはHomebrewでサクッと入れることにしました。

$ brew install node@16

Quickstartなどをとりあえずやってみて「そもそもNode.jsってなんだっけ?」というのを思い出し、次にNode.jsからデータベースに接続してみようと思いました。

macOSにはDockerがインストールされているし、サクッとDockerコンテナーで動かそうかなと思いました。 DockerでMySQLサーバーを動かす場合、アーキテクチャーに注意する必要があります。

2019年まででかつ現行で使われているMacはIntelのCPUが実装されています。2020年以降のMacははARMベースのM1アーキテクチャのプロセッサが実装されています。私が現在使っているMacもM1 Macなので、x86しかサポートされていないDocker公式のMySQLイメージは動作しません。 というわけで、マルチアーキテクチャーに対応しているMariaDBイメージを利用しました。

$ docker container run -d --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 33306:3306 docker.io/mariadb:latest

接続はポートマッピングした方のポートで。パスワードは設定したものを。

$ mysql -h 192.168.0.6 -P 33306 -u root -p

その後はテスト用のDBとテーブルを作成し、データを投入します。

> CREATE DATABASE list_app;
> USE list_app;

> CREATE TABLE users (id int, name varchar(10));
> INSERT INTO users VALUES (1,'Yamada');
> INSERT INTO users VALUES (2,'Tanaka');
> INSERT INTO users VALUES (3,'Suzuki');

次に、Node.jsのmysqlモジュールを導入します。

$ npm install mysqljs/mysql

あれ、MariaDBなのにMariaDBモジュール?と思うかもしれません。 その辺は後述します。

次のような感じでmysql.jsを書きます。 単にMySQL上のテーブルを表示するだけのコードです。

const mysql = require('mysql');
const connection = mysql.createConnection({
  host: '192.168.0.6',
  port: '33306',
  user: 'root',
  password: 'my-secret-pw',
  database: 'list_app'
});
 
connection.connect();
 
connection.query('SELECT * FROM users', function (error, results, fields) {
  if (error) throw error;
  console.log(results[0]);
});
 
connection.end();

実行結果はこちらです。

$ node mysql.js  
[
  RowDataPacket { id: 1, name: 'Yamada' },
  RowDataPacket { id: 2, name: 'Tanaka' },
  RowDataPacket { id: 3, name: 'Suzuki' }
]

うまくいきました。ではmariadbモジュールを使った場合はどうなるかというと

$ cat mariadb.js
const mysql = require('mariadb');
...

$ node mariadb.js
/Users/ytooyama/working/nodejs/mariadb.js:10
connection.connect();
           ^

TypeError: connection.connect is not a function
    at Object.<anonymous> (/Users/ytooyama/working/nodejs/mariadb.js:10:12)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

node.jsでMariaDBに接続をしようとすると「TypeError: connection.connect is not a function」って言われます。試しにrequire('mysql');に書き換えてみたら、すんなり動きました。多分MariaDBサーバーが新しすぎてMariaDBモジュールでは対応できないのかな。

ちなみに「MySQL Serverを使えば良いじゃん」と言われそうですが、先に説明したように当初はDockerコンテナーでMySQL Serverを動かそうと思っていたため、M1のMacで動かないのを懸念していました。

hub.docker.com

mysql/mysql-serverというイメージはコミュニティがメンテしているようです。こっちのバージョン8はarm64なDocker for Mac下でも動きます(ただし正式サポートはLinux環境のみ)。

hub.docker.com

試しにmysql/mysql-serverイメージを使い、このように実行して

$ docker container run -d --name some-mysql8 -e MYSQL_USER=root -e MYSQL_ROOT_PASSWORD=my-secret-pw -e MYSQL_PASSWORD=my-secret-pw   -p 53306:3306 docker.io/mysql/mysql-server:latest
$ docker exec -it some-mysql8 mysql -uroot -p

mysql> ALTER USER 'root'@'%' IDENTIFIED BY 'my-secret-pw';

mysql> CREATE DATABASE list_app;
mysql> USE list_app;

mysql> CREATE TABLE users (id int, name varchar(10));
mysql> INSERT INTO users VALUES (1,'Yamada');
mysql> INSERT INTO users VALUES (2,'Tanaka');
mysql> INSERT INTO users VALUES (3,'Suzuki');

接続できるようになったものの

$ mysql -h 192.168.0.6 -P 53306 -u root -p

どうやらnode.jsのMySQLモジュールの問題なのかエラーになっています。

$ node mysql.js 
/Users/ytooyama/working/nodejs/mysql.js:13
  if (error) throw error;
             ^

Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
    at Handshake.Sequence._packetToError (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/protocol/sequences/Sequence.js:47:14)
    at Handshake.ErrorPacket (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/protocol/sequences/Handshake.js:123:18)
    at Protocol._parsePacket (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/protocol/Protocol.js:291:23)
    at Parser._parsePacket (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/protocol/Parser.js:433:10)
    at Parser.write (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/protocol/Parser.js:43:10)
    at Protocol.write (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/protocol/Protocol.js:38:16)
    at Socket.<anonymous> (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/Connection.js:88:28)
    at Socket.<anonymous> (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/Connection.js:526:10)
    at Socket.emit (node:events:390:28)
    at addChunk (node:internal/streams/readable:315:12)
    --------------------
    at Protocol._enqueue (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/protocol/Protocol.js:144:48)
    at Protocol.handshake (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/protocol/Protocol.js:51:23)
    at Connection.connect (/Users/ytooyama/working/nodejs/node_modules/mysql/lib/Connection.js:116:18)
    at Object.<anonymous> (/Users/ytooyama/working/nodejs/mysql.js:10:12)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47 {
  code: 'ER_NOT_SUPPORTED_AUTH_MODE',
  errno: 1251,
  sqlMessage: 'Client does not support authentication protocol requested by server; consider upgrading MySQL client',
  sqlState: '08004',
  fatal: true
}

原因はエラーに書かれている通り、このクライアント(モジュール?)はデフォルトの認証方式をサポートしていないということなんでしょう。 エラーメッセージで検索したら、全世界で同じ問題に頭を抱える人がたくさんいました。

その中で回避策がまとまっていたのでこちらをご紹介。

www.chuken-engineer.com

これによると、二つ回避策があるそうです。

  • mysql_native_passwordに変更する
  • Node MySQL 2に切り替える

まず、2つ目を試すことにしました。 これを入れて

$ npm install mysql2

mysql2モジュールを使うことにする。

const mysql = require('mysql2');
...

この後実行してみるものの、mysqlモジュールと同じエラーが発生しました。 また、別の方法も手元ではうまくいかないようです。ただ、rootユーザーをゆるゆるにする回避策はちょっといけないと私は思います。

mysql> ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'my-secure-password';

新規ユーザー作成時の認証方式をネイティブパスワードに変更する方法もあるようですが、せっかくセキュアな設定がデフォルトなのに危険な設定に切り替えるのもちょっとどうかなと思いました。というわけで、この設定はしない方向で。

[mysqld]
default_authentication_plugin=mysql_native_password

結論

最終的には専用のユーザーを作って、mysql_native_passwordを使うように変更しました。 当然ながらnode.jsのコードに書かれているユーザーなどの情報を変更する必要があります。

ログイン
$ mysql -uroot -p
ユーザー作成
> CREATE USER 'nodejsuser'@'localhost' IDENTIFIED BY 'my-secure-password';
ユーザーに全権限追加
> GRANT all ON *.* TO 'nodejsuser'@'localhost';
ネイティブパスワードに変更
> ALTER USER 'nodejsuser'@'localhost' IDENTIFIED WITH mysql_native_password BY 'my-secure-password';
権限の変更をデータベースに反映
> FLUSH PRIVILEGES;
> quit
$ node mysql.js
[
  RowDataPacket { id: 1, name: 'Yamada' },
  RowDataPacket { id: 2, name: 'Tanaka' },
  RowDataPacket { id: 3, name: 'Suzuki' }
]

蛇足

テストのたびにコンテナーでMySQLだったり他のDBを動かしてましたが、HomebrewでMySQL Serverを入れてしまいました。 ちなみに以下の情報によると、HomebrewでMySQL Serverをインストールすると以下のパスにmy.confが配置されるようです。

stackoverflow.com

まさか、Intel MacとM1 Macでパスが違うとは思わなかったです。30分くらい探しました。そして探した挙句に設定は変えなかったので、30分は無駄な時間でした。いや無駄なことない。無駄なことは...

  • /usr/local/etc/my.cnf for x86 Mac
  • /opt/homebrew/etc/my.cnf for M1 Mac

近日中にコンテナーでMySQL Server 8を動かして同じように動くか試す予定です。

Dockerコンテナーの場合を書きました。

ytooyama.hatenadiary.jp

このブログサイトはJavaScriptを使っていますが、読み込んでいるJavaScriptは全てはてなが提供しているものであり、筆者が設置しているものではありません。