Skip to content

Instantly share code, notes, and snippets.

@finist
Last active February 8, 2020 19:56
Show Gist options
  • Save finist/5654cb67c5b8ebf75461f7b2dc6aaad0 to your computer and use it in GitHub Desktop.
Save finist/5654cb67c5b8ebf75461f7b2dc6aaad0 to your computer and use it in GitHub Desktop.
Setup Ruby on Rails application to remote server with Ansible

Установка Ruby on Rails приложения на удаленный сервер с помощью Ansible

Установка Ansible на Ubuntu 16.04

подключаемся к серверу и ставим Ansible

sudo apt-add-repository ppa:ansible/ansible
sudo apt-get update
sudo apt-get install ansible

возвращаемся на локальную машину, и создаем директорию ansible, все дальнейшие действия должны выполняться в этой директории

Создание host файла

cоздаем host файл, в котором прописываем, группу и ip сервера

[web]
46.101.243.109

Создаем файл конфигурации

создаем файл конфигурации rails.yml, в этом файле будут описаны переменные и задачи которые Ansible должен выполнить на сервере.

---
# Указываем группу прописанную в hosts, дальнейшие действия будут выполнены для 
# всех ip входящих в эту группу
- hosts: web

  # Указываем от имени какого пользователя подключаться к серверу для выполнения задач
  remote_user: root

  # Блок переменных используемых в задачах и шаблонах
  vars:
    project_name: rails_app
    project_path: /srv/{{ project_name }}

  # Блок задач. Задачи входящие в этот блок будут выполнены по порядку
  tasks:

Ниже описаны переменные и задачи для каждого действия по настройки сервера. В итоге должен получится один файл с блоками vars и tasks в которых содержаться все нужные переменные и задачи

Создание пользователя

vars:

user_name: develop
user_group: admin

tasks:

- name: Create 'dev' user with {{ user_group }} group
  user:
    name: '{{ user_name }}'
    comment: Developer
    shell: /bin/bash
    group: '{{ user_group }}'

- name: Set authorized key took from file for '{{ user_name }}' user
  authorized_key:
    user: '{{ user_name }}'
    state: present
    key: "{{ lookup('file', './id_rsa.pub') }}"

- name: Allow 'admin' group to have passwordless sudo
  lineinfile:
    dest: /etc/sudoers
    state: present
    regexp: '^%admin'
    line: '%admin ALL=(ALL) NOPASSWD: ALL'
    validate: 'visudo -cf %s'

создаем файл id_rsa.pub в котором содержиться публичный ключ.

Установка системных библиотек

tasks:

- name: Install system libs
  apt: pkg={{ item }} state=installed update-cache=yes
  with_items:
    - build-essential
    - git-core
    - libssl-dev
    - libreadline-dev
    - zlib1g-dev
    - nodejs

Установка Rbenv и Ruby

vars:

ruby_version: 2.4.4

tasks:

- block:
    - name: Install rbenv
      git:
        repo: https://github.com/rbenv/rbenv.git
        dest: ~/.rbenv

    - name: Set rbenv init and path
      lineinfile:
        dest: ~/.bashrc
        state: present
        regexp: "{{ item.regexp }}"
        line: "{{ item.line }}"
      with_items:
        - { regexp: '^PATH.+rbenv', line: 'PATH="$HOME/.rbenv/bin:$PATH"' }
        - { regexp: '^eval.+rbenv', line: 'eval "$(rbenv init -)"' }

    - name: Install rbenv vars plugin
      git:
        repo: https://github.com/rbenv/rbenv-vars.git
        dest: ~/.rbenv/plugins/rbenv-vars

    - name: Install ruby-build
      git:
        repo: https://github.com/rbenv/ruby-build.git
        dest: ~/.rbenv/plugins/ruby-build

    - name: Check ruby {{ ruby_version }} installed
      shell: ~/.rbenv/bin/rbenv versions --bare | grep {{ ruby_version }}
      register: ruby_installed
      ignore_errors: yes

    - name: Install ruby {{ ruby_version }}
      command: ~/.rbenv/bin/rbenv install {{ ruby_version }} -v
      when: ruby_installed is failed

    - name: Set global ruby is {{ ruby_version }}
      shell: ~/.rbenv/bin/rbenv global {{ ruby_version }}

    - name: Create gemrc file
      file:
        path: ~/.gemrc
        state: touch

    - name: Config gem
      lineinfile:
        dest: ~/.gemrc
        state: present
        regexp: '^gem.*no-document'
        line: 'gem: --no-document'

    - name: Check bundler installed
      shell: ~/.rbenv/bin/rbenv exec gem list | grep bundler | cat
      register: bundler_installed

    - name: Install bundler
      command: ~/.rbenv/bin/rbenv exec gem install bundler
      when: bundler_installed.stdout == ''

  become: yes
  become_user: '{{ user_name }}'

Установка Postgres

vars:

postgres_version: 9.6

tasks:

- name: Add postgres repository
  apt_repository:
    repo: deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main
    state: present

- name: Import postgres repository signing key
  apt_key:
    url: https://www.postgresql.org/media/keys/ACCC4CF8.asc
    state: present

