Ansible: Up and Running (1st/2nd edition) by Lorin Hochstein
02 Jun 2018Ansible:
The 1st edition of the book not only covers less topics but it felt outdated as there were many updates over the years for Ansible. 2nd edition luckily only improves on a good base of the book bringing (alomst) latest development, and now (2nd edition) feels like a great start to pick up this wonderful tool.
Intro:
- ping/test all hosts in inventory file
ansible -m ping -i inventory.yaml all
- you can define the default inventory file in your ~/.ansible.cfg or in the current folder ./ansible.cfg
[defaults]
hostfile = inventory.yaml
host_key_checking = False
- to run a command on a host defined in the inventory:
ansible -i inventory.yaml -m command -a uptime host_one
- the
-m command
part can be omitted as it’s default- we can rewrite the command as
ansible -a uptime host_one
, as mentioned ansible.cfg defines the inventory file - the part after -m represents a module, so depending on the module being executed we can get different information returned
-
-m setup
can retrieve information about the host --m ping
uses the ping module to check if the host is reachable
- we can rewrite the command as
- add
-s
to sudo
- the
yum: state=present name=git
in the playbook, will actually use the yum module- or name= if in the vars: section we set our_package: git
include: playbook.yaml our_package=nginx
because we can override the variable- vars can be used as custom input fields, such as one-off/custom passwords
- handlers - a special task that can be executed if it is registered as a change
- they don’t run in the given order, but in the order they are notified
- playbooks will stop when encountering the first error, unless
ignore_errors: true
- debug can be enabled in tasks with
debug: var=my_output
- debug can be enabled in tasks with
- first
shell:pwd
then we define register asregister: pwd_output
, then we can use the variable as ``- we can filter the output/variable value by using either failed or success , e.g. ``
- Sample playbook
- name: Configure nginx
hosts: docker
sudo: True # JSON formatting is different
tasks:
- name: install nginx
apt: name=nginx # [OR]
shell: yum install -y nginx # [OR]
yum: state=present name=nginx # [OR]
service: state=started name=nginx
- name: copy index.html
template: src=templates/index.html.j2 dest=/usr/share/nginx/html/index.html
mode=0644
- YAML folding syntax, like this:
- name: install nginx
apt: >
name=nginx
update_cache=yes
- to get access to online docs
ansible-doc service
- to use handlers, we need to
notify
an external task- handlers are ran only once, no matter how many times they are triggered, at the end of the playbook
tasks:
- name: update nginx config
file: [...]
notify: nginx restart
handlers:
- name: nginx restart
service: name=nginx state=restarted
- we can create templates in the
templates
folder, for example the nginx file will be calledtemplates/nginx.conf.j2
as it will use Jinja2 templating
server {
[...]
server_name ;
- The Inventory file(s)
- we can specify hostnames, alternate ssh ports, user, private key file
- ansible creates a special group for
all
- we can specify groups of groups w/
:children
- we can use wildcards when specifying hostnames, for a “cattle vs. pets approach”
- we can use per host / group variables
[prod]
server-http.example
server-https1.example zone=blue
server-https[2-6].example zone=red
[test]
virtual85
[all:vars]
ntp_host=time.us.world
[prod:vars]
db_user=packet
db_port=1521
Variables
- for better flexibility, vars can be set in files
- ansible looks for host variable files in a directory called
host_vars
and group variable files in a directory calledgroup_vars
- ansible looks for host variable files in a directory called
- for external managed inventory (e.g. EC2) we can get or create a dynamic inventory script
- variables can be set in 1 or more locations
- directly in the playbook by using a
vars
section - by specifying a
vars_files: - test.yml
section inside the playbook - in files adjacent to the inventory file(s)
- directly in the playbook by using a
- all tasks / executions’ steps have a
changed
variable which is set to true/false depending on what that particular step did or didn’t do
Facts
- before running any tasks, ansible collects facts about hosts: cpu, memory, disk info, and so on
- the
setup
module gets called automatically to gather facts, they are all stored inansible_facts
- any module can return ansible facts
- we can also define variables using
set_fact
- or we can use
register: snap_result
and thenset_fact snap=
to store its stdout value
- or we can use
- we also always have available built-in variables:
hostvars
all variables from all hosts; `` with which we can determine informationinventory_hostname
current hostgroups
- can be useful for multi host in same domain configs
- variables can be set on the command line using
-e var=value
when executingansible-playbook
- precedence of variable definition is important: 1
command line
2in a yaml file
3facts
4defaults/main of role
Playbooks
- flags:
--check
,--list-tasks
,--list-tags
,--syntax-check
,--list-hosts
,--ask-become-pass
- install
with_items
:
- name: install nginx and libpq-dev
apt:
name: ""
state: present
update_cache: yes
become: true
with_items:
- libpq-dev
- nginx
xip.io
for wilcard DNS publicly available, can be used for testing- for tasks executed on the local machine (control) we can specify
local_action
- we can disable gathering facts (and ssh in turn) by setting
gather_facts
to false/no - running sequentially instead of parallel by setting
serial: 1
- for all the Jinja2 templating more advanced features such as re-formatting, parsing, conditionals can be used
run_once
for database migrations and one-time stuff :Dwith_fileglob
iterating over a set of files- we can have pre and post - tasks, that will be triggered before/after others
- for pip we can pass a requirements file as well
- for certain roles we can include requirements for sub-roles, so called dependent roles
- iterating over items:
with_items
,with_lines
,with_dict
,with_sequence
,with_inventory_hostnames
, … - include s - can be static and do different things depending on the place
- or dynamic, depending of various variables/facts
- include: Redhat.yml
when: ansible_os_family == 'Redhat'
- include: Debian.yml
when: ansible_os_family == 'Debian'
- Blocks - mechanism for grouping tasks, e.g.
- block - name: abc - name: def when: ansible_os_family == Debian
- we can have improved error handling with block constructs, by having
rescue
andalways
clauses, which are triggered only on particular situations
- we can have improved error handling with block constructs, by having
- the hosts in the playbook can be specified as a list or a pattern like
all
an uniongroupa:groupb
or intersectiondev:&test
, … or a couple of other set operations - we can as well limit on which hosts we run the playbook(s) with
--limit
- we can execute commands on the control host as well with
local_action
; we can further limit its actions by specifyingrun_once
- we can limit (read serialize) our actions, for example when adjusting members of a load balancer pool, by specifying
serial: 1
- to prevent situations where we break the LB config by removing all hosts, the
max_fail_percentage: 50
(50 or any other value) can be used - the somewhat-opposite of serial:1 can be achieved w/
serial: 50%
(since Ansible 2.2)
- to prevent situations where we break the LB config by removing all hosts, the
- Running strategies:
linear
(default),free
- in which new tasks are started on a host if previous steps have finished executing - we can set up role dependencies, which run before the parent dependency using
dependencies: - role: ssl
for the web hosts which need SSL certs before installing the web server - Vagrant support
Custom modules
we can create custom Python modules - Ansible provides a starter project to help us with this task
- many options are available, such as:
- mutually exclusive task options
- changed and msg are mandatory to be implemented
- option choices
- support for dry-run (check mode)
- there is limited support to write the module in any scripting language (as long as it’s installed on the remote host)
Callback plugins
- will perform custom actions in response to events, such as starting a task or completing a task
- we can update the ansible.cfg file with the new plugins
- settings per plugin need to be set in order for them to perform the expected actions, such as JABBER_SERV, JABBER_USER, JABBER_PASS, … for the jabber notifications
[defaults]
callback_whitelist = mail, slack, junit, jabber, hipchat, logstash, ...
Speeding up Ansible
- Using ControlPersist in SSH, to keep only one connection per host
- Pipelining commands, skipping copying the code
- requiretty must be disabled on the host
- fact gathering can be disabled per playbook or made by default optional
- locally JSON or a Redis/Memcached backend can be used for caching
roles/django/meta/main.yaml
dependencies:
- { role: web }
- { role: memcached }
Galaxy:
- ansible-galaxy is an open source repository of roles contributed by the community (stored on GitHub)
Vault:
- to create a vault and then edit to to insert information
ansible-vault create _filename_
- to encrypt an existing file
ansible-vault encrypt _filename_
- once you start using vault it’s recommended that you always run either view or edit and never decrypt
- encryption is reversible, so if decided, you can as well stop using vault
- we need to create a special folder structure in order for the vault to automatically find the vaulted passwords
--- group_vars
\--- <environment>
\--> vault # all variables start w/ vault_
\--> vars # define variables as test_variable: ""
Jinja2 templating:
- filters can be applied to many items; e.g. file paths -
dirname
orrealpath
=> ``- plenty default filters, such as:
failed
,changed
,skipped
- many others for files:
basename
,dirname
,realpath
- many others for files:
- plenty default filters, such as:
- we can also define our own filters
- we can lookup a file to get its content across to our server
ec2_key: name=mykey key_material=""
Vagrant
a quick intro into using Vagrant with Ansible
AWS:
- use the
ec2
module already available for Ansible - use auth tokens and not user/pass
- dynamic inventory
- very important to tag all resources
- we can use
wait_for
in order to take into account the creation time
- we can use
- to make the creation idempotent we need to tag the instances and use
exact_count: X
andcount_tag: {type: NAME_OF_TAG}
then we won’t get new instances if the VMs requested are created - in order to create new Amazon Machine Images (AMIs) the recommended way is to use Packer form Hashicorp + Ansible Remote Provisioner
- it will take a JSON template as input
- many, many more modules -
route53
,s3
,cloudformation
debugging
- we can set strategy as debug, so on exceptions we will get into an interactive python debugger
- we can use the
assert
module as well to trigger early warnings about issues - various other checks/lists
--list-tasks, --list-tags, --check, --syntax-check
--diff
can be used together with--check
to see what will be changed
- we can as well step through the playbook with
--step
or start at a particular point--start-at-task
Docker:
- Ansible Container is a novel way to build, running, testing and deploying containers -
- push/pull/orchestrate (running multiple dockers) are available through modules
- strictly docker commands
- useful commands:
docker pull <image_name>
docker image rm <hash>
- remove from disk given image/containerdocker ps
- list running containersdocker images
- list available images locallydocker run -d -p 2222:22 <image>
- start image, mapping ssh port, -d = detach or daemon :)
Windows
- using WinRM, PowerShell required as well
- all modules start with
win_
tasks:
- name: install software security updates
win_updates:
category_names:
- SecurityUpdates
- CriticalUpdates
register: update_result
Others
- a short chapter mentioning Ansible Tower
- a chapter dedicated on how to manage network devices (switches, load balancers) is a good intro to the subject and gives a solid overview of what to expect