GitHub ActionsでMySQL8系の認証方法を変更してRailsから接続する方法

MySQL8では5系から比べると大幅にパフォーマンスが改善されたり、Window関数が使えるようになるなど様々な改善がされています。
ただ、デフォルトの認証方式が`caching_sha2_password`になったため、この認証方式に対応していないRailsからはデフォルト設定のまま接続することができなくなりました。

何も対応せずにRailsからDB接続しようとすると下記のようなエラーが発生します。

```
Mysql2::Error::ConnectionError: Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/mariadb19/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory
```

これを解消するにはMySQLの認証方式を以前の`mysql_native_password`に変更すれば良いです。

開発環境はdocker-composeを使っている場合は下記のようにcommandに`--default-authentication-plugin=mysql_native_password`を記載すれば変更できます。
オプションについては[MySQLの公式ページ](https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_default_authentication_plugin)をご覧ください。

```yaml:docker-compose.yml
    db:
      image: mysql:8.0.23
      command: --default-authentication-plugin=mysql_native_password
      environment:
        MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
      volumes:
        - ./tmp/mysql:/var/lib/mysql
```

ここまではググれば関連記事が大量に出てきたのですが、GitHub ActionsでRailsとMySQL8を接続させる場合には同様のやり方では上手くいかず、ググってもピンポイントな記事が見つからなかったのでこの記事を書くことにしました。

# GitHub Actionsではどうなるのか？

GitHub ActionsでRSpecを動かす場合を考えます。

docker-composeと同じノリで書くと下記のようになります。
[リファレンス](https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions)を見ると、`command`はなくて代わりに`options`というものがあるのでそちらで指定してみます。

```yaml:.github/workflows/ci.yml
name: CI

on: pull_request

jobs:
  rspec:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    env:
      RAILS_ENV: test
      DB_HOST: 127.0.0.1
      DB_PORT: 33060
    services:
      db:
        image: mysql:8.0.23
        options: --default-authentication-plugin=mysql_native_password
        ports:
          - 33060:3306
        env:
          MYSQL_ALLOW_EMPTY_PASSWORD: yes
          BIND-ADDRESS: 0.0.0.0
    steps:
      - uses: actions/checkout@v2.3.4
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1.64.1
        with:
          ruby-version: 3.0.0
          bundler-cache: true
      - name: bundle install
        run: |
          gem install bundler
          bundle install --jobs 4 --retry 3 --path vendor/bundle
      - name: migration
        run: |
          bundle exec rails db:create
          bundle exec rails db:test:prepare
      - name: run rspec
        run: bundle exec rspec
```

これを実行すると下記のエラーが発生します。

```
  /usr/bin/docker create --name 79005cee16264952b54833428bec9736_mysql8023_27d475 --label 442333 --network github_network_cc57110ece5a47fe9679c23b82763cab --network-alias db -p 33060:3306 --default-authentication-plugin=mysql_native_password -e "MYSQL_USER=root" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -e "BIND-ADDRESS=0.0.0.0" -e GITHUB_ACTIONS=true -e CI=true mysql:8.0.23
  unknown flag: --default-authentication-plugin
  See 'docker create --help'.
  Error: Exit code 125 returned from process: file name '/usr/bin/docker', arguments 'create --name 79005cee16264952b54833428bec9736_mysql8023_27d475 --label 442333 --network github_network_cc57110ece5a47fe9679c23b82763cab --network-alias db -p 33060:3306 --default-authentication-plugin=mysql_native_password -e "MYSQL_USER=root" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -e "BIND-ADDRESS=0.0.0.0" -e GITHUB_ACTIONS=true -e CI=true mysql:8.0.23'.
```

`unknown flag: --default-authentication-plugin`と言っています。`--default-authentication-plugin`がわからないみたいですね。

エラーメッセージの直前に実行されたコマンドがあるので見てみると、`--default-authentication-plugin`がmysqlのオプションではなく、dockerのオプションに指定されていることがわかります（リファレンスにもきちんとDockerのオプションと書いてあります）。
dockerコマンドに対して指定してしまっているので、unknownと言われているんですね。

改めて[リファレンス](https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions)を読んでもimage(mysql)に対してオプションを指定する方法は現時点ではないようです。

では、どうするか？
実はMySQLにはもう1つ設定値を外部から指定する方法があります。

# MySQLの設定ファイルを使う

MySQLの設定ファイル`my.conf`を見てみると下記のように外部ファイルを読み込む記載があります。

```conf:/etc/mysql/my.cnf
# Custom config should go here
!includedir /etc/mysql/conf.d/
```

そしてGithub Actionsの[リファレンス](https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions)をみると、serviceにvolumesを指定することでファイルを共有できそうです。

これらの仕組みを使います。

まず追加する設定ファイルを用意します。
my.cnfファイルの書き方は下記をご覧ください。
https://dev.mysql.com/doc/refman/5.6/ja/option-files.html

```conf:mysqlconf.d/custom.cnf
[mysqld]
default_authentication_plugin=mysql_native_password
```

次にvolumesを指定します。
(host側も`mysql/conf.d`にしようとしたのですが、Github Actionsではhost側に`/`が含まれていると正しく実行できなかったので下記のような１階層のディレクトリにしました）

```diff:.github/workflows/ci.yml
    services:
      db:
        image: mysql:8.0.23
-       options: --default-authentication-plugin=mysql_native_password
+       volumes:
+         - mysqlconf.d:/etc/mysql/conf.d
        ports:
          - 33060:3306
        env:
          MYSQL_ALLOW_EMPTY_PASSWORD: yes
          BIND-ADDRESS: 0.0.0.0
```

これらの修正を行うと先ほど失敗していたMySQL Serviceも生成され、DB接続もできるようになりました。

```console
...
Starting db service container

...

/usr/bin/docker create --name c32ec17009124eab8461d555fc75a8e5_mysql8023_2e2c19 --label 442333 --network github_network_d9a9770fae8e43fcb9fc76e256a37c24 --network-alias db -p 33060:3306  -e "MYSQL_USER=root" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -e "BIND-ADDRESS=0.0.0.0" -e GITHUB_ACTIONS=true -e CI=true -v "mysqlconf.d":"/etc/mysql/conf.d" mysql:8.0.23

...

Run bundle exec rails db:create
  bundle exec rails db:create
  bundle exec rails db:test:prepare
  shell: /bin/bash -e {0}
  env:
    RAILS_ENV: test
    DB_HOST: 127.0.0.1
    DB_PORT: 33060
Created database 'app_test'

...
```