Непонятная идемпотентная установка MySQL Playbook
Я хочу настроить сервер MySQL на AWS, используя Ansible для управления конфигурацией.
Я использую AMI по умолчанию от Amazon (ami-3275ee5b), который использует yum
для управления пакетами.
Когда исполняется Playbook ниже, все идет хорошо. Но когда я запускаю его во второй раз, задача Configure the root credentials
терпит неудачу, потому что старый пароль MySQL больше не соответствует, так как он был обновлен в последний раз, когда я запускал эту книгу.
Это делает Playbook не-идемпотентным, что мне не нравится. Я хочу иметь возможность запускать Playbook столько раз, сколько захочу.
- 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
Каким был бы лучший способ решить эту проблему, а это значит, сделать Idempotent в Playbook? Спасибо заранее!
Ответы
Ответ 1
Несложная версия для безопасной установки MySQL.
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
Шаблоны/корень/my.cnf.j2
[client]
user=root
password={{ mysql_root_password }}
Ссылки
Ответ 2
Я опубликовал об этом на coderwall, но я воспроизведу улучшение dennisjac в комментариях к моему оригинальному сообщению.
Трюк с этим идемпотентным образом означает, что модуль mysql_user загрузит файл ~/.my.cnf, если он найдет его.
Сначала я изменяю пароль, а затем копирую файл .my.cnf с учетными данными пароля. Когда вы попытаетесь запустить его во второй раз, модуль myqsl_user ansible найдет .my.cnf и использует новый пароль.
- 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
Шаблон .my.cnf выглядит следующим образом:
[client]
user=root
password={{ mysql_root_password }}
Изменить: добавлены привилегии, рекомендованные Dhananjay Nene в комментариях, и изменили интерполяцию переменных, чтобы использовать фигурные скобки вместо знака доллара.
Ответ 3
Это альтернативное решение, предложенное @LorinHochStein
Одно из моих ограничений заключалось в том, чтобы гарантировать, что никакие пароли не хранятся в текстовых файлах в любом месте сервера. Таким образом .my.cnf не было практическим предложением
Решение:
- 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
И в файле vars
current_password: foobar
new_password: "{{ current_password }}"
Если вы не изменяете пароль mysql, запустите в командной строке исполняемый файл в командной строке, как обычно.
При изменении пароля mysql добавьте следующее в командную строку. Указание его в командной строке позволяет параметру, установленному в командной строке, иметь приоритет над значением, установленным по умолчанию в файле vars.
$ ansible-playbook ........ --extra-vars "new_password=buzzz"
После запуска команды измените файл vars следующим образом
current_password=buzzz
new_password={{ current_password }}
Ответ 4
Добавляя к предыдущим ответам, я не хотел, чтобы перед выполнением команды был ручной шаг, т.е. я хочу развернуть новый сервер и просто запустить playbook без ручного изменения пароля root в первый раз. Я не верю, что {{mysql_password}} будет работать в первый раз, когда пароль root будет нулевым, потому что mysql_password все равно должен быть определен где-нибудь (если вы не хотите переопределить его с -e).
Итак, я добавил правило для этого, которое игнорируется, если оно терпит неудачу. Это в дополнение к любой другой команде здесь и появляется перед ней.
- 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
Для доступного 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
Ну, это немного осложнилось. Я потратил целый день на это и придумал решение, перечисленное ниже. Ключевым моментом является то, как Ansible устанавливает сервер MySQL. Из документов mysql_user модуль (последнее примечание на странице):
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.
Эта проблема с пустым или пустым паролем была большим сюрпризом.
Роль
---
- 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
Обработчик
---
- name: restart mysql
service: name=mysqld state=restarted
.my.cnf.j2
[client]
user=root
password={{ db_root_password }}
Ответ 7
Далее будет работать (вставьте my.cnf между двумя вызовами mysql_user)
- 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
Я знаю, что это старый вопрос, но я делюсь своей рабочей книгой для тех, кто ее ищет:
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
Важно запустить/перезапустить сервер mysql до установки пароля root. Кроме того, я пробовал все, что было отправлено на этот пост [дата], и обнаружил, что необходимо пройти login_password
и login_user
.
(т.е.). Любое воспроизведение после установки mysql_user
user:root
и password= {{ SOMEPASSWORD }}
, вы должны подключиться с помощью login_password
и login_user
для любого последующего воспроизведения.
Примечание. Ниже приведено with_items
, основанное на том, какие созданные по умолчанию хосты Ansible &/MariaDB.
Пример обеспечения безопасности сервера MariaDB:
---
# '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
Я добавляю свой собственный подход к различным подходам (centos 7).
Переменная mysql_root_password должна храниться в невидимом хранилище (лучше) или передаваться в командной строке (хуже)
- 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 [email protected] 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
Мы потратили много времени на эту проблему. Для MySQL 5.7 и выше мы пришли к выводу, что проще просто игнорировать учетную запись root и устанавливать разрешения для обычного пользователя MySQL.
Причины
- Установить пароль root сложно
- Плагин
unix_socket
auth конфликтует со стандартным плагином auth - Надежная смена пароля root после отключения плагина
unix_socket
практически невозможна - Ansible не очень подходит для атомарного изменения пароля root за один шаг
- Использование обычной учетной записи в целом работает хорошо
Если вы откажетесь от идемпотентности, то сможете заставить ее работать нормально. Однако, поскольку ценностное предложение в том, что идемпотентность возможна, мы обнаруживаем, что разработчики тратят время с неверным предположением.
Само существование опции взлома, такой как check_implicit_admin
начинает намекать, что детерминированная установка MySQL не так проста. Если это действительно детерминистический, не должно быть "проверки", должно быть только "делать".