В данной статье будет рассказано, как быстро и легко настроить сервер и задеплоить на него Ruby on Rails приложение используя Ansible и Capistrano.
Я думаю вы тоже задались этим вопросом. Ansible – это ПО, написанное на python, которое занимается автоматизацией рутиных и повторяющихся задач, таких как управление и настройка серверов. Большой плюс Ansible в том, что вам не требуется устанавливать какое либо дополнительное ПО на серверах (ведь везде и так уже есть python).
В основе этого инструмента лежат так называемые playbook (в chef это кукбук) – файлы в формате YAML, которые содержат перечь задач, необходимых к выполнению. Ansible используют для настройки сотен своих серверов такие крупные компании как Twitter, Electronic Arts, Evernote и GoPro.
Важно: Подойдёт любой VPS сервер, но мы рассмотрим разворачивание, на серверах DigitalOcean.
- вам необходимо иметь публичный ssh ключ (~/.ssh/id_rsa.pub) (если у вас нету ssh ключа, сгенерируйте, следуя этому руководству)
- аккаунт на DigitalOcean
Для начала давайте добавим наш ssh ключ (~/.ssh/id_rsa.pub
) в DigitalOcean на страничке Settings Security
Теперь можно создать наш сервер (дроплет):
- Нажимаем Create Droplets
- Выбираем название, план за 5$ (больше мощностей нам не надо), Ubuntu 16.04.x x64
- Выбираем наш ssh ключ (очень важный шаг)
- Создаём дроплет
Вуаля, наш сервер создаётся, можно пока заварить чай или чашечку кофе. Хотя этот процесс очень быстрый и ждать придётся всего лишь около минуты.
На страничке дроплетов теперь появился новый сервер, нам нужен его IP.
Проверим что сервер доступен, зайдём на него ssh root@ip
.
Если вы видите Welcome to Ubuntu 16.04.3 LTS
, значит всё хорошо, можно идти дальше и можно выйти с сервера exit
. Больше мы на него руками заходить не будем (в этом туториале).
Далее, нам необходимо установить Ansible на рабочий компьютер:
brew install ansible
sudo apt-get install software-properties-common
sudo apt-add-repository ppa:ansible/ansible
sudo apt-get update
sudo apt-get install ansible
Установка для других платформ описана в документации Ansible
Проверим нашу установку:
➜ ansible --version
ansible 2.4.0.0
В проекте создаём папку config/provision
(на самом деле, можно выбрать другое местоположение, это не важно). В корне только что созданной папки создаём playbook.yml
и остальные файлы (в них будут лежать конфиги и ключи). Получится следующая структура:
- app
- config
- provision
- configs
database.yml
nginx.conf
pg_hba.conf
settings.yml
- keys
id_rsa.pub
id_rsa
playbook.yml
Ключи для папки keys
вы копируете из ~/.ssh
и обязательно добавляете в .gitignore.
Код всех остальных конфигов представлен ниже. Как вы можете заметить, там встречаются странные конструкции {{ }}
– это динамические вставки переменных (Ansible позволяет это делать). Мы можем объявить (в playbook.yml
) набор переменных для повторного использования и для того, чтобы избежать повторяемости в конфигах.
production:
adapter: postgresql
username: postgres
encoding: utf8
pool: 8
database: {{ app_name }}_production
Nginx – это простой, быстрый и надёжный веб-сервер, который будет обрабатывать все входящие запросы, а так же кешировать всю статику (конструкция про assets ниже). Это позволит отдавать пользователю наши картинки, css и js очень быстро, не тратя время на повторные обращения.
upstream backend {
server unix:/home/{{ user }}/applications/{{ app_name }}/shared/tmp/sockets/puma.sock;
}
server {
listen 80;
client_max_body_size 10m;
root /home/{{ user }}/applications/{{ app_name }}/current/public;
try_files $uri/index.html $uri.html $uri @{{ app_name }};
location ~ ^/assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
location @{{ app_name }} {
proxy_pass http://backend;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
}
Назначить
Ты можешь назначить автора этой статьи
своим персональным наставником
Этот конфиг необходим для PostgreSQL, в нём прописаны правила аутентификации клиента. Тут сказано, что мы позволяем локальному пользователю postgres
подключаться к любой БД с любым именем.
# TYPE DATABASE USER ADDRESS METHOD
local all postgres trust
Этот файл опциональный, в нём могут хранится различные production настройки для нашего Rails приложения, например секретные ключи или токены. Если ваш проект находится в открытом доступе, то этот файл стоит добавить в .gitignore
.
secret_token: ваш_секретный_токен
Далее, напишем наш плейбук. Я приведу код всего плейбука сразу, с расставленными внутри объясняющими комментариями. Итак, поехали.
---
- hosts: 'all'
remote_user: 'root'
gather_facts: no
pre_tasks:
- name: 'install python2'
raw: sudo apt-get -y install python-simplejson
vars:
ruby_version: '2.4.2'
user: 'deploy'
home: '/home/{{ user }}'
rbenv_root: '{{ home }}/.rbenv'
app_name: 'davetoxa'
application: '{{ home }}/applications/{{ app_name }}'
tasks:
### Install packages ###
- name: 'apt | update'
action: 'apt update_cache=yes'
- name: 'apt | install dependencies'
action: 'apt pkg={{ item }}'
with_items:
- 'build-essential'
- 'libssl-dev'
- 'libyaml-dev'
- 'libreadline6-dev'
- 'zlib1g-dev'
- 'libcurl4-openssl-dev'
- 'git'
- 'nginx'
- 'redis-server'
- 'postgresql'
- 'postgresql-contrib'
- 'libpq-dev'
- 'imagemagick'
- 'libmagickwand-dev'
- 'nodejs'
- 'htop'
### Create an account with ssh keys ###
- name: 'account | create'
user: 'name={{ user }} shell=/bin/bash'
- name: 'account | copy authorized keys'
shell: 'mkdir -p {{ home }}/.ssh -m 700 && cp /root/.ssh/authorized_keys {{ home }}/.ssh && chown -R {{ user }}:{{ user }} {{ home }}/.ssh'
- name: 'account | copy ssh private key'
copy: 'src=keys/id_rsa dest={{ home }}/.ssh/id_rsa owner={{ user }} group={{ user }} mode=0600'
- name: 'account | copy ssh public key'
copy: 'src=keys/id_rsa.pub dest={{ home }}/.ssh/id_rsa.pub owner={{ user }} group={{ user }} mode=0644'
### Install ruby ###
- name: 'rbenv | clone repo'
git: 'repo=git://github.com/sstephenson/rbenv.git dest={{ rbenv_root }} accept_hostkey=yes'
- name: 'rbenv | check ruby-build installed'
command: 'test -x {{ rbenv_root }}/plugins/ruby-build'
register: 'plugin_installed'
ignore_errors: yes
- name: 'rbenv | add bin to path'
shell: echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> {{ home }}/.bashrc
when: 'plugin_installed|failed'
- name: 'rbenv | init'
shell: echo 'eval "$(rbenv init -)"' >> {{ home }}/.bashrc
when: 'plugin_installed|failed'
- name: 'rbenv | clone ruby-build repo'
git: 'repo=git://github.com/sstephenson/ruby-build.git dest={{ rbenv_root }}/plugins/ruby-build accept_hostkey=yes'
when: 'plugin_installed|failed'
- name: 'rbenv | check ruby {{ ruby_version }} installed'
shell: 'RBENV_ROOT={{ rbenv_root }} PATH="$RBENV_ROOT/bin:$PATH" rbenv versions | grep {{ ruby_version }}'
register: 'ruby_installed'
ignore_errors: yes
- name: 'rbenv | install ruby {{ ruby_version }}'
shell: 'RBENV_ROOT={{ rbenv_root }} PATH="$RBENV_ROOT/bin:$PATH" rbenv install {{ ruby_version }}'
when: 'ruby_installed|failed'
- name: 'rbenv | set global ruby {{ ruby_version }}'
shell: 'RBENV_ROOT={{ rbenv_root }} PATH="$RBENV_ROOT/bin:$PATH" rbenv global {{ ruby_version }}'
when: 'ruby_installed|failed'
- name: 'rbenv | rehash'
shell: 'RBENV_ROOT={{ rbenv_root }} PATH="$RBENV_ROOT/bin:$PATH" rbenv rehash'
when: 'ruby_installed|failed'
- name: 'rbenv | create .gemrc'
lineinfile: 'dest={{ home }}/.gemrc owner={{ user }} group={{ user }} mode=0644 line="gem: --no-ri --no-rdoc" create=yes'
when: 'ruby_installed|failed'
- name: 'ruby | install bundler'
shell: 'RBENV_ROOT={{ rbenv_root }} PATH="$RBENV_ROOT/bin:$PATH" rbenv exec gem install bundler'
when: 'ruby_installed|failed'
- name: 'rbenv | change owner'
shell: 'chown -R {{ user }}:{{ user }} {{ rbenv_root }}'
when: 'ruby_installed|failed'
### Manage database ###
- name: 'postgresql | check user'
shell: 'psql -U postgres -c "\copyright"'
register: 'postgres_login'
ignore_errors: yes
- name: 'postgresql | set auth type'
copy: 'src=configs/pg_hba.conf dest=/etc/postgresql/9.5/main/pg_hba.conf owner=postgres group=postgres mode=0644'
when: 'postgres_login|failed'
- name: 'postgresql | restart service'
service: name=postgresql state=restarted
when: 'postgres_login|failed'
- name: 'postgresql | create shared directory'
shell: 'mkdir -p {{ application }}/shared/config -m 775 && chown -R {{ user }}:{{ user }} {{ home }}/applications'
when: 'postgres_login|failed'
- name: 'postgresql | copy database.yml'
template: 'src=configs/database.yml dest={{ application }}/shared/config/database.yml owner={{ user }} group={{ user }} mode=0644'
when: 'postgres_login|failed'
- name: 'postgresql | create database'
shell: 'createdb -U postgres -O postgres -E UTF8 -l en_US.UTF-8 {{ app_name }}_production'
when: 'postgres_login|failed'
### Setup nginx ###
- name: 'nginx | check config'
command: 'test -f /etc/nginx/sites-enabled/{{ app_name }}.conf'
register: 'nginx_config_copied'
ignore_errors: yes
- name: 'nginx | createdir'
shell: 'rm /etc/nginx/sites-enabled/default; mkdir -p etc/nginx/sites-enabled/'
when: 'nginx_config_copied|failed'
- name: 'nginx | copy config'
template: 'src=configs/nginx.conf dest=/etc/nginx/sites-enabled/{{ app_name }}.conf owner=root group=root mode=0644'
when: 'nginx_config_copied|failed'
- name: 'nginx | restart service'
service: name=nginx state=restarted
when: 'nginx_config_copied|failed'
Теперь можно запустить установку. Вместо моего IP вставьте ваш:
cd config/provision && ansible-playbook -i188.226.255.173, playbook.yml
Вы увидете примерно следующую картину: лог Ansible.
Установка займёт около 7 минут (95% всего времени занимает установка ruby). Если вам необходимо поменять версию Ruby, то вы просто меняете её в переменных и прогоняете заново плейбук, все остальные шаги будут проигнорированы, так как они не были изменены.
Записаться
Хочешь узнать ещё больше?
Запишись на обучение к автору!
Теперь, когда плейбук выполнен, мы можем зайти по IP и увидеть 502 Bad Gateway
- эту ошибку нам отдаёт nginx. Дело в том, что мы настроили только сервер и нам необходимо ещё задеплоить само приложение. Для этого мы воспользуемся Capistrano – это замечательный инструмент для деплоя написанный на Ruby.
Добавим в Gemfile
следующее:
group :development do
# Гем, который добавляет специфические для Rails таски, такие как прогон миграций и компиляция ассетов
gem 'capistrano-rails'
# Гем, добавляющий возможности bundle к capistrano
gem 'capistrano-bundler'
# Добавление поддержки Rbenv (менеджера версий для Ruby)
gem 'capistrano-rbenv'
# Интеграция пумы и капистрано
gem 'capistrano3-puma'
end
Выполним установку всех новых гемов и установку capistrano
:
bundle install
cap install
В итоге у нас появятся 2 новых файла: config/deploy.rb
и config/deploy/production.rb
. Давайте их настроим:
# Название приложения
set :application, 'davetoxa'
# Путь к git репозиторию
set :repo_url, 'git@github.com:davetoxa/davetoxa.git'
# Ветка по-умолчанию
set :branch, 'master'
# Директория для деплоя
set :deploy_to, '/home/deploy/applications/davetoxa'
set :log_level, :info
# Копирующиеся файлы и директории (между деплоями)
set :linked_files, %w{config/database.yml config/settings.yml}
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/uploads}
# Ruby свистелки
set :rbenv_type, :user
set :rbenv_ruby, '2.4.2'
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"
set :rbenv_roles, :all
# А это рекомендуют добавить для приложений, использующих ActiveRecord
set :puma_init_active_record, true
Подправим config/deploy/production.rb
, вписав туда свой ip.
Также, не забудьте подправить Capfile
, чтобы подключились capistrano related гемы, которые мы добавляли пару минут назад в Gemfile
Теперь, когда установка выполнена, можно задеплоить само приложение
cap production deploy
Лог деплоя должен выглядеть примерно вот так - файл capistrano.yml
Теперь приложение полностью задеплоено и работает, можно пройти по ip и увидеть что всё в порядке.
Как вы увидели, установка сервера и деплой приложения – это очень простой и быстрый процесс. Один раз написали плейбук, прогнали и всё работает, без каких либо дополнительных и нудных настроек на сервере. Теперь плейбук можно использовать как для другого сервера, так и для нового проекта. Буду рад ответить на любые вопросы в комментариях.