Billy Fung

Vagrant, VM and Development

Billy Fung / 2016-08-14


Creating production environment locally

Currently the workflow that I use for local development involves using Vagrant to create a virtual machine and then run Ansible to provision the machine with environment that will be on the production system. The benefits of this is that the virtual machine can be easily created and destroyed (in case you mess something up) and you will know exactly what happens when you push to production. This is ideal, since you will most likely be setting up a server that will not be the exact same as your local machine.

Local development

The gist goes Vagrantfile -> Ansible -> development. The Vagrantfile file I generally have looks like this:

    # -*- mode: ruby -*-
    # vi: set ft=ruby :
    
    # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
    VAGRANTFILE_API_VERSION = "2"
    
    Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
        config.vm.box = "bento/ubuntu-16.04"
    
        config.vm.define "machine"
        config.vm.hostname = "machine"
        config.vm.network "private_network", ip: "192.168.100.200"
        config.vm.network :forwarded_port, guest: 5432, host: 5432
        config.vm.network :forwarded_port, guest: 8000, host: 8000
    
        config.vm.provision :ansible do |ansible|
            ansible.playbook = "ansible/site.yml"
            ansible.groups = {
                "vagrant" => ["machine",]
            }
        end
    
        config.vm.provider "virtualbox" do |v|
            v.memory = 2048
            v.cpus = 2
        end
    
    end
    

This Vagrantfile will create an Ubuntu OS, and then it will set up the networking, namely the private IP and ports forwarded for your usage. This part is very important, because opening the ports will allow your machine to talk to other applications that might need to use it. Vagrant will also open up a SSH port by default.

Provisioning

The set after the VM configuration is to provision the VM, which in this case is done with Ansible. Provisioning is essentially the procedure after you’re acquired all the ingredients. Ansible uses a concept called a playbook, which consists of a set of “roles” that are run in order to install packages and other tasks. A simple ansible/site.yml playbook will look like:

    - name: Provision project
      hosts: all
      become: true
    
      roles:
        - common
        - db
        - project

This shows us what roles will need to be looked at, and the host that the playbook is pointed at. Within the roles folder, there will be tasks, handlers, and conf. The Ansible docs are fairly well written, and I found them to be very useful. A quick example is that within the common role, you would have a task that does all the apt-get installations:

    - name: Install basic utilities
      apt: name={{ item }} state=present
      become: yes
      with_items:
        - vim
        - git
        - curl
        - tree
        - zip
        - unzip
        - screen
        - iotop
        - htop

With the concept of playbooks, it will allow you to have specific recipes for dev (dev-site.yml), and for prod (prod-site.yml).

Port Forwarding

In order to be able to communicate between the VM, and your computer that you used to make the VM, is to have the ports configured properly, and no firewall conflicts.

For example, if you have a PostgreSQL database, and a Flask app, you might want to be able to run both services and be able to communicate with them in another VM, or just locally. This means that the port must be opened in the VM, but there also needs to be a firewall rule in a task/main.yml file:

    - name: Allow SSH
      ufw: rule=allow port=22
    
    - name: Allow Postgres
      ufw: rule=allow port=5432
    
    - name: Set firewall default policy
      ufw: state=enabled policy=reject
    
    - name: Allow HTTP-development
      ufw: rule=allow port=8000

Doing so means you can run your Flask app on the VM, and go to your local browser and be able to view it. This is pretty much required for any web development since you’ll want to be able to go to your browser and visit localhost:8000 to view what your webpage looks like, instead of being limited to running curl within your VM.