Setting up servers manually can be tedious and error-prone. Therefore, we used Chef1 at our company2 for defining the server layout and configuration in one place. But learning Chef was not a very easy and straight forward process and new team members are facing a steep learning curve. Therefore, when learning about Ansible 3 as a very easy to use alternative, I was happy to try it out. Now, I completed the conversion of one of our main install scripts from Chef to Ansible. Today, I want to show you, how you can start using Ansible after some minutes.
Whenever playing with servers, using Virtual machines is a good idea. So, install VirtualBox and Vagrant first.
Vagrant Setup
If you do not know about Vagrant: That is a very easy to use way to script virtual machines for fast creation of new boxes over and over again. It is strongly recommended, when starting with server provisioning and testing.
Install VirtualBox^(or VMWare instead), which might also work and Vagrant4. Check if there are newer versions, than used in the snippet below.
wget http://files.vagrantup.com/packages/b12c7e8814171c1295ef82416ffe51e8a168a244/vagrant_1.3.1_x86_64.deb
sudo dpkg -i vagrant_1.3.1_x86_64.deb
cd somedir
vagrant init
Vagrant init will create a Vagrantfile
. You should read and modify it, before downloading and starting the VM.
Here is my current one:
# Vagrantfile
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "precise32"
config.vm.box_url = "http://files.vagrantup.com/precise32.box"
# config.vm.network :private_network, ip: "192.168.111.222"
config.vm.provision "ansible" do |ansible|
ansible.playbook = "vagrant.yml"
end
config.vm.network :forwarded_port, guest: 8080, host: 3751
config.vm.network :forwarded_port, guest: 80, host: 3750
# config.vm.network :private_network, ip: "192.168.33.10"
# config.vm.network :public_network
# config.ssh.forward_agent = true
# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"
config.vm.provider :virtualbox do |vb|
vb.customize ["modifyvm", :id, "--memory", "1024", "--name", "ansible-plaything"]
end
end
I configure this particular box to use a Ubuntu precise 32bit image and enable ansible by pointing it to a (not yet existing) vagrant.yml
. Furthermure, I forward 2 ports into the VM to be able, to test the webapp inside. I also increase the RAM size to 1gig (VirtualBox only).
Start the VM by:
touch vagrant.yml # just create the file, so vagrant does not complain about missing
vagrant up
This should fail in some way, because we didn’t create the vagrant.yml
yet. Nevertheless, the VM should run in background and you can manually connect with:
vagrant ssh
# or ssh -p 2222 vagrant@localhost
# and PW:vagrant
any time. Besides, the vagrant commands halt
, destroy
and provision
are useful.
Ansible Setup
Ansible requires Python, which should be no deal on any *NIX machine. For Ubuntu and Debian there are packages, of course:
sudo apt-add-repository ppa:rquillo/ansible
sudo apt-get update
sudo apt-get install ansible
For other systems, look up in the Getting started docs
Now, the binarys ansible
and ansible-playbook
should be available.
Getting started with ansible
As pointed in the Vagrantfile, we should create a vagrant.yml
and fill in the first script:
---
# vagrant.yml
- hosts: all
user: vagrant
sudo: True
tasks:
- name: Update APT package cache
apt: update_cache=yes
- name: Install packages
apt: pkg=$item state=installed
with_items:
- bash-completion
- curl
- dnsutils
- fail2ban
- htop
- imagemagick
- iotop
- liblzma-dev
- libpcre3-dev
- mosh
- openssl
- pkg-config
- realpath
- vim
- zlib1g-dev
- name: Copy .bashrc for root
copy: src=files/bashrc dest=/root/.bashrc owner=root
-# include: ag.yml
In above yaml:
hosts: all
is a matcher, to target specific types of servers (more, see under Inventory in the docs).- we set the install user to vagrant (instead of root/current user) with sudo.
- furthermore define 4 tasks:
- Refresh of packages (read: apt-get update)
- Installation of some apt-packages. Here, you can see the template syntax
with_items
, which will loop over all the items in the list and execute the command. A command usally consists of at least 2 parts: a (optional but recommended) name, and the command itself. Here, we reference the apt-module with 2 arguments, a package and a expected state (install the packages ~ possible could also be remove/purge). - The second command will copy a file .bashrc to the root server and set the owner to root. To make this run, create a folder/file
./files/bashrc
in the project folder and insert your desired settings. - Last, we also include another playbook,
ag.yml
, which will be included into the playbook. First comment it out, it will be used in the next section.
You can create an empty ag.yml or leave it out and install your server:
vagrant provision
# or:
ansible-playbook -i vagrant_ansible_inventory_default --verbose --user=vagrant --private-key=~/.vagrant.d/insecure_private_key vagrant.yml
Vagrant will run some command similar to the second one. This is also the syntax to keep in mind, when deploying that installation to a real machine.
Reuse
Ansible has 2 ways of structuring: inclusions and roles.
Inclusion
Just uncommend/insert the - include: ag.yml
line in the playbook and create this:
---
# ag.yml
- name: Install packages for Ag
apt: pkg=$item state=installed
with_items: [ automake,pkg-config,libpcre3-dev,zlib1g-dev,liblzma-dev, git, build-essential]
- name: Checkout Ag
git: repo=https://github.com/ggreer/the_silver_searcher.git
dest=/usr/local/src/ag
update=no
- name: Compile Ag
command: bash build.sh chdir=/usr/local/src/ag
creates=/usr/local/src/ag/ag
- name: Install Ag
command: make install chdir=/usr/local/src/ag
creates=/usr/local/bin/ag
This will install the awesome code search tool Ag directly from github onto the server. This presents a very good example where we:
- installing some packages - this time with condensed array syntax
- Check out the Git of Ag to /usr/local/src/ag
- Run a shell command “build.sh” in this directory. A lot of commands have the argument
creates=FILENAME
, which will check afterwards if the file was created. It will also skip the whole command, if that file already exists. Note that you can use linebreaks in YAML or write the whole command in one line. - Run make install, again with checking for existing
Roles
The other kind of reuse pattern are roles.
Create a folder roles/common/tasks
and move the ag.yml to roles/common/main.yml
:
mkdir -p roles/common/tasks
mv ag.yml roles/common/tasks/main.yml
Then, instead of including the ag.yml
, add a role to the server group:
---
# vagrant.yml
- hosts: all
user: vagrant
sudo: True
roles:
- common
#...
Our folders and files:
├── files
│ └── bashrc
├── roles
│ └── common
│ └── tasks
│ └── main.yml
├── vagrant_ansible_inventory_default
├── Vagrantfile
└── vagrant.yml
A role can have it’s own tasks, templates5, files and handlers6.
Read this gist for a complete overview over all command options, handlers6, files and vars. Also check out the full module reference, which are the built-in commands, like apt, git, mysql and so on.
Conclusion
- [+] easy to understand, easy to write through yaml
- [+] very humble requirements: No daemon/dedicated server needed, just SSH and python.
- [+] easy variables - compared to Chef node attributes, which have like can be defined at a gazillion places and different syntaxes
- [+] Batteries included: A lot of modules already included, so no need to browse Github for recipes
- [-] Not as powerful as Ruby - Loops/conditionals are somewhat more limited to use
I won’t look back to Chef. Chef always seemed to me a bit over-engineered and breaking a fly on the wheel for my usecases. Ansible looks very good, when you have to manage, say like <100 servers.
ansible all -m setup -i vagrant_ansible_inventory_default --user=vagrant --private-key=~/.vagrant.d/insecure_private_key
-
Chef on Opscode http://www.opscode.com/chef/ ↩
-
pludoni GmbH http://www.pludoni.de/team ↩
-
Thread on hackernews about Ansible https://news.ycombinator.com/item?id=5244000 ↩
-
http://vagrantup.com ↩
-
Besides static files, you can also create template-files, which are just files with special ```` -syntax, to copy variables there. You can define your own variables in the
vars:
section of the playbook. Furthermore, there are some predefined variables, which you can display: ↩ -
Handlers: are just commands that are executed after changes - like restarting a server after the config changed. They are defined in an independent section (handlers:) or into a different file, when using roles (roles/ROLENAME/handlers/main.yml) ↩ ↩2