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.
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
provision are useful.
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-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: allis 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/bashrcin 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.
Ansible has 2 ways of structuring: inclusions and roles.
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
The other kind of reuse pattern are roles.
Create a folder
roles/common/tasks and move the ag.yml to
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
- [+] 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 ↩
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