Подтвердить что ты не робот

Непонятная идемпотентная установка 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? Спасибо заранее!

4b9b3361

Ответ 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.

Причины

  1. Установить пароль root сложно
  2. Плагин unix_socket auth конфликтует со стандартным плагином auth
  3. Надежная смена пароля root после отключения плагина unix_socket практически невозможна
  4. Ansible не очень подходит для атомарного изменения пароля root за один шаг
  5. Использование обычной учетной записи в целом работает хорошо

Если вы откажетесь от идемпотентности, то сможете заставить ее работать нормально. Однако, поскольку ценностное предложение в том, что идемпотентность возможна, мы обнаруживаем, что разработчики тратят время с неверным предположением.

Само существование опции взлома, такой как check_implicit_admin начинает намекать, что детерминированная установка MySQL не так проста. Если это действительно детерминистический, не должно быть "проверки", должно быть только "делать".