There might be missing information
Available online at https://willthames.github.io/devops-singapore-2016/
yum install ansiblebrew install ansiblepip install ansibleAnsible 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.
ansibleUsing 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-docansible-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!
becomeThe 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-checker)
There are a number of elements to a playbook, that can be separated into the following classes of elements:
hosts, forks, serialremote_user, become, become_user etc.vars, vars_files, vars_prompt etc.pre_tasks, rolestasks, handlersPlaybooks 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)