3.2 Ansible Playbooks Advanced 2

when meet a certain condition to execute

In some scenarios, we deploy a software, or execute a command is conditional, for example, when it is detected that the output of a command contains an OK string, then further operations are performed

- shell: my_command_here 
  register: my_command_result

- command: do-something-to-my-app
  when: "'ready' in myapp_result.stdout"

Or it may detect that a certain variable is defined before performing certain subsequent operations

#此时 is_db_server 需要是bool值
- yum: name=mysql-server state=present 
  when: is_db_server

#避免变量未定义报错
- yum: name=mysql-server state=present
  when: (is_db_server is defined) and is_db_server

#尝试转换 is_db_server 为bool值
- yum: name=mysql-server state=present 
  when: is_db_server | bool

Reference example: https://gitee.com/as4k/ysansible/blob/master/registered_var/main1.yml

Ansible's various analysis of playbooks all happen on the control node, and the relevant operations are done at the control node and then sent to the controlled node, so the controlled node hardly needs any dependency packages.

loop

Similar to the requirements of copying multiple files in batches and creating multiple users in batches, we can use loops to improve efficiency

    # 这里的 item 是固定用法
    - name: Add several users
      ansible.builtin.user:
        name: "{
   
   { item }}"
        state: present
        password: "{
   
   { '123456' | password_hash('sha512', 'mysecretsalt') }}"
      loop:
        - testuser1
        - testuser2

The data type is a dictionary, an example is as follows

    - name: Create files
      # ansible.builtin.file:
      file:
        dest: "{
   
   { item.path }}"
        state: touch
        mode: "{
   
   { item.mode }}"
      loop:
        - { path: '/tmp/foo1.txt', mode: '0444' }
        - { path: '/tmp/foo2.txt', mode: '0444' }
        - { path: '/tmp/foo3.txt', mode: '0444' }

Reference example: https://gitee.com/as4k/ysansible/blob/master/playbooks_loops

ignore_errors ignore errors

