Представьте: Вы — веб-разработчик, который только недавно освоил Ruby on
Rails. И тут Ваш первый проект подходит к стадии, когда его нужно
выложить в интернет.
Вы, конечно, можете залить его на Heroku, но тамошние цены немного
кусаются. Остается только купить VPS, настроить его и выложить проект
туда.
«Что может быть проще? Найду какой-нибудь гайд, да следаю всё по нему» —
подумаете Вы. Вот только гайдов, которые не просто выкладывают команды,
но и объясняющие, что эти команды делают, — единицы, да и те используют
уже устаревшую вторую версию Capistrano.
Поэтому я решил написать свой гайд, в котором постараюсь подробно
рассмотреть:
Первичную настройку сервера
Установку и настройку nginx (с модулем PageSpeed), postgresql, redis
Установку rvm, rails
Настройку гема foreman для управления процессами Вашего приложения
Настройку сервера Unicorn
Настройку гема Capistrano (v3.1) для автоматизации деплоя
Я надеюсь, что этот гайд будет полезен не только новичкам, но и
разработчикам со стажем.
Первичная настройка сервера
Вы купили свой первый VPS, установили ОС (я использую ubuntu 12.04 LTS и
все команды буду выкладывать именно под неё), зашли по SSH. Что делать
дальше?
Первым делом сменим пароль для пользователя root коммандой passwd
Создадим нового пользователя: adduser deployer
Разрешим ему пользоваться коммандой sudo: visudo
и дописываем: deployer ALL=(ALL:ALL) ALL
Изменим настройки ssh сервера (запретим логин под root, доступ по
доменному имени и разрешим логин только под нашим новым пользователем).
Добавляем в файл ‘/etc/ssh/sshd_config’:
1
PermitRootLogin no UseDNS no AllowUsers deployer
Перезапустим ssh сервер коммандой: reload ssh
Чтобы не вводить каждый раз пароль при подключении по ssh, нам надо
скопировать ssh ключ с Вашей машины на сервер. Самый простой способ
сделать это — выполнить на локальной машине ssh-copy-id deployer@123.123.123.123
(На маке требуется установка ssh-copy-id, сделать можно через brew, на
Windows не знаю автоматизированного средства для копирования ключей, но
в интернете есть много интересного на эту тему).
Так же, пока уж мы под рутом, можно создать SWAP файл, если у Вас мало
RAM. Делается это так: dd if=/dev/zero of=/swapfile bs=1024 count=512k mkswap /swapfile swapon /swapfile
Далее в файле ‘/etc/fstab’ добавляем строчку: /swapfile none swap sw 0 0
И далее выполняем: echo 0 > /proc/sys/vm/swappiness sudo chown root:root /swapfile sudo chmod 0600 /swapfile
Можно перезагрузиться и проверить наличие SWAP файла коммандой swapon -s
Установка и настройка nginx
На этот раз логинемся под нашим новым пользователем коммандой ssh deployer@123.123.123.123 (на локальном компьютере).
Лично я использую модуль PageSpeed, поэтому nginx собираю сам. Но
сначала нам надо обновить репозитории, обновить систему, и скачать
необходимые для успешной сборки пакеты:
wget https://github.com/pagespeed/ngx_pagespeed/archive/v1.7.30.1-beta.zip
unzip v1.7.30.1-beta.zip
cd ngx_pagespeed-1.7.30.1-beta
wget https://dl.google.com/dl/page-speed/psol/1.7.30.1.tar.gz
tar -xzvf 1.7.30.1.tar.gz
wget http://nginx.org/download/nginx-1.4.4.tar.gz
tar -xzvf nginx-1.4.4.tar.gz
cd nginx-1.4.4
./configure --add-module=$HOME/ngx_pagespeed-1.7.30.1-beta
make
sudo checkinstall
Для управления nginx напишем upstart
скрипт. Создаём файл ‘/etc/init/nginx.conf’ со следующим содержимым:
Теперь вы можете выполнять sudo start/stop/restart/status nginx
Наш nginx.conf лежит по адресу ‘/usr/local/nginx/conf/nginx.conf’, но
пока мы его трогать не будем. Мы его зальем автоматически при первом
деплое приложения.
Для наших веб приложений мы создадим нового пользователя и новую группу,
добавим себя в эту группу, и создадим папку:
1234567
sudo useradd -s /sbin/nologin -r nginx
sudo groupadd web
sudo usermod -a -G web nginx
sudo usermod -a -G web deployer
sudo mkdir /var/www
sudo chgrp -R web /var/www
sudo chmod -R 775 /var/www
Для того, чтобы мы смогли писать в папку придется разлогиниться и зайти
снова под нашим пользователем.
Установка и настройка PostgreSQL
В репозиториях ubuntu лежит устаревшая версия, так что мы добавим
сторонний репо. В файл ‘/etc/apt/sources.list.d/pgdg.list’ добавим: deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main
Затем добавляем ключ репозитория и устанавливаем PostgreSQL:
sudo -u postgres psql
create user deployer with password 'ваш пароль';
alter role deployer superuser createrole createdb replication;
\q
Чтобы иметь доступ с локального компьютера в файле
‘/etc/postgresql/9.3/main/postgresql.conf’ изменим параметр
listen_addresses = 'localhost'на listen_addresses = '*'и добавим в
файл ‘/etc/postgresql/9.3/main/pg_hba.conf’ строчку
1
host all deployer ваш.внешний.ip.адрес 255.255.255.0 md5
Перезагружаем postgresql коммандой sudo service postgresql restart
Установка и настройка Redis
Если Вы используте gem resque, то
Вам нужно установить Redis. Ибо в репозитории устаревшая версия, я его
собираю из исходников, к тому же это занимает немного времени:
12345678
sudo apt-get install tcl8.5
wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
make testsudo cp src/redis-server /usr/local/bin
sudo cp src/redis-cli /usr/local/bin
Redis по умолчанию не защищен паролем и открыт всем, поэтому поставим
пароль: в файле ‘redis.conf’ добавляем параметр requirepassс нашим
паролем. Redis легко брутфорсится, поэтому я делаю пароль не менее 100
символов. Также, чтобы потом не вылетало ошибок, меняем параметр dirна
/var/www/other, предварительно создав такую папку (
mkdir /var/www/other).
Копируем наш конфиг командой sudo cp redis.conf /etc/redis/redis.conf
Создаем upstart скрипт по адресу ‘/etc/init/redis-server.conf’ со
следующим содержанием:
Мы будем использовать git на удаленном сервере для деплоя нашего
приложения. Можно и настроить git сервер на нашем VPS, но зачем, если
есть удобные бесплатные решения. Итак, создаем репозиторий на
GitHub/BitBucket (у BitBucket приватные репозитории бесплатны), но не
спешим заливать туда наш проект, сначала отредактируем .gitignore файл
(он находится в корне приложения), чтобы в репо не попала никакая
конфидициальная информация (особенно это важно, если репо публичный), да
и заодно лишние файлы нам там не нужны:
12345
/config/database.yml # доступ к базе данным/Procfile # про него я еще расскажу/config/deploy/ # файлы Capistrano/shared/ # файлы, которых нет в репозитории, но они будут скопированы на сервер при первом деплое приложения/public/system/ # если установлен Paperclip
Теперь можно сделать первый коммит и запушить проект в git.
Также нам надо добавить ключ нашего сервера в админку Github/BitBucket,
это обязательное условие, т.к. из репозитория изменения будут
загружаться на сервер. Как это сделать можно узнать в Helpе сервиса.
gem foreman
foreman — гем для управления
процессами приложения. На локальной машине он позволяет запускать сразу
все процессы, указанные в Procfile, одной коммандой foreman start и показывает их вывод.
На сервере командой foreman export upstart он создает upstart скрипт для легкого управления приложением с помощью
команд start/stop/restart. Но об этом потом. Пока просто установим его,
создадим Procfile в корне приложения, и наполним его для локального
использования. У меня он выглядит так.
worker_processes2working_directory"/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/current"# available in 0.94.0+# listen on both a Unix domain socket and a TCP port,# we use a shorter backlog for quicker failover when busylisten"/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/socket/.unicorn.sock",:backlog=>64listen8080,:tcp_nopush=>true# nuke workers after 30 seconds instead of 60 seconds (the default)timeout30# feel free to point this anywhere accessible on the filesystempid"/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/run/unicorn.pid"# By default, the Unicorn logger will write to stderr.# Additionally, ome applications/frameworks log to stderr or stdout,# so prevent them from going to /dev/null when daemonized here:stderr_path"/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/log/unicorn.stderr.log"stdout_path"/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/log/unicorn.stdout.log"# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cowpreload_apptrueGC.respond_to?(:copy_on_write_friendly=)andGC.copy_on_write_friendly=true# Enable this flag to have unicorn test client connections by writing the# beginning of the HTTP headers before calling the application. This# prevents calling the application for connections that have disconnected# while queued. This is only guaranteed to detect clients on the same# host unicorn runs on, and unlikely to detect disconnects even on a# fast LAN.check_client_connectionfalsebefore_forkdo|server,worker|# the following is highly recomended for Rails + "preload_app true"# as there's no need for the master process to hold a connectiondefined?(ActiveRecord::Base)andActiveRecord::Base.connection.disconnect!# The following is only recommended for memory/DB-constrained# installations. It is not needed if your system can house# twice as many worker_processes as you have configured.## # This allows a new master process to incrementally# # phase out the old master process with SIGTTOU to avoid a# # thundering herd (especially in the "preload_app false" case)# # when doing a transparent upgrade. The last worker spawned# # will then kill off the old master process with a SIGQUIT.old_pid="#{server.config[:pid]}.oldbin"ifold_pid!=server.pidbeginsig=(worker.nr+1)>=server.worker_processes?:QUIT::TTOUProcess.kill(sig,File.read(old_pid).to_i)rescueErrno::ENOENT,Errno::ESRCHendend## Throttle the master from forking too quickly by sleeping. Due# to the implementation of standard Unix signal handlers, this# helps (but does not completely) prevent identical, repeated signals# from being lost when the receiving process is busy.# sleep 1endafter_forkdo|server,worker|# per-process listener ports for debugging/admin/migrations# addr = "127.0.0.1:#{9293 + worker.nr}"# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)# the following is *required* for Rails + "preload_app true",defined?(ActiveRecord::Base)andActiveRecord::Base.establish_connection# if preload_app is true, then you may also want to check and# restart any other shared sockets/descriptors such as Memcached,# and Redis. TokyoCabinet file handles are safe to reuse# between any number of forked children (assuming your kernel# correctly implements pread()/pwrite() system calls)end
Заменяем ИМЯ_ПРИЛОЖЕНИЯ на Ваше имя приложения, которое Вы потом
зададите в настройках Capistrano.
Capistrano
Capistrano — весьма удобное
средство для деплоя приложения, пусть и сначала таковым не кажется.
Установим его с нужными дополнениями, добавив в Gemfile:
Уже сейчас, просто указав адрес сервера, репозитория и рабочую папку,
Capistrano:
Загрузит ваше приложение из репозитория на сервер в папку
рабочая_папка/имя_приложения/releases/дата_релиза/, не удаляя
старую версию (по умолчанию он хранит 5 последних версий
приложений).
Выполнит bundle install.
Выполнит db:migrate.
Выполнит assets:precompile.
Создаст symlink из папки приложения в папку
рабочая_папка/имя_приложения/current
Но нам этого мало. Нам нужно реализовать следующее:
При первом деплое выполнить настройку nginx, unicorn, загрузить
некоторые файлы, которые не будут меняться, создать upstart скрипт
(с помощью foreman).
Перед каждым деплоем автоматически выполнять git add, git commit,
git push (сообщение к коммиту спрашивать у пользователя). После
каждого деплоя создавать симлинки и перезапускать Unicorn.
Нужные только в первый раз файлы будем хранить в папке shared (в папке
проекта на локальной машине), не зря мы ее добавили в .gitignore.
Сначала создадим там nginx.conf примерно с таким содержимым:
user nginx web;
pid /var/run/nginx.pid;
error_log /var/www/log/nginx.error.log;
events { worker_connections 1024; # increase if you have lots of clients accept_mutex off; # "on" if nginx worker_processes > 1 use epoll; # enable for Linux 2.6+# use kqueue; # enable for FreeBSD, OSX}http {# nginx will find this file in the config directory set at nginx build time include mime.types;
types_hash_max_size 2048;
server_names_hash_bucket_size 64;
# fallback in case we can't determine a type default_type application/octet-stream;
# click tracking! access_log /var/www/log/nginx.access.log combined;
# you generally want to serve static files with nginx since neither# Unicorn nor Rainbows! is optimized for it at the moment sendfile on;
tcp_nopush on; # off may be better for *some* Comet/long-poll stuff tcp_nodelay off; # on may be better for some Comet/long-poll stuff# we haven't checked to see if Rack::Deflate on the app server is# faster or not than doing compression via nginx. It's easier# to configure it all in one place here for static files and also# to disable gzip for clients who don't get gzip/deflate right.# There are other gzip settings that may be needed used to deal with# bad clients out there, see http://wiki.nginx.org/NginxHttpGzipModule gzip on;
gzip_http_version 1.0;
gzip_proxied any;
gzip_min_length 0;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
gzip_proxied expired no-cache no-store private auth;
gzip_comp_level 9;
gzip_types text/plain text/xml text/css
text/comma-separated-values
text/javascript application/x-javascript
application/atom+xml;
# this can be any application server, not just Unicorn/Rainbows! upstream app_server { server unix:/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/socket/.unicorn.sock fail_timeout=0;
} server {# PageSpeed pagespeed on;
pagespeed FileCachePath /var/ngx_pagespeed_cache;
location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+"{ add_header """";
} location ~ "^/ngx_pagespeed_static/"{} location ~ "^/ngx_pagespeed_beacon$"{} location /ngx_pagespeed_statistics { allow 127.0.0.1; allow 5.228.169.73; deny all;
} location /ngx_pagespeed_global_statistics { allow 127.0.0.1; allow 5.228.169.73; deny all;
} pagespeed MessageBufferSize 100000;
location /ngx_pagespeed_message { allow 127.0.0.1; allow 5.228.169.73; deny all;
} location /pagespeed_console { allow 127.0.0.1; allow 5.228.169.73; deny all;
} charset utf-8;
# enable one of the following if you're on Linux or FreeBSD listen 80 default deferred; # for Linux# listen 80 default accept_filter=httpready; # for FreeBSD# If you have IPv6, you'll likely want to have two separate listeners.# One on IPv4 only (the default), and another on IPv6 only instead# of a single dual-stack listener. A dual-stack listener will make# for ugly IPv4 addresses in $remote_addr (e.g ":ffff:10.0.0.1"# instead of just "10.0.0.1") and potentially trigger bugs in# some software.# listen [::]:80 ipv6only=on; # deferred or accept_filter recommended client_max_body_size 4G;
server_name _;
# ~2 seconds is often enough for most folks to parse HTML/CSS and# retrieve needed images/icons/frames, connections are cheap in# nginx so increasing this is generally safe... keepalive_timeout 5;
# path for static files root /var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/current/public;
# Prefer to serve static files directly from nginx to avoid unnecessary# data copies from the application server.## try_files directive appeared in in nginx 0.7.27 and has stabilized# over time. Older versions of nginx (e.g. 0.6.x) requires# "if (!-f $request_filename)" which was less efficient:# http://bogomips.org/unicorn.git/tree/examples/nginx.conf?id=v3.3.1#n127 try_files $uri/index.html $uri.html $uri @app;
location ~ ^/(assets)/ { root /var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/current/public;
expires max;
add_header Cache-Control public;
} location @app {# an HTTP header important enough to have its own Wikipedia entry:# http://en.wikipedia.org/wiki/X-Forwarded-For proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# enable this if you forward HTTPS traffic to unicorn,# this helps Rack set the proper URL scheme for doing redirects:# proxy_set_header X-Forwarded-Proto $scheme;# pass the Host: header from the client right along so redirects# can be set properly within the Rack application proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with# redirects, we set the Host: header above already. proxy_redirect off;
# set "proxy_buffering off" *only* for Rainbows! when doing# Comet/long-poll/streaming. It's also safe to set if you're using# only serving fast clients with Unicorn + nginx, but not slow# clients. You normally want nginx to buffer responses to slow# clients, even with Rails 3.1 streaming because otherwise a slow# client can become a bottleneck of Unicorn.## The Rack application may also set "X-Accel-Buffering (yes|no)"# in the response headers do disable/enable buffering on a# per-response basis.# proxy_buffering off; proxy_pass http://app_server;
}# Rails error pages error_page 500 502 503 504 /500.html;
location= /500.html { root /var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/current/public;
}}}
Это мой конфиг для nginx, там необходимо заменить ‘ИМЯПРИЛОЖЕНИЯ’ на
Ваше имя приложения, указанное в первой строчке config/deploy.rb (set
:application, ‘ИМЯПРИЛОЖЕНИЯ’).
Теперь создаем там же (в /shared/) файл Procfile с таким содержанием:
Это конфиг для приложения с двумя resque workerами. Если Вы не
используете Resque, то просто оставьте только первую строчку.
Там же создаём database.yml с настройками базы данных и application.yml,
если пользуетесь гемом Figaro.
Нвш Capistrano скрипт будет выполнять некоторые команды от имени
суперпользователя на сервере. Чтобы разрешить ему делать это, выполняем
команду sudo visudo на сервере и добавляем строчку:
Осталось только настроить Capistrano. В файле ‘config/deploy/production’
делаем изменения:
server 'IP сервера', user: 'deployer', roles: %w{web app db}
В файл ‘config/deploy.rb’ добавляем сверху:
Что всё это значит? Первые строчки — конфиг. Затем мы описываем задачи.
Есть задача foreman, у которой есть 4 действия: start, stop, restart
status. При выполнении ‘cap production foreman:start’ на локальной
машине на сервере будет выполнено ‘sudo start ИМЯ_ПРИЛОЖЕНИЯ’, но пока
что это нам ничего не даст, ибо foreman еще не создал upstart скрипты.
Идем дальше: есть задача git, у которой есть действие deploy. При
выполнении ‘cap production git:deploy’ пользователя спросят комментарий
к коммиту и будет выполнено:
deploy.rb
123
gitadd-Agitcommit-m'КОММЕНТАРИЙ'gitpush
Совсем не сложно, правда? Но этими командами мы не будем пользоваться
сами, они будут выполняться при выполнении других скриптов. Теперь
внутри ‘namespace :deploy do’ добавляем:
Есть задача deploy и мы добавили 4 новых действия: setup (первичная
настройка), foreman_init (создание upstart скрипта для приложения),
symlink (создание символьных ссылок) и restart (перезагрузка
приложения). Также мы указываем после/перед какими стадиями, что нужно
выполнить.
deploy:setup выполняет первичную настройку сервера: загружает файлы из
папки shared на локальном компьютере в папку shared на сервере,
настраивает nginx, создает нужные папки и запускает
deploy:foreman_init, который в свою очередь создает upstart скрипты
через foreman и копирует их в /etc/init, после чего мы можем управлять
нашим приложением командами
sudo start/stop/restart/status ИМЯ_ПРИЛОЖЕНИЯ. Перед deploy:setup
выполняется три первых шага обычного деплоя приложения, а именно
заливаются файлы на сервер и выполняется bundle install. После каждого
деплоя создаются новый симлинки и перезагружается Unicorn. Осталось
только в конец этого файла добавить before :deploy, 'git:deploy'и
теперь перед каждым деплоем автоматически будут коммититься новые
изменения.
Еще раз:
выполняем cap production deploy:setupпри самом первом деплое
Вашего приложения.
выполняем cap production deployпри каждом деплое Вашего
приложения.
Вот именно так я всегда деплою свои приложения на VPS. Конечно, этот
способ не есть истина в последней инстанции, но я постарался на примере
объяснить как работает Capistrano, чтобы даже у новичка не было проблем
с изменением скрипта под свои нужды. Так же я не утверждаю, что мои
nginx.conf и unicorn.rb идеальны, но у меня с ними уже почти год все
работает на несильно мощных VPS и не было проблем даже под нагрузкой.