Configuring Nginx and Unicorn with Chef

In this post I’ll explain how to configure Nginx and Unicorn with Chef and will share with you some insights and best practices you may find useful while configuring your nodes.

Nginx and Unicorn Configuration

The first step on setting up Nginx and Unicorn configuration with Chef is to download the corresponding cookbooks. You can do this with any cookbook management tool like Berkshelf or Librarian-Chef. In this example I will use Berkshelf.

# Berksfile

site :opscode

cookbook 'nginx'
cookbook 'runit'
cookbook 'unicorn'

Lock your cookbooks in specific versions and be very careful when you update a cookbook.

After adding the cookbooks to the Berksfile file, run:

$> berks install --path cookbooks

Because we will installling nginx from source, using the nginx::source recipe, you should include the runit cookbook. Runit is a service supervision tool, within it’s core features it is cabable of re-starting a supervised service when it exits unexpectedly. Runit is not a strict dependency because its use can be controlled by an attribute, so you should add the cookbook by yourself.

Node Configuration

Typically, you will dump all your node’s configuration JSON attributes in the sample-application.json node configuration file.

But I consider it is a best practice to encapsulate common configuration into chef roles to achieve maximum reusability and maintainability.

# roles/web.json

{
  "name": "web",
  "chef_type": "role",
  "json_class": "Chef::Role",
  "description": "HTTP Server",
  "default_attributes": {
    "user": {
      "name": "deploy"
    },
    "nginx": {
      "default_site_enabled": false,
      "source": {
        "modules": ["http_gzip_static_module", "http_ssl_module"]
      },
      "version": "1.3.15"
    },
    "unicorn": {
      "options": { "backlog": "64" },
      "preload_app": true,
      "worker_processes": "2",
      "worker_timeout": "30"
    }
  },
  "run_list": [
    "recipe[runit]",
    "recipe[nginx::source]",
    "recipe[unicorn]"
  ]
}

Note that the web role is application agnostic, meaning this that you will not find application specific attributes.

Specific application configuration should be placed in the node’s configuration file. In this example, I am configuring Unicorn for a Ruby on Rails application which uses ActiveRecord and is deployed with Capistrano.

# nodes/node.json

{
  "app": {
    "name": "sample-application"
  },
  "unicorn": {
    "after_fork": "defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection",
    "before_fork": "defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!",
    "config_file": "/home/deploy/sample-application/shared/unicorn.rb",
    "listen": "/tmp/unicorn.sample-application.sock",
    "pid": "/home/deploy/sample-application/shared/pids/unicorn.pid",
    "stderr_path": "/home/deploy/sample-application/shared/log/unicorn.log",
    "stdout_path": "/home/deploy/sample-application/shared/log/unicorn.log",
    "working_directory": "/home/deploy/sample-application/current"
  },
  "run_list": [
    "role[web]"
  ]
}

Because we instructed Nginx not to create a default site, we should do this through a custom recipe.

# site-cookbooks/sample-application/recipes/default.rb

template "#{node.nginx.dir}/sites-available/#{node.app.name}" do
  source "site.erb"
  mode 0644
  owner node.nginx.user
  group node.nginx.user
end

nginx_site "#{node.app.name}"

The site.erb file is a custom template for the ngnix site configuration (server blocks, etc.). You can see an example of this templates inside the nginx cookbook, in nginx/templates/default/default-site.erb. See http://wiki.nginx.org/Configuration for more configuration details.

Thanks for reading, and see you in the next post.

devops

Comments

comments powered by Disqus