【问题标题】:Ansible idempotent MySQL installation PlaybookAnsible 幂等 MySQL 安装 Playbook
【发布时间】:2013-05-02 21:32:48
【问题描述】:

我想在 AWS 上设置一个 MySQL 服务器,使用 Ansible 进行配置管理。 我正在使用来自 Amazon (ami-3275ee5b) 的默认 AMI,它使用 yum 进行包管理。

当执行下面的剧本时,一切顺利。但是当我第二次运行它时,任务Configure the root credentials 失败了,因为 MySQL 的旧密码不再匹配,因为它已在我上次运行此 Playbook 时更新。

这使得 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

解决此问题的最佳方法是什么,这意味着使 Playbook 具有幂等性?提前致谢!

【问题讨论】:

  • 下面的答案似乎已经过时了。使用在 Ansible 1.3 中添加到 mysql_user 的“check_implicit_admin”标志。在设置密码时将其设置为 true 对我有用。

标签: mysql command-line amazon-web-services amazon-ec2 ansible


【解决方案1】:

我在 coderwall 上发布了有关此内容的信息,但我将在我原来的帖子的 cmets 中重现 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 在 cmets 中推荐的权限,并将变量插值更改为使用大括号而不是美元符号

【讨论】:

  • 谢谢!我在 Coderwall 上看到了您的帖子,也尝试了您发布的代码。但我得到的输出是:failed: [...] msg: unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials. 我得到了 4 次,每个主机一次。
  • $ansible_hostname 是 Ansible 定义的东西,还是我必须自己添加变量?
  • 谢谢,成功了!我已经通过github.com/lanshark/ansible-server-playbooks/blob/master/… 的一些任务完成了您的回答,再次感谢!
  • @Voles 如果此处的答案不完整,请随时对其进行编辑以添加任何缺失的内容。
  • 我已经编辑了您的帖子,但被拒绝了:'此编辑在原始帖子中更改太多;帖子的原始含义或意图将丢失。'
【解决方案2】:

用于安全 MySQL 安装的 Ansible 版本。

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

模板/root/my.cnf.j2

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

参考文献

【讨论】:

  • 新用户注意事项。如果你得到failed: [...] msg: unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials.,请检查你正在操作的数据库是否存在。
  • http://ansible.cc/docs/modules.html#mysql-user 不再存在。更新链接:http://docs.ansible.com/ansible/mysql_user_module.html.
  • 在添加 login_user=root (在第一次运行时使用此用户,在复制 .my.cnf 之前)并删除项目“{{ ansible_hostname }}”(在我的案例已解决到 localhost 并且与最后一项相同)
  • 我对上述答案提出了编辑,但编辑们装傻了,没有给出任何理由。所以我要把这个写下来给任何阅读的人。对于delete anonymous MySQL server部分,使用host_all="yes"代替host="{{ server_hostname }}",更简单更有效。参考:docs.ansible.com/ansible/latest/modules/mysql_user_module.html
【解决方案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 密码时,像往常一样在命令行上运行 ansible playbook。

修改mysql密码时,在命令行中添加以下内容。在命令行上指定它允许命令行上设置的参数优先于 vars 文件中默认设置的参数。

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

运行命令后修改 vars 文件如下

current_password=buzzz
new_password={{ current_password }}

【讨论】:

  • 这是一个很好的解决方案,但是对于那些 with_items 值,我相信通过 -e 更改密码时第二项会失败。它仍在尝试使用旧密码。但是,如果您在那之后重新运行 ansible,它将起作用。如果你有多个数据库服务器,那就更麻烦了。
【解决方案4】:

以下将起作用(在 2 个 mysql_user 调用之间插入 my.cnf)


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

