(2017, 2019)
Ansible is an IT automation package. Its big competitor is SaltStack.
Overview:
Ansible works over ssh, so we don’t need to install anything on clients. The control machine must have Python (2.6+ or 3.5+).
# apt-get install ansible
Even on the control machine, there’s no database, no running daemon, just commands to run as needed.
http://docs.ansible.com/ansible/latest/intro_installation.html
Ansible uses any settings it finds in ~/.ssh/config
(e.g., jump host).
By default, it uses SFTP but can be configured to use SCP.
Ansible assumes the use of SSH keys, but can be given the --ask-pass
or --ask-become-pass
option.
Ansible doesn’t just have to connect remotely over SSH. The transports are pluggable, and there are options for managing things locally, as well as managing chroot, lxc, and jail containers. A mode called ‘ansible-pull’ can also invert the system and have systems ‘phone home’ via scheduled git checkouts to pull configuration directives from a central repository.
http://docs.ansible.com/ansible/latest/intro_getting_started.html
Basic definitions:
Ansible looks for its configuration in this order:
ANSIBLE_CONFIG
environment variable$PWD/ansible.cfg
$HOME/ansible.cfg
/etc/ansible/ansible.cfg
The config file defines the location of the hosts inventory:
[defaults]
inventory = /etc/ansible/hosts
Ansible acts on multiple machines from the inventory.
A dynamic inventory is possible (LDAP, Cobbler, etc.), but the file /etc/ansible/hosts
is the default inventory (or specify with the -i
flag):
# Ex 1: Ungrouped hosts, specify before any group headers.
bigby.example.com
192.168.100.22
www[1:26].example.com
[dbservers]
everest.example.com
db-[a:f].example.com
[kvm]
mizzen.example.com
zol.example.com
tiger.example.com
For hosts with non-22 SSH ports, specify them like foo.example.com:22000
.
The inventory can contain variable definitions, per-host or per-group, for later use in playbooks:
firefly.example.com http_port=8080
[flint]
super_secret=true
Make groups-of-groups using the :children
suffix:
[north_america:children]
michigan
illinois
newyork
Two implicit groups exist: all
and ungrouped
.
Have Ansible scan the hosts file to list all hosts it recognizes or all hosts from a particular group:
$ ansible all --list-hosts
$ ansible webservers --list-hosts
Check which of the hosts in the inventory respond to pings:
$ansible all -m ping
firefly.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
tiger.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
kosmokrator.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
mizzen.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
nostromo.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
Ad-hoc commands are things too quick, simple, or single-use to write as a playbook.
http://docs.ansible.com/ansible/latest/intro_adhoc.html
Ping all the hosts:
$ ansible all -m ping
We have a [kvm]
group of hypervisors specified in /etc/ansible/hosts
. What guests are on each?
$ ansible kvm --ask-become-pass --become -a "virsh list --all"
For commands that require shell features (even ones as basic as pipes) use the “shell” module:
$ ansible detroit -m shell -a 'echo $TERM'
(The default module is command
.)
The -f
flag sets the number of parallel processes (default 5).
$ ansible kvm -f 10 -m shell -a 'cat /etc/group | tac'
So far we’ve been demoing simple command execution, but most Ansible modules are not simple imperative scripts. Instead, they use a declarative model, calculating and executing the actions required to reach a specified final state. Furthermore, they achieve a form of idempotence by checking the current state before they begin, and if the current state matches the specified final state, doing nothing. However, we also recognize that running arbitrary commands can be valuable, so Ansible easily supports both.
Transfer a file directly to many servers:
$ ansible atlanta -m copy -a "src=/etc/hosts dest=/tmp/hosts"
Change file ownership or permissions with the file
module:
$ ansible webservers -m file -a "dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan"
Delete directories (recursively) and delete files:
$ ansible webservers -m file -a "dest=/path/to/c state=absent"
Add or remove users:
$ ansible all -m user -a "name=foo password=<crypted password here>"
$ ansible all -m user -a "name=foo state=absent"
Trigger a Git pull:
$ ansible webservers -m git -a "repo=https://foo.example.org/repo.git dest=/srv/myapp version=HEAD"
Make sure a service is started, restarted, or stopped:
$ ansible webservers -m service -a "name=httpd state=started"
$ ansible webservers -m service -a "name=httpd state=restarted"
$ ansible webservers -m service -a "name=httpd state=stopped
Long-running operations can run in the background, with optional timeout and status polling:
$ ansible all -B 3600 -P 0 -a "/usr/bin/long_running_operation --do-stuff"
$ ansible web1.example.com -m async_status -a "jid=488359678239.2844"
$ ansible all -B 1800 -P 60 -a "/usr/bin/long_running_operation --do-stuff"
Report “facts” — discovered variables about the client(s):
$ ansible all -m setup
Playbooks are Ansible’s configuration, deployment, and orchestration language.
http://docs.ansible.com/ansible/latest/playbooks.html
Write playbooks in YAML. http://docs.ansible.com/ansible/latest/YAMLSyntax.html
Before trying to edit YAML with Vim, add this to your .vimrc
:
au! BufNewFile,BufReadPost *.{yaml,yml} set filetype=yaml foldmethod=indent
autocmd FileType yaml setl ts=2 sw=2 sts=2 expandtab
Each playbook lists one or more “plays”. A “play” links hosts to tasks. Tasks are calls to Ansible modules.
---
- hosts: webservers
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: name=httpd state=latest
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
notify:
- restart apache
- name: ensure apache is running (and enable it at boot)
service: name=httpd state=started enabled=yes
handlers:
- name: restart apache
service: name=httpd state=restarted
Run a playbook:
$ ansible-playbook myplaybook.yml
Check the playbook syntax:
$ ansible-playbook --check myplaybook.yml
Lint a playbook (Debian has an ansible-lint
package):
$ ansible-lint myplaybook.yml
Walk through a playbook step-by-step:
$ ansible-playbook --step myplaybook.yml
Make a playbook executable and head it with this shebang like to run it like a shell script:
#!/usr/bin/ansible-playbook
A playbooks can contain {{ variables }}
.
Register the results of shell commands as variables like:
tasks:
- shell: /usr/bin/foo
register: foo_result
ignore_errors: True
http://docs.ansible.com/ansible/latest/modules.html
Show available modules:
$ ansible-doc -l
Ansible offers many modules from the very basic and universal to the esoteric.
Interesting modules include: apt, at, copy, cron, dnf, docker*, git, group, hostname, htpasswd, include, ipnetns (v2.5+), iptables, letsencrypt, lvol (lvm), mail, mount, mysql, nmcli, package, sysctl, systemd, user, virt (libvirt), vyos_, win_*, yum.
Ansible Vault stores secrets. Vault does file-level symmetric encryption. One of the main problems it solves is preserving secrets as part of a versioned repository without exposing them.
$ ansible-vault create secrets.yml
New Vault password:
Confirm New Vault password:
$ cat secrets.yml
$ANSIBLE_VAULT;1.1;AES256
32323261326237346266386136343866346538303061336562326132313536663137656664306266
3435326361666537373536613638323935393662336263370a323065646233366464346432646664
39373837386531306435636438363066393838356364376533636230346630613163643437396235
3538326537346362300a363966386138363037643930393561663436653664653030666234303965
61333934633935383931646166613533643636666334303337353337396536653937
$ ansible-vault edit secrets.yml
Vault password:
$ ansible-vault view secrets.yml
Vault password:
A very secret thing.
$ ansible-vault rekey secrets.yml
Vault password:
New Vault password:
Confirm New Vault password:
Rekey successful
How is this more useful that any generic symmetric encryption utility? We can name the secret values, and use them as variables in an Ansible playbook.
$ ansible-vault encrypt_string --stdin-name 'key-for-secret'
New Vault password:
Confirm New Vault password:
Reading plaintext input from stdin. (ctrl-d to end input)
My secret value.key-for-secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
32623531636165636338343630373936613133346130336532393338306330326663646132643339
6266363365643937333837383934663732646631353335620a333033363362383839336565323966
39616332323039363461363930396633643735366135633465653936643065356266313965343063
6436383833646163660a306162626136366563303261663636643330666263613839303235633532
38343964613037316434353762373765663138626464663466656631303633353331
Encryption successful
(Terminate value entry with CTRL-d, not ENTER!)
Paste the resulting encrypted variable definition into the Ansible playbooks along with any other variables. Use the variable later in the playbook by enclosing it in (quoted for YAML safety) Jinja-style double-curly-brackets.
- hosts: webservers
vars:
http_port: 8080
key-for-secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
32663335323337646165663536633432303633396131346432643231353632316539626537343236
3064613431666565623237666136363561636634633936340a383236363137353663346666613334
65623831613664653036396130386461623066363532386133653363326566323539616336363065
6134313630303132350a393031353735626237356238653430373239653635323837363666303733
6261
[…]
- name: Add web config
webconfig:
set_passwd: "{{ key-for-secret }}"
When running a playbook with encrypted variables, use --ask-vault-pass
:
ansible-playbook --ask-vault-pass -i inventory_file my_playabook.yml