MySQL 8のAnsibleハマりポイント(rootのパスワード変更とか)

MySQL 8のAnsibleハマりポイント(rootのパスワード変更とか)
目次

MySQL のメジャーバージョン 8 が 2018/4 にリリースされました。 今回はPacker+Ansibleで MySQL8のAMIを作成しようとして苦労したところをまとめます。

MySQL8のAMIを作りたい

普段、AWSを利用する上ではRDSを使うことが多いので、MySQL5.x系を選択することになります。 今回はMySQL8を使った研修を社内で実施するため、MySQL8のAMIを作る必要がありました。

環境情報

今回は以下のような環境で実施しています。

  • CentOS 7
  • Ansible 2.6.1
  • Packer 1.1.3

playbookのサンプル

先に結論を書きます。 ansible playbookのサンプルは以下のようになりました。

なお、今回はメインのタスク定義の部分だけとし、その他の部分やPackerは冗長になるので割愛しています。

 1---
 2- name: download epel-release
 3  yum:
 4    name: https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
 5    state: present
 6- name: delete mariadb
 7  yum:
 8    name: mariadb-libs
 9    state: removed
10- name: install mysql
11  yum:
12    name: "{{ item }}"
13    state: present
14  with_items:
15    - mysql-community-devel*
16    - mysql-community-server*
17    - MySQL-python
18- name: copy my.cnf
19  copy:
20    src: ../files/etc/my.cnf
21    dest: /etc/my.cnf
22    mode: 0644
23- name: enable mysql
24  systemd:
25    name: mysqld
26    state: restarted
27    enabled: yes
28- name: get root password
29  shell: "grep 'A temporary password is generated for root@localhost' /var/log/mysqld.log | awk -F ' ' '{print $(NF)}'"
30  register: root_password
31- name: update expired root user password
32  command: mysql --user root --password={{ root_password.stdout }} --connect-expired-password --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mysql.root.password }}';"
33- name: create mysql client user
34  mysql_user:
35    login_user: root
36    login_password: "{{ mysql.root.password }}"
37    name: "{{ item.name }}"
38    password: "{{ item.password }}"
39    priv: '*.*:ALL,GRANT'
40    state: present
41    host: '%'
42  with_items:
43    - "{{ mysql.users }}"

ポイント解説

上から順番にポイントを解説していきます。

mariadb-libsを削除する

CentOS 7にデフォルトでインストールされている mariadbのモジュールは削除しましょう。 MySQLインストール時にモジュールの競合を起こしてうまくいきません。

1- name: delete mariadb
2  yum:
3    name: mariadb-libs
4    state: removed

MySQL-pythonをインストールする

ansibleで mysql_user モジュールを使いたい場合には MySQL-python をプロビジョニング対象のサーバにインストールする 必要があります。

なお、 MySQL-python はPython2上でしか動作しない点も注意してください。

1- name: install mysql
2  yum:
3    name: "{{ item }}"
4    state: present
5  with_items:
6    - mysql-community-devel*
7    - mysql-community-server*
8    - MySQL-python # これ

MySQLのデフォルト認証プラグインの変更

MySQL8からセキュリティ強化の目的で、デフォルトの認証プラグインが変更されています。 詳しくは ここ を読んでください。

そのため、以前の認証プラグインに変更するために my.cnf を修正する必要があります。

以下のように default-authentication-plugin=mysql_native_password を追記した my.cnf を準備し、

1[mysqld]
2default-authentication-plugin=mysql_native_password

/etc/my.cnf にコピーしてあげます。

1- name: copy my.cnf
2  copy:
3    src: ../files/etc/my.cnf
4    dest: /etc/my.cnf
5    mode: 0644

変更を反映するために、mysqld を再起動してあげます。

1- name: enable mysql
2  systemd:
3    name: mysqld
4    state: restarted
5    enabled: yes

ログファイルからrootのパスワードを取得して初期化する

これがめんどくさいところでした。

MySQL8はrootの初期パスワードを /var/log/mysqld.log にこっそり出力します。

初期パスワードをログファイルから抽出して変数に登録した後( register )、 mysql コマンドを直で発行して root ユーザのデフォルトパスワードを変更します。

1- name: get root password
2  shell: "grep 'A temporary password is generated for root@localhost' /var/log/mysqld.log | awk -F ' ' '{print $(NF)}'"
3  register: root_password # これで一回変数登録
4- name: update expired root user password
5  command: mysql --user root --password={{ root_password.stdout }} --connect-expired-password --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mysql.root.password }}';"

なぜ mysql_user ではなく command モジュールを使うの? と思うことでしょう。

例えば、以下のように、rootでloginし、root自身を操作するような書き方を想定するかもしれません。

1-  mysql_user:
2    login_user: root
3    login_password: "{{ root_password }}"
4    name: root
5    password: "{{ sometinng new password }}"
6    priv: '*.*:ALL,GRANT'
7    state: present
8    host: '%'

実はこれだと、以下のようなエラーが発生します。

1unable to connect to database, check login_user and login_password are correct or /root/.my.cnf has the credentials

こちらは Ansible の isuue にも報告がされていました。

そのため、少し邪道感はありますが、issueがfixするまでは、mysqlコマンドを直接発行して変更をする、という手段をとります。

データベース接続するユーザを作成する

アプリケーションから接続する時に使うmysqlのユーザを作成します。 操作するユーザ(login_user) を root とし、更新済みのパスワードで接続( login_password )します。

これはMySQL自体の話ですが、 host は接続元のホストを適切に設定してください。今回は研修用途のどうでもいいサーバなので % としています。 逆に host が未設定だと、localhostからの接続しか許可されません。

 1- name: create mysql client user
 2  mysql_user:
 3    login_user: root
 4    login_password: "{{ mysql.root.password }}"
 5    name: "{{ item.name }}"
 6    password: "{{ item.password }}"
 7    priv: '*.*:ALL,GRANT'
 8    state: present
 9    host: '%' # hostを設定しないと、localhostからの接続しか受け付けない
10  with_items:
11    - "{{ mysql.users }}"

まとめ

今回は MySQL8初期化のplaybookのはまりポイントを紹介しました。 文書化すると案外簡素になりましたが、MySQL8による変更点と、Ansibleそのものの振る舞いとを切り分けをしたこともあり、実作業はなかなか時間がかかっています。 Packer+Ansibleのデバッグ効率を上げるために、ローカルのVagrantに対して実施していましたがもっと作業スピードを上げたいところです。

参考にさせていただいたサイト