Millet Porridge

English version of https://corvo.myseu.cn

0%

Simple Usage of Ansible

The Occasion

Since work uses puppet quite a lot, and puppet changed considerably after its major version upgrade, and since puppet is written in ruby — a tech stack I don’t have — I could only reuse other people’s modules. So in my spare time I researched alternatives and discovered Ansible. Of course similar tools include chef and saltstack, but I’ve only used Ansible briefly; if I get the chance I’ll study those two as well.

For in-depth use of Ansible please consult the official documentation; this post only covers my own usage and understanding — please point out anything inappropriate.

Simple Usage

Installation

On my machine I can just use yaourt -S ansible; other Linux distributions can try their own package managers — it should be there too.

Connecting to the First Machine

Ansible connects over ssh, which means we need to give it some SSH parameters. Here I paste my own configuration directly as a reference:

1
2
3
4
5
6
7
8
9
10
# Save the configuration in a file called hosts — note, NOT /etc/hosts; these are two different things
ansible_study ❤️ cat hosts git:master*
# ansible group name: web
[web]
# host identifier: ubt
# IP address: 192.168.137.165
# SSH port: 22
# user root
# private key location
ubt ansible_host=192.168.137.165 ansible_port=22 ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_rsa_raspberry

If you’re not sure you can connect, better to try an ssh connection first; as long as it connects, you’re good.

1
ssh [email protected] -p 22 -i ~/.ssh/id_rsa_raspberry

Viewing Some Metadata

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# use -i hosts to specify the input file
# ubt: specify the host
# -m setup: run the setup module, showing all names
ansible_study ❤️ ansible -i hosts ubt -m setup git:master*
ubt | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.137.165"
],
"ansible_all_ipv6_addresses": [
"fe80::a00:27ff:febe:a041"
],
...
}
}

The result may be long; I suggest using ansible -i hosts ubt -m setup | less to scroll through it.

The First playbook

Using a playbook, configuration can be solidified and reused. official playbook docs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ansible_study ❤️  cat site_test.yml                                  git:master*
---
# specify the host name
- hosts: ubt
tasks:
- name: Test
# print the root directory
command: ls /

# execute this playbook
ansible_study ❤️ ansible-playbook -i hosts -v site_test.yml git:master*
Using /etc/ansible/ansible.cfg as config file
/home/corvo/Projects/ansible_study/hosts did not meet host_list requirements, check plugin documentation if this is unexpected
/home/corvo/Projects/ansible_study/hosts did not meet script requirements, check plugin documentation if this is unexpected

PLAY [ubt] *********************************************************************

TASK [Gathering Facts] *********************************************************
ok: [ubt]

TASK [Test] ********************************************************************
changed: [ubt] => {"changed": true, "cmd": ["ls", "/"], "delta": "0:00:00.002360", "end": "2019-03-18 17:51:39.505902", "rc": 0, "start": "2019-03-18 17:51:39.503542", "stderr": "", "stderr_lines": [], "stdout": "bin\nboot\ndev\netc\nhome\ninitrd.img\ninitrd.img.old\nlib\nlib64\nlost+found\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsnap\nsrv\nsys\ntmp\nusr\nvar\nvmlinuz\nvmlinuz.old", "stdout_lines": ["bin", "boot", "dev", "etc", "home", "initrd.img", "initrd.img.old", "lib", "lib64", "lost+found", "media", "mnt", "opt", "proc", "root", "run", "sbin", "snap", "srv", "sys", "tmp", "usr", "var", "vmlinuz", "vmlinuz.old"]}

PLAY RECAP *********************************************************************
ubt : ok=2 changed=1 unreachable=0 failed=0

You can see the result printed by the [Test] task.

A Visualization Interface for Ansible

After using Ansible I also found a GUI tool: ara, open-sourced by Redhat. The usage is:

1
2
3
4
5
6
7
8
9
10
11
12
# Install ARA
pip install ara

# load the ara environment
source <(python -m ara.setup.env)

# this runs the script with ansible-playbook
# ansible-playbook myplaybook.yml

# after the script finishes, you can see the results
ara-manage runserver
# Browse http://127.0.0.1:9191

ara

Comparing Some Ansible & Puppet Operations

Next I’ll introduce some practices from my current usage, covering both Ansible and puppet. Since the parts below are all used in daily work, I focus the comparison on these items; please point out anything inappropriate.

Creating Files and Folders

Using puppet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# create a folder
file { "/etc/nginx/conf.d":
ensure => directory,
owner => "root",
group => "root",
}

# create a file
file { "/etc/nginx/nginx.conf":
ensure => present,
owner => "root",
group => "root",
content => template("nginx/nginx.conf.erb"),
require => Package["nginx"],
}

# to create a symlink, just change ensure to link

Using Ansible

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- name: Create nginx dir
file:
path: /var/log/nginx
state: directory

- name: Install nginx config
template:
src: nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: 0775

# to create a symlink, just change state to link

