There might be missing information
Available online at https://willthames.github.io/devops-singapore-2016/
yum install ansible
brew install ansible
pip install ansible
Ansible is most easily used if you can ssh as a normal user to a host without a password, and then sudo to root without a password.
ssh-keygen -f ansible
ssh-add ~/.ssh/ansible
ssh-copy-id -f ~/.ssh/ansible $targethost
Test with the ping
module:
ansible -m ping target
echo "ansible_user (ALL) NOPASSWD: ALL" > \
/etc/sudoers.d/ansible
In some cases you will want to lock this down further, perhaps with a password (you can enter a sudo password if you add -K
to your command line).
ansible -a whoami target
ansible -a whoami -b target
(Note that ansible
runs the command
module by default so no -m
is needed)
ping
and command
modules.See the Ansible Module Index for a full list of categories.
ansible
Using the ansible
command line utility, it's easy to run a simple module to get all of the facts from a repo
ansible -m setup target
or run an ad-hoc task
ansible -m file -a "state=directory path=~/throwaway" target
ansible-doc
ansible-doc
is very useful for finding the syntax of a module without having to look it up online
e.g. ansible-doc mysql_user
A playbook is, at its simplest, a list of tasks to run in sequence against a list of hosts. The setup
task is run first.
- hosts: target
tasks:
- name: ensure ~/throwaway doesn't exist
file:
path: ~/throwaway
state: absent
A task comprises the module to run and the arguments with which to run the task.
There are also several modifiers to the task, determining whether it is run, who it is run by, where it is run, and others.
Best practices suggest always naming tasks — it's easier to follow what is happening if the tasks are well named.
Naming tasks also allows the use of --start-at-task
to allow ansible-playbook to start at a later point in the playbook
The task in the previous playbook looks like this when named:
TASK [ensure ~/throwaway doesn't exist] ****************************************
ok: [target]
and when not named:
TASK [file] ********************************************************************
ok: [target]
You can tell a task to run only if certain conditions are met.
- name: install httpd (Red Hat)
yum:
name: httpd
when: 'ansible_distribution == "RedHat"'
- name: install apache (Debian)
apt:
name: apache2
when: 'ansible_distribution == "Debian"'
See more: Ansible Conditionals
with_items
etc.A task can loop over a list of items (or indeed many other kinds of data structures). For example, you can provide a list of packages for apt or yum to install, or a list of definitions of database users (username, password, privileges etc).
See more: Ansible Loops
with_items
example- name: add database users
mysql_user:
- name: "{{ item.name }}"
password: "{{ item.password }}"
with_items:
- name: bob
password: abc123
- name: sys
password: superSekrit!
become
The become
modifiers are useful when you need a task to run as a different user to the rest of the playbook. Security best practices suggest using a standard user for most tasks and elevating privilege when required — but even if you're running the playbook as root, you might need to become e.g. the postgres
user to run a DB related task.
See more: Become
become
example- name: install package
yum:
name: httpd
state: present
become: yes
- name: connect to the database
command: psql -U postgres 'select * from pg_stat_activity()'
become_user: postgres
Note that the previous task could be written
yum: name=httpd state=present
Ansible have said that the preferred format is the YAML dictionary format, as the key=value form can lead to strange errors (we'll see this later with the --syntax-check
er)
There are a number of elements to a playbook, that can be separated into the following classes of elements:
hosts
, forks
, serial
remote_user
, become
, become_user
etc.vars
, vars_files
, vars_prompt
etc.pre_tasks
, roles
tasks
, handlers
Playbooks can also include other playbooks, using include
at the play level, or include task files by using the include
task
- hosts: all
include: another_playbook.yml
tasks:
- include: setup.yml
- include: configure.yml
Ansible can be run in test mode using the -C
or --check-mode
flags.
ansible-playbook -C examples/playbook.yml
Because ansible doesn't know if a command will have an effect in dry run mode, tasks that don't support dry run (particularly command
and shell
tasks) are skipped by default. You can force them to be run using the check_mode
flag (previously the always_run
flag)
- name: get version of coreutils rpm
command: rpm -q coreutils
args:
warn: no
changed_when: False
check_mode: yes
You can also do things differently when in check mode by changing behaviour based upon when: ansible_check_mode
.
Diff mode is incredibly useful in conjunction with check mode to see how a file would change, before the change is made. Diff mode can be run by passing -D
or --diff-mode
to ansible-playbook
If a playbook runs twice, the expected result is that nothing should change on the second run — ansible should report zero changes.
This should be true whether 10 seconds later, or 10 months later.
shell
and command
will always return changed: True
. Use of changed_when: False
when running read-only commands is encouraged to minimise false alarms: - name: get list of files in a directory
command: ls /path/to/directory
register: directory_contents
changed_when: false
check_mode: true
creates
or removes
with commands to prevent an action happening if it's already happenedwhen
with a pre-check read-only task (with changed_when: False
) to see if an action needs to happen before doing it- name: check tuned profile
command: tuned-adm active
register: tuned_adm
changed_when: False
- name: set tuned profile
command: tuned-adm profile virtual-guest
when: "'Current active profile: virtual-guest' \
not in tuned-adm.stdout"
Templates allow you to generate configuration files from values set in various inventory properties. This means that you can store one template in source control that applies to many different environments.
Ansible uses the Jinja templating language. The language offers control structures ({% for %}
, {% if %}
etc.) and filters that process variables (e.g.{{ "hello"|upper }}
would print HELLO
.
$db_host = '{{ database_host }}';
$db_name = '{{ database_name }}';
$db_user = '{{ database_username }}';
$db_pass = '{{ database_password }}';
$db_port = {{ database_port}};
Templates are populated using
- template:
src: path/to/template.j2
dest: /path/to/result
Because configuration files for an application can end up with similar names in different directories, reflect the target destination in the source repository
- name: configure logrotate
template:
src: etc/logrotate.d/httpd.conf.j2
dest: /etc/logrotate.d/httpd.conf
- name: configure apache
template:
src: etc/httpd/conf/httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
owner: apache
Handlers are tasks that are 'fired' when a task makes a change
tasks:
- name: install httpd configuration file
template:
src: etc/httpd/conf/httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
notify: reload apache
handlers:
- name: reload apache
service:
name: apache
state: reloaded
This is another mechanism of ensuring a change is made only when it needs to be (as if no change is made to the configuration, the handler will not fire)
Handlers are run at the end of all tasks — if you want to run them earlier, you can use the meta
task:
- meta: flush_handlers
Hopefully you received and followed the instructions to set up your Ansible environment.
Log on to the control VM
Have a look in playbooks/simple
Run the deploy-web-service.yml playbook
Adding extra -v
arguments will increase the verbosity of ansible's output.
-v
— just output tasks and plays and whether or not it's successful-v
— shows the results of modules-vv
— shows the files from which tasks come-vvv
— shows what commands are being executed under the hood on the target machine-vvvv
— shows what callbacks have been loaded-vvvvv
— shows some additional ssh configuration informationYou can (and should) configure your debug messages to appear only at certain verbosities
debug:
msg: "This will appear with -vv but not before"
verbosity: 2
For any self-contained set of playbooks (this might be all of your playbooks, or playbooks just for a particular application), the following is a reasonable directory structure
.
├── ansible.cfg
├── inventory
│ └── group_vars
│ └── web
└── playbooks
├── simple
│ ├── deploy-web-service.yml
│ └── templates
│ ├── index.html.j2
│ └── python-web.service.j2
└── with_roles
If you need modules or plugins, they can also be installed in the top level directory. Something like
.
├── ansible.cfg
├── inventory
├── library
├── playbooks
└── plugins
├── filter
└── lookup
is likely a reasonable setup.
[defaults]
hostfile = ./inventory
roles_path = ./roles
library = ./library
filter_plugins = ./plugins/filter
lookup_plugins = ./plugins/lookup
callback_whitelist = profile_tasks,timer
[ssh_connection]
pipelining = True
control_path = %(directory)s/ssh-%%h-%%p-%%r
hostfile = ./inventory
allows inventory to be stored next to the playbooks it is forlibrary = ./library
allows the easy installation of custom modules near the code (useful if a module is not available from Ansible or a bug has been fixed for a newer/unreleased version)filter_plugins = ./plugins/filter
— allows for addition of new plugins for Jinja templatingcallback_whitelist = profile_tasks,timer
turns on timing information for individual tasks and for the playbook run as a wholepipelining = True
speeds up the execution of modules as ansible only has to run one ssh command, not three.control_path
reduces the length of the default setting, which is useful if you have long hostnames (as the setting can only be 106 characters)