I want to setup a MySQL server on AWS, using Ansible for the configuration management.
I am using the default AMI from Amazon (ami-3275ee5b), which uses yum
for package management.
When the Playbook below is executed, all goes well. But when I run it for a second time, the task Configure the root credentials
fails, because the old password of MySQL doesn't match anymore, since it has been updated the last time I ran this Playbook.
This makes the Playbook non-idempotent, which I don't like. I want to be able to run the Playbook as many times as I want.
- hosts: staging_mysql
user: ec2-user
sudo: yes
tasks:
- name: Install MySQL
action: yum name=$item
with_items:
- MySQL-python
- mysql
- mysql-server
- name: Start the MySQL service
action: service name=mysqld state=started
- name: Configure the root credentials
action: command mysqladmin -u root -p $mysql_root_password
What would be the best way to solve this, which means make the Playbook idempotent? Thanks in advance!
Adding to the previous answers, I didn't want a manual step before running the command, ie I want to spin up a new server and just run the playbook without having to manually change the root password first time. I don't believe {{ mysql_password }} will work the first time, when root password is null, because mysql_password still has to be defined somewhere (unless you want to override it with -e).
So I added a rule to do that, which is ignored if it fails. This is in addition to, and appears before, any of the other commands here.
Well, this came a bit complicated. I've spent a whole day on this and came up with the solution listed below. The key point is how Ansible installs MySQL server. From the docs of mysql_user module (last note on page):
That issue with blank or null password was a big surprise.
Role:
Handler:
.my.cnf.j2:
I posted about this on coderwall, but I'll reproduce dennisjac's improvement in the comments of my original post.
The trick to doing it idempotently is knowing that the mysql_user module will load a ~/.my.cnf file if it finds one.
I first change the password, then copy a .my.cnf file with the password credentials. When you try to run it a second time, the myqsl_user ansible module will find the .my.cnf and use the new password.
The .my.cnf template looks like this:
Edit: Added privileges as recommended by Dhananjay Nene in the comments, and changed variable interpolation to use braces instead of dollar sign.
We have spent a lot of time on this issue. For MySQL 5.7 and above we concluded it is easier to simply ignore the root account, and set permissions on a regular MySQL user.
Reasons
unix_socket
auth plugin conflicts with the standard auth pluginunix_socket
plugin is almost impossibleIf you abandon idempotency, then you can get it to work fine. However, since the ansible value proposition is that idempotency is possible, we find that developers waste time with the wrong assumption.
The mere existence of a hack option like
check_implicit_admin
starts to hint to us that deterministic MySQL setup is not that easy. If it's actually deterministic, there should be no "check", there should only be "do".It is important to start/re-start the mysql server prior to setting the root password. Also, I had tried everything posted up to this post [date] and discovered it is imperative to pass
login_password
andlogin_user
.(i.e.) Any Plays after setting the
mysql_user
user:root
andpassword= {{ SOMEPASSWORD }}
, you must connect usinglogin_password
andlogin_user
for any subsequent play.Note: The
with_items
below is based on what Ansible &/ MariaDB default hosts created.Example for Securing a MariaDB Server:
I'm adding my own take on the various approaches (centos 7).
The variable mysql_root_password should be stored in an ansible-vault (better) or passed on the command-line (worse)