📜 ⬆️ ⬇️

Server and deployment configuration: rvm, rails, puma, nginx, mina

Prehistory


I was pushed to create this article by a recent post about deploe . The following article describes how to deploy a project based on rbenv, but I will describe the situation with rvm and setting upstart.

Tasks and requirements


So, given: the simplest RubyOnRails application. In the case of the deployment of my project, I set myself the following tasks:

Why Ubuntu?
I just got used to it (more precisely, to its derivative - Linux Mint).

Why puma, and not unicorn or passenger?
I heard quite good reviews about puma, and unicron has a ugly site. Passenger, in my opinion, violates the principle of a single duty - I want to have a web server and an application server.
')
Why RVM?
I got used to it - I have it installed on the local machine, I want to see it in production.

Why mina?
It really is easier than capistrano and faster. The speed is achieved due to the fact that for each task capistrano creates a separate ssh connection. Mina forms the shell script and executes it within one connection.

In this case, the task is easily divided into 3 stages:
  1. Make sure that the application starts correctly (without automation);
  2. Configure the server so that our rails application works as a full service;
  3. Based on this, configure automated deployment using mina.


First start


A webapp user has been created on the server, on behalf of which our application will run. Installed rvm (in this example, only for the user webapp), ruby ​​version we need, nginx and so on. The following line is present in the Gemfile:
# ... gem 'puma', group: :production # ... 

Preset mina

Add the following line to the Gemfile:
 gem 'mina', group: :development 

Then we generate the configuration file with the command:
 mina init 

Then we will bring the created file config / deploy.rb to the following form:
config / deploy.rb
 require 'mina/bundler' require 'mina/rails' require 'mina/git' set :domain, 'awesome_address' set :user, 'webapp' set :deploy_to, '/home/webapp/awesome' set :repository, 'https://github.com/awesome_user/awesome.git' set :branch, 'master' set :shared_paths, ['config/database.yml', 'config/secrets.yml', 'config/puma.rb', 'log'] task :setup => :environment do queue! %[mkdir -p "#{deploy_to}/#{shared_path}/log"] queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/log"] queue! %[mkdir -p "#{deploy_to}/#{shared_path}/config"] queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/config"] queue! %[mkdir -p "#{deploy_to}/#{shared_path}/puma"] queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/puma"] queue! %[touch "#{deploy_to}/#{shared_path}/config/database.yml"] queue! %[touch "#{deploy_to}/#{shared_path}/config/secrets.yml"] queue! %[touch "#{deploy_to}/#{shared_path}/config/puma.rb"] queue %[echo "-----> Be sure to edit '#{deploy_to}/#{shared_path}/config/database.yml', 'secrets.yml' and puma.rb."] end desc "Deploys the current version to the server." task :deploy => :environment do deploy do # Put things that will set up an empty directory into a fully set-up # instance of your project. invoke :'git:clone' invoke :'deploy:link_shared_paths' invoke :'bundle:install' #invoke :'rails:db_migrate' #invoke :'rails:assets_precompile' #invoke :'deploy:cleanup' end end 


This file commented out the lines of code responsible for the implementation of routine operations for the deployment of the project. For the time being, we will carry out these actions manually.

I also want to note that I do not add the mina configuration file to the version control.

Then run the command:
 mina setup 

And we get the necessary folder structure on the server, as well as configuration files that must be filled with correct data. It is assumed that these files will not be added to version control, but stored exclusively on the server. Below is an example of the minimum configuration for puma.
puma.rb
 environment "production" bind "unix:///home/webapp/awesome/shared/puma/puma.sock" pidfile "/home/webapp/awesome/shared/puma/puma.pid" state_path "/home/webapp/awesome/shared/puma/puma.state" activate_control_app 


Also, I do not purposely store the puma.rb file in version control, since I believe that the server startup configuration can be individual as well as the database settings.

For a note, it is enough to create a file config / puma.sample.rb with an example in the application.

Now let's launch the deployment of our application:
 mina deploy 

After executing this command, a symbolic link / home / webapp / awesome / current will appear on the server with the code of our project on the server.