By default, the following two situations are considered errors by Ansible

  1. For shell or command module, return non-zero is an error, return 0 is correct (consistent with shell's own logic)
  2. For the module to return failure, it is an error (the module's built-in mechanism, basically no need to pay attention)

Once an instruction returns an error, Ansible will stop on the machine and continue to execute the following instructions, but other machines will not be affected. The following example makes Ansible ignore the error

    # 忽略错误,无论是否有错误,继续往下执行
    - name: test2
      shell: cat /etc/redhat-release-xxx
      ignore_errors: yes

Insert picture description here

ignore_errorsYou can only ignore the error of the instruction execution itself. For similar machines that cannot be connected or the YAML file syntax error, you can’t ignore it.

Reference example https://gitee.com/as4k/ysansible/blob/master/common/ignore_errors.yml

changed_when defines what is changed

If we are not using Ansible's own modules, but shellor commandmodules, then no matter how simple the instructions, Ansible will think that the state of the target machine has changed

Not all required functions Ansible has good module support. In fact, it is common to use a large number of shell commands in Ansible Playbook. If there are a lot of changedstatus output every time the playbook is executed , can you not panic! Therefore, we need to control it, or tell Ansible under what circumstances it is called status changed. For example, in shell commands, we know that if the return value is 0, it means normal, and non-zero means abnormal. We can use this to write many shells. Script, similar usage in Ansible

    # .rc 在这里是固定用法表示 return code
    - name: Report 'changed' when the return code is not equal to 0
      shell: cat /etc/redhat-release
      register: my_result
      changed_when: "my_result.rc != 0"

If the conditions are more complicated, for example, multiple conditions need to be met at the same time, then write like the following

- name: Combine multiple conditions to override 'changed' result
  command: /bin/fake_command
  register: result
  changed_when:
    - '"ERROR" in result.stderr'
    - result.rc == 2

Reference example https://gitee.com/as4k/ysansible/blob/master/changed_when/main1.yml

failed_when defines what is wrong

changed_whenIt’s Ansible that doesn’t know what a change is, and we tell it, similarly, failed_whenAnsible doesn’t know what is an error, or what counts as an error condition. We define the conditions for the error and tell Ansible to judge it as an error in this case, as follows In the example above, we use the return value of the command to perform string matching to determine whether the command is executed successfully or not

    - name: test5
      shell: cat /tmp/ok.txt
      register: my_result
      failed_when: "'error' in my_result.stdout"
#my_result.stdout 表示标准输出
#my_result.stderr 表示标准错误
#my_result.rc     表示返回值

If multiple conditions need to be met at the same time, that is, and

  failed_when:
    - result.rc == 0
    - '"No such" not in result.stdout'

If needed or condition

  failed_when: diff_cmd.rc == 0 or diff_cmd.rc >= 2

Reference example https://gitee.com/as4k/ysansible/blob/master/changed_when/main1.yml

delegate_to task delegation

We know that Ansible binds the machine when executing instructions. For example, if we see the following command

ansible-playbook main1.yml  --limit 192.168.31.100

By default, we all think that the instructions on this playbook will be executed on the 192.168.31.100 machine one by one (not considering abnormal operations like using ssh to execute scripts remotely)

However, sometimes, when we deploy a set of services, we need to use multiple machines, and it is not that machine A executes xxx, and then goes to B to execute xxx. This order may be similar to the order of A, B, and A. , For example, we manually do the master-slave of the MySQL database from scratch, the general process may be

  1. Build the main library A
  2. Build slave library B
  3. Go to the main library A to check the binlog location
  4. Perform change master on the slave library… build master-slave

If the Ansible corresponding to the above operation, that is, a playbook that installs the slave library, not only does it need to execute related commands on the slave library, but also needs to execute some related commands on the main library across machines, similar to this kind of task is not in the current Machine execution, but needs to be delegated to other machines to perform operations, you need to use delegate_to

The following example demonstrates adding a hosts record analysis to the specified machine

    - name: add test domain to another host
      lineinfile:
        path: /etc/hosts
        regexp: '^192.168.31.100'
        line: 192.168.31.100 as4k.top
      delegate_to: 192.168.31.101

Reference example https://gitee.com/as4k/ysansible/blob/master/common/delegate_to.yml

wait_for wait for a condition to be met

When some services are started, other services need to be running normally before they can be started. For example, many programs must require the database to be connected normally before starting, otherwise they will fail wait_forto start, providing us with such as waiting for a port to connect and waiting for a file to exist. Before continuing to execute Ansible instructions

Wait 10 seconds before continuing to execute the instructions behind the playbook

    - name: Sleep for 10 seconds and continue with play
      wait_for:
        timeout: 10

Wait for a port to open before continuing

    - name: 等待5秒,检查目标机器8000端口是否打开,如果持续10秒还没有打开,返回失败
      wait_for:
        port: 8000
        delay: 5
        timeout: 10
        #超时时间默认为300秒

Wait for a file to exist before continuing

    - name: Wait until the file /tmp/foo is present before continuing
      wait_for:
        path: /tmp/foo

Wait for a file to not exist before continuing

    - name: Wait until the lock file is removed
      wait_for:
        path: /var/lock/file.lock
        state: absent

Wait for a file to contain a certain string before continuing

    - name: Wait until the string "completed" is in the file /tmp/foo before continuing
      wait_for:
        path: /tmp/foo
        search_regex: completed

Reference example https://gitee.com/as4k/ysansible/blob/master/common/wait_for.yml

Of course, there are many methods for the management of dependencies between services, such as a built-in dependency check mechanism for each program, such as automatic retry when the application detects that the database cannot be connected

Fully execute the playbook locally

Sometimes we just test some of the basic functions of Ansible, we can configure it locally without secret, execute it locally, but Ansible has special parameters that can make this thing faster

Use parameters --connection=local

Insert picture description here
Reference example https://gitee.com/as4k/ysansible/blob/master/common/localhost_playbook.yml

prompt allows users to enter information interactively

Similar to a shell script, Ansible can also interactively wait for the user to input information, and store it in a variable for later use, such as waiting for the user to enter an account and password.

  vars_prompt:
    - name: share_user
      prompt: "What is your username?"
      private: no

Reference example https://gitee.com/as4k/ysansible/blob/master/common/prompt.yml

Insert picture description here

Unless necessary, this method is best to be used less or not. Ansible is originally an automated configuration tool. The information that needs to be transmitted is recommended to be directly placed in the text in the form of variables. This method can also achieve a certain degree of security in disguise.

tags to classify tasks

Tag the tasks in the playbook, so that when executing, you can specify to run only those with a certain tag, or not to run with a certain tag

    - name: hello3 4 5
      debug:
        msg: "hello3 4 5"
      tags:
      - hello3
      - hello4

If no tag is specified, all will be executed

ansible-playbook  tags.yml --limit 192.168.31.100

Specify a specific tag, then only perform the tasks below the tag

ansible-playbook  tags.yml --tags hello2 --limit 192.168.31.100

Skip the hello2 label (i.e. negate)

ansible-playbook  tags.yml --skip-tags hello2 --limit 192.168.31.100

Specify multiple tags

ansible-playbook  tags.yml --tags "hello3,hello4,hello5" --limit 192.168.31.100

Reference example https://gitee.com/as4k/ysansible/blob/master/common/tags.yml

When using tags, it’s best to have relevant specifications within the team, do a good job of documentation, and don’t abuse the tag function everywhere, otherwise it will be troublesome to maintain. Just imagine that there are a bunch of tags in a playbook and there are no documents. Then these tags It is almost impossible to know what function it is, you can only read and analyze it line by line

block task grouping

Block is a function introduced by Ansible 2.0. It groups a bunch of tasks so that we can carry out unified group-level control. For example, when condition 1 is satisfied, tasks A, B, and C are executed, and when condition 2 is satisfied, tasks D, E, and E are executed. F. For similar needs, it is more convenient to use blocks to divide A, B, and C into one group, and divide D, E, and F into another group.

---
- hosts: web
  tasks:
    #Install and configure Apache on RHEL/CentOS hosts. 
    - block:
        - yum: name=httpd state=present
        - template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf 
        - service: name=httpd state=started enabled=yes
      when: ansible_os_family == 'RedHat'
      become: yes

    #Install and configure Apache on Debian/Ubuntu hosts. 
    - block:
        - apt: name=apache2 state=present
        - template: src=httpd.conf.j2 dest=/etc/apache2/apache2.conf 
        - service: name=apache2 state=started enabled=yes
      when: ansible_os_family == 'Debian' 
      become: yes

The block can also be used to achieve the function of catching exceptions in similar programming languages

    - block:
        - name: look something
          shell: cat /etc/redhat-release
          # shell: cat /etc/redhat-release-xxx
      rescue:
        - name: rescue xxx
          debug:
            msg: "命令执行失败 走这里(执行成功这里被忽略)"
      always:
        - name: always xxx
          debug:
            msg: "无论命令是否执行成功 这里都走"

This way is more complicated to understand and not easy to maintain. For simple handling of failure, use the failed_when and changed_when mentioned above

Reference example https://gitee.com/as4k/ysansible/blob/master/common/block.yml

Reference

https://docs.ansible.com/ansible/latest/user_guide/playbooks_error_handling.html
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/wait_for_module.html
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html

Guess you like

Origin blog.csdn.net/xys2015/article/details/113857594