Ansible
- Learning Ansible basics
- 30 天入門 Ansible 及 Jenkins
- GitHub - geerlingguy/ansible-for-devops: Ansible for DevOps examples.
- video
- labs
- Ansible Lab environment using Docker
- change the ansible/base/Dockerfile from ubuntu:17.10 to ubuntu:20.04
- docker compose build base
- docker compose build master
- docker compose build host
- docker compose up -d
- Ansible Lab environment using Docker
- workshop
- config
- User Guide — Ansible Documentation
- Tips and tricks — Ansible Documentation
- Collection Index — Ansible Documentation
- In Ansible 2.10 and later, most modules are hosted in collections.
- Using collections — Ansible Documentation
- 如何貢獻你的第一支 Ansible Collection 以及 Ansible Tower Workflow 應用簡介 | 野生的工程師
- CLI
- Parallelism
- How to implement parallelism and rolling updates in Ansible | Enable Sysadmin
- 8 ways to speed up your Ansible playbooks | Enable Sysadmin
- How to speed up Ansible playbooks drastically ?
- Controlling where tasks run: delegation and local actions — Ansible Documentation
- Asynchronous actions and polling — Ansible Documentation
- variables
- GUI
- Ansible UI: Semaphore UI vs Tower - Semaphore UI
- jenkins - Is it a good idea to make Ansible and Rundeck work together, or using either one is enough? - Stack Overflow
- Is there any existing UI for Ansible? - Quora
- Intro to RedHat Ansible Automation Platform | NetGru - YouTube
- Semaphore, RunDeck and AWX Ansible : r/devops
- AWX vs Ansible Automation Platform : r/ansible
- Ansible Tower vs. AWX vs. Red Hat Ansible Automation Platform : r/ansible
- Integrating Ansible Core with Rundeck for Simplified Automation Is good idea ? : r/ansible
- Red Hat Enterprise Linux Automation with Ansible (RH294)
- control node
- python
- inventory file
- ssh
- managed nodes
- ssh
- inventory file
- organize managed nodes in centralized files
- static, dynamic
- format
- YAML
- INI
- group, variables
- Groups can contain child groups, and hosts can be members of multiple groups.
- playbook
- automation blueprints that Ansible uses to deploy and configure managed nodes.
- format
- YAML
- play, task, module
- Index of all Modules — Ansible Documentation
- A task is the application of a module to perform a specific unit of work.
- A play is a sequence of tasks to be applied, in order, to one or more hosts selected from your inventory.
- RH294
- RH294 Red Hat Enterprise Linux Automation with Ansible - Basic and Variable data
- RH294 Red Hat Enterprise Linux Automation with Ansible - Task flow
- RH294 Red Hat Enterprise Linux Automation with Ansible - advanced and debug
- RH294 Red Hat Enterprise Linux Automation with Ansible - Automating Linux Administration Tasks
idempotent In general, tasks in Ansible Playbooks are idempotent, and it is safe to run a playbook multiple times. If the targeted managed hosts are already in the correct state, no changes should be made. ``` yml=
not idempotent
Every time the play is run, it rewrites /etc/resolv.conf even if it already consists of the line nameserver 192.0.2.1.
- name: Non-idempotent approach with shell module shell: echo "nameserver 192.0.2.1" > /etc/resolv.conf
idempotent
does not rewrite the /etc/resolv.conf file if it already consists of the correct content
- name: Idempotent approach with copy module
copy:
dest: /etc/resolv.conf
content: "nameserver 192.0.2.1\n"
## inventory - [Passing multiple inventory sources](https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html#passing-multiple-inventory-sources) - [Using inventory directories and multiple inventory sources](https://docs.ansible.com/ansible/latest/inventory_guide/intro_dynamic_inventory.html#using-inventory-directories-and-multiple-inventory-sources) - [Is there any option to list groups in Ansible? - Stack Overflow](https://stackoverflow.com/questions/33363023/is-there-any-option-to-list-groups-in-ansible) - Nested Groups - Host Specifications with Ranges - server[01:20].example.com matches all hosts named server01.example.com through server20.example.com. - Dynamic Inventory - For example, a dynamic inventory program could contact your Red Hat Satellite server or Amazon EC2 account, and use information stored there to construct an Ansible inventory ``` bash= # ansible {host pattern} --list-hosts ansible canada --list-hosts -v
``` bash= ansible-inventory --list ansible-inventory --list | jq "keys" ansible-inventory --graph
### Inline inventory
- [Is possible to not have an inventory file and just provide an ip at command line ? : r/ansible](https://www.reddit.com/r/ansible/comments/z7ehm7/is_possible_to_not_have_an_inventory_file_and/)
- [ansible.builtin.advanced\_host\_list inventory – Parses a ‘host list’ with ranges — Ansible Community Documentation](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/advanced_host_list_inventory.html)
``` bash
ansible -i 192.0.2.5, all -m ping
config
``` bash= ansible --version
- [ansible/ansible.cfg at devel · ansible/ansible · GitHub](https://github.com/ansible/ansible/blob/devel/examples/ansible.cfg)
- [避免 Ansible 無法存取第一次登入的 Server - Yowko's Notes](https://blog.yowko.com/ansible-bypass-fingerprint-check/)
- [ansible.builtin.sh shell – POSIX shell (/bin/sh) — Ansible Documentation](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/sh_shell.html#parameter-remote_tmp)
\>=2.12
``` bash=
ansible-config init --disabled > ansible.cfg
module
``` bash=
lists all modules installed on a system
ansible-doc -l
display detailed documentation for a module.
ansible-doc [module name]
ansible-doc ping
produces example output that can serve as a model for how to use a particular module
ansible-doc -s ping
- [Module Index — Ansible Documentation](https://docs.ansible.com/ansible/2.9/modules/modules_by_category.html)
- [Files modules — Ansible Documentation](https://docs.ansible.com/ansible/2.9/modules/list_of_files_modules.html)
### Files modules
Ensuring a File Exists on Managed Hosts
``` yml
- name: Touch a file and set permissions
file:
path: /path/to/file
owner: user1
group: group1
mode: 0640
state: touch
Copying and Editing Files on Managed Hosts ``` yml= - name: Copy a file to managed hosts copy: src: file dest: /path/to/file
``` yml=
- name: Retrieve SSH key from reference host
fetch:
src: "/home/{{ user }}/.ssh/id_rsa.pub"
dest: "files/keys/{{ user }}.pub"
To ensure a specific single line of text exists in an existing file, ``` yml= - name: Add a line of text to a file lineinfile: path: /path/to/file line: 'Add this line to the file' state: present
To add a block of text to an existing file, use the blockinfile module:
``` yml=
- name: Add additional lines to a file
blockinfile:
path: /path/to/file
block: |
First line in the additional block of text
Second line in the additional block of text
state: present
Removing a File from Managed Hosts ``` yml= - name: Make sure a file does not exist on managed hosts file: dest: /path/to/file state: absent
Retrieving the Status of a File on Managed Hosts
``` yml=
- name: Verify the checksum of a file
stat:
path: /path/to/file
checksum_algorithm: md5
register: result
- debug
msg: The checksum of the file is {{ result.stat.checksum }}
TASK [Get md5 checksum of a file] *****************************************
ok: [hostname]
TASK [debug] **************************************************************
ok: [hostname] => {
"msg": "The checksum of the file is 5f76590425303022e933c43a7f2092a3"
}
``` yml= - name: Examine all stat output of /etc/passwd hosts: localhost
tasks: - name: stat /etc/passwd stat: path: /etc/passwd register: results
- name: Display stat results
debug:
var: results
The **synchronize** module is a wrapper around the rsync tool, which simplifies common file management tasks in your playbooks.
yml=
- name: synchronize local file to remote files
synchronize:
src: file
dest: /path/to/file
```
apt
ad hoc command
a way of executing a single Ansible task quickly ``` bash=
ansible {host-pattern} -m {module} [-a 'module arguments'] [-i inventory]
ansible localhost -m ping
know the difference between 'command' and 'shell' module
ansible localhost -m command -a /usr/bin/hostname ansible localhost -m shell -a set
## variables
- [Using Variables — Ansible Documentation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable)
- [Using Variables: Variables discovered from systems: Facts — Ansible Documentation](https://docs.ansible.com/ansible/2.9/user_guide/playbooks_variables.html#variables-discovered-from-systems-facts)
- Facts are information derived from speaking with your remote systems. You can find a complete set under the ansible_facts variable, most facts are also ‘injected’ as top level variables preserving the `ansible_` prefix
- [Discovering variables: facts and magic variables — Ansible Documentation](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_vars_facts.html#information-about-ansible-magic-variables)
- access information about Ansible operations, including the python version being used, the hosts and groups in inventory, and the directories for playbooks and roles, using "magic" variables
- [Special Variables — Ansible Documentation](https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html#special-variables)
- [Using Variables: Accessing information about other hosts with magic variables — Ansible Documentation](https://docs.ansible.com/ansible/2.9/user_guide/playbooks_variables.html#accessing-information-about-other-hosts-with-magic-variables)
- [Using Variables: Variable precedence: Where should I put a variable? — Ansible Documentation](https://docs.ansible.com/ansible/2.9/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable)
- [variables - Ansible - set\_facts Set a boolean value based on register.stdout\_lines containing a string - Stack Overflow](https://stackoverflow.com/questions/75316547/ansible-set-facts-set-a-boolean-value-based-on-register-stdout-lines-containin)
- [vars from a JSON or YAML file](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#vars-from-a-json-or-yaml-file)
precedence determines which value is used
simplified list of ways to define a variable, ordered from lowest precedence to highest:
- Group variables defined in the **inventory**.
- Group variables defined in files in a **group_vars** subdirectory in the same directory as the inventory or the playbook.
- Host variables defined in the **inventory**.
- Host variables defined in files in a **host_vars** subdirectory in the same directory as the inventory or the playbook.
- **Host facts**, discovered at runtime.
- Play variables in the playbook (vars and vars_files).
- Task variables.
- Extra variables defined on the **command line**.
``` bash=
# Overriding Variables from the Command Line
ansible-playbook main.yml -e "package=apache"
# Capturing Command Output with Registered Variables
---
- name: Installs a package and prints the result
hosts: all
tasks:
- name: Install the package
yum:
name: httpd
state: present
register: install_result
- debug:
var: install_result
secrets
To use Ansible Vault, a command-line tool named ansible-vault
is used to create, edit, encrypt, decrypt, and view files.
Files are protected with symmetric encryption using AES256 with a password as the secret key.
``` bash= ansible-vault create secret.yml
you can use a vault password file to store the vault password
ansible-vault create --vault-password-file=vault-pass secret.yml
Viewing an Encrypted File
ansible-vault view secret1.yml
Editing an Existing Encrypted File
ansible-vault edit secret.yml
Encrypting an Existing File
ansible-vault encrypt secret1.yml secret2.yml
Decrypting an Existing File
ansible-vault decrypt secret1.yml --output=secret1-decrypted.yml
Changing the Password of an Encrypted File
ansible-vault rekey secret.yml
To run a playbook that accesses files encrypted with Ansible Vault,
provide the vault password interactively
ansible-playbook --vault-id @prompt site.yml
specify a file that stores the encryption password in plain text.
You can also use the ANSIBLE_VAULT_PASSWORD_FILE environment variable to specify the default location of the password file.
ansible-playbook --vault-password-file=vault-pw-file site.yml
### Recommended Practices for Variable File Management
:::success
The recommended practice is to define inventory variables using host_vars and group_vars directories, and not to define them directly in the inventory files.
:::
### Facts
- [ansible - How to loop through interface facts - Server Fault](https://serverfault.com/questions/762079/how-to-loop-through-interface-facts)
- [Ansible combine registered variable from multiple hosts - Stack Overflow](https://stackoverflow.com/questions/74213702/ansible-combine-registered-variable-from-multiple-hosts)
- [ansible.utils.update\_fact module – Update currently set facts — Ansible Documentation](https://docs.ansible.com/ansible/latest/collections/ansible/utils/update_fact_module.html)
Ansible facts are variables that are automatically discovered by Ansible on a **managed host**.
Also, Administrators can create custom facts which are stored locally on each managed host.
Some variables are not facts or configured through the setup module, but are also automatically set by Ansible, it's called **magic variables**
``` bash=
# test magic variables
ansible localhost -m debug -a 'var=hostvars["localhost"]'
Normally, every play runs the setup module automatically before the first task in order to gather facts.
Some of the facts gathered for a managed host might include: - The host name - The kernel version - The network interfaces - The IP addresses - The version of the operating system - Various environment variables - The number of CPUs - The available or free memory - The available disk space
``` bash=
檢查facts
ansible localhost -m setup ansible localhost -m setup > /tmp/localhost_setup_dump
檢查hostvars
ansible localhost -m debug -a "var=hostvars[inventory_hostname]" ansible localhost -m debug -a "var=ansible_host" ansible localhost -m debug -a "var=ansible_version" ansible localhost -m debug -a "var=group_names" ansible localhost -m debug -a "var=groups"
ansible -i inventory test001 -m debug -a "var=hostvars[inventory_hostname]"
/tmp/host_vars_test001
same result
ansible localhost -m ansible.builtin.setup
localhost | SUCCESS => { "hostvars[inventory_hostname]": { "ansible_check_mode": false, "ansible_config_file": null, "ansible_connection": "local", "ansible_diff_mode": false, "ansible_facts": {}, "ansible_forks": 5, "ansible_inventory_sources": [ "/etc/ansible/hosts" ], "ansible_playbook_python": "/home/supermicro/.pyenv/versions/3.10.0/bin/python", "ansible_python_interpreter": "/home/supermicro/.pyenv/versions/3.10.0/bin/python", "ansible_verbosity": 0, "ansible_version": { "full": "2.13.6", "major": 2, "minor": 13, "revision": 6, "string": "2.13.6" }, "group_names": [], "groups": { "all": [], "ungrouped": [] }, "inventory_hostname": "localhost", "inventory_hostname_short": "localhost", "omit": "__omit_place_holder__f658d3d6364c746326fb9b151c3079026761b9e9", "playbook_dir": "/home/supermicro/srccode/customers" } }``` yaml=
- hosts: all tasks:
- name: Prints various Ansible facts debug: msg: > The default IPv4 address of {{ ansible_facts.fqdn }} is {{ ansible_facts.default_ipv4.address }}
you can manually gather facts at any time by running a task that uses the setup module
``` yaml= tasks: - name: Manually gather facts setup:
:::info
Before Ansible 2.5, facts were injected as individual variables prefixed with the string ansible_ instead of being part of the ansible_facts variable. For example, the ansible_facts['distribution'] fact would have been called ansible_distribution.
:::
#### Custom Facts
These facts are integrated into the list of standard facts gathered by the setup module when it runs on the managed host.
By default, the setup module loads custom facts from files and scripts in each managed host's ==/etc/ansible/facts.d== directory.
Custom facts can be defined in a static file, formatted as an INI file or using JSON. They can also be executable scripts which generate JSON output.
Dynamic custom facts allow the values for these facts, or even which facts are provided, to be determined programmatically when the play is run.
The name of each file or script must end in .fact in order to be used. Dynamic custom fact scripts must output JSON-formatted facts and must be executable.
an example of a static custom facts file
``` ini=
[packages]
web_package = httpd
db_package = mariadb-server
[users]
user1 = joe
user2 = jane
``` json= { "packages": { "web_package": "httpd", "db_package": "mariadb-server" }, "users": { "user1": "joe", "user2": "jane" } }
For example, assume that the preceding custom facts are produced by a file saved as /etc/ansible/facts.d/custom.fact on the managed host. In that case, the value of ansible_facts['ansible_local']['custom']['users']['user1'] is joe.
##### practice
setup_facts.yml
``` yaml=
---
- name: Install remote facts
hosts: webserver
vars:
remote_dir: /etc/ansible/facts.d
facts_file: custom.fact
tasks:
- name: Create the remote directory
file:
state: directory
recurse: yes
path: "{{ remote_dir }}"
- name: Install the new facts
copy:
src: "{{ facts_file }}"
dest: "{{ remote_dir }}"
/home/student/data-facts/custom.fact
``` INI= [general] package = httpd service = httpd state = started enabled = true
playbook.yml
``` yaml=
- name: Install Apache and starts the service hosts: webserver
tasks: - name: Install the required package yum: name: "{{ ansible_facts['ansible_local']['custom']['general']['package'] }}" state: latest
- name: Start the service
service:
name: "{{ ansible_facts['ansible_local']['custom']['general']['service'] }}"
state: "{{ ansible_facts['ansible_local']['custom']['general']['state'] }}"
enabled: "{{ ansible_facts['ansible_local']['custom']['general']['enabled'] }}"
bash=
ansible-playbook playbook.yml
```
Task control
- Loops — Ansible Documentation
- Conditionals — Ansible Documentation
- Handlers: Running Operations On Change
- Using conditionals in loops
loop
:::warning - Migrating from with_X to loop — Ansible Documentation - Ansible: Register iterating with the loop - Stack Overflow
Before Ansible 2.5, most playbooks used a different syntax for loops. Multiple loop keywords were provided, which were prefixed with with_, followed by the name of an Ansible look-up plug-in
- with_items
- with_file
- with_sequence
``` yml= vars: data: - user0 - user1 - user2 tasks: - name: "with_items" debug: msg: "{{ item }}" with_items: "{{ data }}"
:::
``` yml=
- name: Postfix is running
service:
name: postfix
state: started
- name: Dovecot is running
service:
name: dovecot
state: started
rewritten by loop
``` yml= - name: Postfix and Dovecot are running service: name: "{{ item }}" state: started loop: - postfix - dovecot
The list used by loop can be provided by a variable
``` yml=
vars:
mail_services:
- postfix
- dovecot
tasks:
- name: Postfix and Dovecot are running
service:
name: "{{ item }}"
state: started
loop: "{{ mail_services }}"
a List of Hashes or Dictionaries ``` yml= - name: Users exist and are in the correct groups user: name: "{{ item.name }}" state: present groups: "{{ item.groups }}" loop: - name: jane groups: wheel - name: joe groups: root
Using Register Variables
``` yml=
---
- name: Loop Register Test
gather_facts: no
hosts: localhost
tasks:
- name: Looping Echo Task
shell: "echo This is my item: {{ item }}"
loop:
- one
- two
register: echo_results
- name: Show echo_results variable
debug:
var: echo_results
``` yml=
-
name: Loop Register Test gather_facts: no hosts: localhost tasks:
-
name: Looping Echo Task shell: "echo This is my item: {{ item }}" loop:
- one
- two register: echo_results
-
name: Show stdout from the previous task. debug: msg: "STDOUT from previous task: {{ item.stdout }}" loop: "{{ echo_results['results'] }}"
### Conditionally usecase of conditionals in Ansible: - A hard limit can be defined in a variable (for example, min_memory) and compared against the available memory on a managed host. - The output of a command can be captured and evaluated by Ansible to determine whether or not a task completed before taking further action. For example, if a program fails, then a batch is skipped. - Use Ansible facts to determine the managed host network configuration and decide which template file to send (for example, network bonding or trunking). - The number of CPUs can be evaluated to determine how to properly tune a web server. - Compare a registered variable with a predefined variable to determine if a service changed. For example, test the MD5 checksum of a service configuration file to see if the service is changed. conditionals can be combined with either the **and** or **or** keywords, and grouped with parentheses. When a list is provided to the when keyword, all of the conditionals are combined using the and operation. The expressions used with **when** clauses in Ansible Playbooks are **Jinja2 expressions**. :::info A common convention places any when keyword that might be present after the task's name and the module (and module arguments). ::: ``` yml= --- - name: Simple Boolean Task Demo hosts: all vars: run_my_task: true tasks: - name: httpd package is installed yum: name: httpd when: run_my_task
-
``` yml=
- name: Test Variable is Defined Demo hosts: all vars: my_service: httpd
tasks: - name: "{{ my_service }} package is installed" yum: name: "{{ my_service }}" when: my_service is defined
the ansible_distribution variable is a fact determined during the Gathering Facts task, and identifies the managed host's operating system distribution.
``` yml=
---
- name: Demonstrate the "in" keyword
hosts: all
gather_facts: yes
vars:
supported_distros:
- RedHat
- Fedora
tasks:
- name: Install httpd using yum, where supported
yum:
name: http
state: present
when: ansible_distribution in supported_distros
Operation | Example |
---|---|
Equal (value is a string) | ansible_machine == "x86_64" |
Equal (value is numeric) | max_memory == 512 |
Less than | min_memory < 128 |
Greater than | min_memory > 256 |
Less than or equal to | min_memory <= 256 |
Greater than or equal to | min_memory >= 512 |
Not equal to | min_memory != 512 |
Variable exists | min_memory is defined |
Variable does not exist | min_memory is not defined |
Boolean variable is true. The values of 1, True, or yes evaluate to true. | memory_available |
Boolean variable is false. The values of 0, False, or no evaluate to false. | not memory_available |
First variable's value is present as a value in second variable's list | ansible_distribution in supported_distros |
:::info When you use when with loop for a task, the when statement is checked for each item. :::
Combining Loops and Conditional Tasks, the mariadb-server package is installed by the yum module if there is a file system mounted on / with more than 300 MB free.
``` yml= - name: install mariadb-server if enough space on root yum: name: mariadb-server state: latest loop: "{{ ansible_mounts }}" when: item.mount == "/" and item.size_available > 300000000
Combining register variables and Conditional Tasks, Evaluates the output of the Postfix task. If the exit code of the systemctl command is 0, then Postfix is active and this task restarts the httpd service.
``` yml=
---
- name: Restart HTTPD if Postfix is Running
hosts: all
tasks:
- name: Get Postfix server status
command: /usr/bin/systemctl is-active postfix
ignore_errors: yes
register: result
- name: Restart Apache HTTPD based on Postfix status
service:
name: httpd
state: restarted
when: result.rc == 0
Handlers
Handlers are tasks that respond to a notification triggered by other tasks.
Tasks only notify their handlers when the task changes something on a managed host.
If a task that includes a notify statement does not report a changed result (for example, a package is already installed and the task reports ok), the handler is not notified. The handler is skipped unless another task notifies it. Ansible notifies handlers only if the task reports the changed status.
When a task makes a change to a managed host, it reports the changed state and notifies handlers. When a task does not need to make a change, it reports ok and does not notify handlers.
Handlers can be considered as inactive tasks that only get triggered when explicitly invoked using a notify statement.
Handlers always run in the order specified by the handlers section of the play. They do not run in the order in which they are listed by notify statements in a task, or in the order in which tasks notify them.
If no task notifies the handler by name then the handler will not run.
If one or more tasks notify the handler, the handler will run exactly once after all other tasks in the play have completed.
Normally, handlers are used to reboot hosts and restart services.
the Apache server is only restarted by the restart apache handler when a configuration file is updated and notifies it ``` yml= tasks: - name: copy demo.example.conf configuration template template: src: /var/lib/templates/demo.example.conf.template dest: /etc/httpd/conf.d/demo.example.conf notify: - restart apache
handlers: - name: restart apache service: name: httpd state: restarted
### Task Failure
By default, if a task fails, the play is aborted.
However, this behavior can be overridden by ignoring failed tasks. You can use the **ignore_errors** keyword in a task to accomplish this.
if the notapkg package does not exist then the yum module fails, but having ignore_errors set to yes allows execution to continue.
``` yml=
- name: Latest version of notapkg is installed
yum:
name: notapkg
state: latest
ignore_errors: yes
Normally when a task fails and the play aborts on that host, any handlers that had been notified by earlier tasks in the play will not run.
If you set the force_handlers: yes keyword on the play, then notified handlers are called even if the play aborted because a later task failed.
Forcing Execution of Handlers ``` yml=
-
hosts: all 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
You can use the **failed_when** keyword on a task to specify which conditions indicate that the task has failed.
This is often used with **command** modules that may successfully execute a command, but the command's output indicates a failure.
The **fail** module can also be used to force a task failure.
You can use the fail module to provide a clear failure message for the task.
``` yml=
tasks:
- name: Run user creation script
shell: /usr/local/bin/create_users.sh
register: command_result
failed_when: "'Password missing' in command_result.stdout"
rewrite by fail module ``` yml= tasks: - name: Run user creation script shell: /usr/local/bin/create_users.sh register: command_result ignore_errors: yes
- name: Report script failure fail: msg: "The password is missing in the output" when: "'Password missing' in command_result.stdout"
block
In playbooks, blocks are clauses that logically group tasks, and can be used to control how tasks are executed.
Blocks also allow for error handling in combination with the rescue and always statements.
If any task in a block fails, tasks in its rescue block are executed in order to recover. After the tasks in the block clause run, as well as the tasks in the rescue clause if there was a failure, then tasks in the always clause run.
a task block can have a when keyword to apply a conditional to multiple tasks ``` yml= - name: block example hosts: all tasks: - name: installing and configuring Yum versionlock plugin block: - name: package needed by yum yum: name: yum-plugin-versionlock state: present - name: lock version of tzdata lineinfile: dest: /etc/yum/pluginconf.d/versionlock.list line: tzdata-2016j-1 state: present when: ansible_distribution == "RedHat"
- block: Defines the main tasks to run.
- rescue: Defines the tasks to run if the tasks defined in the block clause fail.
- always: Defines the tasks that will always run independently of the success or failure of tasks defined in the block and rescue clauses.
``` yml=
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
privilege
在 target node /etc/sudoers 增加ansible ALL=(ALL) NOPASSWD:ALL 就可以不用打密碼template
- template – Template a file out to a remote server — Ansible Documentation
- Ansible Template with Jinja formatting - Stack Overflow
Ansible uses the Jinja2 templating system for template files.
Ansible also uses Jinja2 syntax to reference variables in playbooks
Variables and logic expressions are placed between tags, or delimiters. - Jinja2 templates use {% EXPR %} for expressions or logic (for example, loops) - {{ EXPR }} are used for outputting the results of an expression or a variable - Use {# COMMENT #} syntax to enclose comments that should not appear in the final file.
The following example shows how to create a template for /etc/ssh/sshd_config with variables and facts retrieved by Ansible from managed hosts.
When the associated playbook is executed, any facts are replaced by their values in the managed host being configured.
jinja2 template(/tmp/j2-template.j2)
# {{ ansible_managed }}
# DO NOT MAKE LOCAL MODIFICATIONS TO THIS FILE AS THEY WILL BE LOST
Port {{ ssh_port }}
ListenAddress {{ ansible_facts['default_ipv4']['address'] }}
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
SyslogFacility AUTHPRIV
PermitRootLogin {{ root_allowed }}
AllowGroups {{ groups_allowed }}
AuthorizedKeysFile /etc/.rht_authorized_keys .ssh/authorized_keys
PasswordAuthentication {{ passwords_allowed }}
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials no
UsePAM yes
X11Forwarding yes
UsePrivilegeSeparation sandbox
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem sftp /usr/libexec/openssh/sftp-server
When the Jinja2 template for a configuration file has been created, it can be deployed to the managed hosts using the template module, which supports the transfer of a local file on the control node to the managed hosts.
``` yml= tasks: - name: template render template: src: /tmp/j2-template.j2 dest: /tmp/dest-config-file.txt
ansible_managed = Ansible managed - name: time debug: msg: "current time: {{ now(fmt='%m-%d-%Y %H:%M:%S') }}"### Control Structures
You can use Jinja2 control structures in template files to reduce repetitive typing, to enter entries for each host in a play dynamically, or conditionally insert text into a file.
uses a for statement to run through all the values in the users variable
you can use this to generate an /etc/hosts file from host facts dynamically.
``` yml=
- name: /etc/hosts is up to date
hosts: all
gather_facts: yes
tasks:
- name: Deploy /etc/hosts
template:
src: templates/hosts.j2
dest: /etc/hosts
templates/hosts.j2
{% for host in groups['all'] %}
{{ hostvars[host]['ansible_facts']['default_ipv4']['address'] }} {{ hostvars[host]['ansible_facts']['fqdn'] }} {{ hostvars[host]['ansible_facts']['hostname'] }}
{% endfor %}
formats the expression output using JSON/YAML
formats the expression output using JSON/YAML human readable format
expect strings in either JSON or YAML format, respectively, to parse them.
ip_addr
local test - GitHub - network-automation/ipaddr_filter: examples with the ipaddr filter
``` bash= ansible-playbook ipaddr_test.yml
playbook
ipaddr_test.yml
``` yaml=
---
- hosts: localhost
connection: local
gather_facts: false
vars:
sean_subnet: "192.168.1.0/24"
tasks:
- template:
src: ./template.j2
dest: ./rendered
template template.j2
ipaddr filter examples:
The var sean_subnet is: {{ sean_subnet }}
Jinja2: {% raw %} IP Address: {{sean_subnet | ipaddr }} {% endraw %}
Renders: IP Address: {{ sean_subnet | ipaddr }}
Jinja2: {% raw %} IP Address: {{sean_subnet | ipaddr('netmask') }} {% endraw %}
Renders: IP Address: {{ sean_subnet | ipaddr('netmask') }}
Jinja2: {% raw %} IPv4 Address: {{ sean_subnet | ipv4 }} {% endraw %}
Renders: IPv4 Address: {{ sean_subnet | ipv4 }}
Jinja2: {% raw %} IPv6 Address: {{ sean_subnet | ipv6 }} {% endraw %}
Renders: IPv6 Address: {{ sean_subnet | ipv6 }}
Jinja2: {% raw %} Network: {{ sean_subnet | ipaddr('network') }} {% endraw %}
Renders: Network: {{ sean_subnet | ipaddr('network') }}
Jinja2: {% raw %} Prefix: {{ sean_subnet | ipaddr('prefix') }} {% endraw %}
Renders: Prefix: {{ sean_subnet | ipaddr('prefix') }}
Jinja2: {% raw %} What is the first usable IP on {{sean_subnet}}? {{ sean_subnet | ipaddr('next_usable') }} {% endraw %}
Renders: What is the first usable IP on {{sean_subnet}}? {{ sean_subnet | ipaddr('next_usable') }}
Jinja2: {% raw %} What is the range of usable IP address on {{sean_subnet}}? {{ sean_subnet | ipaddr('range_usable') }} {% endraw %}
Renders: What is the range of usable IP address on {{sean_subnet}}? {{ sean_subnet | ipaddr('range_usable') }}
Jinja2: {% raw %} What is the last usable IP address on {{sean_subnet}}? {{ sean_subnet | ipaddr('last_usable') }} {% endraw %}
Renders: What is the last usable IP address on {{sean_subnet}}? {{ sean_subnet | ipaddr('last_usable') }}
Jinja2: {% raw %} What is the 2nd usable address on {{sean_subnet}}? {{ sean_subnet | next_nth_usable(2) }} {% endraw %}
Renders: What is the 2nd usable address on {{sean_subnet}}? {{ sean_subnet | next_nth_usable(2) }}
--------
Loop method for providing multiple IP addresses for use:
Jinja: {% raw %}
{% for n in range(10) %}
{{ sean_subnet | next_nth_usable(n) }}
{% endfor %} {% endraw %}
Renders:
{% for n in range(10) %}
{{ sean_subnet | next_nth_usable(n) }}
{% endfor %}
advanced playbook
host patterns
It is a recommended practice to enclose host patterns used on the command line in single quotes to protect them from unwanted shell expansion.
sample inventory
[student@controlnode ~]$ cat myinventory
web.example.com
data.example.com
[lab]
labhost1.example.com
labhost2.example.com
[test]
test1.example.com
test2.example.com
[datacenter1]
labhost1.example.com
test1.example.com
[datacenter2]
labhost2.example.com
test2.example.com
[datacenter:children]
datacenter1
datacenter2
[new]
192.168.2.1
192.168.2.2
- the name for a single managed host listed in the inventory.
- inventory host groups
- there is a special group named all that matches all managed hosts in the inventory
- There is also a special group named ungrouped, which includes all managed hosts in the inventory that are not members of any other group
- asterisk (*) wildcard character
- The wildcard host patterns match all inventory names, hosts, and host groups. They do not distinguish between names that are DNS names, IP addresses, or groups, which can lead to some unexpected matches.
- accomplishing the same thing as the all host pattern
- '*'
- contain a particular substring
- '*.example.com'
- matches all inventory names that end in .example.com
- '192.168.2.*'
- match the names of hosts or host groups that start with 192.168.2.
- 'datacenter*'
- match the names of hosts or host groups that begin with datacenter
- '*.example.com'
- comma-separated list
- labhost1.example.com,test2.example.com,192.168.2.2
- list of managed hosts
- lab,datacenter1
- list of groups
- labhost1.example.com,test2.example.com,192.168.2.2
- You can also mix managed hosts, host groups, and wildcards
- lab,data*,192.168.2.2
- logical AND, an item in a list starts with an ampersand character (&)
- lab,&datacenter1
- machines in the lab group only if they are also in the datacenter1 group
- lab,&datacenter1
- logical NOT, a list by using the exclamation point or "bang" character (!) in front of the host pattern
- datacenter,!test2.example.com
- datacenter group, except test2.example.com
- all,!datacenter1
- all hosts in the test inventory, except the managed hosts in the datacenter1 group
- datacenter,!test2.example.com
It is possible to point an alias at a particular IP address in your inventory by setting the ==ansible_host== host variable.
For example, you could have a host in your inventory named ==dummy.example==, and then direct connections using that name to the IP address ==192.168.2.1== by creating a ==host_vars/dummy.example== file containing the following host variable:
regular expression and exclude example
ansible-playbook -i config/inventory.all --limit '~k22is[0-9]{3},~k22il[0-9]{3},!k22is001,!k22is002' -f 50 -kK playbooks/install.yml
include/import
manage large playbooks by importing or including other playbooks or tasks from external files, either unconditionally or based on a conditional test
There are two operations that Ansible can use to bring content into a playbook. You can include content, or you can import content.
When you include content, it is a dynamic operation. Ansible processes included content during the run of the playbook, as content is reached.
When you import content, it is a static operation. Ansible preprocesses imported content when the playbook is initially parsed, before the run starts.
:star:dynamic include vs a static import
You can create a dedicated directory for task files, and save all task files in that directory. Then your playbook can simply include or import task files from that directory.
``` yml= - name: Prepare the web server import_playbook: web.yml
- name: Prepare the database server import_playbook: db.yml
``` yml=
- name: Install web server hosts: webservers tasks:
-
import_tasks: webserver_tasks.yml
:::info Not every role will have all of these directories. :::- When using the import_tasks feature, conditional statements set on the import, such as **when**, are applied to each of the tasks that are imported. - You **cannot use loops** with the import_tasks feature. - If you use a variable to specify the name of the file to import, then you cannot use a host or group inventory variable. ### role - [Roles — Ansible Documentation](https://docs.ansible.com/ansible/2.9/user_guide/playbooks_reuse_roles.html) - [Roles - using-roles-at-the-play-level — Ansible Documentation](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#using-roles-at-the-play-level) - roles - [Roles - Including roles: dynamic reuse — Ansible Documentation](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#including-roles-dynamic-reuse) - include_role - [Best way to organize roles/directory structure? : r/ansible](https://www.reddit.com/r/ansible/comments/g2s6wt/best_way_to_organize_rolesdirectory_structure/) Ansible roles provide a way for you to make it easier to **reuse** Ansible code generically. You can package, in a standardized directory structure, all the tasks, variables, files, templates, and other resources needed to provision infrastructure or deploy applications. A well-written role will allow you to pass variables to the role from the playbook that adjust its behavior, setting all the site-specific hostnames, IP addresses, user names, secrets, or other locally-specific details you need. In addition to writing, using, reusing, and sharing your own roles, you can get roles from other sources. Some roles are included as part of Red Hat Enterprise Linux, in the rhel-system-roles package. You can also get numerous community-supported roles from the Ansible Galaxy website. The top-level directory defines the name of the role itself. Files are organized into subdirectories that are named according to each file's purpose in the role, such as **tasks** and **handlers**. The **files** and **templates** subdirectories contain files referenced by tasks in other YAML files. When you use a roles section to import roles into a play, the roles will run first, before any tasks that you define for that play. For each play in a playbook, tasks execute as ordered in the tasks list. After all tasks execute, any notified handlers are executed. When a role is added to a play, role tasks are added to the beginning of the tasks list. If a second role is included in a play, its tasks list is added after the first role. Role handlers are added to plays in the same manner that role tasks are added to plays. Each play defines a handlers list. Role handlers are added to the handlers list first, followed by any handlers defined in the handlers section of the play. If a role is not in the project's roles directory, the roles_path will be checked to see if the role is installed in one of those directories, first match being used. ``` bash= [user@host roles]$ tree user.example user.example/ ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── README.md ├── tasks │ └── main.yml ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml
-
defaults
- The main.yml file in this directory contains the default values of role variables that can be overwritten when the role is used. These variables have low precedence and are intended to be changed and customized in plays.
- files
- This directory contains static files that are referenced by role tasks.
- handlers
- The main.yml file in this directory contains the role's handler definitions.
- meta
- The main.yml file in this directory contains information about the role, including author, license, platforms, and optional role dependencies.
- tasks
- The main.yml file in this directory contains the role's task definitions.
- templates
- This directory contains Jinja2 templates that are referenced by role tasks.
- tests
- This directory can contain an inventory and test.yml playbook that can be used to test the role.
- vars
- The main.yml file in this directory defines the role's variable values. Often these variables are used for internal purposes within the role. These variables have high precedence, and are not intended to be changed when used in a playbook.
Define a specific variable in either vars/main.yml or defaults/main.yml, but not in both places. Default variables should be used when it is intended that their values will be overridden.
:::warning Roles should not have site-specific data in them. They definitely should not contain any secrets like passwords or private keys. :::
one way to call Ansible roles.
``` yml=
- hosts: remote.example.com
roles:
- role1
- role2
In certain scenarios, it may be necessary to execute some play tasks before the roles. To support such scenarios, plays can be configured with a pre_tasks section. Any task listed in this section executes before any roles are executed. If any of these tasks notify a handler, those handler tasks execute before the roles or normal tasks.
Plays also support a post_tasks keyword. These tasks execute after the play's normal tasks, and any handlers they notify, are run.
``` yml= - name: Play to illustrate order of execution hosts: remote.example.com pre_tasks: - debug: msg: 'pre-task' notify: my handler roles: - role1 tasks: - debug: msg: 'first task' notify: my handler post_tasks: - debug: msg: 'post-task' notify: my handler handlers: - name: my handler debug: msg: Running my handler
Roles can be added to a play using an ordinary task, not just by including them in the roles section of a play. Use the include_role module to dynamically include a role, and use the import_role module to statically import a role.
``` yml=
- name: Execute a role as a task
hosts: remote.example.com
tasks:
- name: A normal task
debug:
msg: 'first task'
- name: A task to include role2 here
include_role: role2
tag
Tags are metadata that you can attach to the tasks in an Ansible playbook. They allow you to selectively target certain tasks at runtime, telling Ansible to run (or not run) certain tasks.
When using an import (such as import_tasks), Ansible also applies related tags to all of the imported tasks.
When a dynamic include_task is used, the tags apply only to the include itself. The tasks within the include do not inherit the tags, so they must also be defined on the included tasks
``` bash= ansible-playbook example.yml --list-tags ansible-playbook example.yml --tags "configuration,packages" --list-tasks
## dubug
- [debug – Print statements during execution — Ansible Documentation](https://docs.ansible.com/ansible/2.9/modules/debug_module.html)
- [Best Practices — Ansible Documentation](https://docs.ansible.com/ansible/2.9/user_guide/playbooks_best_practices.html)
- [Check Mode (“Dry Run”) — Ansible Documentation](https://docs.ansible.com/ansible/2.9/user_guide/playbooks_checkmode.html)
- [Testing Strategies — Ansible Documentation](https://docs.ansible.com/ansible/2.9/reference_appendices/test_strategies.html)
- [8 ways to speed up your Ansible playbooks | Enable Sysadmin](https://www.redhat.com/sysadmin/faster-ansible-playbook-execution)
- [Ansible: No python interpreters found for host - DEV Community](https://dev.to/vidyasagarmsc/ansible-no-python-interpreters-found-for-host-4dpf)
- [How to fix 'Permission denied' error in Ansible script module | LabEx](https://labex.io/tutorials/ansible-how-to-fix-permission-denied-error-in-ansible-script-module-415726)
- AppArmor on Ubuntu
-v, -vv
``` bash=
# syntax check
ansible-playbook --syntax-check webserver.yml
# The `ansible-playbook` command also provides a `--diff` option. This option reports the changes made to the template files on managed hosts. If used with the --check option, those changes are displayed in the command's output but not actually made.
ansible-playbook --check --diff webserver.yml
Log Files
By default, Ansible is not configured to log its output to any log file.
It provides a built-in logging infrastructure that can be configured through the log_path
parameter in the default
section of the ansible.cfg
configuration file, or through the $ANSIBLE_LOG_PATH
environment variable.
Debug Module
This module can display the value for a certain variable at a certain point in the play.
displays the value at run time of the ansible_facts['memfree_mb'] fact as part of a message printed to the output of ansible-playbook ``` yml= - name: Display free memory debug: msg: "Free memory for this system is {{ ansible_facts['memfree_mb'] }}"
displays the value of the output variable.
``` yml=
- name: Display the "output" variable
debug:
var: output
verbosity: 2
Troubleshooting Ansible Managed Hosts
You can use the ansible-playbook --check
command to run smoke tests on a playbook.
This option executes the playbook without making changes to the managed hosts' configuration.
collections
offiline installation
- https://docs.ansible.com/ansible/latest/collections_guide/collections_downloading.html
mkdir -p /tmp/ansible-collections-offiline
ansible-galaxy collection download -r /tmp/self-requirements.yml -p /tmp/ansible-collections-offiline
self-requirements.yml
---
collections:
- name: ansible.posix
version: 1.5.4
- name: ansible.utils
version: 2.10.3
- name: community.general
version: 7.3.0
- name: community.crypto
version: 2.15.1
- name: cisco.nxos
version: 5.1.0