web-dev-qa-db-fra.com

Ansible idempotent Installation de MySQL Playbook

Je souhaite configurer un serveur MySQL sur AWS, en utilisant Ansible pour la gestion de la configuration . J'utilise l'AMI par défaut d'Amazon ( AMI-3275ee5b ), qui utilise yum pour la gestion des packages.

Lorsque le Playbook ci-dessous est exécuté, tout va bien. Mais lorsque je l'exécute une seconde fois, la tâche Configure the root credentials échoue, car l'ancien mot de passe de MySQL ne correspond plus, car il a été mis à jour la dernière fois que j'ai exécuté ce Playbook.

Cela rend le Playbook non-idempotent, ce que je n'aime pas. Je veux pouvoir exécuter le Playbook autant de fois que je le souhaite.

- 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

Quelle serait la meilleure façon de résoudre ce problème, ce qui signifie que le Playbook serait idempotent? Merci d'avance!

40
ndequeker

Version Ansible pour une installation sécurisée de 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

templates/root/my.cnf.j2

[client]
user=root
password={{ mysql_root_password }}

Références

29
ndequeker

J'ai posté à ce sujet sur coderwall , mais je vais reproduire l'amélioration de dennisjac dans les commentaires de mon message d'origine.

L'astuce pour le faire de façon idoti- cace est de savoir que le module mysql_user chargera un fichier ~/.my.cnf s'il en trouve un.

Je change d'abord le mot de passe, puis copie un fichier .my.cnf avec les informations d'identification du mot de passe. Lorsque vous essayez de l'exécuter une seconde fois, le module myqsl_user ansible trouvera le fichier .my.cnf et utilisera le nouveau mot de passe.

- 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

Le modèle .my.cnf ressemble à ceci:

[client]
user=root
password={{ mysql_root_password }}

Edit: Ajout des privilèges recommandés par Dhananjay Nene dans les commentaires et modification de l'interpolation des variables pour utiliser des accolades au lieu du signe dollar.

35
Lorin Hochstein

C'est une solution alternative à celle proposée par @LorinHochStein

L'une de mes contraintes était de m'assurer qu'aucun mot de passe ne soit stocké dans des fichiers de texte en clair, où que ce soit sur le serveur. Donc. Mon.cnf n'était pas une proposition pratique

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

Et dans le fichier vars

current_password: foobar
new_password: "{{ current_password }}"

Lorsque vous ne modifiez pas le mot de passe mysql, lancez ansible playbook en ligne de commande, comme d’habitude.

Lorsque vous modifiez le mot de passe mysql, ajoutez ce qui suit à la ligne de commande. En le spécifiant sur la ligne de commande, le paramètre défini sur la ligne de commande a priorité sur celui par défaut du fichier vars.

$ ansible-playbook ........ --extra-vars "new_password=buzzz"

Après avoir exécuté la commande, changez le fichier vars comme suit

current_password=buzzz
new_password={{ current_password }}
5
Dhananjay Nene

En ajoutant aux réponses précédentes, je ne voulais pas d’étape manuelle avant d’exécuter la commande, c’est-à-dire que je souhaitais créer un nouveau serveur et lancer le playbook sans avoir à modifier manuellement le mot de passe root pour la première fois. Je ne crois pas que {{mysql_password}} fonctionnera la première fois, lorsque le mot de passe root est nul, car mysql_password doit encore être défini quelque part (à moins que vous ne souhaitiez le remplacer par -e).

J'ai donc ajouté une règle pour le faire, qui est ignorée si elle échoue. Ceci est en plus de, et apparaît avant, l'une des autres commandes ici.

- 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
mahemoff

Pour 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
4
anneb

Eh bien, cela est venu un peu compliqué. J'ai passé une journée entière sur ce sujet et j'ai proposé la solution ci-dessous. Le point clé est la façon dont Ansible installe le serveur MySQL. Depuis la documentation de mysql_user module (dernière note de la 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.

Ce problème avec un mot de passe vide ou nul était une grosse surprise.

Rôle:

---

- 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

Gestionnaire:

---

- name: restart mysql
  service: name=mysqld state=restarted

.my.cnf.j2:

[client]
user=root
password={{ db_root_password }}
3
oblalex

Ce qui suit fonctionnera (insérez my.cnf entre 2 appels 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"
1
smuniyappa

Il est important de démarrer/redémarrer le serveur mysql avant de définir le mot de passe root. De plus, j'avais tout essayé jusqu'à la poste [date] et j'avais découvert qu'il était impératif de transmettre login_password et login_user.

(i.e.) Toute lecture après avoir défini mysql_useruser:root et password= {{ SOMEPASSWORD }}, vous devez vous connecter en utilisant login_password et login_user pour toute lecture ultérieure.

Remarque: Le with_items ci-dessous est basé sur ce que les hôtes par défaut d'Ansible &/MariaDB ont créé.

Exemple de sécurisation d'un serveur 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'
0
user742030

Nous avons passé beaucoup de temps sur cette question. Pour MySQL 5.7 et les versions ultérieures, nous avons conclu qu'il était plus simple d'ignorer le compte root et de définir les autorisations sur un utilisateur MySQL standard.

Les raisons

  1. La définition du mot de passe root est difficile
  2. unix_socket auth plugin est en conflit avec le plugin auth standard
  3. Changer le mot de passe root de manière fiable après la désactivation du plugin unix_socket est presque impossible
  4. Ansible n'est pas bien adapté à la modification atomique du mot de passe root en une seule étape
  5. L'utilisation d'un compte normal fonctionne globalement bien

Si vous abandonnez idempotency, vous pourrez alors le faire fonctionner correctement. Cependant, comme la proposition de valeur ansible est que l’idempotence est possible, nous constatons que les développeurs perdent du temps avec la fausse hypothèse.

La simple existence d’une option de type hack telle que check_implicit_admin commence à nous faire comprendre que la configuration déterministe de MySQL n’est pas si facile. Si c'est réellement déterministe, il ne devrait pas y avoir de "vérification", il devrait y avoir seulement "faire".

0
Ryan

Je sais que c'est une vieille question, mais je partage mon livre de travail avec ceux qui le cherchent

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

mon.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 }}
0
Arbab Nazar

J'ajoute ma propre vision des différentes approches (centos 7).

La variable mysql_root_password devrait être stockée dans un ansible-vault (meilleur) ou passée sur la ligne de commande (pire)

- 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 }}
0
Ben