Доброго времени суток!
Совсем недавно мой коллега познакомил меня с замечательным инструментом автоматизации ручного труда под названием 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 находится в репозитории.
Спасибо за уделенное мне время, надеюсь был полезен.
Автор: freylis