Настройка сервера для разгортвання Rails прыкладання пры дапамозе Ansible

Не так даўно мне было неабходна напісаць некалькі ansible playbooks для падрыхтоўкі сервера да дэплою rails прыкладання. І, на здзіўленне, я не знайшоў простага пакрокавага мануала. Капіяваць чужы плэйбук без разумення таго, што адбываецца, я не хацеў і ў выніку прыйшлося чытаць дакументацыю, збіраючы ўсё самастойна. Магчыма камусьці я змагу дапамагчы гэты працэс паскорыць пры дапамозе гэтага артыкула.

Перш за ўсё варта разумець, што ansible дае вам зручны інтэрфейс для выканання загадзя вызначанага спісу дзеянняў на выдаленым серверы (серверах) праз SSH. Тут няма ніякай магіі, нельга паставіць убудову і атрымаць са скрынкі zero downtime дэплой свайго прыкладання з докерам, маніторынгам і іншымі плюшкамі. Для таго каб напісаць плэйбук вы павінны ведаць што менавіта вы хочаце зрабіць і як гэта зрабіць. Таму мяне не ўладкоўваюць гатовыя плэйбукі з гітхаба, ці артыкулы выгляду: "Скапіюйце і запусціце, - будзе працаваць".

Што нам трэба?

Як я ўжо казаў, для таго каб напісаць плэйбук трэба ведаць, што вы хочаце зрабіць і як гэта зрабіць. Давайце вызначымся з тым, што нам патрэбна. Для Rails прыкладанні нам спатрэбіцца некалькі сістэмных пакетаў: nginx, postgresql (redis, etc). Апроч гэтага нам патрэбен ruby ​​вызначанай версіі. Ставіць яго лепш за ўсё праз rbenv (rvm, asdf…). Запускаць усё гэта з-пад root карыстальніка - заўсёды дрэнная ідэя, таму трэба стварыць асобнага карыстальніка, і наладзіць яму правы. Пасля гэтага неабходна заліць наш код на сервер, скапіяваць канфігі для nginx, postgres, etc і запусціць усе гэтыя сэрвісы.

У выніку паслядоўнасць дзеянняў такая:

  1. Лагінсям пад рутам
  2. усталёўваны сістэмныя пакеты
  3. ствараем новага карыстальніка, наладжваем правы, ssh ключ
  4. наладжваем сістэмныя пакеты (nginx etc) і запускаем іх
  5. Ствараем карыстальніка ў БД (можна адразу і базу стварыць)
  6. Лагінімся новым карыстальнікам
  7. Усталёўваны rbenv і ruby
  8. Усталёўваны бандлер
  9. Заліваем код прыкладання
  10. Запускаем Puma сервер

Прычым апошнія этапы можна рабіць пры дапамозе capistrano, прынамсі яна са скрынкі ўмее капіяваць код у рэлізныя дырэкторыі, перамыкаць рэліз симлинком пры паспяховым дэплоі, капіяваць канфігі з shared дырэкторыі, рэстартаваць puma і.т.д. Усё гэта можна зрабіць і пры дапамозе Ansible, але навошта?

Файлавая структура

Ansible мае строгую файлавую структуру для ўсіх сваіх файлаў, таму лепш за ўсё трымаць усё гэта ў асобнай дырэкторыі. Прычым не так важна, будзе яна ў самым rails дадатку, ці асобна. Можна захоўваць файлы ў асобным git рэпазітары. Асабіста мне зручней за ўсё аказалася стварыць дырэкторыю ansible у /config дырэкторыі rails прыкладання і захоўваць усё ў адным рэпазітары.

Simple Playbook

Playbook - гэта yml файл, у якім пры дапамозе адмысловага сінтаксісу апісана, што і як ansible павінен зрабіць. Давайце створым першы плэйбук, які не робіць нічога:

---
- name: Simple playbook
  hosts: all

Тут мы проста гаворым, што наш playbook называецца Simple Playbook і што выконвацца яго змесціва павінна для ўсіх хастоў. Мы можам захаваць яго ў /ansible дырэкторыі з імем playbook.yml і паспрабаваць запусціць:

ansible-playbook ./playbook.yml

PLAY [Simple Playbook] ************************************************************************************************************************************
skipping: no hosts matched

Ansible кажа што не ведае хастоў, якія б адпавядалі спісу all. Іх трэба пералічыць у спецыяльным inventory файле.

Давайце створым яго ў той жа ansible дырэкторыі:

123.123.123.123

Вось так проста паказваем хост (у ідэале хост свайго VPS для тэстаў, ці ж можна localhost прапісаць) і захоўваем яго пад імем inventory.
Можна паспрабаваць запусціць ansible з invetory файлам:

ansible-playbook ./playbook.yml -i inventory
PLAY [Simple Playbook] ************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************

PLAY RECAP ************************************************************************************************************************************

Калі ў вас ёсць доступ па ssh да паказанага хаста то ansible падлучыцца і збярэ інфармацыю аб выдаленай сістэме. (дэфолтны TASK [Gathering Facts] ) пасля чаго дасць кароткую справаздачу аб выкананні (PLAY RECAP).

Па змаўчанні для злучэння выкарыстоўваецца імя карыстальніка пад якім вы залагінены ў сістэме. На хасце яго, хутчэй за ўсё, не будзе. У playbook файле можна паказаць якога карыстача выкарыстоўваць для падлучэння пры дапамозе дырэктывы remote_user. Гэтак жа інфармацыя аб выдаленай сістэме вам часта можа быць непатрэбная і не варта марнаваць час на яе збор. Гэтую задачу гэтак жа можна выключыць:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  become: true
  gather_facts: no

Паспрабуйце яшчэ раз запусціць playbook і пераканацца, што злучэнне працуе. (Калі вы пазначылі root карыстальніка, то гэтак жа трэба пазначыць дырэктыву become: true, каб атрымаць падвышаныя правы. Як напісана ў дакументацыі: become set to ‘true’/’yes’ to activate privilege escalation. хаця не зусім зразумела, навошта).

Магчыма вы атрымаеце памылку выкліканую тым, што ansible не можа вызначыць інтэрпрэтатар пітона, тады яго можна паказаць уручную:

ansible_python_interpreter: /usr/bin/python3 

дзе ў вас ляжыць python можна даведацца камандай whereis python.

Устаноўка сістэмных пакетаў

У стандартнай пастаўцы Ansible уваходзіць мноства модуляў для працы з рознымі сістэмнымі пакетамі, дзякуючы чаму нам не даводзіцца з любой нагоды пісаць bash скрыпты. Цяпер нам спатрэбіцца адзін з такіх модуляў для абнаўлення сістэмы і ўстаноўкі сістэмных пакетаў. У мяне на VPS стаіць Ubuntu Linux адпаведна для ўсталёўкі пакетаў я выкарыстоўваю apt-get и модуль для яго. Калі ў вас выкарыстоўваецца іншая аперацыйная сістэма то, магчыма, спатрэбіцца іншы модуль (падушыце я ў пачатку казаў, што трэба загадзя ведаць што і як будзем рабіць). Аднак сінтаксіс, хутчэй за ўсё, будзе падобным.

Дапоўнім наш плэйбук першымі задачамі:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  become: true
  gather_facts: no

  tasks:
    - name: Update system
      apt: update_cache=yes
    - name: Install system dependencies
      apt:
        name: git,nginx,redis,postgresql,postgresql-contrib
        state: present

Task - гэта як раз задача якую ansible будзе выконваць на выдаленых серверах. Мы даем задачы імя, каб адсочваць яе выкананне ў логу. І апісваем, пры дапамозе сінтаксісу канкрэтнага модуля, што яму трэба зрабіць. У дадзеным выпадку apt: update_cache=yes - кажа абнавіць пакеты сістэмы пры дапамозе модуля apt. Другая каманда крыху больш складаная. Мы перадаем у модуль apt спіс пакетаў, і які гаворыцца што іх state павінен стаць present, тоесць які гаворыцца ўсталяваць гэтыя пакеты. Падобнай выявай, мы можам сказаць іх выдаліць, ці абнавіць, проста памяняўшы state. Звярніце ўвагу, што для працы rails з postgresql нам патрэбен пакет postgresql-contrib, які мы зараз усталёўваем. Аб гэтым ізноў жа трэба ведаць і зрабіць, ansible сам па сабе гэтага рабіць не будзе.

Паспрабуйце запусціць playbook яшчэ раз і праверыць, што пакеты ўсталююцца.

Стварэнне новых карыстальнікаў.

Для працы з карыстачамі ў Ansible гэтак жа ёсць модуль - user. Дадамо яшчэ адзін task (я схаваў ужо вядомыя часткі плэйбука за каментарам, што б не капіяваць яго цалкам кожны раз):

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Add a new user
      user:
        name: my_user
        shell: /bin/bash
        password: "{{ 123qweasd | password_hash('sha512') }}"

Мы ствараем новага карыстальніка, усталёўваны яму schell і пароль. І тут жа сутыкаемся з некалькімі праблемамі. Што калі імёны карыстальнікаў павінны быць для розных хастоў рознымі? Ды і захоўваць пароль у адчыненым выглядзе ў плэйбуку вельмі дрэнная ідэя. Для пачатку вынесем імя карыстальніка і пароль у зменныя, а бліжэй да канца артыкула я пакажу як пароль зашыфраваць.

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"

пры дапамозе падвойных фігурных дужак у плэйбуках усталёўваюцца зменныя.

Значэнні зменных мы пакажам у inventory файле:

123.123.123.123

[all:vars]
user=my_user
user_password=123qweasd

Звярніце ўвагу на дырэктыву [all:vars] - Яна кажа аб тым, што наступны блок тэксту - гэта зменныя (vars) і яны дастасавальныя для ўсіх хастоў (all).

Гэтак жа цікавая канструкцыя "{{ user_password | password_hash('sha512') }}". Справа ў тым, што ansible не ўстанаўлівае карыстальніка праз user_add як вы б рабілі гэта ўручную. А захоўвае ўсе дадзеныя напроста, з-за чаго пароль мы таксама павінны загадзя пераўтварыць у хэш, што і робіць дадзеная каманда.

Давайце дадамо нашага карыстальніка ў групу sudo. Аднак перад гэтым неабходна пераканацца, што такая група ёсць таму, што за нас гэтага ніхто рабіць не будзе:

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Ensure a 'sudo' group
      group:
        name: sudo
        state: present
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"
        groups: "sudo"

Усё дастаткова проста, у нас гэтак жа ёсць модуль group для стварэння груп, з сінтаксісам вельмі падобным на apt. Пасля чаго дастаткова прапісаць гэтую групу карыстальніку (groups: "sudo").
Гэтак жа карысна дадаць гэтаму карыстачу ssh ключ, што б мы маглі лагініцца пад ім без пароля:

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Ensure a 'sudo' group
      group:
      name: sudo
        state: present
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"
        groups: "sudo"
    - name: Deploy SSH Key
      authorized_key:
        user: "{{ user }}"
        key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
        state: present

У дадзеным выпадку цікавая канструкцыя "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" – яна капіюе змесціва файла id_rsa.pub (у вас назоў можа і адрознівацца), тоесць публічную частку ssh ключа і загружае яго ў спіс аўтарызаваных ключоў для карыстача на сервер.

ролі

Усе тры задачы для стварэння карыстацца можна лёгка аднесці да адной групы задач, і нядрэнна было б захоўваць гэтую групу асобна ад асноўнага плэйбука, што б ён занадта не разрастаўся. Для гэтага ў ansible існуюць ролі.
Згодна з паказанай у самым пачатку файлавай структуры, ролі неабходна пакласці ў асобную дырэкторыю roles, для кожнай ролі - асобная дырэкторыя з аналагічнай назвай, усярэдзіне дырэкторый tasks, files, templates, etc
Створым файлавую структуру: ./ansible/roles/user/tasks/main.yml (main - гэта асноўны файл які будзе падгружацца і выконвацца пры падлучэнні ролі да плэйбука, у ім можна падлучаць іншыя файлы ролі). Цяпер можна перанесці ў гэты файл усе задачы якія адносяцца да карыстача:

# Create user and add him to groups
- name: Ensure a 'sudo' group
  group:
    name: sudo
    state: present

- name: Add a new user
  user:
    name: "{{ user }}"
    shell: /bin/bash
    password: "{{ user_password | password_hash('sha512') }}"
    groups: "sudo"

- name: Deploy SSH Key
  authorized_key:
    user: "{{ user }}"
    key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
    state: present

У асноўным жа плэйбуку неабходна паказаць выкарыстоўваць ролю user:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  gather_facts: no

  tasks:
    - name: Update system
      apt: update_cache=yes
    - name: Install system dependencies
      apt:
        name: git,nginx,redis,postgresql,postgresql-contrib
        state: present

  roles:
    - user

Гэтак жа, магчыма мае сэнс выконваць абнаўленне сістэмы раней за ўсіх астатніх задач, для гэтага можна перайменаваць блок tasks у якім яны вызначаны ў pre_tasks.

Настройка nginx

Nginx у нас павінен быць ужо ўсталяваны, неабходна яго сканфігураваць і запусціць. Давайце рабіць гэта адразу ў ролі. Ствараем файлавую структуру:

- ansible
  - roles
    - nginx
      - files
      - tasks
        - main.yml
      - templates

Цяпер нам спатрэбяцца файлы і шаблоны. Розніца паміж імі ў тым, што файлы ansible капіюе напрамую, як ёсць. А шаблоны павінны мець пашырэнне j2 і ў іх можна выкарыстоўваць значэнні зменных пры дапамозе тых жа падвойных фігурных дужак.

Давайце ўключым nginx у main.yml файле. Для гэтага ў нас ёсць модуль systemd:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes

Тут мы не толькі які гаворыцца, што nginx павінен быць started (то бок запускаем яго), але адразу які гаворыцца што ён павінен быць enabled.
Цяпер скапіюем канфігурацыйныя файлы:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes

- name: Copy the nginx.conf
  copy:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    backup: yes

- name: Copy template my_app.conf
  template:
    src: my_app_conf.j2
    dest: /etc/nginx/sites-available/my_app.conf
    owner: root
    group: root
    mode: '0644'

Мы ствараем асноўны канфігурацыйны файл nginx (можна ўзяць яго прама з сервера, ці напісаць самастойна). І гэтак жа канфігурацыйны файл для нашага прыкладання ў sites_available дырэкторыю (гэта не абавязкова але карысна). У першым выпадку мы выкарыстаем модуль copy для капіявання файлаў (файл павінен ляжаць у /ansible/roles/nginx/files/nginx.conf). У другім - які капіюецца шаблон, падстаўляючы значэнні зменных. Шаблон павінен ляжаць у /ansible/roles/nginx/templates/my_app.j2). І выглядаць ён можа прыкладна так:

upstream {{ app_name }} {
  server unix:{{ app_path }}/shared/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name {{ server_name }} {{ inventory_hostname }};
  root {{ app_path }}/current/public;

  try_files $uri/index.html $uri.html $uri @{{ app_name }};
  ....
}

Звярніце ўвагу на ўстаўкі {{ app_name }}, {{ app_path }}, {{ server_name }}, {{ inventory_hostname }} - гэта ўсё зменныя, значэнні якіх ansible падставіць у шаблон перад капіяваннем. Гэта карысна, калі выкарыстоўваць плэйбук для розных груп хастоў. Напрыклад мы можа дапоўніць наш inventory файл:

[production]
123.123.123.123

[staging]
231.231.231.231

[all:vars]
user=my_user
user_password=123qweasd

[production:vars]
server_name=production
app_path=/home/www/my_app
app_name=my_app

[staging:vars]
server_name=staging
app_path=/home/www/my_stage
app_name=my_stage_app

Калі мы запусцім зараз наш плэйбук, то ён выканае паказаныя задачы для абодвух хастоў. Але пры гэтым для staging хаста зменныя будуць адрознівацца ад production, і не толькі ў ролях і плэйбуках, але і ў канфігах nginx. {{ inventory_hostname }} не трэба паказваць у inventory файле - гэта спецыяльная пярменная ansible і тамака захоўваецца хост для якога выконваецца плэйбук у дадзены момант.
Калі вы жадаеце мець inventory файл для некалькіх хастоў, а запускаць толькі для адной групы, гэта можна зрабіць наступнай камандай:

ansible-playbook -i inventory ./playbook.yml -l "staging"

іншы варыянт - мець асобныя inventory файлы для розных груп. Ці можна камбінаваць два падыходу, калі ў вас шмат разныз хастоў.

Вернемся да налады nginx. Пасля капіравання канфігурацыйных файлаў нам неабходна стварыць сімлінк у sitest_enabled на my_app.conf з sites_available. І перазапусціць nginx.

... # old code in mail.yml

- name: Create symlink to sites-enabled
  file:
    src: /etc/nginx/sites-available/my_app.conf
    dest: /etc/nginx/sites-enabled/my_app.conf
    state: link

- name: restart nginx
  service:
    name: nginx
    state: restarted

Тут усё проста - ізноў модулі ansible з досыць стандартным сінтаксісам. Але ёсць адзін момант. Перазапускаць nginx кожны раз не мае сэнсу. Вы звярнулі ўвагу, што мы не пішам каманды віду: "зрабіць вось гэта вось так", сінтаксіс выглядае хутчэй як "вось у гэтага павінен быць вось такі стан". І часцей за ўсё менавіта так ansible і працуе. Калі група ўжо існуе, ці сістэмны пакет ужо ўсталяваны, то ansible праверыць гэта і прапусціць задачу. Таксама файлы не будуць капіявацца, калі яны цалкам супадаюць з тым, што ўжо ёсць на серверы. Мы можам гэтым скарыстацца і перазапускаць nginx толькі калі канфігурацыйныя файлы былі змененыя. Для гэтага існуе дырэктыва register:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes

- name: Copy the nginx.conf
  copy:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    backup: yes
  register: restart_nginx

- name: Copy template my_app.conf
  template:
    src: my_app_conf.j2
    dest: /etc/nginx/sites-available/my_app.conf
    owner: root
    group: root
    mode: '0644'
  register: restart_nginx

- name: Create symlink to sites-enabled
  file:
    src: /etc/nginx/sites-available/my_app.conf
    dest: /etc/nginx/sites-enabled/my_app.conf
    state: link

- name: restart nginx
  service:
    name: nginx
    state: restarted
  when: restart_nginx.changed

Калі адзін з канфігурацыйных файлаў змяняецца, тое будзе выканана капіяванне і зарэгістраваная зменная restart_nginx. І толькі калі гэтая зменная была зарэгістраваная, выканаецца перазапуск сэрвісу.

Ну і, вядома, трэба дадаць ролю nginx у асноўны playbook.

Настройка postgresql

Нам неабходна ўлучыць postgresql пры дапамозе systemd сапраўды гэтак жа як мы гэта рабілі з nginx, а гэтак жа стварыць карыстача, якога мы будзе выкарыстоўваць для доступу да базы дадзеных і саму базу дадзеных.
Створым ролю /ansible/roles/postgresql/tasks/main.yml:

# Create user in postgresql
- name: enable postgresql and start
  systemd:
    name: postgresql
    state: started
    enabled: yes

- name: Create database user
  become_user: postgres
  postgresql_user:
    name: "{{ db_user }}"
    password: "{{ db_password }}"
    role_attr_flags: SUPERUSER

- name: Create database
  become_user: postgres
  postgresql_db:
    name: "{{ db_name }}"
    encoding: UTF-8
    owner: "{{ db_user }}"

Я не буду распісваць, як дадаваць зменныя ў inventory, гэта ўжо рабілася шмат разоў, гэтак жа як і сінтаксіс модуляў postgresql_db і postgresql_user. Больш звестак можна знайсці ў дакументацыі. Тут найбольш цікавая дырэктыва become_user: postgres. Справа ў тым, што па змаўчанні доступ да postgresql базы ёсць толькі ў карыстача postgres і толькі лакальна. Дадзеная дырэктыва дазваляе нам выконваць каманды ад імя гэтага карыстальніка (калі, вядома, у нас ёсць доступ).
Гэтак жа, магчыма, вам давядзецца дапісаць радок у pg_hba.conf што б адкрыць доступ новаму карыстальніку да базы. Гэта можна зрабіць гэтак жа, як мы мянялі канфіг nginx.

Ну і вядома трэба дадаць ролю postgresql у асноўны плэйбук.

Устаноўка ruby ​​праз rbenv

У ansible няма модуляў для працы з rbenv, а ўсталёўваецца ён шляхам кланавання git рэпазітара. Таму гэтая задача становіцца самай нестандартнай. Створым для яе ролю /ansible/roles/ruby_rbenv/main.yml і пачнем яе запаўняць:

# Install rbenv and ruby
- name: Install rbenv
  become_user: "{{ user }}"
  git: repo=https://github.com/rbenv/rbenv.git dest=~/.rbenv

Мы зноў выкарыстоўваем дырэктыву become_user што б працаваць з-пад створанага намі для гэтых мэт карыстальніка. Бо rbenv усталёўваецца ў яго home дырэкторыі, а не глабальна. І гэтак жа мы выкарыстоўваем модуль git для таго, каб схіляваць рэпазітар, паказваючы repo і dest.

Далей нам неабходна прапісаць rbenv init у bashrc і тамака жа дадасьць rbenv у PATH. Для гэтага ў нас ёсць модуль lineinfile:

- name: Add rbenv to PATH
  become_user: "{{ user }}"
  lineinfile:
    path: ~/.bashrc
    state: present
    line: 'export PATH="${HOME}/.rbenv/bin:${PATH}"'

- name: Add rbenv init to bashrc
  become_user: "{{ user }}"
  lineinfile:
    path: ~/.bashrc
    state: present
    line: 'eval "$(rbenv init -)"'

Пасля чаго трэба ўсталяваць ruby_build:

- name: Install ruby-build
  become_user: "{{ user }}"
  git: repo=https://github.com/rbenv/ruby-build.git dest=~/.rbenv/plugins/ruby-build

І, нарэшце, усталяваць ruby. Гэта робіцца праз rbenv, гэта значыць проста bash камандай:

- name: Install ruby
  become_user: "{{ user }}"
  shell: |
    export PATH="${HOME}/.rbenv/bin:${PATH}"
    eval "$(rbenv init -)"
    rbenv install {{ ruby_version }}
  args:
    executable: /bin/bash

Мы гаворым, якую каманду выканаць і чым. Аднак тут мы натрапім на тое, што ansible не запускае код, які змяшчаецца ў bashrc перад запускам каманд. А значыць, rbenv давядзецца вызначаць прама ў гэтым жа скрыпце.

Наступная праблема злучана з тым, што shell каманда не мае станы з пункта гледжання ansible. Тое ёсць аўтаматычнай праверкі, устаноўлена гэтая версія ruby ​​ці не - не будзе. Мы можам зрабіць гэта самастойна:

- name: Install ruby
  become_user: "{{ user }}"
  shell: |
    export PATH="${HOME}/.rbenv/bin:${PATH}"
    eval "$(rbenv init -)"
    if ! rbenv versions | grep -q {{ ruby_version }}
      then rbenv install {{ ruby_version }} && rbenv global {{ ruby_version }}
    fi
  args:
    executable: /bin/bash

І застаецца ўсталяваць bundler:

- name: Install bundler
  become_user: "{{ user }}"
  shell: |
    export PATH="${HOME}/.rbenv/bin:${PATH}"
    eval "$(rbenv init -)"
    gem install bundler

І зноў жа дадаць нашу ролю ruby_rbenv у асноўны плэйбук.

Абагуленыя файлы.

У цэлым на гэтым настройку можна было б скончыць. Далей застаецца запусціць capistrano і яно само скапіюе код, створыць патрэбныя каталогі і запусціць прыкладанне (калі наладжана ўсё дакладна). Аднак часцяком capistrano неабходны дадатковыя канфігурацыйныя файлы, такія як database.yml або .env Іх можна скапіяваць сапраўды гэтак жа як файлы і шаблоны для nginx. Ёсць толькі адна тонкасць. Перад капіраваннем файлаў неабходна стварыць для іх структуру каталогаў, нешта накшталт такога:

# Copy shared files for deploy
- name: Ensure shared dir
  become_user: "{{ user }}"
  file:
    path: "{{ app_path }}/shared/config"
    state: directory

мы паказваем толькі адну дырэкторыю і ansible аўтаматычна створыць бацькоўскія, калі трэба.

Ansible Vault

Мы ўжо натыкаліся на тое, што ў зменных могуць аказвацца сакрэтныя дадзеныя, такія як пароль карыстальніка. Калі вы стварылі .env файл для прыкладання, і database.yml то там, павінна быць яшчэ больш такіх крытычных звестак. Іх добра было б схаваць ад старонніх вачэй. Для гэтага выкарыстоўваецца ansible vault.

Створым файл для перименных /ansible/vars/all.yml (тут можна ствараць розныя файлы для розных груп хастоў, сапраўды гэтак жа як у inventory файле: production.yml, staging.yml, etc).
У гэты файл неабходна перанесці ўсе зменныя, якія павінны быць зашыфраваны, выкарыстоўваючы стандартны yml сінтаксіс:

# System vars
user_password: 123qweasd
db_password: 123qweasd

# ENV vars
aws_access_key_id: xxxxx
aws_secret_access_key: xxxxxx
aws_bucket: bucket_name
rails_secret_key_base: very_secret_key_base

Пасля чаго гэты файл можна зашыфраваць камандай:

ansible-vault encrypt ./vars/all.yml

Натуральна, пры шыфраванні неабходна будзе ўсталяваць пароль для дэшыфроўкі. Можаце паглядзець, што апынецца ўсярэдзіне файла пасля выкліку гэтай каманды.

Пры дапамозе ansible-vault decrypt файл можна расшыфраваць, змяніць і потым зашыфраваць зноў.

Для працы расшыфроўваць файл не трэба. Вы захоўваеце яго ў зашыфраваным выглядзе і запускаеце playbook з аргументам --ask-vault-pass. Ansible спытае пароль, дастане зменныя і выканае задачы. Усе дадзеныя застануцца зашыфраванымі.

Цалкам каманда для некалькіх груп хастоў і ansible vault будзе выглядаць прыкладна так:

ansible-playbook -i inventory ./playbook.yml -l "staging" --ask-vault-pass

А поўны тэкст плэйбукаў і роляў я вам не дам, пішыце самі. Таму што ansible штука такая - калі не разумееш што трэба зрабіць, то і ён табе не зробіць.

Крыніца: habr.com

Дадаць каментар