Ansible Cheat Sheet
Purpose:
- Remember things that would be embarassing to forget.
- Be able to automate things fast.
Install
sudo apt update && sudo apt install -y ansible
sudo dnf install ansible # Fedora
git clone https://github.com/ansible/ansible.git
cd ./ansible
source ./hacking/env-setup
ansible myhost --become -k -K -m raw -a "yum install -y python3" # hosts without python
Configure
In order:
- ANSIBLE_CONFIG (environment variable if set)
- ansible.cfg (in the current directory)
- ~/.ansible.cfg (in the home directory)
- /etc/ansible/ansible.cfg
ansible.cfg
[defaults]
inventory = ./hosts
host_key_checking = False
nocows = True
Show current effective config:
ansible-config list # Print all config options
ansible-config dump # Dump configuration
ansible-config view # View configuration file
Inventory
- INI or YAML inventory
/etc/ansible/hosts
192.0.3.25
server1.lab.net
server2.lab.net
[webservers]
web1
web2
web3
[web:vars]
port=8080
x=50
datadir="/data"
[web:children]
dev
prod
[dev]
test1
[prod]
server1
Variable files/dirs for groups and hosts:
/etc/ansible/group_vars/all | all |
/etc/ansible/group_vars/web.yaml | var file for web group |
/etc/ansible/group_vars/db/data.yaml | var file for db group |
/etc/ansible/host_vars/host1.yaml | vars for host1 |
/etc/ansible/host_vars/test1/data1.yaml | file for host test1 |
Adhoc Runs
ansible all -m ping # ping all hosts
ansible all -a "uname -a" # command module - default
ansible all -m ping -u user1 -bkK # sudo to root, ask pass
ansible web -m shell -a 'echo test > output.txt' # webhosts, shell module
ansible web -m copy -a "src=/etc/hosts dest=/tmp/hosts" # copy module with args
ansible web -a "/sbin/reboot" -bkK -f 10 -u testuser # 10 parallel forks
ansible all -m ansible.builtin.setup # see all facts
Playbooks
ansible-playbook test.yaml # run a playbook ( need SSH key )
ansible-playbook test.yaml -bkK # prompt for SSH pass and sudo pass and become
ansible-playbook test.yaml --limit host1 # limit to specified host
ansible-playbook test.yml -f 10 # with more options ( 10 forks )
ansible-playbook test.yaml --check
ansible-playbook test.yaml --diff
ansible-playbook test.yaml --list-hosts
ansible-playbook test.yaml --list-tasks
ansible-playbook test.yaml --syntax-check
Playbook example:
web_deploy.yaml
- name: Update db servers
hosts: databases
remote_user: admin
become: yes
become_user: postgres
vars:
trigger_task: true
supported_os:
- RedHat
- Fedora
vars_files:
- my_variables.yaml
- db_params.yaml
gather_facts: false
tasks:
- name: Ensure postgresql is at the latest version
ansible.builtin.yum:
name: postgresql
state: latest
Privilege Escalation - Become
Commandline args:
-b ( --become ) ( typcially sudo for root )
-k ( --ask-pass )
-K ( --ask-become-pass ) # prompt for become password
--become_user=postgres # doesn't imply --become
--become-method=su # sudo is default
-C --check # check only
-u user1 # connect as a user
-f 10 # 10 parallel forks
Playbook options ( specify inside playbook, don’t need to but you can ):
become: yes # enable become, doesn't imply prompting
become_user: postgres # user to become, default is root, doesn't imply become
become_method: su # alternate methods you could use
remote_user: admin # user for initial SSH connection
- NOTE - remote_user used to be ‘user’
- NOTE - Might need to install sshpass before you can prompt for SSH passwords( see install page )
- NOTE - Change user after login ( usually sudo ): play level or task level
ISSUE
- if become user is unprivileged the modules that are copied over are made world readable ( sec issue )
- sometimes this also doesn’t work and breaks things
- workarounds exist, including pipelining, ( or chownin when copied as root in v 2.1 and up )
- v 2.1 and up won’t allow world readable by default (results in error), fix with this:
ansible.cfgallow_world_readable_tmpfiles
Vars
Vars on command line:
ansible-playbook release.yml -e "version=1.23.45 other_variable=foo"
ansible-playbook release.yml --extra-vars '{"version":"1.23.45","other_variable":"foo"}' # JSON for non-YAML
ansible-playbook release.yml --extra-vars "@some_file.json" # JSON or YAML file
var1: !unsafe 'this variable has {{ characters that should not be treated as a jinja2 template' # unsafe chars
Prompt for variable:
vars_prompt:
- name: ansible_password
prompt: "Enter password"
private: yes
- name: ansible_b
Vars file:
---
somevar: somevalue
password: magic
a: 1
b: 2
c: 3
list1:
- apple
- banana
- toast
var1: "test"
Register Variables:
- name: Read file
ansible.builtin.shell: cat /etc/passwd | head -n 4
register: result
- name: Print Var
ansible.builtin.debug:
var: result
- name: Print Msg
ansible.builtin.debug:
msg: "This is it: {{ result }}"
- name: Print stdout
ansible.builtin.debug:
var: result.stdout
- name: Print stdout lines
ansible.builtin.debug:
var: item
loop: "{{ result.stdout_lines }}"
- name: Print result
ansible.builtin.debug:
var: result.rc
- name: Check if string found
ansible.builtin.debug:
msg: "Found"
when: result.stdout.find('root') != -1
List / Dictionary / Print / Loop
with_ | can be used for looping |
loop | added in 2.5, not full replacement for with_, equivalent to with_list, best for simple loops |
---
- name: A Test Playbook
hosts: all
vars:
list1:
- RedHat
- Fedora
- Debian
- Ubuntu
dict1:
OS: Ubuntu
IP: 192.168.3.2
CPU: intel
Mem: 32
tasks:
- name: Print List / Dict
ansible.builtin.debug:
msg: "{{ list1 }} {{ dict1 }} {{ list1[2] }} {{ dict1['OS'] }}"
- name: Loop Over List
ansible.builtin.debug:
msg: "Value: {{ item }}"
loop: "{{ list1 }}"
- name: Loop Over Dictionary
ansible.builtin.debug:
msg: "Key: {{ item.key }} Value: {{ item.value }}"
loop: "{{ dict1|dict2items }}"
- name: Example
ansible.builtin.debug:
var: item
loop:
- test1
- test2
- name: Loop over list of
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
....
loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}" # no
....
with_fileglob: '*.txt' # yes
Conditionals
tasks:
- name: "shut down Debian flavored systems"
command: /sbin/shutdown -t now
when: ansible_facts['os_family'] == "Debian"
when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") or
(ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "7")
when:
- ansible_facts['distribution'] == "CentOS"
- ansible_facts['distribution_major_version'] == "6"
- command: /bin/false
register: result
ignore_errors: True
- command: /bin/something
when: result is failed # or succeeded or skipped
Filters
Loads of useful Filters exist: Filters
tasks:
- shell: cat /data/some_silly_config.yaml
register: result
- debug:
msg: '{{ item }}'
loop: '{{ result.stdout | from_yaml_all | list }}'
{{ dict | dict2items }} # dictionary to list of key value pairs ( for looping )
{{ tags | items2dict }} # reverse of dict2items, list of key value pairs back to dict
Lookups
- Use “allow_unsafe=True” to allow Jinja2 templates to evaluate lookup values.
Ways to use a lookup:
lookup(‘dict’, dict_variable, wantlist=True) | return a list |
query(‘dict’, dict_variable) | return a list |
q(‘dict’, dict_variable) | short form of query |
with_dict: | for looping |
Show a list of all lookups ( list plugins and filter for lookup ):
ansible-doc -t lookup -l
Lookup Examples
vars:
motd_value: "{{ lookup('file', '/etc/motd') }}"
tasks:
- debug:
msg: "motd value is {{ motd_value }}"
- debug:
msg: "{{ lookup('fileglob', '/etc/*') }}"
- debug:
msg: "{{ lookup('fileglob', '/etc/*', wantlist=True) }}"
- debug:
msg: "{{ query('fileglob', '/etc/*') }}"
- debug:
msg: "TEST: {{ item }}"
with_fileglob:
- '/etc/*'
- debug:
msg: "TEST: {{ item }}"
with_file:
- '/etc/hosts'
- '/etc/passwd'
Common Facts
Some common facts:
tasks:
- debug: var=ansible_facts['distribution']
- debug: var=ansible_facts['distribution_major_version']
- debug: var=ansible_facts['os_family']
- debug: var=ansible_facts['all_ipv4_addresses']
- debug: var=ansible_facts['default_ipv4']
- debug: var=ansible_facts['env']
- debug: var=ansible_facts['hostname']
- debug: var=ansible_facts['interfaces']
- debug: var=ansible_facts['kernel']
Templates
- name: Test Playbook
hosts: all
vars:
color: blue
size: 30
food:
- apple
- pizza
- rice
data:
- host: localhost
IP: 127.0.0.1
- host: silly-wombat1
IP: 104.131.68.105
tasks:
- name: Template test
template:
src: deployments/test.conf.j2
dest: /data/test.conf
deployments/test.conf.j2
This is a test template.
Selected color is {{ color }}
That's it.
{% if size > 25 %}
Large enough
{% else %}
Too small
{% endif %}
Here is the list:
{% for f in food %}
{{ f }}
{% endfor %}
{% for x in data %}
{{ x['IP'] }} {{ x['host'] }}
{% endfor %}
Raw section to escape special characters:
\{% raw %\} ... \{% endraw %\}
Common Tasks
Command / Shell / Script / Raw
tasks:
- name: enable selinux
command: /sbin/setenforce 1
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True
- name: Run a script with arguments (free form)
script: script1.sh -a 1234
- name: Run a script with arguments (using 'cmd' parameter)
script:
cmd: /opt/app1/script1.sh -a 1234
- name: Install Python on RHEL / Fedora
ansible.builtin.raw: dnf install -y python3
File
- name: Change file ownership, group and permissions
ansible.builtin.file:
path: /etc/foo.conf
owner: foo
group: foo
mode: '0644'
- name: Touch the same file, but add/remove some permissions
ansible.builtin.file:
path: /data/set1
state: directory
recurse: yes
mode: u+rw,g-wx,o-rwx
...
...
state: link
state: absent
Copy
- name: Copy ansible inventory file to client
copy:
src=/etc/ansible/hosts
dest=/etc/ansible/hosts
owner=root
group=root
mode=0644
Synchronize
Sync from control host to remote host:
- name: Synchronization of src on the control machine to dest on the remote hosts
ansible.posix.synchronize:
src: some/relative/path
dest: /some/absolute/path
...
...
mode: pull # pull instead of default push
delegate_to: delegate.host # run it from another host
Replace
- name: Replace old hostname with new hostname (requires Ansible >= 2.4)
ansible.builtin.replace:
path: /etc/hosts
regexp: '(\s+)old\.host\.name(\s+.*)?$'
replace: '\1new.host.name\2'
- name: Replace between the expressions (requires Ansible >= 2.4)
ansible.builtin.replace:
path: /etc/hosts
after: '(?m)^<VirtualHost [*]>'
before: '</VirtualHost>'
regexp: '^(.+)$'
replace: '# \1'
Lineinfile
- Ensure that a line is in a file or replace a line in a file.
- Line is placed at the end of file by default unless matching insertafter/insertbefore/regex.
- regexp/search_string fall back to insertafter/insertbefore if no match
- NOTE - When replacing, make sure the modified line still matches for consistent behavior on subsequent runs.
- NOTE - Blockinfile also exists !!!!!!!!!!!!!!!
backup: true | make a dated backup of file |
insertafter: “regex” | insert after last line that matches this |
insertbefore “regex” | insert before last line that matches this |
firstmatch: true | modify insertafter/insertbefore to match first line |
regexp: “regex” | replace last line matched, or remove if “state: absent” ( use regex ) |
search_string: “string” | replace last line matched, or remove if “state: absent” ( use literal ) |
line: “string” | the actual string we want |
state: absent | remove line instead, default is present, |
- name: Ensure SELinux is set to enforcing mode
ansible.builtin.lineinfile:
path: /etc/selinux/config
regexp: '^SELINUX='
line: SELINUX=enforcing
- name: Make sure group wheel is not in the sudoers configuration
ansible.builtin.lineinfile:
path: /etc/sudoers
state: absent
regexp: '^%wheel'
- name: Ensure the default Apache port is 8080
ansible.builtin.lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: '^Listen '
insertafter: '^#Listen '
line: Listen 8080
Packages and Services
- name: Update repo cache and install nginx package
ansible.builtin.apt:
name: nginx
- name: Install nginx package ( Ubuntu/Debian )
ansible.builtin.apt:
name: nginx
update_cache: yes
- name: Remove the nginx package ( Red Hat )
ansible.builtin.dnf:
name: nginx
state: absent
- name: Make sure a service is started, enabled, and not masked
ansible.builtin.systemd_service:
state: started
name: nginx
enabled: true
masked: no
...
...
state: stopped
state: restarted
state: reloaded
Users / Groups
User and group:
- name: Create a user
ansible.builtin.user:
name: jsmith
password: {{ 'mypassword' | password_hash('sha512', 'mysecretsalt') }}
- name: Ensure group "docker" exists with correct gid
ansible.builtin.group:
name: docker
state: present
gid: 1750
SSH Keys
Managing up SSH keys is easy:
- name: Set authorized key taken from file
ansible.posix.authorized_key:
user: charlie
state: present
key: "{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"