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:
yum
apt
package
easy_install
service
group
user
copy
get_url
- download filesfetch
unarchive
file
- create directories and filescopy
- can copy app foldercron
lineinfile
- 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 sshscript
set_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