In this post, I am going to be creating a demo Rails stack using Chef, Vagrant and Berkshelf.
Chef is an open source configuration management system, it allows you to treat your infrastructure as a code. Vagrant allows you to create Virtualbox machines, they also have support for Vmware. And, finally Berkshelf handles cookbook dependencies, you can think of it as a bundler for your cookbooks. One of the things I like about Brekshelf is that it comes with set of principles, it really helps you to leverage community cookbooks, I hope to demonstrate that in this blog post.
Manage cookbook dependencies using Berkshelf
I assume that you already have Chef, Vagrant and Berkshelf installed. Now, lets think about all the different software we need for Rails stack. We need apache, mysql, Ruby, Rails ,git, and we obviously need code repo which we are going to deploy . I created a new app using:
rails new rails_base
I hosted the code on github. Lets start by creating a new cookbook using Berkshelf.
berks cookbook rails_cookbook
Our first step is to add dependencies to our newly created cookbook, cd into rails_cookbook and add these lines to metadata.rb.
depends "mysql" depends "ssh_known_hosts" depends "application_ruby" depends "database"
Basically we are adding community cookbook dependcies for our cookbook. We’ll use git cookbook to install git, mysql cookbook will install mysql-client ,server and also ruby driver for mysql. I am also including community cookbook ssh_known_hosts, this cookbook will add github to known hosts, so it doesn’t fail during checkout. Application_ruby is the main cookbook which will install passenger, rails and all the other dependencies for a rails app, like bundler, rake and etc. Database cookbook allows you to manage databases, you can do things like creating users and database they have support for multiple database, in our case we are going to use it for mysql. Since we’ve added these recipes into metada.rb, lets go ahead and use berkshelf to download them. You can do that by doing this command
berks install Using rails_cookbook (0.1.0) Using application_ruby (2.1.0) Using git (2.6.0) Using dmg (2.0.0) Using build-essential (1.4.0) Using yum (2.3.0) Using windows (1.9.0) Using chef_handler (1.1.4) Using runit (1.1.4) Using mysql (3.0.2) Using openssl (1.0.2) Using ssh_known_hosts (1.0.0) Using partial_search (1.0.2) Using database (1.4.0) Using postgresql (3.0.2) Using apt (1.10.0) Using aws (0.101.2) Using xfs (1.1.0) Using unicorn (1.3.0) Using apache2 (1.6.6) Using passenger_apache2 (2.0.4) Using application (3.0.0)
By default all these cookbook get downloaded to ~/.berkshelf . You can override by setting environment variable BERKSHELF_PATH. Now lets go ahead and create a recipe for our cookbook. Open recipes/default.rb
include_recipe "git" include_recipe "mysql" include_recipe "mysql::server" include_recipe "mysql::ruby" mysql_connection_info = {:host => "localhost", :username => 'root', :password => 'rootpass'}
This will install git, mysql, mysql server and mysql support for ruby. My_connection_info hash contains information for root user, it will install mysql and set the root password to ‘rootpass’
Next, we need to create user for our rails app, in my case I will create a username demo. I am going to use chef resources user and group resource to create the user and group. We are also going create ‘demo’ database and ‘demo’ user in mysql.
Mysql database user and OS user
group "demo" do gid 505 action :create # see actions section below end user "demo" do uid 505 gid 505 action :create end mysql_database 'demo' do connection mysql_connection_info action :create end mysql_database_user 'demo' do connection mysql_connection_info password 'awesome_password' action :create end
Rails app deployment
Lets focus actual rails components, paste following config in the default.rb
application "demo.nclouds.com" do path "/usr/local/www/demo" owner "demo" group "demo" deploy_key node['demo']['deploy_key'] repository 'git@github.com:jtgiri/rails_base.git' revision "HEAD" rails do gems ['bundler'] database do database "demo" username "demo" password "awesome_password" end end passenger_apache2 do end end
demo.nclouds.com is the domain we are going to use for testing. But We need to create a template for our vhost. So let’s create template in template/default/demo.nclouds.com.conf.erb . Since application cookbook is just using apache cookbook, I copy pasted the template from this link.
<VirtualHost *:80> ServerName <%= @params[:server_name] %> ServerAlias <% @params[:server_aliases].each do |a| %><%= a %> <% end %> DocumentRoot <%= @params[:docroot] %> RewriteEngine On <Directory <%= @params[:docroot] %>> Options <%= [@params[:directory_options] || "FollowSymLinks" ].flatten.join " " %> AllowOverride <%= [@params[:allow_override] || "None" ].flatten.join " " %> Order allow,deny Allow from all </Directory> <Directory /> Options FollowSymLinks AllowOverride None </Directory> <Location /server-status> SetHandler server-status Order Deny,Allow Deny from all Allow from 127.0.0.1 </Location> LogLevel info ErrorLog <%= node['apache']['log_dir'] %>/<%= @params[:name] %>-error.log CustomLog <%= node['apache']['log_dir'] %>/<%= @params[:name] %>-access.log combined <% if @params[:directory_index] -%> DirectoryIndex <%= [@params[:directory_index]].flatten.join " " %> <% end -%> RewriteEngine On RewriteLog <%= node['apache']['log_dir'] %>/<%= @application_name %>-rewrite.log RewriteLogLevel 0 # Canonical host, <%= @params[:server_name] %> RewriteCond %{HTTP_HOST} !^<%= @params[:server_name] %> [NC] RewriteCond %{HTTP_HOST} !^$ RewriteRule ^/(.*)$ https://<%= @params[:server_name] %>/$1 [L,R=301] RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f RewriteCond %{SCRIPT_FILENAME} !maintenance.html RewriteRule ^.*$ /system/maintenance.html [L] </VirtualHost>
path “/usr/local/www/demo” is straight forward, it’s going to create the document root and deploy the app in “/usr/local/www/demo”, owner “demo”group “demo” is user for running rails app, deploy_key is private key for deploying the app. Since it’s really long, I just created an attribute for deploy key in attributes/default.rb . BTW you should create attributes for like app user and etc. I am keeping it simple so you can visualize all the different attributes in one file. revision “HEAD” mean it’s going to deploy code from HEAD.
rails do gems ['bundler'] <br>database do database "demo" username "demo" password "awesome_password" end end passenger_apache2 do end end
Here is important information about bundler from application ruby cookbook readme file.
For applications that use Bundler, if a Gemfile.lock is present then gems will be installed withbundle install –deployment, which results in gems being installed inside the application directory.
-
gems: an Array of gems to install
-
bundler: if true, bundler will always be used; if false it will never be. Defaults to true if gemsincludes bundler
passenger_apache2 will install passenger. If you type vagrant up now, chef will install and configure all this software on the VM. If you got get an error:
STDERR: touch: cannot touch `/usr/local/www/demo/current/tmp/restart.txt’: No such file or directory .
Just run vagrant provision it should fix it. Now, If you goto https://33.33.33.10 in your browser you should see demo rails app. Alternatively you can edit your hosts file and add this entry
33.33.33.10 demo.nclouds.com
https://demo.nclouds.com should load fine. Which mean vhost is configured and passenger is installed. You can ssh into the VM by doing vagrant ssh to verify everything. As you can see we were able to build different pieces needed by Rails by leveraging community cookbooks. I hope it was helpful! I’ve put the code on github