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に対して実施していましたがもっと作業スピードを上げたいところです。