Skip to content

Instantly share code, notes, and snippets.

@davetoxa
Last active February 18, 2024 18:51
Show Gist options
  • Save davetoxa/c0a24ad76bfd2b6e5b80 to your computer and use it in GitHub Desktop.
Save davetoxa/c0a24ad76bfd2b6e5b80 to your computer and use it in GitHub Desktop.

Настройка и деплой Ruby on Rails приложения на сервер, с использованием ansible и capistrano 3.

В данной статье будет рассказано, как быстро и легко настроить сервер и задеплоить на него Ruby on Rails приложение используя Ansible и Capistrano.

Настройка сервера при помощи Ansible

Что такое Ansible и зачем нам это нужно?

Я думаю вы тоже задались этим вопросом. Ansible – это ПО, написанное на python, которое занимается автоматизацией рутиных и повторяющихся задач, таких как управление и настройка серверов. Большой плюс Ansible в том, что вам не требуется устанавливать какое либо дополнительное ПО на серверах (ведь везде и так уже есть python).

В основе этого инструмента лежат так называемые playbook (в chef это кукбук) – файлы в формате YAML, которые содержат перечь задач, необходимых к выполнению. Ansible используют для настройки сотен своих серверов такие крупные компании как Twitter, Electronic Arts, Evernote и GoPro.

Предустановки:

Важно: Подойдёт любой VPS сервер, но мы рассмотрим разворачивание, на серверах 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 на рабочий компьютер:

Mac OS:
brew install ansible
Ubuntu:
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) набор переменных для повторного использования и для того, чтобы избежать повторяемости в конфигах.

database.yml - наш рельсовый конфиг базы данных
production:
  adapter: postgresql
  username: postgres
  encoding: utf8
  pool: 8
  database: {{ app_name }}_production
nginx.conf - конфиг nginx сервера

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;
    }
}

Назначить Ты можешь назначить автора этой статьи
своим персональным наставником

pg_hba.conf

Этот конфиг необходим для PostgreSQL, в нём прописаны правила аутентификации клиента. Тут сказано, что мы позволяем локальному пользователю postgres подключаться к любой БД с любым именем.

# TYPE DATABASE USER ADDRESS METHOD
local all postgres trust
settings.yml

Этот файл опциональный, в нём могут хранится различные production настройки для нашего Rails приложения, например секретные ключи или токены. Если ваш проект находится в открытом доступе, то этот файл стоит добавить в .gitignore.

secret_token: ваш_секретный_токен

Далее, напишем наш плейбук. Я приведу код всего плейбука сразу, с расставленными внутри объясняющими комментариями. Итак, поехали.

playbook.yml
---
- 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, то вы просто меняете её в переменных и прогоняете заново плейбук, все остальные шаги будут проигнорированы, так как они не были изменены.

Записаться Хочешь узнать ещё больше?
Запишись на обучение к автору!

Деплой приложения при помощи capistrano

Теперь, когда плейбук выполнен, мы можем зайти по 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 и увидеть что всё в порядке.

Как вы увидели, установка сервера и деплой приложения – это очень простой и быстрый процесс. Один раз написали плейбук, прогнали и всё работает, без каких либо дополнительных и нудных настроек на сервере. Теперь плейбук можно использовать как для другого сервера, так и для нового проекта. Буду рад ответить на любые вопросы в комментариях.

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