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

2 minute read

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は冗長になるので割愛しています。

---
- name: download epel-release
  yum:
    name: https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
    state: present
- name: delete mariadb
  yum:
    name: mariadb-libs
    state: removed
- name: install mysql
  yum:
    name: "{{ item }}"
    state: present
  with_items:
    - mysql-community-devel*
    - mysql-community-server*
    - MySQL-python
- name: copy my.cnf
  copy:
    src: ../files/etc/my.cnf
    dest: /etc/my.cnf
    mode: 0644
- name: enable mysql
  systemd:
    name: mysqld
    state: restarted
    enabled: yes
- name: get root password
  shell: "grep 'A temporary password is generated for [email protected]' /var/log/mysqld.log | awk -F ' ' '{print $(NF)}'"
  register: root_password
- name: update expired root user password
  command: mysql --user root --password={{ root_password.stdout }} --connect-expired-password --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mysql.root.password }}';"
- name: create mysql client user
  mysql_user:
    login_user: root
    login_password: "{{ mysql.root.password }}"
    name: "{{ item.name }}"
    password: "{{ item.password }}"
    priv: '*.*:ALL,GRANT'
    state: present
    host: '%'
  with_items:
    - "{{ mysql.users }}"

ポイント解説

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

mariadb-libsを削除する

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

- name: delete mariadb
  yum:
    name: mariadb-libs
    state: removed

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

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

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


- name: install mysql
  yum:
    name: "{{ item }}"
    state: present
  with_items:
    - mysql-community-devel*
    - mysql-community-server*
    - MySQL-python # これ

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

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

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

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

[mysqld]
default-authentication-plugin=mysql_native_password

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

- name: copy my.cnf
  copy:
    src: ../files/etc/my.cnf
    dest: /etc/my.cnf
    mode: 0644

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

- name: enable mysql
  systemd:
    name: mysqld
    state: restarted
    enabled: yes

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

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

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

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


- name: get root password
  shell: "grep 'A temporary password is generated for [email protected]' /var/log/mysqld.log | awk -F ' ' '{print $(NF)}'"
  register: root_password # これで一回変数登録
- name: update expired root user password
  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自身を操作するような書き方を想定するかもしれません。


-  mysql_user:
    login_user: root
    login_password: "{{ root_password }}"
    name: root
    password: "{{ sometinng new password }}"
    priv: '*.*:ALL,GRANT'
    state: present
    host: '%'

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

unable 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からの接続しか許可されません。


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

まとめ

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

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

Comments