- name: Install postgresql libs
  apt: pkg={{ item }} state=installed update-cache=yes
  with_items:
    - postgresql-{{ postgres_version }}
    - libpq-dev
    - python-psycopg2

- name: Add postgres user
  become: true
  become_user: postgres
  postgresql_user:
    name: '{{ project_name }}'
    password: "secret"
    role_attr_flags: CREATEDB,NOSUPERUSER

- name: Create {{ project_name }}_production database
  become: true
  become_user: postgres
  postgresql_db:
    name: '{{ project_name }}_production'

- name: Restart postgres after configure
  service:
    name: postgresql
    state: restarted

Установка nginx

tasks:

- name: Install nginx
  apt: pkg=nginx state=installed update_cache=true

- name: Set nginx config
  template:
    src: ./templates/nginx.j2
    dest: /etc/nginx/sites-enabled/{{ project_name }}

- name: Restart nginx after configure
  service:
    name: nginx
    state: restarted

создаем шаблон templates/nginx.j2, для конфига nginx

upstream app {
  # Path to Puma SOCK file, as defined previously
  server unix://srv/{{ project_name }}/shared/sockets/puma.sock fail_timeout=0;
}

server {
  listen 80;
  server_name servername;

  root {{ project_path }}/current/public;

  try_files $uri/index.html $uri @app;

  location @app {
    proxy_pass http://app;
    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;
  client_max_body_size 4G;
  keepalive_timeout 10;
}

Настраиваем структуру проекта

vars:

secret_key_base: "{{ lookup('password', '/dev/null length=128 chars=ascii_letters,digits') }}"
core_count: 1

tasks:

- name: Create project directories
  file:
    path: '{{ item }}'
    state: directory
    owner: '{{ user_name }}'
    group: '{{ user_group }}'
    mode: g+rx,u+rwx
  with_items:
    - '{{ project_path }}'
    - '{{ project_path }}/releases'
    - '{{ project_path }}/shared/log'
    - '{{ project_path }}/shared/config'
    - '{{ project_path }}/shared/pids'
    - '{{ project_path }}/shared/sockets'
    - '{{ project_path }}/shared/tmp/cache'
    - '{{ project_path }}/shared/vendor/bundle'
    - '{{ project_path }}/shared/public/assets'

- block:

    - name: Create database.yml
      template:
        src: ./templates/database.yml.j2
        dest: '{{ project_path }}/shared/config/database.yml'

    - name: Check secrets.yml present
      stat:
        path: '{{ project_path }}/shared/config/secrets.yml'
      register: secrets_yml

    - name: Create secrets.yml
      template:
        src: ./templates/secrets.yml.j2
        dest: '{{ project_path }}/shared/config/secrets.yml'
      when: secrets_yml.stat.exists != true

    - name: Create puma config
      template:
        src: ./templates/puma.rb.j2
        dest: '{{ project_path }}/shared/config/puma.rb'

    - name: Add repository host to known hosts
      shell: "ssh-keyscan -H {{ repository_host }} >> ~/.ssh/known_hosts"

  become: yes
  become_user: '{{ user_name }}'

создаем шаблон templates/database.yml.j2

production:
  adapter: postgresql
  database: {{ project_name }}_production
  pool: 25
  timeout: 5000
  host: localhost
  username: {{ project_name }}
  password: secret

создаем шаблон templates/secrets.yml.j2

production:
  secret_key_base: {{ secret_key_base }}

создаем шаблон templates/puma.rb.j2

# Change to match your CPU core count
workers {{ core_count }}

# Min and Max threads per worker
threads 1, 5

app_dir = "{{ project_path }}"
shared_dir = "#{app_dir}/shared"

# Default to production
rails_env = ENV['RAILS_ENV'] || "production"
environment rails_env

# Set up socket location
bind "unix://#{shared_dir}/sockets/puma.sock"

# Logging
stdout_redirect "#{shared_dir}/log/puma.stdout.log", "#{shared_dir}/log/puma.stderr.log", true

# Set master PID and state locations
pidfile "#{shared_dir}/pids/puma.pid"
state_path "#{shared_dir}/pids/puma.state"
activate_control_app

on_worker_boot do
  require "active_record"
  ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
  ActiveRecord::Base.establish_connection(YAML.load_file("#{app_dir}/current/config/database.yml")[rails_env])
end

Создание systemd сервиса для запуска rails сервера

tasks:

- name: Create puma service
  template:
    src: ./templates/puma.service.j2
    dest: /etc/systemd/system/{{ project_name }}_puma.service
    mode: 0644
  notify:
    - reload systemctl

создаем шаблон templates/puma.service.j2

[Unit]
Description=Puma web server
After=network.target

[Service]
Type=simple
User={{ user_name }}

Environment=RAILS_ENV=production

WorkingDirectory={{ project_path }}/current

ExecStart=/home/{{ user_name }}/.rbenv/bin/rbenv exec bundle exec puma -C {{ project_path }}/shared/config/puma.rb
ExecStop=/home/{{ user_name }}/.rbenv/bin/rbenv exec bundle exec pumactl -S {{ project_path }}/shared/tmp/pids/puma.state stop
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target

отдельным блоков в rails.yml добавляем:

handlers:
  - name: reload systemctl
    command: systemctl daemon-reload

этот блок должен быть на одном уровне с vars, tasks

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