[Из песочницы] 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 #

# linux username 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:

# really password user_password: z

user_homedir:»/home/{{ username }}»

mysql_root_user: root # root mysql user 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'

# set empty string if not used requirements: '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)tasks: ---

# 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 MySQL user 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 tasks:  — 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 project directory file: path={{ project_dir }} state=directory

— name: create logs directory 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 requirements %}requirements={{ project_homedir }}/{{ requirements }}{% endif %}

# листинг uwsgi.j2 приводить не буду, дабы не растягивать статью. Файл есть в репозитории  — name: copy uwsg file 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: create media 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 находится в репозитории.

Спасибо за уделенное мне время, надеюсь был полезен.

© Habrahabr.ru