📜 ⬆️ ⬇️

Ruby on rails: production and deploy for dummies

A year ago, I brought my first rail application to an acceptable form. The issue of using ready-made code in production did not interest me earlier. Why should I? An uncomplicated language, a concise framework - it is clearly something not more complicated than overcoming the mental brake after PHP.

The Rails development team recommends using Phusion Passenger , it is something like mod_php - installed, posted files and flew. At the time of studying the issue on the forums there were enough battles about the performance of solutions; Passenger in them was not listed as a favorite.

For advice on alternatives, I asked the technical director of the site with a million unik per day — he sent me to google on the topic of Nginx and Unicorn. Instructions for setting up the production, found in Habré, dated 2009 year. Among other things, it simply overwhelmed the flaws of the lessons “How to draw an owl”.
')
Some parts of the process are chewed in English here and there, but the monolithic tutorial was never caught. In the traditions of the rail community lies the principle that the results and problem-solving experience should be shared.

The text will hardly seem interesting to the experienced railkeepers, but if they find time for comments, I will be grateful and will make the necessary corrections.

- What will be discussed?
- This instruction will help beginners to choose and configure hosting, as well as prepare an existing project for the initial deployment and systematic rolling out updates.

- And more?
- We will use Ubuntu 14.04, RVM, Nginx, Unicorn and Capistrano. The text consists of two chapters: project preparation and server settings. All local manipulations are described in Mac OS X. For the execution of procedures, a modern IDE like RubyMine will not be superfluous; if not, TextMate will do . All described actions are fixed in a special repository .

Chapter One Hosting


Hosting selection


Sweet couple Nginx and Unicorn has a very impressive appetite for RAM, and the rails themselves require the installation of a number of additional software. These restrictions clearly indicate the need for a VPS. There is an option to use specialized hosting like Heroku , but for beginners it will be useful to do the setup process by hand.

In the context of this text, I will use the newly created Digital Ocean droplet based on Ubuntu 14.04. (Promotional codes for a month — two free uses on the Internet are enough, who needs a referral with ten dollars in the account — I will give a link.)

Updating system packages


We go into the system under the root and update the packages:

sudo apt-get update sudo apt-get upgrade 

Installing Git and NodeJS


 sudo apt-get install git-core nodejs 

Create user


I assume that you (like me) have a commercial interest in deployment. Let's create a separate user (in other words, the client), in the home directory of which we will deploy the application. In addition to the obvious, this approach provides advantages in the form of a separate RVM (ruby version manager), which allows the use of different versions of the interpreter and gems for different clients and applications. We will create a demo user and add it to the sudo group.

 sudo adduser demo sudo adduser demo sudo 

We close the session, log in as a demo user.

Cancel password request for sudo


Some deployment procedures will require superuser privileges. To ensure that commands are executed using Capistrano and do not cause errors, you must disable the password request. Edit the sudoers : sudo nano / etc / sudoers file.

 #   : %sudo ALL=(ALL:ALL) ALL #     : %sudo ALL=(ALL) NOPASSWD:ALL 

The official Capistrano documentation explains that the native deployment phases do not require sudo; however, if it comes to automating all processes, you cannot do without passwordless sudo.

SSH key generation


You will need two pairs of keys. With the help of one of them, you (and Capistrano) will be authorized on the server from the local computer. The second pair will give the server access to the repository (the so-called deployment key). In both cases, leave the code phrases empty.

First pair (must be generated locally ):


 ssh-keygen -t rsa -b 2048 

If you are familiar with the procedure for generating and using keys, then choose the path to the file and durability to your liking; otherwise, keep the default.

