可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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!
回答1:
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.
- 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
# 'localhost' needs to be the last item for idempotency, see
# http://ansible.cc/docs/modules.html#mysql-user
- name: update mysql root password for all root accounts
mysql_user: name=root host={{ item }} password={{ mysql_root_password }} priv=*.*:ALL,GRANT
with_items:
- "{{ ansible_hostname }}"
- 127.0.0.1
- ::1
- localhost
- name: copy .my.cnf file with root password credentials
template: src=templates/root/.my.cnf dest=/root/.my.cnf owner=root mode=0600
The .my.cnf template looks like this:
[client]
user=root
password={{ mysql_root_password }}
Edit: Added privileges as recommended by Dhananjay Nene in the comments, and changed variable interpolation to use braces instead of dollar sign.
回答2:
Ansible version for a secure MySQL installation.
mysql_secure_installation.yml
- 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
# 'localhost' needs to be the last item for idempotency, see
# http://ansible.cc/docs/modules.html#mysql-user
- name: update mysql root password for all root accounts
mysql_user: name=root host={{ item }} password={{ mysql_root_password }}
with_items:
- "{{ ansible_hostname }}"
- 127.0.0.1
- ::1
- localhost
- name: copy .my.cnf file with root password credentials
template: src=templates/root/my.cnf.j2 dest=/root/.my.cnf owner=root mode=0600
- name: delete anonymous MySQL server user for $server_hostname
action: mysql_user user="" host="{{ server_hostname }}" state="absent"
- name: delete anonymous MySQL server user for localhost
action: mysql_user user="" state="absent"
- name: remove the MySQL test database
action: mysql_db db=test state=absent
templates/root/my.cnf.j2
[client]
user=root
password={{ mysql_root_password }}
References
- The original answer by Lorin Hochstein
- https://github.com/gaspaio/ansible-devbox/blob/master/roles/mysql/tasks/server.yml
回答3:
This is an alternative solution to the one proposed by @LorinHochStein
One of my constraints was to ensure that no passwords are stored in plain text files anywhere on the server. Thus .my.cnf was not a practical proposition
Solution :
- name: update mysql root password for all root accounts from local servers
mysql_user: login_user=root
login_password={{ current_password }}
name=root
host=$item
password={{ new_password }}
priv=*.*:ALL,GRANT
with_items:
- $ansible_hostname
- 127.0.0.1
- ::1
- localhost
And in the vars file
current_password: foobar
new_password: "{{ current_password }}"
When not changing the mysql password run ansible playbook on command line as usual.
When changing the mysql password, add the following to the command line. Specifying it on the commandline allows the parameter set on the command line to take precedence over the one defaulted to in the vars file.
$ ansible-playbook ........ --extra-vars "new_password=buzzz"
After running the command change the vars file as follows
current_password=buzzz
new_password={{ current_password }}
回答4:
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.
- name: Change root user password on first run
mysql_user: login_user=root
login_password=''
name=root
password={{ mysql_root_password }}
priv=*.*:ALL,GRANT
host={{ item }}
with_items:
- $ansible_hostname
- 127.0.0.1
- ::1
- localhost
ignore_errors: true
回答5:
For ansible 1.3+ :
- name: ensure mysql local root password is zwx123
mysql_user: check_implicit_admin=True login_user=root login_password="zwx123" name=root password="zwx123" state=present
回答6:
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):
MySQL server installs with default login_user of ‘root’ and no password. To secure this user as part of an idempotent playbook, you must create at least two tasks: the first must change the root user’s password, without providing any login_user/login_password details. The second must drop a ~/.my.cnf file containing the new root credentials. Subsequent runs of the playbook will then succeed by reading the new credentials from the file.
That issue with blank or null password was a big surprise.
Role:
---
- name: Install MySQL packages
sudo: yes
yum: name={{ item }} state=present
with_items:
- mysql
- mysql-server
- MySQL-python
- name: Start MySQL service
sudo: yes
service: name=mysqld state=started enabled=true
- name: Update MySQL root password for root account
sudo: yes
mysql_user: name=root password={{ db_root_password }} priv=*.*:ALL,GRANT
- name: Create .my.cnf file with root password credentials
sudo: yes
template: src=.my.cnf.j2 dest=/root/.my.cnf owner=root group=root mode=0600
notify:
- restart mysql
- name: Create a database
sudo: yes
mysql_db: name={{ db_name }}
collation=utf8_general_ci
encoding=utf8
state=present
- name: Create a database user
sudo: yes
mysql_user: name={{ db_user }}
password={{ db_user_password }}
priv="{{ db_name }}.*:ALL"
host=localhost
state=present
Handler:
---
- name: restart mysql
service: name=mysqld state=restarted
.my.cnf.j2:
[client]
user=root
password={{ db_root_password }}
回答7:
The following will Work (Insert my.cnf in between 2 mysql_user calls)
- name: 'Install MySQL'
yum: name={{ item }} state=present
with_items:
- MySQL-python
- mysql
- mysql-server
notify:
- restart-mysql
- name: 'Start Mysql Service'
action: service name=mysqld state=started enabled=yes
- name: 'Update Mysql Root Password'
mysql_user: name=root host=localhost password={{ mysql_root_password }} state=present
- name: 'Copy Conf file with root password credentials'
template: src=../templates/my.cnf.j2 dest=/root/.my.cnf owner=root mode=0600
- name: 'Update Rest-Mysql Root Password'
mysql_user: name=root host={{ item }} password={{ mysql_root_password }} state=present
with_items:
- "{{ ansible_hostname }}"
- "{{ ansible_eth0.ipv4.address }}"
- 127.0.0.1
- ::1
- name: 'Delete anonymous MySQL server user from server'
mysql_user: name="" host={{ ansible_hostname }} state="absent"
回答8:
I know this is an old question, but I am sharing my working playbook for those, who are looking for it:
mysql.yml
---
- name: Install the MySQL packages
apt: name={{ item }} state=installed update_cache=yes
with_items:
- mysql-server-5.6
- mysql-client-5.6
- python-mysqldb
- libmysqlclient-dev
- name: Copy the configuration file (my.cnf)
template: src=my.cnf.j2 dest=/etc/mysql/my.cnf
notify:
- Restart MySQL
- name: Update MySQL root password for all root accounts
mysql_user: name=root host={{ item }} password={{ mysql_root_pass }} state=present
with_items:
- "{{ ansible_hostname }}"
- 127.0.0.1
- ::1
- localhost
- name: Copy the root credentials as .my.cnf file
template: src=root.cnf.j2 dest=~/.my.cnf mode=0600
- name: Ensure Anonymous user(s) are not in the database
mysql_user: name='' host={{ item }} state=absent
with_items:
- localhost
- "{{ ansible_hostname }}"
- name: Remove the test database
mysql_db: name=test state=absent
notify:
- Restart MySQL
vars.yml
---
mysql_port: 3306 #Default is 3306, please change it if you are using non-standard
mysql_bind_address: "127.0.0.1" #Change it to "0.0.0.0",if you want to listen everywhere
mysql_root_pass: mypassword #MySQL Root Password
my.cnf.j2
[client]
port = 3306
socket = /var/run/mysqld/mysqld.sock
[mysqld_safe]
socket = /var/run/mysqld/mysqld.sock
nice = 0
[mysqld]
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = {{ mysql_port }}
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking
bind-address = {{ mysql_bind_address }}
key_buffer = 16M
max_allowed_packet = 64M
thread_stack = 192K
thread_cache_size = 8
myisam-recover = BACKUP
query_cache_limit = 1M
query_cache_size = 16M
log_error = /var/log/mysql/error.log
expire_logs_days = 10
max_binlog_size = 100M
[mysqldump]
quick
quote-names
max_allowed_packet = 64M
[mysql]
[isamchk]
key_buffer = 16M
!includedir /etc/mysql/conf.d/
root.cnf.j2
[client]
user=root
password={{ mysql_root_pass }}
回答9:
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
and login_user
.
(i.e.) Any Plays after setting the mysql_user
user:root
and password= {{ SOMEPASSWORD }}
, you must connect using login_password
and login_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:
---
# 'secure_mariadb.yml'
- name: 'Ensure MariaDB server is started and enabled on boot'
service: name={{ mariadb_service_name }} state=started enabled=yes
# localhost needs to be the last item for idempotency, see
# http://ansible.cc/docs/modules.html#mysql-user
- name: 'Update Mysql Root Password'
mysql_user: name=root
host={{ item }}
password={{ root_db_password }}
priv=*.*:ALL,GRANT
state=present
with_items:
- 127.0.0.1
- ::1
- instance-1 # Created by MariaDB to prevent conflicts between port and sockets if multi-instances running on the same computer.
- localhost
- name: 'Create MariaDB main configuration file'
template: >
src=my.cnf.j2
dest=/etc/mysql/my.cnf
owner=root
group=root
mode=0600
- name: 'Ensure anonymous users are not in the database'
mysql_user: login_user=root
login_password={{ root_db_password }}
name=''
host={{ item }}
state=absent
with_items:
- 127.0.0.1
- localhost
- name: 'Remove the test database'
mysql_db: login_user=root
login_password={{ root_db_password }}
name=test
state=absent
- name: 'Reload privilege tables'
command: 'mysql -ne "{{ item }}"'
with_items:
- FLUSH PRIVILEGES
changed_when: False
- name: 'Ensure MariaDB server is started and enabled on boot'
service: name={{ mariadb_service_name }} state=started enabled=yes
# 'End Of File'
回答10:
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)
- name: "Ensure mariadb packages are installed"
yum: name={{ item }} state="present"
with_items:
- mariadb
- mariadb-server
- name: "Ensure mariadb is running and configured to start at boot"
service: name=mariadb state=started enabled=yes
# idempotently ensure secure mariadb installation --
# - attempts to connect as root user with no password and then set the root@ mysql password for each mysql root user mode.
# - ignore_errors is true because this task will always fail on subsequent runs (as the root user password has been changed from "")
- name: Change root user password on first run, this will only succeed (and only needs to succeed) on first playbook run
mysql_user: login_user=root
login_password=''
name=root
password={{ mysql_root_password }}
priv=*.*:ALL,GRANT
host={{ item }}
with_items:
- "{{ ansible_hostname }}"
- 127.0.0.1
- ::1
- localhost
ignore_errors: true
- name: Ensure the anonymous mysql user ""@{{ansible_hostname}} is deleted
action: mysql_user user="" host="{{ ansible_hostname }}" state="absent" login_user=root login_password={{ mysql_root_password }}
- name: Ensure the anonymous mysql user ""@localhost is deleted
action: mysql_user user="" state="absent" login_user=root login_password={{ sts_ad_password }}
- name: Ensure the mysql test database is deleted
action: mysql_db db=test state=absent login_user=root login_password={{ mysql_root_password }}
回答11:
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
- Setting the root password is difficult
unix_socket
auth plugin conflicts with the standard auth plugin
- Reliably changing the root password after disabling
unix_socket
plugin is almost impossible
- Ansible is not well suited to atomically changing the root password in one step
- Using a normal account broadly works well
If 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".