Ansible notes
Agentless automation.
Ansible is a tool for provisioning servers, managing their configuration, and deploying applications.
Ansible = configuration management (Pupet, Chef) + deployment (Capistrano, Fabric) + ad-hoc task execution (Func, ssh).
Ansible aims to be:
- clear
- fast
- complete
- efficient
- secure
Initial release: 2012.
When developers begin to think of infrastructure as part of their application, stability and performance become normative.
Ansible for DevOps by Jeff Geerling
Components
Commands
Aka tasks.
Ad-hoc commands:
ansible <group> -b -m <command> -a <arguments> # -b == become (root)
ansible <group> -m ping -u <username>
ansible mygroup -b -m yum -a "name=ntp state=present"
ansible .... --limit "192.168.60.1" # limit to one instance, can use patterns
Verbose:
ansible ... -vvvv
Combine commands into playbooks.
Available modules
Aka "task plugins" or "library plugins".
See Ansible modules:
yumaptpackageeasy_installservicegroupusercopyget_url- download filesfetchunarchivefile- create directories and filescopy- can copy app foldercronlineinfile- find a line using regexp and replaceshell- ansible's command module is the preferred option for running commands on a host. However, command doesn't run the command via the remote shell/bin/sh, so options like<>|&and local environment variables won't workraw- raw command via sshscriptset_fact- dynamically define variablesdebug
Playbooks
Playbook - a set of tasks (plays) that will be run against a particular server or set of servers.
Example:
---
- hosts: all
become: yes
vars_files:
- vars.yml
vars:
- myvar: myvalue
pre_tasks:
...
tasks:
- name: Install ... {{ myvar }}
yum: ...
notity:
- do something
- include: my-tasks.yml
post_tasks:
...
handlers:
- do something
...
- include: my-handlers.yml
Privileged access (use sudo):
become: yes
Handlers: Running Operations On Change.
ansible-playbook utility
ansible-playbook arguments:
--inventory--check, check, but don't change (dry run)
Conditional statements
when:
- apt:
name: vim
when: is_local
# when: "(is_local is defined) and is_local"
# when: "'ready' in my_result.stdout"
# when: "my_result.stdout.find(another_var + '/some.js') == -1"
changed_when
failed_when
wait_for - e.g. wait for a server start listening on a port
Join playbooks
- hosts: all
tasks:
...
- include: playbook1.yml
- include: playbook2.yml
vars:
- myvar: myvalue
And can combine with when and with loop statements.
Or use ansible roles.
Inventories
[app]
102.168.60.1
102.168.60.2
[db]
102.168.60.3
# group "multi"
[multi:children]
app
db
[multi:vars]
ansible_ssh_user=myuser
ansible_ssh_private_key_file=~/...
Can specify which inventory to use:
ansible-playbook myplaybook.yml -i inventories/myinventory
Inventories can be dynamic.
Variables
Use all lowercase letters.
Passing a variables file:
ansible-playbook playbook.yaml --extra-vars "@vars.yml"
Registered variables: save command stdout and stderr.
- shell: <my_command>
register:
my_command_result
# "{{ my_command_result.stdout }}"
Facts are information derived from speaking with your remote systems.
Passing configuration through ENV variables
Ansible set ENV variables -> the app loads configuration from ENV variables.
Using ~/.bash_profile:
- tasks
- name: "Set an environment variable"
lineinfile:
path=~/.bash_profile
regexp=^ENV_VAR=
line=ENV_VAR=value
For system-wide use: /etc/environment.
Per play:
- name:
...
environment:
my_env_var: my_value
Per shell command can use templates:
cd {{ app_home }} && ENV_KEY1={{ env_val1 }} ENV_KEY2={{ env_val2 }} {{ app_venv }}/bin/python myapp.py
Task:
- shell: "{{ lookup('template', 'mytemplate.j2') }}"
Per supervisor config:
[program:myapp]
command={{ app_home }}/.venv/bin/python myapp.py
process_name=myapp
user={{ app_user }}
directory={{ app_home }}
environment=ENV_KEY1="{{ env_value1 }}",ENV_KEY2="{{ env_value2 }}"
Sensitive values
ansible/variables/<env>-sensitive.yml
# (don't put it under git index)
# or specify them in command line:
ansible-playbook myplaybook.yml --extra-vars="myvar=myvalue"
So vars_files will look like:
vars_files:
- common.yml
- "myservice_{{ env }}.yml"
- "myservice_{{ env }}_sensitive.yml"
And env can be specified inside inventories:
# ...
[all:vars]
env=development
Variable prompt:
vars_prompt:
- name: username
prompt: "What is username?"
default: anonymous
- name: password
prompt: "What is password?"
private: yes
confirm: yes
Or use Ansible vault.
Ansible vault
Encryption: AES-256.
Edit:
ansible-vault edit
Run:
ansible-playbook playbook.yml --ask-vault-pass
Works faster with:
pip install cryptography
String encryption:
ansible-vault encrypt_string <some string> --vault-password-file <password file>
This command will output a string ready to be included in a YAML file.
File encryption:
ansible-vault encrypt <file path> --vault-password-file <password file>
Replace the file.
Roles
Combines tasks, configuration, templates in one reusable package.
my_role/
meta/
main.yml
tasks/
main.yml
- hosts: all
roles:
- my_role
Meta:
---
dependencied: []
Ansible galaxy
A repository for roles.
ansible-galaxy init <role name>
Usage
Documentation
ansible-doc <module name>
Debug
Verbose:
ansible-playbook playbook.yml -v
Environments
Can specify environment inside inventories:
# ...
[all:vars]
env=development
And then use it when loading variables:
vars_files:
- "myservice_{{ env }}.yml"
Tags
Allow to run (or exclude) subset of a playbook's tasks, roles, and handlers.
roles:
- {role: myrole, tags: ['install', 'setup']}
tasks:
- name: "My task"
someaction: someargs
tags:
- setup
ansible-playbook myplaybook.yml --tags "install"
ansible-playbook myplaybook.yml --skip-tags "install"
Vagrant
Use for testing on development machine.
Vagrant is a server templating tool (other are Docker, Packer), create an image instead of configuring each server.
Working with Vagrant:
vagrant box add <repository>/<image>
vagrant init <repository>/<image>
vagrant up
vagrant ssh
vagrant halt # shut down
vagrant destroy # completely destroy the box
vagrant ssh-config # ssh details
Vagrant features:
- network interface management
- shared folder management
- multi-machine management
- provisioning
Example Vagrantfile:
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/xenial64"
# NFS shared folder
# config.vm.synced_folder "./.shared/", "/var/townintel_shared", type: "nfs"
config.vm.define "myinstance" do |app|
app.vm.hostname = "myservice.mysite.com"
app.vm.network:private_network, ip: "10.100.0.10"
app.vm.provision "ansible" do |ansible|
ansible.verbose = "v"
ansible.inventory_path = "./inventories/development"
ansible.playbook = "myservice.yml"
end
end
# ...
end
Reach host machine
Host machine is available at 10.0.2.2.
AWS guide
See Ansible AWS Guide. Ansible for AWS book.
Run ansible through a bastion host
See Running Ansible Through an SSH Bastion Host by Scott Lowe.
Example:
# custom ssh configuration file
Host 10.10.10.*
ProxyCommand ssh -W %h:%p bastion.example.com
IdentityFile ~/.ssh/private_key.pem
Host bastion.example.com
Hostname bastion.example.com
User ubuntu
IdentityFile ~/.ssh/private_key.pem
ControlMaster auto
ControlPath ~/.ssh/ansible-%r@%h:%p
ControlPersist 5m
# ansible.cfg
[ssh_connection]
ssh_args = -F ./ssh.cfg -o ControlMaster=auto -o ControlPersist=30m
control_path = ~/.ssh/ansible-%%r@%%h:%%p
Doesn't work for me ^, so I just put the configuration to ~/.ssh/config.
Python3
Specify it in inventories:
[all:vars]
ansible_python_interpreter=/usr/bin/python3
Copy source code
One way:
---
- tasks
- name: "Synchronize the source"
synchronize:
dest: "/home/{{ user }}/{{ app }}/"
src: "{{ item }}"
rsync_opts:
- "--exclude=*.pyc"
become_user: "{{ user }}"
with_items:
- ../<some folder>
- ../<some file>
notify: server restart
Examples
Simple Python code deploy
---
- name: Deploy
hosts: <hosts>
become: true
vars_files:
- variables/base.yml
vars:
- user: deploy
tasks:
- name: "apt-get update"
apt:
update_cache: yes
cache_valid_time: 3600
- name: "apt-get install"
apt:
name: "{{ item }}"
state: latest
with_items:
- build-essential
- virtualenv
- python3-dev
- name: "Create deploy group"
group:
name="{{ user }}"
state=present
- name: "Create deploy user"
user:
name="{{ user }}"
shell=/bin/bash
groups="{{ user }}"
append=yes
- name: "Synchronize the source"
synchronize:
dest: "/home/{{ user }}/<project name>/"
src: "{{ item }}"
rsync_opts:
- "--exclude=*.pyc"
become_user: "{{ user }}"
with_items:
- ../<source>
- ../requirements.txt
tags:
- reload
- name: "Install requirements"
pip:
requirements: "/home/{{ user }}/<project name>/requirements.txt"
virtualenv: "/home/{{ user }}/.venv"
become_user: "{{ user }}"
Best practices
Role names
ansible-role-... - easier to find on GitHub.
Project structure
- myproject
- inventories/
- roles/
- variables/
- playbook1.yml
- playbook2.yml
- Vagrantfile
Vocabulary
See also Ansible Glossary.
Idempotence
Idempotence is the ability to run an operation which produces the same result whether run once or multiple times.
ad-hoc
Ad-hoc - made or happening only for a particular purpose or need, not planned before it happens.
Links
Ansible for DevOps by Jeff Geerling Ansible documentation Ansible blog
Licensed under CC BY-SA 3.0