Now you need to copy the contents of the public key (by default ~/.ssh/id_rsa.pub on the local computer) and add it to the ~/.ssh/authorized_keys file on the server. After this simple manipulation with SSH, you can connect to the server without a password. If not, check the server permissions: 700 for ~/.ssh and 600 for ~/.ssh/* .

The second pair (on the server):


 ssh-keygen -t rsa -b 2048 

Similarly, the contents from the server ~/.ssh/id_rsa.pub need to be added to the list of deployment keys (in Github, you can find them in the settings of each repository).

Installing fresh nginx


The Nginx version available in Ubuntu is often older than in the official developer repository. I try to use the latest version, but if you have other views, install Nginx on your own and skip this part.

We will add the official Ubuntu repositories to the sudo nano system list /etc/apt/sources.list. Add lines to the end of the file:

 # Nginx official repository deb http://nginx.org/packages/ubuntu/ trusty nginx deb-src http://nginx.org/packages/ubuntu/ trusty nginx 

Remember that the settings used are relevant only for Ubuntu 14.04. For information on installing on other versions of the OS look on the developer's site . To install Nginx from these repositories, you will also need to download and add the key to the system:

 wget http://nginx.org/keys/nginx_signing.key sudo apt-key add nginx_signing.key 

Now you can update the list of available packages and install Nginx:

 sudo apt-get update sudo apt-get install nginx 

Remove default configs from /etc/nginx/conf.d . (Of course, do not try to do it if you are not working on a “clean” server.) We will create a virtual host for the application in the next chapter.

 sudo rm /etc/nginx/conf.d/* sudo service nginx restart 

RVM installation


 \curl -sSL https://get.rvm.io | bash -s stable source ~/.rvm/scripts/rvm 

In the case of a clean server, you will also need to install some dependencies:

 rvm requirements 

It remains to install directly the Ruby version you need for your application. For example, the new stable version 2.1.3:

 rvm install 2.1.3 rvm --default use 2.1.3 

You can check the installation correctness using the commands ruby ​​-v and rvm info

Installing Application Components


Surely, your application will use a number of components (not including gems), like a database or Imagemagick GPU, you will have to complete the installation yourself. Here you should add a small remake (if you forget about that - the automatic deployment will end with an error): the rails sometimes require additional packages to work with some components. For example, to use MySQL, you will need to install, among other things, the libmysqlclient-dev package.

Chapter Two Preparing the Application


Using git


I assume that you already have a ready application. The first rule - the project must be under the control of Git. This is the de facto standard in the rail world (even the .gitignore file in the root of the rail applications that are being created is clearly hinting at this). Moreover, the latest versions of the Capistrano gem, which will be directly responsible for deployment, natively support only this system.

What is Capistrano


The official definition of Capistrano is: “A remote server automation and deployment tool”. From the user's point of view, Capistrano is the thing that will allow you to execute an arbitrary set of commands on a remote server via SSH. There are other deployment tools (for example, Mina), but so far Capistrano is also a standard, especially since it allows you to run parallel deployment of applications directly to a number of servers, including those separated by roles.

Capistrano working principle


On the server, the structure of the application under the control of Capistrano as a whole consists of three directories: repo , releases and shared . The first is a copy of the repository, the second is the releases, the third is the common files needed by the application and not dependent on the release. Also present at the root is the symlink current , which refers to the current release version and the Deployed log file.

When you (from your local computer) give the Capistrano command to execute a deployment, an SSH connection to the server is established and the execution of a simple algorithm begins. To begin, Capistrano checks the remote repository and gets the missing commits. After a new release is created (in the releases directory). The current version of the code is shifted there and a number of tests are also performed there.

Simply put, for each new release, Capistrano executes familiar commands like bundle install , rake db:migrate , rake assets:precompile , constantly checking for conflicts and errors. We missed the semicolon in default.scss , did not commit the current Gemfile.lock , connected Paperclip, but forgot to install on the Imagemagic server? In all these cases, Capistrano will show an error and stop the installation without affecting the current running release. If the deployment was successful, but the result did not suit you, with the help of Capistrano, you can rollback to the previous release.

Organization of configuration files


Each release for Capistrano is a separate entity. At the next deployment, it is completely replaced, so a number of files must be stored outside the application structure (as a rule, content downloaded by users and application-generated content, as well as a number of configs).

We will need a common directory, the necessary files from which we will link to each new release during the deployment; Let's call it shared and create it locally at the root of the mkdir project ./shared, after adding an exception to .gitignore. (I, of course, will not add an exception to the educational repository.)

Now in the general directory we will create in advance the future structure. First we need the config and run folders. In the config put up the secrets.yml database.yml and secrets.yml . (Personally, I prefer to move these two files from the config on the local computer, and then create links to them.)

 mv ./config/database.yml ./shared/config ln -fs ./shared/config/database.yml ./config/database.yml mv ./config/secrets.yml ./shared/config ln -fs ./shared/config/secrets.yml ./config/secrets.yml 

Nginx configuration


Here - in shared/config , - we will create a config for Nginx shared/config/nginx.conf . In its basic form, it consists of two small parts: an upstream and a typical virtual host. Be especially careful with the paths (in this and all further) in configuration files. 90% of the errors that occurred during the delay were related to them.

 upstream unicorn { server unix:/home/demo/application/shared/run/unicorn.sock fail_timeout=0; } server { listen 80 default; root /home/demo/application/current/public; try_files $uri/index.html $uri.html $uri @app; location ^~ /assets/ { expires max; add_header Cache-Control public; } location @app { proxy_pass http://unicorn; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; } error_page 500 502 503 504 /500.html; location = /500.html { root /home/demo/application/current/public; } } 

We use our VPS for a single application, so the host will be used by default. (No need to explain that in a different situation you will have to explicitly specify server_name and take care of the uniqueness of the names of the upstream?)

Unicorn configuration


Since we have created an upstream, which indicated the path to the Unicorn socket, let's move on to its configuration. You must add the Unicorn gem to the Gemfile . Do not forget that the addition of new gems must be accompanied by the implementation of bundle install and the commit to the repository. If we forget about git push, then the old version of the code will be uploaded to the server, which, due to the absence of the specified gems, will cause errors during the delay.

 group :production do gem 'unicorn', '~> 4.8.3' end 

In the shared/config directory, create a file unicorn.rb . For the most part in it, we must point the way to the component parts of our application. For convenience, this can be done using variables.

 #      root = '/home/demo/application' rails_root = "#{root}/current" # ,    Unicorn- pidfile = "#{root}/shared/run/unicorn.pid" pidfile_old = pidfile + '.oldbin' pid pidfile #   worker_processes 1 preload_app true timeout 30 #    listen "#{root}/shared/run/unicorn.sock", :backlog => 1024 #   - stderr_path "#{rails_root}/log/unicorn_error.log" stdout_path "#{rails_root}/log/unicorn.log" #    GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=) #  ,     before_exec do |server| ENV["BUNDLE_GEMFILE"] = "#{rails_root}/Gemfile" end #          before_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! if File.exists?(pidfile_old) && server.pid != pidfile_old begin Process.kill("QUIT", File.read(pidfile_old).to_i) rescue Errno::ENOENT, Errno::ESRCH end end end after_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection end 

Some system values ​​are also indicated ( full list and documentation ). You should pay attention to the number of workers - worker_processes , - each of which uses a certain amount of memory (how much it depends on the application) and timeout (usually from 15 to 30 seconds). Many people also mention preload_app (the value of which false can shorten the start time of the worker); let's look at true , and then you decide for yourself.

Connect and configure Capistrano


Necessary gems


In the Gemfile you need to add Capistrano and a series of gems that implement its connection to RVM, Bundler and Rails.

 group :development do gem 'capistrano', '~> 3.2.1' gem 'capistrano-rvm', '~> 0.1.1' gem 'capistrano-bundler', '~> 1.1.3' gem 'capistrano-rails', '~> 1.1.2' end 

It remains to complete bundle install, and then initialize Capistrano with cap install. A set of files will be created, a list of which you will see in the console. We will work with three of them: Capfile , config/deploy.rb and config/deploy/production.rb . (In config/deploy Capistrano creates default staging.rb and production.rb files. We will only configure the combat server using production.rb .)

Capfile update


All gems related to Capistrano, the functionality of which we are going to use, must be connected to the Capfile created in the root. Take a look at the default file and uncomment the necessary lines or use the following content:

 require 'capistrano/setup' require 'capistrano/deploy' require 'capistrano/rvm' require 'capistrano/bundler' require 'capistrano/rails' Dir.glob('lib/capistrano/tasks/*.rb').each { |r| import r } 

Setting up a production server


Open the file config/deploy/production.rb and carefully look at its contents. It will help you understand the format and customization options. In general, if you use authentication with an SSH key, you will not have to write any exotics; just one line:

 server '178.62.252.46', user: 'demo', roles: %w{web app} 

Deploy scenario


We got to the most interesting part of the file config/deploy.rb . It is he who describes the parameters, procedures and scenario of the upcoming deployment. I will describe each block of the file, but if you want to look at it in its final form, use the repository.

Required parameters


First of all, you need to specify the version of Capistrano for which this script is intended:

 lock '3.2.1' 

Capistrano requires a number of required parameters:

 #  set :repo_url, 'git@github.com:eboyko/deneb.git' #   set :rails_env, 'production' 

As well as the deploy_to parameter, which determines the path for deploying the application on the server. Above, we have already said that we mean commercial interest, so we will make the script file more universal using variables and set the missing parameter:

 #   set :username, 'demo' #   set :application, 'application' #    set :deploy_to, "/home/#{fetch(:username)}/#{fetch(:application)}" 

Let's also set the log_level parameter (default value :debug makes Capistrano too talkative):

 set :log_level, :info 

Notice that Capistrano global variables (like shared_path ) can be used directly; set with set - via fetch method.

Release-independent data

We have already considered the need for a working release to access data created by previous versions. For example, you store files uploaded by users in public/upload . To connect them to each new release, in config/deploy.rb you can set the parameter :linked_dirs :

 set :linked_dirs, %w{public/upload} 

At every public/upload link will be replaced by a symlink leading to the shared/public/upload directory, where data accumulated during previous releases. Similarly, with :linked_files , separate files are linked:

 set :linked_files, %w{config/secrets.yml config/database.yml} 

Note that adding to the :linked_files specified files ( config/secrets.yml and config/database.yml ) is mandatory. Otherwise, the deployment will fail with an error due to the lack of connection to the database.

Procedures


Capistrano allows you to create sets of procedures that can be combined into namespaces for convenience. Namespace :deploy already exists, it can only be supplemented; all the procedures included in it for the production server can be called with the cap production deploy command

Set-up

As a result of the previous steps, we have formed the shared directory, in which, among other things, the configuration files ( shared/config ) are located. The logical first step is to upload them to the server. To do this, all in the same config/deploy.rb , in the namespace :setup , we will write the procedure:

 namespace :setup do desc '     ' task :upload_config do on roles :all do execute :mkdir, "-p #{shared_path}" ['shared/config', 'shared/run'].each do |f| upload!(f, shared_path, recursive: true) end end end end 

As you understand, the procedure involves creating shared_path (because there is no structure on the server yet) and loading the local shared/config directory there. You can execute it with the cap production setup: upload_config command

Nginx Management

I noted above that Capistrano is essentially a way to execute arbitrary commands on a remote server. We downloaded configuration files, including for Nginx. Now we will write several procedures for management: creating a symlink to the config and reload / restart of the service (they will need sudo rights).

 namespace :nginx do desc '   /etc/nginx/conf.d  nginx.conf ' task :append_config do on roles :all do sudo :ln, "-fs #{shared_path}/config/nginx.conf /etc/nginx/conf.d/#{fetch(:application)}.conf" end end desc ' nginx' task :reload do on roles :all do sudo :service, :nginx, :reload end end desc ' nginx' task :restart do on roles :all do sudo :service, :nginx, :restart end end after :append_config, :restart end 

Noticed a nice little thing? - You can set the sequence of execution of various procedures both within one namespace, and between them.

Office Unicorn

Surely there is already a heme that extends Capistrano in the Unicorn control plane, but I was very interested to find out how it actually works and works. Therefore, now, similarly to the previous examples, we will write two procedures (start and end) for Unicorn.

 set :unicorn_config, "#{shared_path}/config/unicorn.rb" set :unicorn_pid, "#{shared_path}/run/unicorn.pid" namespace :application do desc ' Unicorn' task :start do on roles(:app) do execute "cd #{release_path} && ~/.rvm/bin/rvm default do bundle exec unicorn_rails -c #{fetch(:unicorn_config)} -E #{fetch(:rails_env)} -D" end end desc ' Unicorn' task :stop do on roles(:app) do execute "if [ -f #{fetch(:unicorn_pid)} ] && [ -e /proc/$(cat #{fetch(:unicorn_pid)}) ]; then kill -9 `cat #{fetch(:unicorn_pid)}`; fi" end end end 

Do not forget to set the values ​​of the parameters :unicorn_config and :unicorn_pid .

Procedures before and after deployment

After deploying, we need to delete the oldest releases (by default, Capistrano stores the last five), clear the caches and restart Unicorn. The main working block :deploy will look something like this:

 namespace :deploy do after :finishing, 'application:stop' after :finishing, 'application:start' after :finishing, :cleanup end 

Putting procedures in separate files


In order not to clutter up the file deploy.rb , written procedures can (and maybe need) be moved beyond its limits. In Capfile last line is responsible for importing such tasks from the lib/capistrano/tasks directory, this is the place to move this part of the logic.

Deploy execution


From now on, all you need to do to roll out a new version of your application is to commit the changes, do git push and use the cap production deploy command.

By analogy, you can configure a staging server, deploy to which to perform cap staging deploy.

Something does not work? - Leave a comment, let's add an article.

Updates are expected:


- a brief overview of the available application servers (in the context of the choice of Unicorn);
- process management (unicorn worker killer and some manager);
- selection and configuration of an analogue of RVM;
- installing Nginx using PPA;
- changes regarding passwordless sudo;

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


All Articles