Variable Definition and Template Usage

The two differ slightly: puppet uses ruby templates while Ansible uses jinja templates. In real use the difference is actually small; besides ordinary variables, both of course support if and for statements. Readers will definitely use them when actually writing templates.

Using puppet

1
2
3
4
5
6
7
8
9
10
11
12
# this defines a variable in a class, usable only within the class
$serf_home = "/home/serf"

file { "$serf_home/serfev_handler.py":
source => "puppet:///config/serf/serfev_handler.py",
ensure => present,
}

# if we defined some attributes in a json or ini file, we can view them with factor
# in the er file, reference them like this:

requirepass <%= @password %> # this sets the redis password

Using Ansible

1
2
3
4
5
6
7
8
# This is how I use Ansible variables
---
- hosts: ubt
vars:
http_port: 8

# and in the jinja template, use it like this
{{ http_port }}

Creating crontabs

During use I found that crontab configs created by puppet can be viewed directly with crontab -e, while crontab files generated by Ansible are placed in the /etc/cron.d directory — worth noting when using them.

Using puppet

1
2
3
4
5
6
cron { "do-something":
user => "root",
command => "bash /bin/run.sh",
hour => '4',
minute => '0',
}

Using Ansible

Ansible’s cron documentation

1
2
3
4
5
6
7
8
9
- name: Creates a cron file under /etc/cron.d
cron:
name: apt autoupdate
weekday: 2
minute: 0
hour: 12
user: root
job: "apt-get update"
cron_file: ansible_yum-autoupdate

Package Management on debian-family Systems

I only tested briefly on debian and ubuntu; for yum usage, readers please try it yourselves.

Using puppet

1
2
3
4
5
6
7
8
9
10
11
# deb package
package { "redis-server":
ensure => present,
provider => dpkg,
source => "/tmp/redis-server_2.8.17-1~dotdeb.1_amd64.deb",
require => Package["redis-tools"],
}
# install via apt-get
package { "libmysqlclient-dev":
provider => apt,
}

Using Ansible

1
2
- name: Install git
apt: name=git state=present

Conditional Task Execution

That is, when to execute and when to bypass a task — both puppet and Ansible support this. Personally I prefer puppet’s version; if statements feel comfortable. yaml has no if statements, though you can use when, and it also supports embedded jinja statements.

Using puppet

1
2
3
4
5
6
7
if $test_var == "my_vars" {
file { "/etc/nginx/conf.d":
ensure => directory,
owner => "root",
group => "root",
}
}

Using Ansible

1
2
3
4
5
6
7
8
9
10
11
12
# a fairly complex when statement, still practical
- name: Copy my vim files
copy:
src: '~/.vim/vundlerc.vim'
dest: "{{ vim_dir }}/vundlerc.vim"
mode: 0655
owner: "{{ me }}"
group: "{{ me }}"
backup: yes

when: (ansible_distribution == 'Debian' and ansible_distribution_version is version('8', '>')) or
(ansible_distribution == 'Ubuntu' and ansible_distribution_version is version('18.04', '>='))

systemctl and service Operations

This part is mainly about reloading some programs after certain config items are updated. I’ll use reloading Nginx as an example; essentially the difference between the two is not big.

Using puppet

1
2
3
4
5
exec { "reload nginx":
user => 'root',
command => "/usr/sbin/nginx -t && /usr/sbin/nginx -s reload",
refreshonly => true,
}

Using Ansible

1
2
3
---
- name: restart nginx
service: name=nginx state=reloaded enabled=yes

Summary Comparison of puppet and Ansible

In puppet, many features are provided by modules — for example filebeat and mysql are introduced as modules. Values can be passed in a function-call-like form, giving a more procedural feel. Also in puppet, task dependencies are specified explicitly via require; you can write tasks anywhere, and in the end just add the requires properly. puppet also has its own syntax; overall this syntax is fairly easy to understand — reading a section basically tells you what it does. Moreover, the official project provides a module management tool; whatever module you need can be installed directly. In my view, the only bad thing is that the puppet 3→4 version upgrade brought many incompatibilities — also one reason I started researching Ansible.

In Ansible, all playbook configuration files are in yaml format. It feels more like a declarative language. One nice point is that yaml config files are rendered with jinja, so you can still use variables in the config files. Compared with puppet’s module, Ansible has a more abstract structure called role; unlike puppet’s built-in module management, Ansible’s role repositories are scattered all over Github with no official management tool. In my view, at the current stage Ansible is still suitable for writing light deployment configurations; writing large configurations from scratch is not an easy task.

Some Suggestions

From my feeling — since I’m a Python developer — I still recommend Ansible. But if you use puppet for historical reasons, there’s no need to struggle to switch. When using puppet, I suggest keeping up with new versions; some modules don’t support versions below 4.0, and you definitely don’t want to write puppet modules yourself. (If you’re considering writing your own, then what this rookie said doesn’t count.)