Configuration file
When installed, the ansible package provides a base configuration file located at /etc/ansible/ansible.cfg
.
Priority in which the configuration files are processed:
$ANSIBLE_CONFIG
(an environment variable)./ansible.cfg
(in the current directory)~/.ansible.cfg
(the user’s home directory)/etc/ansible/ansible.cfg
To find out what config file is in use, run the following:
1 2 3 4 |
$ ansible --version ansible 2.7.9 config file = /home/ansible/ansible.cfg |
To get started, copy configuration files to the home directory:
1 2 3 |
$ cp /etc/ansible/ansible.cfg ~/ansible.cfg $ cp /etc/ansible/hosts ~/inventory |
Note the maximum number of simultaneous connections that Ansible makes is controlled by the forks parameter in ansible.cfg
:
1 2 |
forks 5 |
Inventory
Example inventory configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ cat ~/inventory [myself] controlnode.example.com host=controlnode ansible_connection=local [web] managedhost[1:2].example.com host=managedhost[1:2] [db] managedhost[3:4].example.com host=managedhost[3:4] ansible_user=dbadmin [lab:children] web db |
Listing hosts from inventory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[ansible@controlnode ~]$ ansible all --list-hosts hosts (5): controlnode.example.com managedhost1.example.com managedhost2.example.com managedhost3.example.com managedhost4.example.com [ansible@controlnode ~]$ ansible web --list-hosts hosts (2): managedhost1.example.com managedhost2.example.com [ansible@controlnode ~]$ ansible db --list-hosts hosts (2): managedhost3.example.com managedhost4.example.com |
There is a special group named “all” that matches all managed hosts in the inventory.
1 2 |
- hosts: all |
There is also a special group named “ungrouped” which matches all managed hosts in the inventory that are not members of any group.
1 2 |
- hosts: ungrouped |
Quote host patterns used on the CLI to protect them from unwanted shell expansion:
1 2 3 |
- hosts: '*.example.com' - hosts: '10.0.0.*' |
Multiple entries in the inventory can be referenced using lists:
1 2 |
- hosts: web,managedhost1.example.com |
If you have static and dynamic inventory files in the same directory, then they are merged and treated as one inventory!
Show inventory for all:
1 |
$ ansible "*" -i ~/inventory --list-hosts |
Target two inventories from the command line like this:
1 |
$ ansible-playbook play.yml -i staging -i production |
Aggregating inventory sources with a directory::
1 2 3 4 5 6 |
inventory/ openstack.yml # configure inventory plugin to get hosts from Openstack cloud dynamic-inventory.py # add additional hosts with dynamic inventory script static-inventory # add static hosts and groups group_vars/ all.yml # assign variables to all hosts |
Privilege escalation configuration in ansible.cfg
:
1 2 3 4 5 6 |
[privilege_escalation] become = false become_method = sudo become_user = root become_ask_pass = false |
How to set the default user to use for playbooks in ansible.cfg
:
1 2 |
remote_user = ansible |
How to set the log file in ansible.cfg
:
1 2 |
log_path = /home/ansible/ansible.log |
Ad Hoc Commands
ping
– Validate if server is up and recheable. No required parameters.
1 2 |
$ ansible all -m ping $ ansible all -m ping --limit managedhost1 |
command
– If no module is defined, Ansible uses the internally predefined “command” module.
12345$ ansible all -m command -a /usr/bin/hostname -o$ ansible all -a "/usr/bin/echo hi"$ ansible all -a "/sbin/reboot"$ ansible all -a "/sbin/reboot" -f 10$ ansible managedhost1 -a '/bin/yum list installed postfix'
yum
– Use yum package manager. Parametersname
andstate
.
1 2 3 |
$ ansible all -m yum -a 'name=tcpdump state=present' $ ansible all -m yum -a 'list=tcpdump' $ ansible managedhost1 -b -m yum -a "name=elinks state=latest" |
service
– Control Daemons. Parametersname
andstate
.
1 |
$ ansible managedhost1 -m service -a 'name=httpd state=started' |
user
– Manipulate system users. Parametersname
.
Creating a user sam in the labservers group:
1 |
$ ansible labservers -i inventory -b -m user -a "name=sam" |
Adding user sam to the wheel group. Append parameter is needed due to we dont wan’t to wipe out group file:
1 |
$ ansible labservers -b -m user -a "name=sam append=yes groups=wheel" |
copy
– Copy files. Parameterssrc
.
1 |
$ ansible all -m copy -a "src=/home/miro/ansible/testfile dest=/tmp/testfile" |
Use the copy module to change content of a file:
1 |
$ ansible all -b -m copy -a 'content="Host managed by Ansible\n" dest=/etc/motd' |
file
– Work with files. Parameterspath
.
1 |
$ ansible managedhost1 -m file -a "path=/home/miro/newfile state=touch" |
Informations about the file:
1 |
$ ansible managedhost1 -m file -a "path=/home/miro/newfile" |
Changing properties of the file:
1 |
$ ansible managedhost1 -m file -a "path=/home/miro/newfile mode=0400" |
setup
– Gather ansible facts. No required parameters.
1$ ansible managedhost1 -b -m setupdebug
– prints statements during execution and can be useful for debugging variables or expressions without necessarily halting the playbook
1 |
$ ansible managedhost1 -m debug -a "var=vars" |
git
– Interact with git repositories. Parametersrepo
anddest
.
Note: if possible, try to avoid the command
, shell
and raw
modules in playbooks! It’s easy to write non-idempotent playbooks this way.
Documentation
Ansible module documentation and playbook snippets:
1 2 3 4 |
$ ansible-doc -l $ ansible-doc -s module_name $ ansible-doc module_name |
Ansible-playbook command
Ansible executes plays and tasks in the order they are presented! How to check for YAML syntax errors:
1 2 |
$ ansible-playbook --syntax-check playbook.yaml |
Limit playbook to specyfied host:
1 |
$ ansible-playbook playbook.yaml -l managedhost1 |
Which host(s) the playbook applies to?
1 2 |
$ ansible-playbook playbook.yaml --list-hosts |
Whats tasks will be performed?
1 2 |
$ ansible-playbook playbook.yaml --list-tasks |
Run one task at a time:
1 2 |
$ ansible-playbook playbook.yaml --step |
Playbook dry-run:
1 2 |
$ ansible-playbook -C playbook.yaml |
Playbook step-by-step execution:
1 2 |
$ ansible-playbook --step playbook.yaml |
The beginning of each play begins with a single dash followed by a space.
To only run the first task, the –tags
argument can be used:
1 |
$ ansible-playbook main.yml --tags 'production' |
To skip tagged task:
1 |
$ ansible-playbook main.yml --skip-tags 'production' |
Playbook
Playbook attributes:
hosts
– hosts which are managed by ansible. Must be defined in every play.user
– user which will run ansible playbookremote_user
– can be used to define the user that runs the tasks.become
– can be used to enable privilege escalation.become_method
– can be used to define the privilege escalation method.become_user
– can define the user account to be used for privilege escalation.gether_facts
– to gether or not to gether the factstasks
– is defined as a list of dictionaries.block
– can be used to group related tasks together.name
– can be used to give a descriptive label to a play.
Skeleton of simple playbook:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
--- - hosts: managedhost1 user: ansible become: yes #remote_user: ansible #become_method: sudo #become_user: root #gether_facts: no vars_files: path_to_file vars: variable1: value1 variable2: value2 tasks: - block: - name: name of task1 - name: name of task2 - name: name of task3 - block: - name: name of task4 - name: name of task5 - name: name of task6 - block: - name: name of task7 - name: name of task8 |
Common modules:
yum
– Installs, upgrade, downgrades, removes, and lists packagesyum_repository
– Add or remove YUM repositoriesservice
– Controls services on remote hosts.file
– Set attributes of files, symlinks or directories.copy
– copies a file from the local or remote machine to a location on the remote machine.firewalld
– manages firewalld rules.template
– tTemplates processed by the Jinja2 templating
language.lineinfile
– ensures a particular line is in a file, or replace
an existing line using a back-referenced regular expression.blockinfile
– This module will insert/update/remove a block of multi-line textreplace
– replace all instances of a pattern within a filecommand
– The given command will be executed on all selected nodes. The command(s) will not be processed through the shell, so variables like$HOME' and operations like
“<“‘,">"',
“|”‘,";"' and
“&”‘ will not work. Use the [shell] module if you need these features.shell
– takes the command name followed by a list of space-delimited arguments.debug
– prints statements during execution and can be useful for
debugging variables or expressionsfetch
– Download a file from managed host.get_url
– Downloads files from HTTP, HTTPS, or FTP to the remote server.uri
– Interacts with HTTP and HTTPS web servicesmysql_user
– Adds or removes a user from a MySQL database.mysql_db
– Add or remove MySQL databases from a remote hostcron
– manage crontab and environment variables entries.at
– schedule a command or script file to run once in the futureselinux
– Configures the SELinux mode and policypackage_facts
– Return information about installed packages as factsparted
– Allows configuring block device partition using theparted' command line tool.
lvg
- create voulme grouplvol
- create logical volumefilesystem
- creates a filesystem.mount
- controls active and configured mount points in /etc/fstab’.archive -
Packs an archive. It is the opposite of [unarchive].unarchive
– Unpacks an archive. It will not unpack a compressed file that does not contain an archive.authorized_key
– Adds or removes SSH authorized keys for particular user accounts.add_host
– create new hosts and groups in inventory
For each play in a playbook, you get to choose which machines in your infrastructure to target and what remote user to complete the steps as. You can use keyword become
on a particular task instead of the play:
1 2 3 4 5 6 7 8 9 10 11 |
--- - hosts: webservers remote_user: ansible serial: 2 tasks: - service: name: httpd state: started become: yes become_method: sudo |
Use the serial keyword to run the hosts through the play in batches. Host variables take precedence over group variables, but variables defined by a playbook take precedence over both.
Including and Importing Files
When you include content, then Ansible processes included content during the run of the playbook, as content is required.
When you import content, Ansible processes imported content when the playbook is initially read, before the run starts.
Note that include
was replaced in Ansible 2.4 with new directives such as:
vars_files
include_vars
include_tasks
import_tasks
import_playbook
You can find documentation in ansible-doc command.
These can be used to enhance the ability to reuse tasks and playbooks:
1 2 3 4 5 6 7 8 9 10 11 |
--- hosts: all tasks: - name: Import task file and use variablesimport_playbook: include_vars: variables.yml include_tasks: tasks1.yml import_tasks: tasks2.yml - name: Include a play after another play import_playbook: otherplays.yml |
Examples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
tasks: - name: Import task file and use variables import_tasks: task.yml vars: package: vsftpd service: vsft - name: Install the {{ package }} package yum: name: "{{ package }}" state: latest - name: Start the {{ service }} service service: name: "{{ service }}" enabled: true state: started |
Ansible Variables
Places to define variables:
vars
,include_vars
- Command line (
-e
) as key=value:
1 |
$ ansible-playbook play.yml -e 'key=value' |
or YAML/JSON
1 |
$ ansible-playbook play.yml -e '{"myVar":"myValue","anotherVar":"anotherValue"}' |
if filename prepend with @
1 |
$ ansible-playbook pla.yml -e "@filename" |
- Variables for hosts and host groups can be defined by creating two directories,
group_vars
andhost_vars
, in the same working directory as the inventory file or directory.
If the value to define varies for each datacenter, a group variable can be set for each datacenter:
1 2 3 4 |
[admin@station project]$ cat ~/project/group_vars/datacenter1 package: httpd [admin@station project]$ cat ~/project/group_vars/datacenter2 package: apache |
If the value to be defined varies for each host in every datacenter, using host variables is recommended:
Shell
1 2 3 4 5 6 7 8 |
[admin@station project]$ cat ~/project/host_vars/demo1.example.com package: httpd [admin@station project]$ cat ~/project/host_vars/demo2.example.com package: apache [admin@station project]$ cat ~/project/host_vars/demo3.example.com package: mariadb-server [admin@station project]$ cat ~/project/host_vars/demo4.example.com package: mysql-server |
The directory structure for project, if it contained all of the example files above, might look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
project ├── ansible.cfg ├── group_vars │ ├── datacenters │ ├── datacenters1 │ └── datacenters2 ├── host_vars │ ├── demo1.example.com │ ├── demo2.example.com │ ├── demo3.example.com │ └── demo4.example.com ├── inventory └── playbook.yml |
- Roles, blocks, and inventories
Whether or not you define any variables, you can access information about your hosts with the Special Variables Ansible provides, including “magic” variables, facts, and connection variables.
The most commonly used magic variables are:
hostvars
groups
group_names
inventory_hostname
Prints all magic variables:
1 |
$ ansible localhost -m debug -a "var=vars" |
1 |
$ ansible localhost -m debug -a "var=hostvars" |
The group_names
variable contains a list of all the groups the current host is in. We can use it to install specific packages:
1 2 3 4 5 6 7 8 9 10 |
tasks: - name: Install webserver packages package: name: "{{ item }}" state: latest loop: - httpd - firewalld when: "'webserver' in group_names" |
The hostvars
variable lets you access variables for another host, including facts that have been gathered about that host.
1 2 3 |
{{ hostvars['managedhost1.example.com']['ansible_default_ipv4']['address'] }} {{ hostvars['managedhost1.example.com']['ansible_fqdn'] }} |
The inventory_hostname
variable is the name of the hostname as configured in Ansible’s inventory host file. The groups variable is a list of all the groups in the inventory.
1 2 |
when: inventory_hostname in groups["webservers"] when: inventory_hostname in groups.webservers |
Ansible Facts
How to print facts for all hosts?
1 2 3 4 5 6 7 |
- name: Ansible fact dump hosts: all tasks: - name: Print all Ansible facts debug: var: ansible_facts |
1 2 3 4 5 6 7 |
- name: Ansible package fact dump hosts: all tasks: - name: Gather package facts package_facts: manager: auto |
Before Ansible 2.5, facts were injected as individual variables prefixed with the string ansible_
instead of being part of the ansible_facts
variable.
At the time of writing this, Ansible recognises both the new fact naming system (using ansible_facts
) and the old pre 2.5 naming system where facts are injected as separate variables.
You can use an ad-hoc command to run the setup module to print the value of all facts:
1 2 |
$ ansible localhost -m setup |
or
1 |
$ ansible localhost -m gather_facts |
Filter results:
1 2 |
$ ansible localhost -m setup -a 'filter=*distribution*' |
Some commonly used facts. In no particular order:
1 2 3 4 5 6 7 8 9 |
ansible_facts['hostname'] = ansible_hostname ansible_facts['fqdn'] = ansible_fqdn ansible_facts['default_ipv4']['address'] = ansible_default_ipv4.address ansible_facts['distribution'] = ansible_distribution ansible_facts['distribution_major_version'] = ansible_major_version ansible_facts['domain'] = ansible_domain ansible_facts['memtotal_mb'] = ansible_memtotal_mb ansible_facts['processor_count'] = ansible_processor_count |
To disable fact gathering for a play, you can set the gather_facts
keyword to “no”:
1 2 |
gather_facts: no |
Custom facts can be defined in a static file, formatted as an INI file or using JSON and placed in /etc/ansible/facts.d
and the file name must end in .fact
.
They can also be executable scripts which generate JSON output, just like a dynamic inventory script. Note that custom fact files cannot be in YAML format!
Creating custom facts on managedhost1:
1 2 3 4 5 6 |
[miro@managedhost1 ~]$ sudo mkdir -p /etc/ansible/facts.d [miro@managedhost1 ~]$ sudo vim /etc/ansible/facts.d/prefs.fact [miro@managedhost1 ~]$ cat /etc/ansible/facts.d/prefs.fact [location] type=physical datacenter=Katowice |
On the controlnode we can print local facts from the managedhost1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[miro@controlnode ansible]$ ansible managedhost1 -m setup -a "filter=ansible_local" managedhost1 | SUCCESS => { "ansible_facts": { "ansible_local": { "prefs": { "location": { "datacenter": "Katowice", "type": "physical" } } } }, "changed": false } |
Ansible Loops, Conditionals
Ansible supports iterating a task over a set of items using the loop
keyword. A simple loop iterates a task over a list of items.
Since Ansible 2.5, the recommended way to write loops is to use the loop
keyword. The old syntax used loops prefixed with with_
.
1 2 3 4 5 6 7 8 9 |
- name: Ensure that packages are present package: name: "{{ item }}" state: present loop: - firewalld - httpd - vsftpd |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- name: Ensure that web server ports are open vars: packages: - http - https firewalld: service: "{{ item }}" immediate: true permanent: true state: enabled loop: "{{ packages }}" |
Ansible conditionals operators
Operator | Example |
Equal | "{{ 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" |
Variable is set to 1, True, or yes | "{{ available_memory }}" |
Variable is set to 0, False, or no | "not {{ available_memory }}" |
Value is present in a variable or an array | "{{ users }} in users["db_admins"]" |
Logical AND and OR operations are supported:
1 2 3 |
when: ansible_domain == "example.com" and ansible_distribution == "RedHat" when: ansible_processor_cores == "1" or ansible_processor_cores == "2" |
Loops and conditionals can be combined:
1 2 3 4 5 6 7 8 9 |
- name: Ensure that packages are present on RedHat package: name: "{{ item }}" state: present loop: - firewalld - httpd when: ansible_distribution == "RedHat" |
Lookups
Lookup plugins allow access to outside data sources. Lookups occur on the local computer, not on the remote computer. One way of using lookups is to populate variables:
1 2 3 4 5 6 |
vars: motd_value: "{{ lookup('file', '/etc/motd') }}" tasks: - debug: msg: "motd value is {{ motd_value }}" |
Ansible Handlers
Handlers always run in the order specified by the handlers section of the play. A handler called by a task in the tasks part of the playbook will not run until all of the tasks under tasks have been processed.
1 2 3 4 5 6 7 8 |
notify: restart httpd service handlers: - name: restart httpd service service: name: httpd state: restarted |
If 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. Use the following to force execution of the handler:
1 2 |
force_handlers: yes |
Handlers are notified when a task reports a “changed” result. Handlers are not notified when it reports an “ok” or “failed” result.
Ignore errors:
1 2 |
ignore_errors: yes |
Commonly Used Files Modules with Examples
The file module acts like chcon
when setting file contexts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
- name: Create a file hosts: localhost become: true tasks: - name: Create a file and set permissions file: path: /root/file owner: root group: root mode: 0640 state: touch setype: user_tmp_t - name: SELinux type is persistently set to user_tmp_t sefcontext: target: /root/file setype: user_tmp_t state: present |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- name: Copy a file hosts: localhost become: true tasks: - name: Copy a file copy: src: file dest: /root/file force: yes owner: root group: ansible mode: 0640 |
1 2 3 4 5 6 7 8 9 10 |
- name: Add a line to a file hosts: localhost become: true tasks: - name: Add a new line to a file lineinfile: path: /root/file line: "this is the new line" state: present |
1 2 3 4 5 6 7 8 9 10 11 12 |
- name: Add block of text to a file hosts: localhost become: true tasks: - name: Add a block of text to an existing file blockinfile: path: /root/file block: | This is the first line to be added. This is the second line to be added. state: present |
1 2 3 4 5 6 7 8 9 10 |
- name: Download a file from managed hosts hosts: localhost become: false tasks: - name: Fetch a file fetch: src: /etc/hosts dest: my-folder flat: no |
1 2 3 4 5 6 7 8 |
- name: Download a file from remote host hosts: localhost become: false - name: Download file from URL get_url: url: http://katello.hl.local/pub/index.html dest: /tmp/index.html |
1 2 3 4 5 6 7 8 9 10 |
- name: Disable SSH root login hosts: localhost become: true tasks: - name: Modify SSH server config lineinfile: dest: "/etc/ssh/sshd_config" regexp: "^PermitRootLogin" line: "PermitRootLogin no" |
Jinja2 Templates
Similar to Puppet. Puppet templates are based upon Ruby’s ERB, Ansible templates are based upon Jinja2.
A file containing a Jinja2 template does not need to have any specific file extension.
Use the template module to deploy it:
1 2 3 4 5 6 7 8 9 |
- name: Use a template file hosts: localhost gather_facts: yes tasks: - name: Deploy index.html template: src: templates/index.j2 dest: /var/www/html/index.html |
1 2 3 |
$ cat templates/index.j2 The system kernel is: {{ ansible_kernel }} |
Roles
Similar to Puppet modules, reusable code in a modular fashion. Create a role skeleton:
1 2 |
$ mkdir roles && cd roles $ ansible-galaxy init my_role |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ tree my_role/ my_role/ ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── README.md ├── tasks │ └── main.yml ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml |
The original way to use roles is via the roles:
option for a play:
1 2 3 4 5 |
--- - name name of a task hosts: webservers roles: - webservers |
Changing a role’s behavior with variables:
1 2 |
--- - <code>name name of a task </code> hosts: webservers roles: - role: webservers variable: value |
As of Ansible 2.4, you can use roles inline with any other tasks using import_role
or include_role
:
1 2 3 4 5 6 7 8 9 10 11 12 |
--- - hosts: webservers tasks: - debug: msg: "before we run our role" - import_role: name: example - include_role: name: example - debug: msg: "after we ran our role" |
The order of execution for your playbook is as follows:
- Any
pre_tasks
defined in the play. - Any handlers triggered so far will be run.
- Each role listed in roles will execute in turn (with dependencies).
- Any tasks defined in the play.
- Any handlers triggered so far will be run.
- Any
post_tasks
defined in the play. - Any handlers triggered so far will be run.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
--- - hosts: remote.example.com pre_tasks: - debug: msg: 'hello' roles: - role1 - role2 tasks: - debug: msg: 'still busy' post_tasks: - debug: msg: 'goodbye' |
Role dependencies are always executed before the role that includes them, and may be recursive.
Search for roles from the CLI:
1 2 3 4 |
$ ansible-galaxy search --author redhat $ ansible-galaxy search --galaxy-tags tomcat $ ansible-galaxy search tomcat --platforms EL |
Ansible Galaxy is a public library of Ansible roles written by users. Similar to Puppet Forge. Install a role from Galaxy:
1 2 |
$ ansible-galaxy install <author.name> -p ~/.ansible/roles |
List installed roles:
1 2 3 |
$ ansible-galaxy list -p ~/.ansible/roles/ - <author.name>, 1.9.5 |
To define role source, use requirements.yml
:
1 2 3 4 5 |
- src: http://katello.hl.local/pub/ansible-haproxy.tar.gz name: haproxy - src: http://katello.hl.local/pub/ansible-mysql.tar.gz name: mysql |
1 2 |
$ ansible-galaxy init -r requirements.yml |
RHEL System Roles
1 2 3 4 5 6 7 8 |
$ sudo yum install rhel-system-roles $ ls /usr/share/doc/rhel-system-roles-1.0/ kdump network postfix selinux timesync |
1 2 3 4 5 6 7 8 9 10 11 12 |
$ ansible-galaxy list - rhel-system-roles.selinux, (unknown version) - linux-system-roles.network, (unknown version) - rhel-system-roles.postfix, (unknown version) - rhel-system-roles.timesync, (unknown version) - rhel-system-roles.kdump, (unknown version) - linux-system-roles.timesync, (unknown version) - linux-system-roles.kdump, (unknown version) - linux-system-roles.postfix, (unknown version) - rhel-system-roles.network, (unknown version) - linux-system-roles.selinux, (unknown version) |
Password Hashing
How to generate SHA512 crypted passwords for the user module? The answer is taken from Ansible FAQ.
1 2 3 4 5 |
- name: Create user with password user: name: sandy password: "{{ user_pw | password_hash('sha512') }}" |
meta – Execute Ansible Actions
Meta tasks are a special kind of task which can influence Ansible internal execution or state. Choices:
1 2 3 4 5 6 7 8 |
* flush_handlers * refresh_inventory * noop * clear_facts * clear_host_errors * end_play * reset_connection |
Example meta task for how to run handlers after an error occurred:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
tasks: - name: Attempt and graceful roll back demo block: - debug: msg: 'I execute normally' notify: run me even after an error - command: /bin/false rescue: - name: make sure all handlers run meta: flush_handlers handlers: - name: run me even after an error debug: msg: 'This handler runs even on error' |
Note that meta is not really a module as such it cannot be overwritten.
Ansible Check Mode (Dry Run)
When ansible-playbook
is executed with --check
or -C
it will not make any changes on remote systems. To modify the check mode behavior of individual tasks, you can use the check_mode option:
1 2 3 4 5 6 7 |
- name: this task will run under checkmode and not change the system lineinfile: line: "PermitRootLogin no" dest: /etc/ssh/sshd_config state: present check_mode: yes |
The above will force a task to run in check mode, even when the playbook is called without --check
.
Ansible Playbook Debugger
Ansible includes a debugger as part of the strategy plugins. This debugger enables you to debug as task.
The debugger keyword can be used on any block where you provide a name attribute, such as a play, role, block or task.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
--- - hosts: ansible2.hl.local tasks: - unarchive: src: files/archive.tgz dest: /tmp/ remote_src: no - name: archive file debugger: on_failed archive: path: /tmp/archive.html dest: /tmp/file.gz format: tgz |
Ansible Ignoring Failed Commands
Playbooks will stop executing any more steps on a host that has a task fail. To ignore this behaviour, set ignore_errors
to true
:
1 2 3 4 |
- name: this will not be counted as a failure command: /bin/false ignore_errors: yes |
This is useful when you expect a task to fail. For example, you are checking if Apache website is reachable. It may be unreachable, but you don’t want the play to fail because of that.
Ansible Run Once
There may be a need to only run a task one time for a batch of hosts. This can be achieved by configuring run_once
on a task:
1 2 3 |
- command: /tmp/upgrade_database.sh run_once: true |
Ansible Aborting the Play
There will be cases when you will need to abort the entire play on failure, not just skip remaining tasks for a host, to avoid breaking the system. The any_errors_fatal
play option will mark all hosts as failed if any fails, causing an immediate abort:
1 2 3 4 5 |
- hosts: all any_errors_fatal: true roles: - database |
Ansible Keywords
These are some of the keywords available on common playbook objects.
Notable play keywords:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
any_errors_fatal become become_method become_user check_mode debugger force_handlers gather_facts handlers hosts ignore_errors name no_log order port post_tasks pre_tasks remote_user roles run_once serial tasks vars vars_files |
Notable block keywords:
1 2 3 4 5 |
always block rescue when |
Notable task keywords:
1 2 3 |
loop register |
Ansible Setting Defaults for Modules
It can be useful to define default arguments for a particular module using the module_defaults
attribute:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
- hosts: all become: true module_defaults: file: owner: root group: devops mode: "664" state: touch tasks: - file: path: /tmp/file1 - file: path: /tmp/file2 - file: path: /tmp/file3 |
It is a time saver when you need to use the same module repeatedly.
Ansible Vault
1 2 |
$ ansible-vault [create|decrypt|edit|encrypt|rekey|view] vaultfile.yml |
By default, Ansible uses functions from the python-crypto
package to encrypt and decrypt vault files. To speed up decryption at startup, you install the python-cryptography
package.
1 2 3 |
$ ansible-vault create vaultfile.yml $ ansible-vault view vaultfile.yml |
Check syntax of a playbook that uses the vault file:
1 2 |
$ ansible-playbook --syntax-check --ask-vault-pass playbook.yml |
Create a password file to use for the playbook execution:
1 2 |
$ echo "ansible" > key $ ansible-playbook --syntax-check --vault-password-file=key playbook.yml |
Ansible Best Practices
Tips for making the most of Ansible and Ansible playbooks.
- Always mention the state. The
state
parameter is optional to a lot of modules. Whetherstate=present
orstate=absent
, it is always best to leave that parameter in your playbooks to make it clear. - Generous use of whitespace to break things up, and use of comments, which start with
#
, is encouraged. - Always name tasks. It is recommended to provide a description about why something is being done!
- Keep it simple. Do not attemtp to use every feature of Ansible together, all at once. Use what works for you!
- Ansible best practice has no limit on the amount of variable and vault files or their names.
Ansible and Vim
When writing playbooks in vim editor, modify its action in response to Tab key entries. Add the following line to $HOME/.vimrc
. It will perform a two space indentation when the Tab key is pressed.
1 2 |
autocmd FileType yaml setlocal ai ts=2 sw=2 et |
Settings explained:
1 2 3 4 5 |
ai - autoindent (turns it on) sw=2 - shiftwidth (indenting is 2 spaces) et - expandtab (do not use actual tab character) ts=2 - tabstop (tabs are at proper location) |
Two other helpful but optional vim settings:
1 2 3 |
nu (show line number) cursorline (line to show current cursor position) |