1. Conditional task syntax
In the example, ansible_ The distribution variable is a fact determined during the Gathering Facts task and is used to identify the operating system branch of the managed host. The variable platform is created by the playbook and contains the operating system distribution list supported by the playbook. If ansible_ If the value of distribution is in the platform list, the condition passes and the task runs.
--- - hosts: 192.168.8.132 vars: platform: - redhat - CentOS tasks: - name: test dnf: name: vsftpd when: ansible_facts['distribution'] in platform
Note the indentation of the when statement. Since the when statement is not a module variable, it must be indented to the highest level of the task and placed outside the module.
The task is a YAML hash / dictionary, and the when statement is just another key in the task, just like the name of the task and the module it uses. The usual practice is to put any possible when keyword after the task name and module (and module parameters).
2. Implement handling procedures
2.1ansible handler
Ansible modules are designed to be idempotent. This means that in a properly written playbook, the playbook and its tasks can run multiple times without changing the managed host unless changes are needed to bring the managed host into the desired state.
However, when the task does change the system, it may be necessary to run further tasks. For example, changing a service profile may require that the service be reloaded for its changed configuration to take effect.
A handler is a task that responds to notifications triggered by other tasks. The task notifies its handler only when it changes something on the managed host. Each handler has a globally unique name and is triggered at the end of the task block in the playbook. If no task notifies the handler by name, the handler will not run. If one or more tasks notify the handler, the handler runs once after all other tasks in play are completed. Because handlers are tasks, you can use the modules they will use for any other task in the handler. Typically, the handler is used to reboot the host and restart the service.
A handler can be considered an inactive task and is triggered only when explicitly invoked with a notify statement. In the following code snippet, the restart apache handler will restart the Apache server only if the configuration file is updated and the task is notified:
--- - hosts: 192.168.8.132 vars: tasks: - name: install httpd dnf: name: httpd state: present - name: config httpd copy: src: /opt/project/playbook/httpd/files/httpd-vhosts.conf dest: /etc/httpd/conf.d/vhost.conf - name: service for httpd service: name: httpd state: started
[root@master playbook]# mkdir httpd [root@master playbook]# cd httpd/ [root@master httpd]# ls [root@master httpd]# ls [root@master httpd]# mkdir files [root@master httpd]# cd files/ [root@master files]# scp 192.168.8.132:/usr/share/doc/httpd/httpd-vhosts.conf . root@192.168.8.132's password: httpd-vhosts.conf 100% 1477 643.1KB/s 00:00 //Modify profile <VirtualHost *:80> DocumentRoot "/var/www/html/abc" ServerName web.example.com </VirtualHost> //Create access directory on controlled machine [root@localhost html]# mkdir abc [root@localhost html]# echo 'test page1' > abc/index.html [root@localhost html]# mkdir pyd [root@localhost html]# echo 'test page2' > pyd/index.html
[root@master project]# ansible-playbook playbook/test.yml
Using the notify statement
--- - hosts: 192.168.8.132 vars: tasks: - name: install httpd dnf: name: httpd state: present - name: config httpd copy: src: /opt/project/playbook/httpd/files/httpd-vhosts.conf dest: /etc/httpd/conf.d/vhost.conf notify: - restart httpd - name: service for httpd service: name: httpd state: started enabled: yes handlers: - name: restart httpd service: name: httpd state: restarted
Now the task configuration file has not been modified. You can see that it does not trigger the restart of the service
[root@master project]# ansible-playbook playbook/test.yml PLAY [192.168.8.132] *************************************************************************** TASK [Gathering Facts] ************************************************************************* ok: [192.168.8.132] TASK [install httpd] *************************************************************************** ok: [192.168.8.132] TASK [config httpd] **************************************************************************** ok: [192.168.8.132] TASK [service for httpd] *********************************************************************** ok: [192.168.8.132] PLAY RECAP ************************************************************************************* 192.168.8.132 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Modify profile
[root@master project]# vim playbook/httpd/files/httpd-vhosts.conf # # VirtualHost example: # Almost any Apache directive may go into a VirtualHost container. # The first VirtualHost section is used for all requests that do not # match a ServerName or ServerAlias in any <VirtualHost> block. # <VirtualHost *:80> DocumentRoot "/var/www/html/abc" ServerName web.example.com </VirtualHost> Listen 8080 <VirtualHost *:8080> DocumentRoot "/var/www/html/pyd" ServerName www.example.com </VirtualHost>
The trigger detects the modification of the configuration file and restarts the service
[root@master project]# ansible-playbook playbook/test.yml PLAY [192.168.8.132] *************************************************************************** TASK [Gathering Facts] ************************************************************************* ok: [192.168.8.132] TASK [install httpd] *************************************************************************** ok: [192.168.8.132] TASK [config httpd] **************************************************************************** changed: [192.168.8.132] TASK [service for httpd] *********************************************************************** ok: [192.168.8.132] RUNNING HANDLER [restart httpd] **************************************************************** changed: [192.168.8.132] PLAY RECAP ************************************************************************************* 192.168.8.132 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@master project]#
2.2 benefits of using handlers
There are several important things to keep in mind when using handlers:
- Handlers always run in the order specified in the handlers section of play. They do not run in the order listed by the notify statement * in the task, or in the order notified to them by the task.
- The handler typically runs after all other tasks in the associated play have been completed. The handler called by a task in the tasks section of the playbook will not run until all tasks under the tasks have been processed.
- The handler name exists in each play namespace. If two handlers are mistakenly given the same name, only one will run.
- Even if there are multiple task notification handlers, the handler runs only once. If there is no task notification handler, it will not run.
- If the task that contains the notify statement does not report the changed result (for example, the package is installed and the task reports ok), the handler is not notified. The handler will be skipped until it is notified by another task. Ansible notifies the handler only if the related task reports the changed status.
The handler is used to perform additional actions when the task makes changes to the managed host. They should not be used as a substitute for normal tasks.
3. Failed to process task
3.1 task errors in management play
Ansible evaluates the return code of the task to determine whether the task succeeded or failed. Generally speaking, when a task fails, ansible will immediately abort the rest of the play on the host and skip all subsequent tasks.
But sometimes, you may want to continue play ing even if the task fails. For example, you might expect a pending task to fail and want to fix it by conditionally running some other task.
Ansible has several functions to manage task errors.
3.2 ignore task failure
--- - hosts: 192.168.8.132 vars: tasks: - name: install httpd dnf: name: httpd2 state: present ignore_errors: yes
3.3 enforce handler after task failure
In general, if a task fails and play is aborted on that host, the handler that receives notification of an earlier task in play will not run. If force is set in play_ Handlers: Yes keyword, the notified handler will be called even if play is aborted due to subsequent task failure.
The following code snippet demonstrates how to use force in play_ Handlers keyword to force the corresponding handler when the task fails:
--- - hosts: 172.16.103.129 force_handlers: yes tasks: - name: a task which always notifies its handler command: /bin/true notify: restart the database - name: a task which fails because the package doesn't exist yum: name: notapkg state: latest handlers: - name: restart the database service: name: mariadb state: restarted
Remember that the handler is notified when the task reports the changed result, but not when the task reports the ok or failed result.
3.4 specified task failure conditions
Failed can be used in tasks_ When keyword to specify a condition indicating that the task has failed. This is usually used in conjunction with command modules, which may successfully execute a command, but the output of the command may indicate failure.
For example, you can run a script that outputs an error message and use that message to define the failure status of a task. The following code snippet demonstrates how to use failed in a task_ When keyword:
--- - hosts: 192.168.8.132 gather_facts: no tasks: - name: test command: /root/test.sh register: result //Printout results - debug: var: result
[root@master project]# ansible-playbook playbook/tom.yml PLAY [192.168.8.132] *************************************************************************** TASK [test] ************************************************************************************ changed: [192.168.8.132] TASK [debug] *********************************************************************************** ok: [192.168.8.132] => { "result": { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "cmd": [ "/root/test.sh" ], "delta": "0:00:00.026520", "end": "2021-07-27 16:03:24.502750", "failed": false, "rc": 0, "start": "2021-07-27 16:03:24.476230", "stderr": "useradd: User“ tom"Already exists", "stderr_lines": [ "useradd: User“ tom"Already exists" ], "stdout": "Change user tom Your password.\npasswd: All authentication tokens have been successfully updated.", "stdout_lines": [ "Change user tom Your password.", "passwd: All authentication tokens have been successfully updated." ] } } PLAY RECAP ************************************************************************************* 192.168.8.132 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
--- - hosts: 192.168.8.132 gather_facts: no tasks: - name: test command: /root/test.sh register: result failed_when: "'Already exists' in result.stderr"
[root@master project]# ansible-playbook playbook/tom.yml PLAY [192.168.8.132] *************************************************************************** TASK [test] ************************************************************************************ fatal: [192.168.8.132]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": true, "cmd": ["/root/test.sh"], "delta": "0:00:00.024721", "end": "2021-07-27 16:06:07.102527", "failed_when_result": true, "rc": 0, "start": "2021-07-27 16:06:07.077806", "stderr": "useradd: User“ tom"Already exists", "stderr_lines": ["useradd: User“ tom"Already exists"], "stdout": "Change user tom Your password.\npasswd: All authentication tokens have been successfully updated.", "stdout_lines": ["Change user tom Your password. ", "passwd: All authentication tokens have been successfully updated."]} PLAY RECAP ************************************************************************************* 192.168.8.132 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
The fail module can also be used to force a task to fail. The above scenario can also be written as two tasks:
We can use the fail module to provide a clear failure message for the task. This method also supports deferred failures, allowing intermediate tasks to be run to complete or roll back other changes.
--- - hosts: 192.168.8.132 gather_facts: no tasks: - name: test command: /root/test.sh register: result ignore_errors: yes //Ignore errors - name: test fail: msg: 'I have already created this user' //If it already exists, I will output it. I have created this user when: "'Already exists' in result.stderr" //If the judgment condition is already in, it is in result Stderr outputs the contents of the previous command
[root@master project]# ansible-playbook playbook/tom.yml PLAY [192.168.8.132] *************************************************************************** TASK [test] ************************************************************************************ changed: [192.168.8.132] TASK [test] ************************************************************************************ fatal: [192.168.8.132]: FAILED! => {"changed": false, "msg": "I have already created this user"} PLAY RECAP ************************************************************************************* 192.168.8.132 : ok=1 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 [root@master project]#
3.5 specify when the task reports "Changed" results
When the task makes changes to the managed host, it reports the changed status and notifies the handler. If the task does not require changes, it reports ok and does not notify the handler.
changed_ The when keyword can be used to control when a task reports that it has changed. For example, the shell module in the next example will be used to obtain Kerberos credentials for subsequent tasks. It usually always reports changed at run time. To resist this change, set changed_when: false so that it only reports ok or failed.
The following example uses a shell module to report changed based on the output of the module collected through registered variables:
[root@master project]# ansible httpd -m shell -a 'echo "redhat"|passwd --stdin tom' 192.168.8.132 | CHANGED | rc=0 >> Change user tom Your password. passwd: All authentication tokens have been successfully updated.
--- - hosts: 192.168.8.132 gather_facts: no tasks: - name: test shell: echo "redhat"|passwd --stdin tom register: result changed_when: "'All authentication tokens have been successfully updated' not in result.stdout" //If all authentication tokens have been successfully updated, output ok notify: - print info handlers: - name: print info debug: msg: "Password setting succeeded"
3.6Ansible blocks and error handling
In playbook, blocks are clauses that logically group tasks, and can be used to control how tasks are executed. For example, a task block can contain the when keyword to apply a condition to multiple tasks:
- name: block example hosts: 172.16.103.129 tasks: - name: installing and configuring Yum versionlock plugin block: //When you use block, the following two tasks are a whole - name: package needed by yum yum: name: yum-plugin-versionlock state: present - name: lock version of tadata lineinfile: dest: /etc/yum/pluginconf.d/versionlock.list line: tzdata-2020j-1 state: present when: ansible_distribution == "Redhat" //I judge once for every task I perform
Through the block, you can also combine the rescue and always statements to handle errors. If any task in the block fails, perform the task in its rescue block to recover. After the task in the block clause and the task in the rescue clause (if there is a failure) run, the task in the always clause runs. Summary:
- block: defines the main task to run
- rescue: defines the task to run when the task defined in the block clause fails
- Always: defines tasks that always run independently, regardless of whether the tasks defined in the block and rescue clauses succeed or fail
The following example demonstrates how to implement blocks in playbook. Even if the task defined in the block clause fails, the task defined in the rescue and always clauses will execute.
tasks: - name: Upgrade DB block: - name: upgrade the database shell: cmd: /usr/local/lib/upgrade-database rescue: - name: revert the database upgrade shell: cmd: /usr/local/lib/revert-database always: - name: always restart the database service: name: mariadb state: restarted
when condition in block is also applied to its rescue and always clauses, if any.
4. Create a file or directory on the managed node
4.1 description document module
The Files module library contains modules that allow users to complete most tasks related to Linux file management, such as creating, copying, editing, and modifying file permissions and other properties. The following table provides a list of common file management modules:
Common file module
Module name | Module description |
---|---|
blockinfile | Inserts, updates, or deletes a multiline text block surrounded by customizable marker lines |
copy | Copy files from a local or remote computer to a location on the managed host. Similar to the file module, the copy module can also set file attributes, including SELinux upper and lower files. |
fetch | This module functions similar to the copy module, but works in the opposite way. This module is used to obtain files from a remote computer to the control node and store them in a file tree organized by host name. |
file | Set attributes such as permissions, ownership, SELinux context, and timestamp of general files, symbolic links, hard links, and directories. This module can also create or delete general files, symbolic links, hard links, and directories. Several other file related modules support the same property setting options as the file module, including the copy module. |
lineinfile | Make sure that a specific line is in a file, or replace an existing line with a back reference regular expression. This module is mainly used when the user wants to change a line of the file. |
stat | Retrieve the status information of the file, similar to the stat command in Linux. |
synchronize | A wrapper around the rsync command to speed up and simplify common tasks. The synchronize module does not provide access to the full functionality of the rsync command, but it is true that the most common calls are easier to implement. The user may still need to call the rsync command directly through the run command module. |
4.2 automation example of files module
Creating, copying, editing, and deleting Files on the managed host is a common task that users can implement using the modules in the Files module library.
The following example shows how these modules can be used to automate common file management tasks.
4.3 ensure that files exist on the managed host
Use the file module to process files on the managed host. Its working mode is similar to the touch command. If it does not exist, an empty file is created. If it does exist, its modification time is updated. In this example, in addition to working with files, Ansible ensures that the owner, group, and permissions of the file are set to specific values.
- name: Touch a file and set permissions file: path: /path/to/file owner: user1 group: group1 mode: 0640 state: touch
4.3 copying and editing files on managed hosts
Copy files from managed host to host
--- - hosts: 192.168.8.132 gather_facts: no tasks: - name: test fetch: src: /root/test.sh dest:/root/
You can see the folder named after the managed host IP
[root@master project]# ls /root/ 1 Template document desktop apr-util-1.6.1.tar.gz initial-setup-ks.cfg 192.168.8.132 Video download anaconda-ks.cfg httpd-2.4.48.tar.gz playbook public Picture music apr-1.7.0.tar.gz httpd.conf
To add a text block to an existing file, use the blockinfile module:
Note: when using the block infile module, comment block tags are inserted at the beginning and end of the block to ensure idempotency
--- - hosts: 192.168.8.132 gather_facts: no tasks: - name: test blockinfile: path: /tmp/abc block: | hello world hello pyd state: present create: yes
[root@localhost ~]# cat /tmp/abc # BEGIN ANSIBLE MANAGED BLOCK hello world hello pyd # END ANSIBLE MANAGED BLOCK [root@localhost ~]#
4.4 deleting files from managed hosts
A basic example of deleting a file from a managed host is to use the file module and the state: absent parameter. The state parameter is optional for many modules. Some modules also support other options.
--- - hosts: 192.168.8.132 gather_facts: no tasks: - name: test file: path: /tmp/abc state: absent
4.5 retrieving the file status on the managed host
The fact that the stat module retrieves files is similar to the stat command in Linux. Parameters provide functions such as retrieving file attributes, determining file attributes, and checking.
The stat module returns a hash dictionary containing the values of the file status data, allowing users to reference each piece of information with separate variables.
The following example registers the results of the stat module and then displays the MD5 validation and of the files it checks.
--- - hosts: 192.168.8.132 gather_facts: no tasks: - name: test stat: path: /root/test.sh register: result - debug: var: result
[root@master project]# ansible-playbook playbook/test.yml PLAY [192.168.8.132] *************************************************************************** TASK [test] ************************************************************************************ ok: [192.168.8.132] TASK [debug] *********************************************************************************** ok: [192.168.8.132] => { "result": { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false, "failed": false, "stat": { "atime": 1627372025.17479, "attr_flags": "", "attributes": [], "block_size": 4096, "blocks": 8, "charset": "us-ascii", "checksum": "42579296c474f32c63a3d795e48236a8f5ecb4a3", "ctime": 1627372019.9387903, "dev": 64768, "device_type": 0, "executable": true, "exists": true, "gid": 0, "gr_name": "root", "inode": 101432870, "isblk": false, "ischr": false, "isdir": false, "isfifo": false, "isgid": false, "islnk": false, "isreg": true, "issock": false, "isuid": false, "mimetype": "text/x-shellscript", "mode": "0755", "mtime": 1627372011.0287907, "nlink": 1, "path": "/root/test.sh", "pw_name": "root", "readable": true, "rgrp": true, "roth": true, "rusr": true, "size": 59, "uid": 0, "version": "1246841604", "wgrp": false, "woth": false, "writeable": true, "wusr": true, "xgrp": true, "xoth": true, "xusr": true } } } PLAY RECAP ************************************************************************************* 192.168.8.132 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@master project]#
4.6 synchronize files between the control node and the managed host
The synchronize module is a wrapper around rsync tools that simplifies common file management tasks in playbook. The rsync tool must be installed on both the local and remote hosts. By default, when using the synchronize module, the "local host" is the host from which the synchronization task originates (usually the control node), and the "target host" is the host to which synchronize is connected.
The following example synchronizes a file located in the Ansible working directory to the managed host:
--- - hosts: 192.168.8.132 gather_facts: no tasks: tasks: - name: test yum: name: rsync state: present - name: test synchronize: src: httpd dest: /opt
[root@localhost ~]# ls /opt/ httpd
There are many ways to use the synchronize module and its many parameters, including synchronizing directories. Run the ansible doc synchronize command to see additional parameters and playbook examples