Skip to content

Instantly share code, notes, and snippets.

@francois-blanchard
Last active December 1, 2017 12:52
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save francois-blanchard/f9372f48c15ffade2c9c to your computer and use it in GitHub Desktop.
Save francois-blanchard/f9372f48c15ffade2c9c to your computer and use it in GitHub Desktop.
Build server with chef for deploy rails app

Configure server for deploy rails app

Objective

Prepare a server for a Rails app with MySQL (percona) + NGINX (passenger)
Deploy rails app in new server with Capistrano v3

For this example we need :

Prepare server

Install the latest Ubuntu server version on your server. (http://www.ubuntu.com/download/server)

Prepare chef

Go to local rails app.
Create a /chef directory and an Gemfile inside

$ mkdir chef
$ cd chef
$ vi Gemfile

In Gemfile add gems :

  • chef
  • knife-solo
  • knife-solo_data_bag
  • librarian-chef
# Gemfile
source "https://rubygems.org"

# chef
# https://github.com/opscode/chef
gem 'chef', '11.10.4'

# knife-solo
# http://matschaffer.github.io/knife-solo/
gem 'knife-solo', '0.4.2'

# knife-solo_data_bag
# http://thbishop.com/knife-solo_data_bag/
gem 'knife-solo_data_bag', '1.0.1'

# librarian-chef
# https://github.com/applicationsonline/librarian-chef
gem 'librarian-chef', '0.0.4'

So install gems

$ bundle install

Init knife solo

Now we are ready for init knife solo.
Check if you are in /chef directory and execute this commande :

$ knife solo init .

Knife created files and directories into chef directory :

chef
├── .chef
│   └── knife.rb
├── cookbooks
├── data_bags
├── nodes
├── roles
└── site-cookbooks

Add cookbook

Let's cook ! We need 2 things :

  • cookbooks
  • nodes

Cookbook it's similar to a gem.
Find gem with https://rubygems.org/
Find cookbook with https://supermarket.getchef.com/
We will use a set of cookbook, open Cheffile and copy this list :

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

# build-essential
# https://github.com/opscode-cookbooks/build-essential
cookbook 'build-essential', '2.0.0'

# apt
# https://github.com/opscode-cookbooks/apt
cookbook 'apt', '2.3.8'

# ark
# https://github.com/bryanwb/chef-ark
cookbook 'ark', '0.7.2'

# hostname
# https://github.com/3ofcoins/chef-cookbook-hostname
cookbook 'hostname', '0.1.0'

# nginx
# https://github.com/opscode-cookbooks/nginx
cookbook 'nginx', '2.7.4'

# ssh_known_hosts
# https://github.com/opscode-cookbooks/ssh_known_hosts
cookbook 'ssh_known_hosts', '1.3.0'

# user
# https://github.com/fnichol/chef-user
cookbook 'user', '0.3.0'

# apt-periodic
# https://github.com/madwork/chef-apt-periodic
cookbook 'apt-periodic', '0.1.1'

# chruby-build
# https://github.com/madwork/chef-chruby-build
cookbook 'chruby-build', '0.2.1'

# fail2ban
# https://github.com/opscode-cookbooks/fail2ban
cookbook 'fail2ban', '2.1.2'

# database
# https://github.com/opscode-cookbooks/database
cookbook 'database', '2.1.6'

# certificate
# https://github.com/atomic-penguin/cookbook-certificate
cookbook 'certificate', '0.5.2'

# percona
# https://supermarket.getchef.com/cookbooks/percona
cookbook 'percona', '~> 0.15.5'

# rvm
# https://supermarket.getchef.com/cookbooks/rvm
cookbook 'rvm', '~> 0.9.2'

# Curl
# https://supermarket.getchef.com/cookbooks/curl
cookbook 'curl', '~> 2.0.0'

For install cookbook, execute :

$ librarian-chef install

Create node

Next create node.
Node it's use for call and config cookbook as you want. Create a node file named myserver.com.json into nodes directory

{
  "platform": "ubuntu",
  "platform_version": "trusty",
  "set_fqdn": "myserver.com",
  "ark": {
    "prefix_root": "/usr/local/src"
  },
  "rvm": {
    "default_ruby": "ruby-2.1.1",
    "rubies": ["ruby-2.1.1"],
    "group_users": ["paco","root"],
    "global_gems": [
      { "name": "passenger"},
      { "name": "rails", "version": "4.1.2" }  
    ]
  },
  "users": ["paco"],
  "run_list": [
    "recipe[apt]",
    "recipe[build-essential]",
    "recipe[ark]",
    "recipe[hostname]",
    "recipe[fail2ban]",
    "recipe[apt-periodic]",
    "recipe[curl]",
    "recipe[user::data_bag]",
    "recipe[rvm::system]",
    "recipe[percona::client]",
    "recipe[percona::server]",
    "recipe[database]",
    "recipe[tool]"
  ]
}

Each cookbook has his own config. Show https://supermarket.getchef.com/ for more details.

Create data bag

We have to use data bag in conf node for percona and user cookbooks.
Data bag allow to générate encrypt data authentification.

First generate `SECRET_FILE:

openssl rand -base64 512 | tr -d '\r\n' > encrypted_data_bag_secret

For Percona :

$ knife solo data bag create passwords mysql --secret-file '/path/to/encrypted_data_bag_secret'
# knife solo data bag open window with id, add data, save and close file
{
  "id": "mysql",
  "user_1": "mdp",
  "user_2": "mdp"
}

You can verified into data_bags directory
mysql.json file is crypted.

If you want edit data bag execute this line :

$ knife solo data bag edit passwords mysql --secret-file '/path/to/encrypted_data_bag_secret'

For User :

$ knife solo data bag create users paco
{
  "id": "paco",
  "comment": "user UNIX",
  "groups": [
    "adm",
    "sudo"
  ],
  "create_group": true,
  "shell": "/bin/bash",
  "ssh_keygen": true,
  "ssh_keys": [
    "ssh-rsa 1",
    "ssh-rsa 2"
  ]
}

Build server

Prepare chef client on server

$ knife solo prepare root@myserver.com

Execute chef recipes

$ knife solo cook root@myserver.com

Deploy rails app with capistrano v3

Install Capistrano

Add capistrano gem into Gemfile of rails app

group :development do
  # ...

  # DEPLOY
  gem 'capistrano', '~> 3.1.0'
  gem 'capistrano-bundler', '~> 1.1.2'
  gem 'capistrano-rails', '~> 1.1.1'
  gem 'capistrano-rvm', github: "capistrano/rvm"
  #

end

Install gem

$ bundle installl

Init capistrano files

$ cap install

Configuration Capistrano (for production deploy)

For production environment we have to change 3 files :

  • Capfile
  • config/deploy.rb
  • config/deploy/production.rb
# Capfile

# Load DSL and Setup Up Stages
require 'capistrano/setup'

# Includes default deployment tasks
require 'capistrano/deploy'

require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails'

# Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }
# config/deploy.rb

# config valid only for Capistrano 3.1
lock '3.1.0'

set :application, 'name_app'
set :repo_url, 'git@github.com:GitName/git_repo.git'
set :tmp_dir, "/path/to/server"

set :scm, :git

set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

set :keep_releases, 5

set :default_env, { rvm_bin_path: '/usr/local/rvm/bin' }

namespace :deploy do
  
  # Create data base first deploy
  namespace :db do
    desc 'Runs rake db:create'
    task :create => [:set_rails_env] do
      on primary fetch(:migration_role) do
        within release_path do
          with rails_env: fetch(:rails_env) do
            execute :rake, "db:create RAILS_ENV=#{fetch(:rails_env)}"
          end
        end
      end
    end
  end
  
  # restart passenger
  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      # Your restart mechanism here, for example:
      execute :touch, release_path.join('tmp/restart.txt')
    end
  end

  before 'deploy:migrate', 'deploy:db:create'
  after :deploy, "deploy:migrate"
  after :publishing, 'deploy:restart'
  after :finishing, 'deploy:cleanup'

end
# config/deploy/production.rb

set :stage, :production
set :branch, "master"
set :deploy_to, '/path/to/server/www/directory'

server 'myserver.com', user: 'paco', roles: %w{web app db}

Deploy rails app

$ cap production deploy

BUGS :

  • Set password when create UNIX user
  • Set configuration percona with my.cnf

SOURCE :

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment