Блог веб-студии RubyRuby

Здесь мы делимся нашим опытом.

Ansible - как оркестровка для серверов с Ruby on Rails на борту.

В этой статье мы поговорим о таком важном процессе, как подготовка сервера для размещения RoR приложения. Скорее всего, каждый веб-разработчик сталкивался с определенными трудностями развертывания среды для своего детища. И несмотря на то, что в арсенале имеется мощный инструмент capistrano, перед его использованием необходимо подготовить только, что проинсталлированную систему. Пусть подготовка и не является трудным процессом, но уж скучным точно. Также немаловажно учесть, что многие проекты могут иметь версионную зависимость определенного софта. Например, postgresql 8.4 или специально собранную версию ffmpeg 1.1.15 c libfaac 1.28. Кроме того, что определенные нюансы могут находится на десятках серверов, тяжело вести учет такого зоопарка.

На сегодняшний день, существует несколько инструментов способных облегчить жизнь системным администраторам. А именно chef, ansible, puppet, salt. Эти продукты активно применяются так называемыми DevOps-инженерами для автоматизации управления конфигурациями. Вообще идея конфигурирования системы в автоматическом или полуавтоматическом режиме, может существенно помочь в борьбе с нарастающей сложностью и надвигающемуся хаосу впринципе.

Как же ansible решает эту проблему?

В основе этого инструмента лежат, так называемые плейбуки – файлы в формате YAML, которые содержат перечень задач, которые необходимо выполнить на целевой системе. Декларативность и простота плейбуков, является одной из сильнейших сторон данного инструмента. Для перечня хостов на которых будут прогонятся плейбуки используется инвертарный файл вида:

1
2
3
4
[webservers] 
адрес хоста1
адрес хоста2
адрес хоста3

Ansible, считает группой все между квадратными скобками, у групп могут быть дочерние группы. При наличии у нас простейшего плейбука, запуск его выполнения будет таким:

1
ansible-playbook -i hosts playbook.yml

playbook.yml – плейбук с набором действий которые необходимо произвести

hosts – инвертарный файл содержащий перечнь хостов.

Для удобного тестирования плейбуков, удобно использовать виртуальные машины, особенно если управлять ими с помощью Vagrant, который в свою очередь имеет возможность использования Ansible в качестве provisioner`а.

Рассмотрим пример плейбука:

1
2
3
4
5
6
7
8
9
---
- hosts: webservers #Указываем группу хостов
  user: vagrant #пользователь под которым ansible будет логиниться
  sudo: yes
  vars_files:
    - vars/defaults.yml #Файл, содержащий переменные которые нам потребуются, например расположения директорий
    
  roles: # Блок ролей, у нас пока одна роль 
    - user

Здесь надо сказать отдельно про роли. В данном случае роль – это выделенная директория с перечнем задач(tasks) и набором шаблонов(templates). Это очень полезный прием, который позволяет разделять настройку на определенные этапы\части. В данном примере есть роль user, ответственная за создание\настройку пользователя.

Пользователь.

Все роли вынесены в отдельную директорию roles. В нашем примере роль user выглядит следующим образом:

roles/user – директория роли

roles/user/tasks – директория задач

roles/user/tasks/main.yml – точка входа в роль, здесь указываются какие файлы включены в роль. Для нас файл будет следующим:

1
- include: user.yml tags=user

атрибут tags позволяет запускать отдельные кусочки ролей, когда это необходимо.

roles/user/tasks/user.yml – файл содержащий перечень задач, выполняемых в рамках нашей роли.

roles/user/templates – директория содержащая конфигурационные файлы, сертификаты. Возможно использование шаблонизатора jinja2.

Рассмотри файл user.yml, так как именно он содержит непосредственные задания для выполнения:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---

  - name: create deploy user
    user: name=deploy comment="deploy user" generate_ssh_key=yes ssh_key_bits=2048 state=present password={{ password }} shell=/bin/bash

  - name: copy my ssh key to the user's .ssh directory
    action: template src=my_current_machine.pub dest={{home_dir}}/.ssh/authorized_keys mode=0700 owner={{deploy}} group={{deploy}}
 
  - name: copy known hosts to deploy user
    action: template src=known_hosts dest={{home_dir}}/.ssh/known_hosts owner={{deploy}} group={{deploy}}

  - name: copy deploy private key to VM
    action: template src=deploy_rsa dest={{home_dir}}/.ssh/deploy_rsa owner={{deploy}} group={{deploy}}

  - name: copy deploy public key to VM
    action: template src=deploy_rsa.pub dest={{home_dir}}/.ssh/deploy_rsa.pub owner={{deploy}} group={{deploy}} mode=0644

  - name: copy ssh_config github 
    action: template src=ssh_config dest={{home_dir}}/.ssh/config owner={{deploy}} group={{deploy}}

  - name: copy sudoers
    action: template src=sudoers dest=/etc/sudoers

Теперь по-порядку:

1). Создаем пользователя и назначаем ему пароль. Единственное, что нужно, так это описать переменную password в vars/defaults.yml. Значением этой переменной будет не сам пароль а лишь хеш, который легко создать с помощью команды:

1
python -c 'import crypt; print crypt.crypt("This is my Password", "$1$SomeSalt$")'

2). Копируем публичный ключ машины(здесь в зависимости от Ваших нужд) откуда запускаем наш плейбук в authorized_keys – это позволит нам заходить на целевую машину без пароля. переменная home_dir должна быть описана в vars/defaults.yml.

3). Копируем файл known_hosts который заранее содержит записи-“отпечатки” например гитхаба или битбакета.

4). Копируем ключи для нашего пользователя, чтобы он мог работать с гитхабом

5). Копируем ssh_config, опять же для работы с гитхабом с содержимым:

1
2
3
Hostname github.com
  IdentityFile /home/deploy/.ssh/deploy_rsa
  StrictHostKeyChecking no

6). Разрешаем sudo для нашего пользователя копированием файла sudoers где фигурирует строка:

1
deploy    ALL=(ALL:ALL) ALL

Проделанные шаги подготовят нашего пользователя для дальнейшей работы.

Веб-сервер.

В данном разделе мы рассмотрим пример настройки веб-сервера, а также установку необходимых компонентов для его работы. Здесь мы не будем разделять веб-сервер nginx и unicorn на подразделы и опишем подготовку работоспособности всей связки nginx+ruby+rails+capistrano.

Итак, файл roles/webserver/tasks/main.yml будет следующего вида:

1
2
3
- include: ruby.yml tags=ruby
- include: deploy.yml tags=deploy
- include: nginx.yml tags=nginx

В файле ruby.yml у нас располагаются инструкции по установке необходимых пакетов системы для последующей работы ruby. Также в этом файле можно описать механизм установки самого ruby, но я предпочитаю доверять это удобному инструменту capistrano.

ruby.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
---
- name: upgrade
  action: apt update_cache=yes upgrade=yes

- name: install ruby dependencies
  action: apt pkg=$item state=installed
  with_items:
    - build-essential
    - automake
    - bison
    - autoconf
    - pkg-config
    - libreadline6
    - libreadline6-dev
    - openssl
    - libssl-dev
    - curl
    - git-core
    - subversion
    - zlib1g
    - zlib1g-dev
    - libyaml-dev
    - libsqlite3-dev
    - libxml2-dev
    - libxslt1-dev
    - curl

Далее расмотрим файл deploy.yml, в котором будут инструкции по установке таких пакетов как htop,vim,nload,libpq-dev и т.д. Здесь важно понимать, что эти пакеты зависят от конкретной системы и не являются обязательными. Приложения vim,htop,nload что называется “на любителя”.

deploy.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- name: Install app dependencies
  action: apt pkg=$item state=installed
  with_items:
    - libpq-dev
    - vim
    - htop
    - nload
    - gawk
    - sqlite3
    - libgdbm-dev
    - libncurses5-dev
    - libtool
    - libffi-dev
    - libcurl3
    - libcurl4-openssl-dev
    - python-psycopg2
    - postfix

- name: Create deployment directory for application
  file: path={{ deploy_directory}} owner=deploy group=deploy state=directory

- name: Remove the config symlink, if exists
  command: rm -rf {{deploy_directory}}/shared/config

- name: Create shared directory for  application
  file: path={{ deploy_directory }}/shared owner=deploy group=deploy state=directory

- name: Create config directory for application
  file: path={{ deploy_directory }}/shared/config owner=deploy group=deploy state=directory

- name: copy database.yml to config directory for application
  action: template src=database.yml dest={{ deploy_directory }}/shared/config mode=0700 owner={{deploy}} group={{deploy}}

После установки приложений следуют инструкции по подготовке директории где будет располагаться приложение. Будут созданы директории shared и shared/config относительно переменной deploy_directory которая описана в файле vars/defaults.yml

Пример defaults.yml:

1
2
3
4
5
6
7
8
9
10
webserver_name: yourdomain.com
deploy_directory: /srv/yourdomain.com
app_name: yourdomain.com
password: yourpassword #created via python 
sudo: True
deploy: deploy
home_dir: /home/deploy
database_name: app_name_db
database_username: db_user
database_password: db_password

Для создания хеша от пароля Вашего пользователя – используйте команду:

1
echo 'import crypt; print crypt.crypt(“YOURPASS”, "$6$YOURSALT")' | python -

Последнее действие нашего файла скопирует файлик database.yml в директорию shared/config, который мы предварительно положим в папку roles/webserver/templates/

Далее переходим к настройке сервера nginx. Для этого рассмотрим пример файла nginx.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- name: Install nginx
  apt: pkg=nginx state=latest

- name: Remove the default app
  command: rm -rf /etc/nginx/sites-enabled/default

- name: Remove the app's symlink, if exists
  command: rm -rf /etc/nginx/sites-enabled/{{ app_name }}

- name: Configure nginx for the app
  template: src=etc_nginx_sites-available_{{ app_name }}.conf.j2 dest=/etc/nginx/sites-available/{{ app_name }} group=www-data owner=www-data force=yes

- name: Enable the app
  command: ln -s /etc/nginx/sites-available/{{ app_name }} /etc/nginx/sites-enabled/{{ app_name }}

- name: Restart nginx
  command: service nginx restart

Здесь мы копируем заранее подготовленный конфигурационный файл, который находится здесь roles/webserver/templates/ Данный файл у нас описан с помощью шаблонизатора jinja2 и имеет вид: etc_nginx_sites-available_yourdomain.com.conf.j2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
upstream {{ app_name }} {
  server unix:{{ deploy_directory }}/shared/unicorn.sock fail_timeout=0;
}

server {
  listen 80;
  server_name www.{{webserver_name}};
  rewrite ^ http://{{webserver_name}}$request_uri? permanent;
}



server {
  gzip on;
  listen 80;
  keepalive_timeout 5;
  root {{ deploy_directory }}/current/public;
  access_log /var/log/nginx/{{ webserver_name }}.access.log;
  error_log /var/log/nginx/{{ webserver_name }}.error.log;

  server_name {{ webserver_name }};

  location / {
    proxy_pass http://{{ app_name }}; # match the name of upstream directive which is defined above
    root {{ deploy_directory }}/current/public;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }

  location ^~ /uploads/ {
    root {{ deploy_directory }}/shared/;
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }
 

  location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
    expires 1y;
    log_not_found off;
  }
 
  location ~ ^/(assets|images|javascripts|stylesheets|swfs|system)/ {
    gzip_static on;
    expires     max;
    add_header  Cache-Control public;
    add_header  Last-Modified "";
    add_header  ETag "";

    break;
  }
 
  error_page 500 502 503 504 /500.html;
    location = /500.html {
      root {{ deploy_directory }}/current/public;
  }

 
}

На этом настройка блока “веб-сервер” закончена.

Resque.

Нашему приложению может потребоваться сервер Redis если например, мы хотим осуществлять выполнение фоновых задач с использованием Resque. Для установки Redis инструкции ansible будут вида:

roles/resque/tasks/main.yml:

1
- include: resque.yml tags=resque

roles/resque/tasks/resque.yml:

1
2
- name: Install Redis
   apt: pkg=redis-server state=latest

Postgresql.

В заключительной части настройки нашего сервера, речь пойдет о базе данных. Инструкции по настройке:

roles/database/tasks/main.yml:

1
- include: postgresql.yml tags=database

roles/database/tasks/postgresql.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- name: Install python-pycurl
  apt: pkg=python-pycurl state=latest
 
- name: Add PostgreSQL repo key
  apt_key: url=http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc
 
- name: Add PostgreSQL repo
  apt_repository: repo='deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main'

- name: Install PostgreSQL
  apt: pkg=postgresql-9.3 state=latest update_cache=true
 
- name: Copy valid pg_hba.conf
  template: src=pg_hba.conf.j2 dest=/etc/postgresql/pg_hba.conf
 
- name: Copy valid postgresql.conf
  template: src=postgresql.conf.j2 dest=/etc/postgresql/postgresql.conf

- name: Restart PostgreSQL
  command: service postgresql restart

- name: Create db user
  action: template src=pg_create_role.sql dest=/tmp/pg_create_role.sql mode=0777 owner={{deploy}} group={{deploy}}

- action: shell sudo -u postgres psql -f /tmp/pg_create_role.sql -o /tmp/pg_create_role.out
- action: file path=/tmp/pg_create_role.sql state=absent

- name: postgresql - create db for production mode
  sudo_user: postgres
  postgresql_db: name="{{ database_name }}_production" encoding="UTF-8" lc_collate="en_US.UTF-8" lc_ctype="en_US.UTF-8" owner={{ database_username }} template="template0"

- name: postgresql - create db for test mode
  sudo_user: postgres
  postgresql_db: name="{{ database_name }}_test" encoding="UTF-8" lc_collate="en_US.UTF-8" lc_ctype="en_US.UTF-8" owner={{ database_username }} template="template0"

- name: postgresql - create db for development
  sudo_user: postgres
  postgresql_db: name="{{ database_name }}_development" encoding="UTF-8" lc_collate="en_US.UTF-8" lc_ctype="en_US.UTF-8" owner={{ database_username }} template="template0"

Здесь мы устанавливаем СУБД Postgresql и копируем заранее подготовленные конфиги roles/database/templates/pg_hba.conf.j2 и roles/database/templates/postgresql.conf.j2 а также копируем скрипт roles/database/templates/pg_create_role.sql и запускаем его для создания пользователя бд.

roles/database/templates/pg_create_role.sql:

1
2
3
4
5
6
7
8
9
10
11
12
DO
$body$
BEGIN
   IF NOT EXISTS (
      SELECT *
      FROM   pg_catalog.pg_user
      WHERE  usename = '{{database_username}}') THEN

      CREATE ROLE {{database_username}} WITH SUPERUSER CREATEDB LOGIN PASSWORD '{{database_password}}';
   END IF;
END
$body$

roles/database/templates/pg_hba.conf.j2:

1
2
3
local   all             all                                     peer
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5

roles/database/templates/postgresql.conf.j2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data_directory = '/var/lib/postgresql/9.3/main'        
hba_file = '/etc/postgresql/9.3/main/pg_hba.conf'    
ident_file = '/etc/postgresql/9.3/main/pg_ident.conf'
external_pid_file = '/var/run/postgresql/9.3-main.pid'        
listen_addresses = 'localhost'                                     
port = 5432                
max_connections = 100            
unix_socket_directories = '/var/run/postgresql'        
ssl = false                
shared_buffers = 24MB            
temp_buffers = 8MB            
log_line_prefix = '%t '                        
lc_messages = 'en_US.UTF-8'                        
lc_monetary = 'en_US.UTF-8'            
lc_numeric = 'en_US.UTF-8'            
lc_time = 'en_US.UTF-8'                
default_text_search_config = 'pg_catalog.english'

На этом подготовка нашего сервера закончена и при настроенном деплое через капистрано сервер+приложение может быть развернуто тремя командами:

1
2
3
ansible-playbook -i hosts playbook.yml
cap deploy:setup
cap deploy

Заказать сайт на Ruby on Rails

Комментарии