【讨论】:

    【解决方案5】:

    除了前面的答案之外,我不想在运行命令之前手动执行步骤,即我想启动一个新服务器并运行 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
    

    【讨论】:

    • 感谢分享,它对我有用。 ignore_errors 规则确实有点困扰我,但它是我见过的全自动安装最干净的解决方案。
    • 是的,实际上它可以使用先前的任务(可能是原始命令)进行更新以检查密码状态,例如在 password_blank 中注册。然后你可以在这个任务中添加“when:password_blank”。
    • 前面的答案无需任何手动步骤即可工作,当您安装 mysql 时,它的密码为空。 mysql_user taks 没有 login_password 参数,这意味着它第一次为空(安装后),任务将能够设置新的 root 密码,它将从 .my 中获取。 cnf(已经有新的root密码)其他运行
    【解决方案6】:

    对于 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
    

    【讨论】:

    • 我一直在想,密码真的需要用引号吗?
    • @ihsan - 如果它们包含空格。
    • 感谢check_implicit_admin=True,适用于ansible 2.5.1
    【解决方案7】:

    嗯,这有点复杂。我为此花了一整天的时间,并想出了下面列出的解决方案。关键是 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 }}
    

    【讨论】:

      【解决方案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】:

        在设置 root 密码之前启动/重新启动 mysql 服务器很重要。此外,我已经尝试了在这篇文章 [日期] 之前发布的所有内容,发现必须通过 login_passwordlogin_user

        (即)设置mysql_useruser:rootpassword= {{ SOMEPASSWORD }} 后的任何播放,您必须使用login_passwordlogin_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 应该存储在 ansible-vault 中(更好)或通过命令行传递(更糟)

          - 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】:

            我们在这个问题上花了很多时间。对于 MySQL 5.7 及更高版本,我们得出结论,简单地忽略 root 帐户,并为常规 MySQL 用户设置权限会更容易。

            原因

            1. 设置root密码很困难
            2. unix_socket auth 插件与标准 auth 插件冲突
            3. 在禁用unix_socket 插件后可靠地更改root 密码几乎是不可能的
            4. Ansible 不太适合一步自动更改 root 密码
            5. 使用普通帐户效果很好

            如果你放弃幂等性,那么你可以让它正常工作。然而,由于 ansible 的价值主张是幂等性是可能的,我们发现开发人员会在错误的假设上浪费时间。

            check_implicit_admin 这样的 hack 选项的存在开始向我们暗示,确定性 MySQL 设置并不容易。如果它实际上是确定性的,就不应该有“检查”,应该只有“做”。

            【讨论】:

              【解决方案12】:

              请尝试使用 ansible playbook 安装 mysql

              主机:slave.server.com 变成:是的

              任务:

              • name: "安装仓库" 外壳:sudo rpm -Uvh http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpm 忽略错误:是的

              • 名称:“安装 MySQL 5.7” 包:name=mysql-community-server state=present

              • name: 启动 MySQL 服务 服务: 名称:mysqld 状态:开始 启用:是

              • name: 查找 MySQL root 密码 外壳:“回声grep 'temporary.*root@localhost' /var/log/mysqld.log | sed 's/.*root@localhost: //'” 注册:mysql_root_pass

              【讨论】:

                【解决方案13】:

                我正在使用 Ansible 2.9.20 并为 8.0.26 版本的 mysql 安装创建了剧本。这个mysql版本安装有一些变化,所以在这里添加对我有用的解决方案。

                MySQL.yml

                ---
                # tasks file for mysql_setup
                - name: Upgrade all packages
                  yum:
                    name: "*"
                    state: latest
                
                - name: Install MySQL repository
                  yum:
                    name: "https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm"
                    state: present
                
                - name: Install MySQL
                  yum:
                    name: ['mysql-community-devel*', 'mysql-community-server*', 'MySQL-python']
                    state: present
                
                - name: copy my.cnf
                  copy:
                    src: ../files/etc/my.cnf
                    dest: /etc/my.cnf
                    mode: 0644
                
                - name: Enable the MySQL service
                  service:
                    name: mysqld
                    state: restarted
                    enabled: true
                
                - name: Read secret file
                  include_vars: "defaults/secret.yml"
                
                - name: get root password
                  shell: "grep 'A temporary password is generated for root@localhost' /var/log/mysqld.log | awk -F ' ' '{print $(NF)}'"
                  register: root_password
                
                - name: Ensure root can login into MySQL localhost using temporary password
                  shell: "mysql -uroot -p'{{ root_password.stdout }}' --connect-expired-password"
                  with_items:
                    - 127.0.0.1
                    - ::1
                    - localhost
                  register: root_login_tmp_pass
                  ignore_errors: yes
                
                - name: update expired root user password
                  command: mysql --user root --password={{ root_password.stdout }} --connect-expired-password --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ secret.passwd_mysql_root }}';"
                  when: root_login_tmp_pass is succeeded
                
                - name: update root user password
                  command: mysql --user root --password={{ secret.current_passwd_mysql_root }} --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ secret.passwd_mysql_root }}';"
                  when: root_login_tmp_pass is failed
                
                - name: Copy root .my.cnf file
                  template:
                    src: ../templates/root-my.cnf.j2
                    dest: /root/.my.cnf
                    owner: root
                    group: root
                    mode: 0600
                
                - name: Create a database
                  mysql_db: name={{ db_name }}
                            collation=utf8_general_ci
                            encoding=utf8
                            state=present
                
                - name: Create a database user
                  mysql_user: name={{ db_user }}
                              password={{ secret.db_user_password }}
                              priv="{{ db_name }}.*:ALL"
                              host=localhost
                              state=present
                

                模板/root-my.cnf.j2

                [client]
                user=root
                password={{ secret.passwd_mysql_root }}
                

                文件/etc/my.cnf

                [mysqld]
                datadir=/var/lib/mysql
                socket=/var/lib/mysql/mysql.sock
                
                log-error=/var/log/mysqld.log
                pid-file=/var/run/mysqld/mysqld.pid
                default-authentication-plugin=mysql_native_password
                

                defaults/main.yml

                ---
                # defaults file for mysql_setup
                db_name: mydb
                db_user: iamuser
                

                defaults/secret.yml

                secret:
                  passwd_mysql_root: RootPassword2!3
                  db_user_password: iamdbpassword
                  current_passwd_mysql_root: currRootPass2!3
                

                运行此 playbook 两次后,您必须使用当前密码 (current_passwd_mysql_root) 和要设置的 root 密码 (passwd_mysql_root) 更新此 secret.yml 文件。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-06-29
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-12-19
                  • 2020-03-28
                  • 2018-01-04
                  • 1970-01-01
                  相关资源
                  最近更新 更多