Nginx configuration
Create the / etc / nginx / sites-available / awesome file with the following contents:
awesome
 upstream awesome { server unix:///home/webapp/awesome/shared/puma/puma.sock; } server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /home/webapp/awesome/current/public; server_name localhost; location / { proxy_pass http://awesome; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ~* ^/assets/ { # Per RFC2616 - 1 year maximum expiry expires 1y; add_header Cache-Control public; # Some browsers still send conditional-GET requests if there's a # Last-Modified header or an ETag header even if they haven't # reached the expiry date sent in the Expires header. add_header Last-Modified ""; add_header ETag ""; break; } } 

Then create a symbolic link:
 cd /etc/nginx/sites-enabled/ ln -s ../sites-available/awesome awesome 

And restart nginx:
 service nginx restart 

Manual application launch
Now we will prepare and launch our project.
We prepare:
 cd /home/webapp/awesome/current bash --login rvm use ruby-2.2.1 bundle install --without development:test --path ./vendor/bundle --deployment RAILS_ENV=production bundle exec rake db:migrate RAILS_ENV=production bundle exec rake assets:precompile 

Run:
 bundle exec puma -C config/puma.rb 

We open the project in the browser - everything should work.

Application as a service


In order for the application to work as a service and run when the system starts / reboots, you need to create an init.d script or upstart configuration.

I chose upstart, because the resulting file is much shorter and easier to read.

In order to correctly make friends upstart and rvm, create a wrapper for the executable files of our gems:
 rvm alias create awesome ruby-2.2.1@default 

Then create the upstart configuration /etc/init/awesome.conf:
awesome.conf
 description "Awesome puma service" # This starts upon bootup and stops on shutdown start on runlevel [2345] stop on runlevel [06] setuid webapp setgid webapp respawn respawn limit 3 30 script cd /home/webapp/awesome/current /home/webapp/.rvm/wrappers/awesome/bundle exec puma -C config/puma.rb end script 

And now we can do this:
 start awesome stop awesome restart awesome status awesome 


Final automation


Since the deployment will occur on behalf of the user webapp, and to restart the service, we need superuser rights, we add the appropriate line in sudoers.

Run the command:
 visudo 

Add:
 webapp ALL=(ALL) NOPASSWD: /sbin/start awesome, /sbin/stop awesome, /sbin/restart awesome, /sbin/status awesome 

And save.

Mina again
In such cases, mina offers to use our own mina / rvm module, however, rvm gives us the ability to create aliases for the ruby ​​and gemset versions - wrappers.

In order for the wrappera bundler to start at a delay, you need to add the following line:
 set :bundle_bin, '/home/webapp/.rvm/wrappers/awesome/bundle' 

Also add a task to restart the server:
 desc "Restart the puma web server." task :restart do queue 'sudo restart awesome' end 

Adding all this and uncommenting the necessary lines in the deploy task, we get the following:
deploy.rb
 require 'mina/bundler' require 'mina/rails' require 'mina/git' set :domain, 'awesome_address' set :user, 'webapp' set :deploy_to, '/home/webapp/awesome' set :repository, 'https://github.com/awesome_user/awesome.git' set :branch, 'master' set :bundle_bin, '/home/webapp/.rvm/wrappers/awesome/bundle' set :shared_paths, ['config/database.yml', 'config/secrets.yml', 'config/puma.rb', 'log'] task :setup => :environment do queue! %[mkdir -p "#{deploy_to}/#{shared_path}/log"] queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/log"] queue! %[mkdir -p "#{deploy_to}/#{shared_path}/config"] queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/config"] queue! %[mkdir -p "#{deploy_to}/#{shared_path}/puma"] queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/puma"] queue! %[touch "#{deploy_to}/#{shared_path}/config/database.yml"] queue! %[touch "#{deploy_to}/#{shared_path}/config/secrets.yml"] queue! %[touch "#{deploy_to}/#{shared_path}/config/puma.rb"] queue %[echo "-----> Be sure to edit '#{deploy_to}/#{shared_path}/config/database.yml', 'secrets.yml' and puma.rb."] end desc "Deploys the current version to the server." task :deploy => :environment do deploy do # Put things that will set up an empty directory into a fully set-up # instance of your project. invoke :'git:clone' invoke :'deploy:link_shared_paths' invoke :'bundle:install' invoke :'rails:db_migrate' invoke :'rails:assets_precompile' invoke :'deploy:cleanup' to :launch do invoke :'restart' end end end desc "Restart the puma web server." task :restart do queue 'sudo restart awesome' end 


All is ready!
Now we can launch the project again and look at the result:
 mina deploy 


Alternatives


Alternatively, puma-jungle can be considered.
These are the upstart and init.d templates of scripts that correctly work with rbenv, rvm and other version managers.

In the case of rvm, you need to make sure that the version of ruby ​​and gemset is correctly identified, if it is used, for this you can use .ruby-version and .ruby-gemset files.

You can put them in version control, or you can create them during deployment through mina. You will also need to use the mina / rvm library and create a task to install the correct version of ruby ​​and gemset.

All together may look like this:
deploy.rb
 # ... # Rvm ruby version and gemset rvm = { ruby_version: 'ruby-2.2.1', ruby_gemset: 'default' } task :environment do invoke :"rvm:use[#{rvm[:ruby_version]}@#{rvm[:ruby_gemset]}]" end # ... desc "Creates appropriate .ruby-version and .ruby-gemset files." task :'rvm:dot_files' do queue! %[echo "#{rvm[:ruby_version]}" > .ruby-version] queue! %[echo "#{rvm[:ruby_gemset]}" > .ruby-gemset] end task :setup => :environment do # ... end task :deploy => :environment do deploy do invoke :'git:clone' invoke :'rvm:dot_files' # ... end end # ... 


the end


This is where my publication ends. I hope it was interesting and useful.

Source: https://habr.com/ru/post/265599/


All Articles