A Vagrant based Minimum Viable Webserver

vagrant chef nginx

Feb 2013

When developing servers or clients that talk HTTP, it’s great to have mock systems that are as close as possible to a live system. Here is a quick hack to make a minimum viable webserver by using Chef-Solo and Librarian-Chef. Librarian-Chef is conceptually very close to the Ruby Bundler gem.

Install librarian-chef

   $ gem install librarian

Init librarian-chef

To get started, we need a file where we can specifiy a node’s cookbooks:

   $ librarian-chef init

This gives us the ‘Cheffile’, which is conceptually close to Bundler’s ‘Gemfile’.

Edit Cheffile

We are going to modify the Cheffile to define our node’s dependencies as follows:

#!/usr/bin/env ruby
#^syntax detection

site 'http://community.opscode.com/api/v1'

cookbook 'nginx-app', :path => "./application-cookbooks"

This means we are going to write a small application cookbook ‘nginx-app’ that lives in the path ‘/application-cookbooks’

Create the application cookbook

In contrast to cookbooks for infrastructure, application cookbooks are adjustments and setups for node specific stuff. Some background on this can be found here They are derived from e.g. community cookbooks, but contain custom stuff. So, let’s create an application cookbook for our webserver:

    knife cookbook create nginx-app -o application-cookbooks

(Eventually, you need to have knife-solo on your system to get this command to work)

This should give something like:

  application-cookbooks
   |-nginx-app
   |---attributes
   |---definitions
   |---files
   |-----default
   |---libraries
   |---providers
   |---recipes
   |---resources
   |---templates
   |-----default

Setup the webserver’s resources

Nginx can be installed with a simple package resource. This is an abstraction around a package installer such as aptitude or yum. Also we need to use a template resource for defining a virtual host:

Recipe in /nginx-app/recipes/default.rb

   package "nginx"
   
   service "nginx" do
     supports :status => true, :restart => true, :reload => true
     action [:enable, :start]
   end
   
   template "/etc/nginx/nginx.conf" do
     notifies :reload, "service[nginx]"
   end

Template in /nginx-app/templates/nginx.conf.erb

  user www-data;
  worker_processes  <%= @node[:nginx][:worker_processes] %>;
  
  error_log  /var/log/nginx/error.log;
  pid        /var/run/nginx.pid;
  
  events {
      worker_connections  1024;
      # multi_accept on;
  }
  
  http {
      include       /etc/nginx/mime.types;
  
      access_log	/var/log/nginx/access.log;
  
      sendfile        on;
      #tcp_nopush     on;
  
      keepalive_timeout  5;
      tcp_nodelay        on;
  
      gzip  on;
      gzip_disable "MSIE [1-6]\.(?!.*SV1)";
  
      include /etc/nginx/conf.d/*.conf;
      include /etc/nginx/sites-enabled/*;
  }

We use an attribute above for the worker, so we also need to define that variable:

  default[:nginx][:dir] = "/etc/nginx"
  default[:nginx][:worker_processes] = 4

Bundle the cookbooks

Now, we have everything in place and we can bundle the cookbooks with the command:

    $ librarian-chef install

Now all cookbooks are downloaded an placed into the local /cookbooks path. Two outputs can be observed, the dependencies were written into Cheffile.lock

SITE
  remote: http://community.opscode.com/api/v1
  specs:
    build-essential (1.3.2)
    nginx (1.2.0)
      build-essential (>= 0.0.0)
      ohai (>= 1.1.4)
    ohai (1.1.6)

PATH
  remote: ./application-cookbooks
  specs:
    nginx-app (0.1.0)
      nginx (>= 0.0.0)

DEPENDENCIES
  nginx-app (>= 0

This shows that our node get some additional cookbooks that are required by the nginx-app cookbook. Let’s also look at the cookbook path:

   cookbooks
   |-build-essential
   |---attributes
   |---recipes
   |-nginx
   |---attributes
   |---definitions
   |---files
   |-----default
   |-------tests
   |---------minitest
   |-----------support
   |---recipes
   |---templates
   |-----debian
   |-----default
   |-------modules
   |-------plugins
   |-----gentoo
   |-----ubuntu
   |-nginx-app
   |---attributes
   |---definitions
   |---files
   |-----default
   |---libraries
   |---providers
   |---recipes
   |---resources
   |---templates
   |-----default
   |-ohai
   |---attributes
   |---files
   |-----default
   |-------plugins
   |---recipes

As we can see, all cookbooks were bundled.

Setup Vagrant

Now, we have all things in place to provision the web server. It’s time to setup Vagrant:

    $ vagrant init Ubuntu-11.04

Edit the Vagrantfile:


Vagrant::Config.run do |config|
  config.vm.box = "Ubuntu-11.04"
  config.vm.network :hostonly, "192.168.33.10"
  config.vm.forward_port 80, 8080
  config.vm.forward_port 9292, 9292

  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = "./cookbooks"
    chef.add_recipe "nginx-app"
  end
end
And provision:
  $ vagrant up
So, that's all to get the webserver running. Now, it would be cool to get some basic Ruby/Rack interface running too. Maybe in the next post with the help of a unicorn cookbook.

Leave me feedback

Follow me on Twitter here.

comments powered by Disqus