Deploy Django додатків з використанням Ansible для чайників

Доброго часу доби!

Зовсім недавно мій колега познайомив мене з чудовим інструментом автоматизації ручної праці під назвою Ansbile. Після чого моментально народилася ідея написати щось своє, що спрощує той самий ручна праця. Що найчастіше доводиться робити руками? Правильно, деплоиться.

У цій статті я розповім про те, як з використанням ansible розкачати django-проект на чистому віддаленому сервері ubuntu 14.04, створивши при цьому для проекту окремого користувача.

Що з себе являє ansible? Набір команд, написаних на python, які дозволяють автоматично виконувати задані дії на віддалених машинах. Чудово! Давайте побудуємо план дій, як ми це робили ручками?
  • Частина 1 (і використанням прав суперкористувача)
    • Встановити на чисту віртуальну машинку софт: mariadb, nginx, supervisor, python-mysqldb, python-virtualenv, python-pip, supervisor, uwsgi;
    • Сконфігурувати nginx config;

    • Сконфігурувати supervisor config;
    • Створити нового користувача системи спеціально під цей проект (опціонально);
  • Частина 2 (від імені користувача, господаря проекту)
    • Скопіювати проект;
    • Створити віртуальне оточення (з необхідними пакетами всередині);

    • Сконфігурувати local_settings.py (у ній ми зберігаємо налаштування доступу до БД, а так само шляху STATIC_ROOT and MEDIA_ROOT);
    • Створити структуру таблиць в базі даних (syncdb для django<1.7, migrate);
    • Зібрати всю статику в одному місці;
  • Частина 3 (знову перемикається в права суперкористувача)
    • Перезапускаємо mysql;
    • Перезапускаємо nginx;

    • Перезапускаємо supervisor;


Без зайвих слів переходимо до справи.

З чого починається ansible — з файлу hosts. В ньому ми вказуємо всі доступні нам машинки, над якими будуть проводитися дії. У нашому випадку машинка одна (про те як робити дії над кількома машинками я розповідати не буду, мета статті не в цьому) і файл виглядає наступним чином:

[project-hosts]
root ansible_ssh_host=192.168.0.102 ansible_ssh_user=freylis ansible_ssh_pass=z ansible_sudo_pass=z
user ansible_ssh_host=192.168.0.102 ansible_ssh_user=example2 ansible_ssh_pass=zz

[user-hosts]
user

[root-hosts]
root

Давайте розберемо.
В секції [project-hosts] перераховані всі наявні в нашому розпорядженні машинки, із зазначенням реквізитів доступу до них. У нашому випадку машинка одна, але вказана два рази: перша містить у собі root реквізити, від імені яких буде вироблятися настройка системи, друга — реквізити ще не створеного користувача, господаря нашого django програми. Перший параметр — аліас цієї машинки.

Секції [user-hosts] і [root-hosts] об'єднують машинки в групи за загальним назначнию (сподіваюся тут зрозуміло. Рутові в одній групі, не-рутові — в іншій).

Отже, з хостами розібралися. Тепер потрібно якось сказати ansible'у що робити.

Але для початку розповім про змінних. Абсолютно ясно, що, наприклад, назву проекту використовується багаторазово: в конфіги nginx, supervisor, шляху до проекту і т.д. У Ansible безліч способів вказувати змінні, але мені більше подобається наступний: в директорії grpou_vars створюємо файл з назвою секції з файлу hosts. Наприклад, я не став заморочуватися і створив файл з назвою project-hosts. Тепер змінні, оголошені в цьому файлі будуть доступні всім машинок, що входять в секцію [project-hosts], тобто глобально по нашому проекту.

Ось все, що знадобилося мені для цього проекту (ansible використовує синтаксис jinja2):

#
# system options
#

# username linux
username:

# about password crypt
# http://docs.ansible.com/faq.html#how-do-i-generate-crypted-passwords-for-the-user-module
# or run `mkpasswd --method=SHA-512`
# here crypted
user_crypt_password:

# password really
user_password: z

user_homedir: "/home/{{ username }}"

mysql_root_user: root # root user mysql
mysql_root_password: ""


#
# project options
#

# project slug ( if u have `example/manage.py` and example/example/settings.py` - `example` is project_slug)
project_slug:

# url or list urls for nginx
project_url:

project_dir: "{{ user_homedir }}/projects/{{ project_slug }}"

project_homedir: "{{ user_homedir }}/projects/{{ project_slug }}/{{ project_slug }}"

# virtualenv name
env: "{{ project_dir }}/env"

# port for uwsgi, must be unique for each project
uwsgi_port: 9000

# mysql database for current project
mysql_database: "{{ project_slug }}"

# mysql user for current project
mysql_user:

# mysql user password for current project
mysql_user_password:

#
# django settings
#

debug: True
local_settings: 'local_settings.py'

# empty string set if not used
вимога: 'requirements.txt'

Давайте спочатку розберемося з настроюванням системи. Створюємо файл root-playbook.yml з наступним змістом:
---
- hosts: root-hosts
sudo: true
roles:
- system

Тут поясню дві речі:

1. hosts: root-hosts — директива, говорить нам про те, машинок для якої групи виконувати наступні дії;
2. roles: system — список директорій з подальшими сценаріями дій.

Давайте вже заглянемо в директорію зі сценаріями. Вона має наступний вигляд:

roles/
system/
handlers/
main.yml
tasks/
main.yml
templates/
nginx.j2
supervisor.j2

Тут:
handlers — містить опис про обробниках. Наприклад, містить опис того, як перезапустити nginx;
tasks — всьому голова. Список завдань для Ansible;
templates — шаблони файлів, необхідних нам для деяких демонів;

Йдемо по порядку.

handlers:
---
- name: restart site
supervisorctl: name={{ project_url }} state=restarted

- name: restart mysql
service: name=mysql state=restarted enabled=yes

- name: restart nginx
service: name=nginx state=restarted enabled=yes

Yml синтаксис Ansible'а зрозумілий: ім'я директиви, сама директива, дія з параметрами. Ці обробники будуть використані в наших завданнях, аля завдання завершилася — будь ласка запустити потрібний хендлер (секція notify)
завдання:

---

# apt-get update
- name: updating the system
apt: update_cache=yes cache_valid_time=86400
notify:
- restart server

# додати apt-key для установки mariadb
- name: Add mariadb apt repository key
apt_key: id=0xcbcb082a1bb943db keyserver=hkp://keyserver.ubuntu.com:80 state=present

# додати репозиторій для установки mariadb
- name: Add mariadb apt repository
apt_repository: repo='deb http://mirror.timeweb.ru/mariadb/repo/10.1/debian wheezy main' state=present

# встановити необхідні пакети
- name: install packages
apt: pkg={{ item.name }} state=present
with_items:
- name: python-mysqldb
- name: python-virtualenv
- name: python-pip
- name: supervisor
- name: mariadb-server
- name: nginx
- name: uwsgi
- name: uwsgi-plugin-python

# скопіювати файл supervisor.conf.j2 з директорії templates в директорію на віддаленому сервері (про це трохи нижче)
- name: copy supervisor config
template: src=supervisor.conf.j2 dest=/etc/supervisor/conf.d/{{ project_url }}.conf
notify:
- restart site

# створити нового користувача системи
- name: create linux user
user: name={{ username }} shell=/bin/bash home={{ user_homedir }} password={{ user_crypt_password }}

# створити користувача mysql для цього проекту
- name: Create user MySQL
mysql_user: >
name={{ mysql_user }}
host=%
password={{ mysql_user_password }}
priv={{ mysql_database }}.*:ALL
login_user={{ mysql_root_user }}
login_password={{ mysql_root_password }}
state=present
notify:
- restart mysql

# create database
- name: Create MySQL database
mysql_db: >
name={{ mysql_database }}
collation=utf8_general_ci
encoding=utf8
login_user={{ mysql_root_user }}
login_password={{ mysql_root_password }}
state=present
notify:
- restart mysql

# скопіювати nginx.j2 конфіг з templates в директорію на віддалено сервері
- name: copy nginx config
template: src=nginx.j2 dest=/etc/nginx/sites-available/{{ project_url }}
notify:
- restart nginx

- name: create symlink nginx config
file: src=/etc/nginx/sites-available/{{ project_url }} dest=/etc/nginx/sites-enabled/{{ project_url }} state=link

Розберемо порядково одну секцію:

— name: updating the system — ім'я, що відображається в процесі деплоя
— apt: update_cache=yes cache_valid_time=86400: apt — ім'я директиви ansible (я називаю їх директивами). update_cache, cache_valid_time — параметри директиви;
— notify: — restart server — дія з handlers, яке необхідно зробити заврешению завдання.

Власне, синтаксис гранично простий. Якщо якісь параметри не ясні — можна почитати в документації до Ansible. Але хотілося б звернути увагу на директиву template. Вона приймає два параметри: src ім'я вихідного файлу, що зберігається в папці templates поточної ролі і dest — куди цей файл потрібно покласти, попередньо отрендерив, використовуючи всі доступні змінні.

Наприклад мій файл nginx.j2 template має наступний вигляд:

server {

root {{ project_dir }}/{{ project_slug }};

access_log {{ project_dir }}/logs/nginx-access.log;
error_log {{ project_dir }}/logs/nginx-errors.log;

server_name {{ project_url }};

gzip on;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain application/xml;

location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:{{ uwsgi_port }};
}

location /static {
root {{ project_dir }};
}

location /media {
root {{ project_dir }};
}

location /robots.txt {
root {{ project_dir }};
}
}

Уважний читач помітив, що директивою user ми створили нового користувача нашої системи. Давайте від його імені розгорнемо наш проект.

Створюємо ще один playbook з ім'ям user-playbook.yml і наступним змістом:

---
- hosts: user-hosts
sudo: false
roles:
- django

- hosts: root-hosts
sudo: true
завдання:
- name: restart site in supervisor
supervisorctl: name={{ project_url }} state=restarted

- name: restart mysql
service: name=mysql state=restarted enabled=yes

- name: restart nginx
service: name=nginx state=restarted enabled=yes

І всередині ми бачимо, що спочатку виконується певна роль django, а потім знову використовуючи права суперкористувача виконуються таски перезапуску демонів. Давайте розберемося з тим, що нам потрібно для розгортання проекту django:

---
- name: create directory project
file: path={{ project_dir }} state=directory

- name: create directory logs
file: path={{ project_dir }}/logs state=directory

- name: create project home directory
file: path={{ project_homedir }} state=directory

# разархивируем архів, попередньо зібраний на локальній машинці
- name: unarchive project archive
unarchive: src=/tmp/django_deploy.tar dest={{ project_homedir }}

- name: create virtualenv
pip: virtualenv={{ env }} virtualenv_site_packages=yes {% if вимога %}вимога={{ project_homedir }}/{{ вимога }}{% endif %}

# лістинг uwsgi.j2 наводити не буду, щоб не розтягувати статтю. Файл є в репозиторії
- name: copy file uwsg
template: src=uwsgi.j2 dest={{ project_homedir }}/uwsgi.{{ project_slug }}.ini

# аналогічно
- name: copy local_settings.py
template: src=local_settings.py dest={{ project_homedir }}/{{ project_slug }}/{{ local_settings }}

- name: syncdb (for django<1.7)
django_manage: command=syncdb virtualenv={{ env }} app_path={{ project_homedir }}

- name: migrate database
django_manage: command=migrate virtualenv={{ env }} app_path={{ project_homedir }}

- name: collectstatic
django_manage: command=collectstatic virtualenv={{ env }} app_path={{ project_homedir }}

- name: media create directory
file: path={{ project_dir }}/media state=directory

# я в своїх проектах юзаю django-tinymce
- name: create `uploads` directory
file: path={{ project_dir }}/media/uploads state=directory


Ось, власне, і все. На чисту систему ми встановили необхідний софт, створили нового юзера, від його імені розгорнули django проект і перезапустили всі сервера.

Все це щастя запускається так:

# якщо деплоимся перший раз (система ще не налаштовувалася) або в систему необхідно внести зміни
ansible-playbook-i hosts root-playbook.yml

# створюємо архів з поточним станом проекту
tar-cf /tmp/django-deploy.tar *

# запустити разворачивалку проекту і перезапуск демонів
ansible-playbook-i hosts user-playbook.yml


Працюючий проект з розгортання на ubuntu server 14.04 знаходиться в репозиторії.

Спасибі за приділений час, сподіваюся був корисний.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.