Ansible is a nice tools since it does not store the state anywhere, instead, it uses a gather facts step before deploying so it will skip the step if not needed (changed = 0)
https://docs.ansible.com/ansible/latest/getting_started/index.html
Inventory file can be in INI
(text) or YAML
format.
(default is /etc/ansible/hosts
)
# inventory.yml
name_of_group:
hosts:
my-host.com:
my_variable: 123 # host variable
my-other-vm:
ansible_host: my-other-vm.com # overwrite ansible variable
vars: # group variables
ntp_server: example.com
name_of_another_group:
children:
name_of_group:
# this means all hosts from name_of_group are also hosts in parent group. If
# you use multiple files child group should be loaded before parent
same in INI format
# inventory.ini
[name_of_group]
my-host.com my_variable=123
my-other-vm ansible_host=my-other-vm.com
Some example commands
# list hosts
ansible -i inventory.yml all --list-hosts
# ah hock commands using modules
ansible -i inventory.yml all -m ping
# you can overwrite inventory
export ANSIBLE_INVENTORY=~/web-tips/ansible_tips/sample/inventory.yml
# or config file
# ansible.cfg
[defaults]
inventory = ~/web-tips/ansible_tips/sample/inventory.yml
# so following command is using env or config so it does not need params
ansible all -m ping
# default module "command" so you do not need to write "-m command"
ansible all -a ls
# instead of all (or *) you can use group name like virtualmachines
# !excluded_group,&intersection_group
ansible virtualmachines -m service -a "name=httpd state=restarted"
# you can also target using limit option
ansible all -m service -a "name=httpd state=restarted" --limit virtualmachines
https://docs.ansible.com/ansible/latest/getting_started/basic_concepts.html
- control node: machine that run ansible cli tools
- managed nodes: ie target hosts on which we run commands (no need ansible to be installed, but python is required to run ansible generated code)
- inventory: a list of managed nodes, hostfile specifies groups
- playbooks: describe execution concept and files on which
ansible-playbook
operates - play: maps managed node (target) to tasks, contains variables, roles and tasks
- role: limited distribution of reusable ansible content (task, handler, variable, plugin, template and file)
- task: action applied to managed host
- handler: special form of task, that executes when notified by a previous task which resulted in changed status
Install using pip https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html
With pipx
sudo apt install pipx
pipx install --include-deps ansible
With pip
python3 -m pip install --user ansible
# make sure it is in path, otherwise put in bashrc
# pip installed ansible command should be accessible
# export PATH=~/.local/bin:$PATH
ansible --version
ansible [core 2.15.5]
python3 -m pip install --user argcomplete
activate-global-python-argcomplete --user
If installed using package manager, than config is in /etc/ansible
Install ansible-navigator
python3 -m pip install ansible-navigator --user
Vim plugins https://docs.ansible.com/ansible/latest/community/other_tools_and_programs.html#vim
When server is reinstalled than known_hosts will not have the key so it will
prompt for confirmation of the new key. You can disable this check in
/etc/ansible/ansible.cfg
or ~/ansible.cfg
or my-app/ansible.cfg
[defaults]
host_key_checking = False
or environment variable
export ANSIBLE_HOST_KEY_CHECKING=False
# or
ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook
For access over ssh as root you need to enabled on target machine
sudo vi /etc/ssh/sshd_config
PermitRootLogin yes
sudo service ssh restart
so on control machine you can copy keys
ssh-copy-id root@192.168.88.241
When you need sudo priviledges you can use become
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_privilege_escalation.html#become
To specify a password for sudo, run ansible-playbook with --ask-become-pass
(-K for short). If you run a playbook utilizing become and the playbook seems to
hang, most likely it is stuck at the privilege escalation prompt. Stop it with
CTRL-c, then execute the playbook with -K and the appropriate password.
Eg rebooting servers
ansible virtualmachines -a "reboot" --become --ask-become-pass
https://docs.ansible.com/ansible/latest/command_guide/intro_adhoc.html#managing-files
# copy file
ansible all -m ansible.builtin.copy -a "src=./playbook.yaml dest=~"
# remove file
ansible all -m ansible.builtin.file -a "path=./playbook.yaml state=absent"
# install package, use root user or ask for pass
ansible all -m ansible.builtin.apt -a "name=vim state=latest" -u root
ansible all -m ansible.builtin.apt -a "name=vim state=latest" --become --ask-become-pass
# remote package
ansible all -m ansible.builtin.apt -a "name=vim state=absent" -u root
# create user
ansible all -m ansible.builtin.user -a "name=foo password=pass"
# restart service
ansible webservers -m ansible.builtin.service -a "name=httpd state=restarted"
Get all info
ansible all -m ansible.builtin.setup
Check mode without actually running it with --check
option
ansible all -m ansible.builtin.copy -a "src=./playbook.yaml dest=~" -C
Run playbook
ansible-playbook -i inventory.yml playbook.yaml
you can set invetory file in env
export ANSIBLE_INVENTORY=~/web-tips/ansible_tips/sample/inventory.yml
ansible-playbook playbook.yaml
set variable
ansible-playbook playbook.yaml -e my_var=foo
Test and verify without actuall change with those options: --check, --diff, --list-hosts, --list-tasks, and --syntax-check
Run on controller machine using connection: local
or changing hosts: 127.0.0.1
# playbook.yml
- hosts: 127.0.0.1
# or
- hosts: virtualmachines
connection: local
Playbook keywords https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html#playbook-keywords
remote_user
playbook keyword (ansible_user
in hosts file) is used for ssh connection (default in configuration is DEFAULT_REMOTE_USER) or-u
on cliregister: file_contents
is used on play to save the output of module return data to a variable, so you candebug: { var: file_contents.stdout }
to printloop: [{ attr: 1 }, { attr: 2}]
is used to loop current playblock: []
is used to group tasks so you can apply same directive or data for all tasks in a blockhandlers:
list of tasks to run whennotify:
is on task that ischanged
Old index of all modules https://docs.ansible.com/ansible/2.9/modules/modules_by_category.html#modules-by-category
Template module https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_templating.html
# templates/test.j2
My name is {{ ansible_facts['hostname'] }}
# main.yml
- name: write hostname to test.txt file
ansible.builtin.template:
src: templates/test.j2
dest: test.txt
Filters https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_filters.html Standard jinja2 filters https://jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters Ansible allows jinja2 loops and conditionals in templates, but not in playbooks (playbooks are pure machine parseable yaml). Template is processed on controller and only the result is sent to target machine.
# use default 5 if some_variable is not defined
{{ some_variable | default(5) }}
# use default admin even it is false or empty string
{{ lookup('env', 'MY_USER') | default('admin', true) }}
# optional variable with `omit`, so mode is not set when item.mode is blank
mode: "{{ item.mode | default(omit) }}"
# by default variable is required unless DEFAULT_UNDEFINED_VAR_BEHAVIOR=false so
# in this case you need to mandatory require variable
{{ variable | mandatory }}
# required with message
galaxy_api_key: "{{ undef(hint='You must specify your Galaxy API key') }}"
# different value for true false or null
{{ enabled | ternary('no shutdown', 'shutdown', omit) }}
# transform dict hash to list array of key: value: objects
{{ dict | dict2items }}
# or list to dictionary
{{ tags | items2dict }}
# convert
{{ some_variable | to_json }}
{{ some_variable | to_yaml }}
{{ some_variable | to_nice_json }}
{{ some_variable | to_nice_yaml }}
{{ some_variable | from_json }}
{{ some_variable | from_yaml }}
# comobine with filters
Test if variable is matching some string when: var is
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_tests.html#playbooks-tests
Standard comparisons https://jinja.palletsprojects.com/en/latest/templates/#comparisons
vars:
url: "https://example.com/users/foo/resources/bar"
tasks:
- debug:
msg: "matched pattern 4"
when: url is regex("example\.com/\w+/foo")
Test if list contains a value
vars:
lacp_groups:
- master: lacp0
network: 10.65.100.0/24
interfaces:
- em1
- em2
tasks:
- debug:
msg: "{{ (lacp_groups|selectattr('interfaces', 'contains', 'em1')|first).master }}"
Check task result with register: name
and use name.stdout
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_conditionals.html#conditions-based-on-registered-variables
tasks:
- shell: /usr/bin/foo
register: result
ignore_errors: true # continue with tasks event this task fails
- debug:
msg: "it failed"
when: result is failed
# in most cases you'll want a handler, but if you want to do something right now, this is nice
# this will executed if task that registered "result" variable changed something
- debug:
msg: "it changed"
when: result is changed
You can create loop from variable
- name: Registered variable usage as a loop list
hosts: all
tasks:
- name: Retrieve the list of home directories
ansible.builtin.command: ls /home
register: home_dirs
- name: Add home dirs to the backup spooler
ansible.builtin.file:
path: /mnt/bkspool/{{ item }}
src: /home/{{ item }}
state: link
loop: "{{ home_dirs.stdout_lines }}"
# same as loop: "{{ home_dirs.stdout.split() }}"
You should use |bool
filter for string like "true", "yes"
tasks:
- name: Run the command if "epic" or "monumental" is true
ansible.builtin.shell: echo "This certainly is epic!"
when: epic or monumental | bool
Check if var is defined. Note that it is different import_tasks
(static,
condition is applied to every task, so debug step will be skipped since x is not
defined) and include_tasks
(condition is applied only to the include
statement, so debug step will not be skipped)
# all tasks within an imported file inherit the condition from the import statement
# main.yml
- hosts: all
tasks:
- import_tasks: other_tasks.yml # note "import"
# - include_tasks: other_tasks.yml # note "include"
when: x is not defined
# other_tasks.yml
- name: Set a variable
ansible.builtin.set_fact:
x: foo
- name: Print a variable
ansible.builtin.debug:
var: x
Conditionals https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_conditionals.html
tasks:
- name: Shut down Debian flavored systems
ansible.builtin.command: /sbin/shutdown -t now
when: ansible_facts['os_family'] == "Debian"
Loops https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_loops.html
repeat the block witn interpolation "{{ item }}"
.
When iterating a list of hashes you can reference with "{{ item.key_name }}"
.
Note that some plugins accepts list so it is better to provide a list than to
use a loop (since it will not iterate one by one, for example apt
module)
When you are registering variable in a loop than result is a list in
register_name.results
- name: Register loop output as a variable
ansible.builtin.shell: "echo {{ item }}"
loop:
- "one"
- "two"
register: echo
changed_when: echo.stdout != "one"
- name: Fail if return code is not 0
ansible.builtin.fail:
msg: "The command ({{ item.cmd }}) did not have a 0 return code"
when: item.rc != 0
loop: "{{ echo.results }}"
You can retry
- name: Retry a task until a certain condition is met
ansible.builtin.shell: /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10
You can iterate over inventory
- name: Show all the hosts in the inventory
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ groups['all'] }}"
# loop: "{{ query('inventory_hostnames', 'all') }}"
# same as
# loop: "{{ lookup('inventory_hostnames', 'all', wantlist=True) }}"
- name: Show all the hosts in the current play
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ ansible_play_batch }}"
Adding loop_control
keys
loop_control:
label: "{{ item.name }}" # limit the output to .name instead all item object
pause: 3 # delay in seconds
index_var: my_idx # you can use this variable inside the loop
loop_var: new_item # rename item to new_item, usefull in nested loops with
# include_tasks: inner.yml
extended: true # you can access to eg ansible_loop.length variables
Delegating tasks https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_delegation.html
include
, add_host
and debug
are always local task
You can delegate to local machine
tasks:
- name: Take out of load balancer pool
ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
delegate_to: 127.0.0.1
# shorthand is
- name: Take out of load balancer pool
local_action: ansible.builtin.command /usr/bin/take_out_of_pool {{ inventory_hostname }}
you can run playbook from remote machine
- hosts: 127.0.0.1
connection: local
Block https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_blocks.html
is used to apply same data to all tasks and you can rescue when task failed
tasks:
- name: Attempt and graceful roll back demo
block:
- name: Do something
ansible.builtin.shell: grep $(whoami) /etc/hosts
- name: Force a failure, if previous one succeeds
ansible.builtin.command: /bin/false
rescue:
- name: All is good if the first task failed
when: ansible_failed.task.name == 'Do Something'
debug:
msg: All is good, ignore error as grep could not find 'me' in hosts
- name: All is good if the first task failed
when: "'/bin/false' in ansible_failed.result.cmd|d([])"
fail:
msg: It's still false!!!
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_handlers.html
Handlers are tasks that are triggered by notify
if that task make a change.
Handlers are executed after all tasks (or when meta: flush_handlers
is called)
- name: Verify apache installation
hosts: webservers
tasks:
- name: Ensure apache is at the latest version
ansible.builtin.yum:
name: httpd
state: latest
- name: Write the apache config file
ansible.builtin.template:
src: /srv/httpd.j2
dest: /etc/httpd.conf
notify:
- Restart apache
- name: Ensure apache is running
ansible.builtin.service:
name: httpd
state: started
handlers:
- name: Restart apache
ansible.builtin.service:
name: httpd
state: restarted
alternatively you can use listen
topic (interpolation with variables is not
supported)
tasks:
- name: restart
notify: "restart web services"
handlers:
- name: Restart apache
service:
name: apache
state: restarted
listen: "restart web services"
Error handling https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_error_handling.html
- name: Do not count this as a failure
ansible.builtin.command: /bin/false
ignore_errors: true
ignore_unreachable: true
force_handlers: true # run handlers even some task fails
# specify when the task is failed (even with non zero return code)
failed_when: diff_cmd.rc == 0 or diff_cmd.rc >= 2
# stop execution on all hosts
any_errors_fatal: true
Set remove environment https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_environment.html
does not affect ansible configuration, to make them facts you need to use on
explicit gather_facts
task. It is used mainly for proxy, and PATH
- name: Install ruby 2.3.1
ansible.builtin.command: rbenv install {{ rbenv_ruby_version }}
args:
creates: '{{ rbenv_root }}/versions/{{ rbenv_ruby_version }}/bin/ruby'
vars:
rbenv_root: /usr/local/rbenv
rbenv_ruby_version: 2.3.1
environment:
CONFIGURE_OPTS: '--disable-install-doc'
RBENV_ROOT: '{{ rbenv_root }}'
PATH: '{{ rbenv_root }}/bin:{{ rbenv_root }}/shims:{{ rbenv_plugins }}/ruby-build/bin:{{ ansible_env.PATH }}'
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse.html You can import playbook statically (before it runs any task in playbook)
- import_playbook: "my_playbook.yml"
Dynamic include_
is used in looping, so each loop will execute the task.
Static import_
is before it runs so it is preprocessed before so not affected
at runtime (eg variable interpolation). You can set vars on each import.
Also options eg when:
is applied to each child task from imported file.
Can not be used in a loop.
- import_tasks: wordpress.yml
vars:
wp_user: timmy
- import_tasks: wordpress.yml
vars:
wp_user: alice
Triggering handlers with include and import
- name: Trigger an included (dynamic) handler
hosts: localhost
handlers:
- name: Restart services
include_tasks: restarts.yml
tasks:
- command: "true"
notify: Restart services
# import
- name: Trigger an imported (static) handler
hosts: localhost
handlers:
- name: Restart services
import_tasks: restarts.yml
tasks:
- command: "true"
notify: Restart apache
- command: "true"
notify: Restart mysql
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html Roles
Folders in which main.yml
is loaded
tasks/main.yml
main list of tasks that the role executeshandlers/main.yml
library/main.yml
modulesdefaults/main.yml
default variablesvars/main.yml
other variablesfiles/main.yml
filestemplates/main.yml
templatesmeta/main.yml
metadata
Roles are searched in collections, roles/
or in playbook folder.
Using roles:
- play level, so for each role
x
it includesroles/x/tasks/main.yml
,roles/x/handlers/main.yml
... and order of execution of roles at play level is same as static import :pre_tasks
, handlers bypre_tasks
, each role listead inroles:
in order listed,tasks
defined in play, handlers by roles or tasks,post_tasks
, handlers bypost_tasks
- hosts: webservers roles: - common - webservers - role: foo_app_instance vars: app_port: 5000 tags: typeA - { role: foo_app_instance, tags: typeB } # different parameters will # execute the role again
- task level, so the order is that first is executed any task before
include_role
task, than the role tasks and than other taskssimilarly- hosts: webservers tasks: - name: Print a message debug: msg: This runs before example role - name: Include example role include_role: name: example - name: Include example role include_role: name: example vars: app_port: 5000 tags: typeA # tag is added only to include_role not to all role tasks - name: Print a message debug: msg: This runs after example role
import_role:
task is working likeroles:
just the order is listed order, if there is a tag, it is applied to all tasks within the role.
Role dependencies are in meta
# roles/myrole/meta/main.yml
dependencies:
- role: common
vars:
port: 5000
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_module_defaults.html Use module defaults so you do not need to repeat params
- hosts: localhost
module_defaults:
ansible.builtin.file:
owner: root
group: root
mode: 0755
tasks:
- name: Create file1
ansible.builtin.file:
state: touch
path: /tmp/file1
- name: Create file2
ansible.builtin.file:
state: touch
path: /tmp/file2
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html
Variables could be defined in playbook files, command line, or as return value
and can be used as module arguments, when conditionals, templates and loops.
Truthy values: True , 'true' , 't' , 'yes' , 'y' , 'on' , '1' , 1 , 1.0
Falsy values: False , 'false' , 'f' , 'no' , 'n' , 'off' , '0' , 0 , 0.0
List: my_list: [1]
, access with my_list[0]
Dict: my_dict: {key: value}
access with my_dict.key
or my_dict[key]
(preferred since dot notation could be a problem is key is python reserved
public attributes like my_dict.add
, .pop
, .sort
). Nested variables works
like {{ ansible_facts["etho"]["ipv4"]["address"] }}
Define in play using vars:
attributes
# playbook.yml
- hosts: virtualmachines
vars:
my_var: my_value
Use var files
# for vars/RedHat.yml
apache: httpd
somethingelse: 42
and
- hosts: webservers
vars_files:
- "vars/common.yml"
# use os_defaults.yml if eg vars/ubuntu.yml not found
- [ "vars/{{ ansible_facts['os_family'] }}.yml", "vars/os_defaults.yml" ]
tasks:
- name: Make sure apache is started
ansible.builtin.service:
name: '{{ apache }}' # use variable from variable file
state: started
Set variable from fact
tasks:
- name: Get the CPU temperature
set_fact:
temperature: "{{ ansible_facts['cpu_temperature'] }}"
- name: Restart the system if the temperature is too high
when: temperature | float > 90
shell: "reboot"
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_prompts.html Prompts
- hosts: all
vars_prompt:
- name: username
prompt: What is your username?
private: false
- name: password
prompt: What is your password?
tasks:
- name: Print a message
ansible.builtin.debug:
msg: 'Logging in as {{ username }}'
Defining variable at runtime using --extra-vars
or -e
argument and json
format for complex vars and use escape if needed
ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"
ansible-playbook release.yml --extra-vars '{"version":"1.23.45","other_variable":"foo"}'
ansible-playbook arcade.yml --extra-vars '{"name":"Conan O'\\\''Brien"}'
Variable set in playbook exists only withing the playbook object scope.
Variables associated with a host or group, inventory or set_fact
or
include_vars
are available to all plays.
Variable precedence
https://docs.ansible.com/ansible/latest/reference_appendices/general_precedence.html#general-precedence-rules
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#understanding-variable-precedence
CLI values -u
> role defaults role/defaults/main.yml
> inventory or script
group vars > inventory group_vars/all
> playbook group_vars/all
> inventory
group_vars/*
> playbook group_vars/*
> inventory or script host vars >
inventory host_vars/*
> playbook host_vars/*
> host facts > play vars > play
vars_prompt
> play vars_files
> role vars role/vars/main.yml
> block vars
(only for tasks in block) > tasks vars:
> include_vars
> set_facts
registered vars > role and include_role params > include params > extra vars
-e
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_vars_facts.html
Facts
You can create local facts using /etc/ansible/facts.d/preferences.fact
and it
will show as
{ "ansible_local": {
"preferences": {
"asd": "1"
}
}
}
Magic variables like hostvars
eg gather all IP addresses from group
{% for host in groups['app_servers'] %}
{{ hostvars[host]['ansible_facts']['eth0']['ipv4']['address'] }}
{% endfor %}
https://docs.ansible.com/ansible/latest/playbook_guide/guide_rolling_upgrade.html Production example rolling upgrade
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_checkmode.html Validating tasks
- check mode
ansible-playbook playbook.yml --check
- diff mode
ansible-playbook foo.yml --check --diff --limit foo.example.com
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_privilege_escalation.html
Privilege escalation become means it will use sudo
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_tags.html
Tags are defined using tags:
on task, role, import so you can skip
ansible-playbook example.yml --tags "configuration,packages"
ansible-playbook example.yml --skip-tags "packages"
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_startnstep.html Debugging with running from specific task
ansible-playbook playbook.yml --start-at-task="install packages"
or ask each task yes/no/continue
ansible-playbook playbook.yml --step
or using debug tag and run only task that will print variable
ansible-playbook playbook.yml --tags debug
or using debugger:
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_debugger.html
[192.0.2.10] TASK: (debug)>
p result._result
p task.args
task.args["data"] = "{{my_var}}"
p task.args
redo
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_strategies.html
strategy linear, or free (some host can continue on other tasks, and not wait
slow host to complete)
Use serial: 3
attribute to run playbook to 3 and after it completes it runs on
another 3 hosts
TODO: https://github.com/yaegassy/coc-ansible
cloudflared_tunnel_tf