- PVSM.RU - https://www.pvsm.ru -

Django work flow (от создания до деплоя)

Django work flow (от создания до деплоя) Речь пойдет о быстром создании и деплое новых проектов, подробнее о том, как нужно экономить свое время.

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

Эта тема не новая и уже достаточно освещена в разных аспектах, я лишь покажу свой вариант.
Для комфортной разработки нам понадобится: PyCharm (ну или какой другой редактор), Python (куда без него), fabric, virtualenv, git и pip.

Как это происходит у меня

Кликаем на кнопочку:
Django work flow (от создания до деплоя)

Вводим название:
Django work flow (от создания до деплоя)

Я запускаю PyCharm, тыкаю на кнопочку Run, ввожу название нового проекта, после чего получаю готовую среду для начала работы. (за кулисами происходит следующее: создается папка проекта, где уже настроены конфиги PyCharma и git`а, создано виртуальное окружение (virtualenv), создан приватный git репозиторий на bitbucket.org, куда уже совершен коммит.

Дальше, мне остается только открыть папку проекта в новом окне PyCharm, написать код, и нажать кнопочку Run для деплоя, после чего радоваться, глядя на то, как это чудо само поднимется и настроится.

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

Структура проекта и его создание

Все просто до безобразия, мы пишем нужные нам скрипты используя fabric [1].

Django work flow (от создания до деплоя)

Все проекты у меня хранятся в одной папке _DJANGO_PROJ_ и имеют простую структуру, состоящую из 5 папок:
src — для исходников
venv — для виртуального окружения
static — для всей статики (collectstatic)
media — для меди файлов
db — для хранения БД

Кроме проектов в каталоге _DJANGO_PROJ_ есть папка deploy, в которой хранится fabfile.py скрипт, который и инициализирует новый проект, так же в ней находятся конфиги для деплоя и шаблон нового проекта.

Посмотрим подробнее на структуру папки src, поскольку все исходные коды проекта будут находиться в ней, она же будет git репозиторием.
Django work flow (от создания до деплоя)

В корне лежат: папка с настройками PyCharma и папка _project_, файлы: .gitignore (для git`a) и requirements.txt (зависимости проекта).

В _project_ находятся настройки проекта и все то, что специфично именно для данного проекта (шаблоны, статика, url.py). (подробнее о структуре Django проекта и requirements.txt [2]) Каталог static в папке _project_ нужен для хранения статических файлов специфичных для данного проекта (например favicon), не путайте его с каталогом static в корне, который нужен для команды collectstatic.

Напишем простой fab скрипт, копирует структуру проекта из шаблонной директории и попутно создает git репозиторий и виртуальное окружение.

def init():
    # все пути отсчитываются от директории fabfile.py файла.
    env.lcwd = os.path.dirname(__file__)

    # спариваем у пользователя название проекта (оно же может являться DNS именем для сайта)
    prompt("project domain: ", "project",
        validate="^([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$")
    puts("create project: {0}".format(env.project))

    with lcd('..'):
        local('mkdir ' + env.project) # создаем директорию проекта
        with lcd(env.project):
            local('mkdir db src venv static media') # нужные нам папки
            with lcd('src'):
                # Копируем инфраструктуру проекта из шаблонной директории
                local('cp -r ../../deploy/project/* .'.replace('/', os.path.sep))
                local_template_render('gitignore.txt', env, '.gitignore')

                # Инициализируем git
                local('git init')
                local('git add *')
                local('git commit -am "init"')

            # Настраиваем виртуальное окружение
            local('virtualenv --clear venv')
            local('./venv/Scripts/activate && pip install --download-cache=../deploy/pip.cache 
                -r ./src/requirements.txt'.replace('/', os.path.sep))

            # настраиваем Django
            local('./venv/Scripts/activate && python ./src/manage.py syncdb --noinput && 
                python ./src/manage.py migrate --noinput'.replace('/', os.path.sep))

После выполнения данного fab скрипты мы получим заготовку для нового проекта проект, которая уже полностью готов к запуску.

Ну и добавим создание bitbucket проекта:

        if BITBUCKET_USER and BITBUCKET_PASSWORD and
                confirm_global('create private bitbucket repository?'):
            env.bit_user=BITBUCKET_USER
            env.bit_password=BITBUCKET_PASSWORD

            import requests as r
            rez = r.post('https://api.bitbucket.org/1.0/repositories/',
                data=dict(name=env.project, is_private=True),
                auth=BITBUCKET_AUTH,
            )
            puts('request status ok: {0}'.format(rez.ok))

            if rez.ok:
                with lcd(env.project):
                    # копируем скрипт для деплоя проекта с bitbucket.org
                    local_template_render('fabfile.txt', env, 'fabs.py')
                    with lcd('src'):
                        local('git remote add origin https://{0}:{2}@bitbucket.org/{0}/{1}.git'
                            .format(env.bit_user, env.project, env.bit_password))
                        local('git push -u origin --all')   # to push changes for the first time

(Ссылка на полную версию кода будет в конце статьи)

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

Итак, создавать новый проект научились.
Дальше разбираемся с деплоем.

Деплой

Деплой будет происходить при помощи клонирования git репозитория и настройки всех нужных пакетов и окружения.
Мне нравится связка Ubuntu + nginx + uWSGI.
Тут уже все намного проще. На эту тему написано много статей, в частности на хабре: kmike [3], Nginx + uWSGI [4], DTemplate [5], Django и Fabric [6], Fabric [7]

def deploy():
    """
    Функция для деплоя проекта с удаленного гит репозитория.

    **Настройки `env`**
    env.user - deploy user name (use for ssh)
    env.password - deploy user password (use for ssh)
    env.hosts - list deploy hosts (use for ssh)

    env.domain - django site domain (DNS) use for:
        - nginx settings
        - uWSGI start settings
        - project dir name

    env.repository - ссылка на гит репозиторий, для клонирования папки src.

    env.no_input_mode - делать все не задавая лишних вопросов.
    """
    # все пути отсчитываются от директории fabfile.py файла.
    env.lcwd = os.path.dirname(__file__)

    require('no_input_mode')

    # Если no_input_mode == True переопределим функцию для задания вопросов,
    # теперь она будет выдавать занчения
    if env.no_input_mode:
        def confirm_local(question, default=True):
            puts(question)
            puts("Use no_input_mode [default: {0}]".format("Y" if default else "N"))
            return default

        confirm = confirm_local
    else:
        confirm = confirm_global

    # Запросим DNS для сайта, если нету.
    validate="^([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$"
    if not env.get("domain"):
        if env.no_input_mode:
            abort("Need set env.domain !")
        else:
            prompt("Project DNS url: ", "domain", env.get('domain_default', ''),
                    validate=validate)
    else:
        if not re.findall(validate, env.domain):
            abort("Invalid env.domain !")

    # Запросим ссылку на git репозитарий
    if not env.get("repository"):
        if env.no_input_mode:
            env.repository = env.repository_default
        else:
            prompt("Deploy from: ", "repository", env.get('repository_default', ''))

    require('repository', 'domain')

    puts("Deploy site: {0} nFrom: {1}".format(env.domain, env.repository))
    DOMAIN_WITHOUT_DOT = env.domain.replace('.', '_')

    # Заполняем настройки
    env.project_user = DOMAIN_WITHOUT_DOT
    env.project_group = DOMAIN_WITHOUT_DOT
    env.project_dir_name = DOMAIN_WITHOUT_DOT
    env.root = posixpath.join(PROJECTS_ROOT, env.project_dir_name)

    env.debug = True

    # Тут уже начинается деплой
    deb.packages(['git'])

    # Используем юзера deploy для файлов проекта
    if not fabtools.user.exists('deploy'):
        fabtools.user.create('deploy', home=PROJECTS_ROOT, group='deploy', create_home=False,
            system=True, shell='/bin/false', create_group_if_need=True)

    # проверяем наличие директории для проектов
    files.directory(PROJECTS_ROOT, use_sudo=True, owner='root', group='root', mode='755')
    with cd(PROJECTS_ROOT):
        # создаем директорию для кеширования pip пакетов
        files.directory('.pip.cache', use_sudo=True, owner='deploy', group='deploy', mode='755')
        pip_cache_dir = posixpath.join(PROJECTS_ROOT, '.pip.cache')

        # создаем директорию для проекта
        if is_dir(env.project_dir_name) and
                confirm("proj dir exist! abort ?", default=False):
            return

        files.directory(env.project_dir_name, use_sudo=True, owner='root', group='root',
            mode='755')

        # создаем нового пользователя для запуска uWSGi
        if not fabtools.user.exists(env.project_user):
            fabtools.user.create(env.project_user, home=env.root,
                group=env.project_group, create_home=False, system=True,
                shell='/bin/false', create_group_if_need=True)

        # создаем инфраструктуру для проекта
        with cd(env.project_dir_name):
            # директория с исходниками
            if not is_dir('src') or
                    confirm("proj src exist! [rm all and re clone / git pull]?",
                            default=False):
                files.directory('src', use_sudo=True, owner='deploy', group='deploy', mode='755')
                with cd('src'):
                    sudo('rm -Rf .??* *')
                    sudo('git clone %(repository)s .' % env, user='deploy')
            else:
                with cd('src'):
                    sudo('git pull', user='deploy')

            # виртуальное окружение
            if not is_dir('venv') or
                    confirm("proj venv dir exist! [rm all and recreate / repeat install]?",
                            default=False):
                files.directory('venv', use_sudo=True, owner='deploy', group='deploy', mode='755')
                with cd('venv'):
                    sudo('rm -Rf .??* *')

            python.virtualenv('venv', use_sudo=True, user='deploy', clear=True)
            with fabtools.python.virtualenv('venv'):
                python.install_requirements('src/requirements.txt', use_mirrors=False,
                        use_sudo=True, user='deploy', download_cache=pip_cache_dir)

            # директории для логов, БД (используем sqlite), статики и медиа файлов.
            files.directory('log', use_sudo=True, owner='root', group='root', mode='755')
            files.directory('db', use_sudo=True, owner=env.project_user,
                group=env.project_group, mode='755')
            files.directory('media', use_sudo=True, owner=env.project_user,
                group=env.project_group, mode='755')
            files.directory('static', use_sudo=True, owner=env.project_user,
                group=env.project_group, mode='755')
            sudo('chown -R ' + env.project_user + ':' + env.project_group +
                ' db* static* media*')

            # инициализируем статику, БД
            with fabtools.python.virtualenv('venv'):
                sudo('python src/manage.py collectstatic --noinput', user=env.project_user)
                sudo('python src/manage.py syncdb --noinput', user=env.project_user)
                sudo('python src/manage.py migrate --noinput', user=env.project_user)
                #sudo('python src/manage.py loaddata fixtures.json', user=env.project_user)

            # Дальше идет настройка веб сервера nginx и uWSGI
            # ------------------- #
            # WEB SERVER SETTINGS #
            # ------------------- #

            # I`m use nginx <-> uWSGI <-> Django

            nginx.server()
            deb.packages(['uwsgi', 'uwsgi-plugin-python'])

            # proj conf!
            if not is_dir('conf') or confirm("proj conf dir exist! [backup and update? / skip]",
                    default=False):
                files.directory('conf', use_sudo=True, owner='root', group='root', mode='755')
                with cd('conf'):
                    local_conf_templates = os.path.join(os.path.dirname(__file__),
                        'template', 'conf')
                    uwsgi_conf = os.path.join(local_conf_templates, 'uwsgi.ini')
                    nginx_conf = os.path.join(local_conf_templates, 'nginx.conf')

                    sudo("rm -Rf *.back")
                    sudo("ls -d *{.conf,.ini} | sed 's/.*$/mv -fu "&" ".back"/' | sh")
                    files.template_file('uwsgi.ini', template_source=uwsgi_conf, context=env,
                        use_sudo=True, owner='root', group='root', mode='644')
                    files.file('reload', use_sudo=True, owner='root', group='root')
                    sudo('ln -sf $(pwd)/uwsgi.ini /etc/uwsgi/apps-enabled/' +
                        env.project_dir_name + '.ini')

                    files.template_file('nginx.conf', template_source=nginx_conf, context=env,
                        use_sudo=True, owner='root', group='root', mode='644')
                    sudo('ln -sf $(pwd)/nginx.conf /etc/nginx/sites-enabled/' +
                        env.project_dir_name)

            sudo('service nginx restart')
            sudo('service uwsgi restart')

Можно сказать еще про структуру каталогов с сайтами на deploy сервере, она почти полностью повторяет структуру каталогов для разработки, добавлены лишь каталоги log и conf.
log — каталог для хранения логов, в частности nginx и uWSGI
conf — каталог с настройками для nginx и uWSGI

Как это выглядит на сервере:
Django work flow (от создания до деплоя)

Итог

Мы получили-таки 2 кнопки, одну для создания нового проекта, вторую для деплоя.

Естественно, все вышеописанное является лишь примером, и, я не агитирую всех делать все именно так.
Так же хочу заметить, что универсального пути нет, поскольку процесс разработки и деплоя — это дело сугубо интимное (личное) и каждый сам решает для себя, как и почему (кто-то любит apache, кому-то нужен redis ..). Вы можете взять мой вариант и переделать его под себя.

Я так же отмечу, что не пытался построить слой абстракций над этим процессом, поскольку, еще раз повторю, что это дело сугубо личное, особенно деплой. Я настоятельно рекомендовал, хотя бы для деплоя пытаться самому написать скрипт, поскольку, иначе, ни о какой безопасности не может быть речи, уж слишком тут тонкий момент.

Но, конечно, можно пользоваться и моим решением.

Полный вариант решения [8]

Мини HOW-TO

Итак, как все-таки всем этим пользоваться!?
(Предполагается что у вас установлен Python v2.* и pip. Так же замечу, что пользователям windows придется ручками поставить пакет fabric)

Клонируем решение в папку с нашими джанго проектами (например в cd ~ или cd %HOMEPATH%).

git clone https://bitbucket.org/pahaz/django-work-flow.git _DJANGO_PROJECTS_

Устанавливаем зависимости, если нужно:

pip install -r deploy/req.txt

Редактируем шаблон для нового проекта _DJANGO_PROJECTS_/deploy/project.

Создаем директорию, куда будут кешироваться pip пакеты — _DJANGO_PROJECTS_/deploy/.pip.cache
Создаем файл с личными деплой настройками — fabsettings.py

Пример:

# conf for init
BITBUCKET_USER = 'pahaz'
BITBUCKET_PASSWORD = 'mypasswd'

# conf for deploy
DEPLOY_DEFAULT_DOMAIN = 'developers.urfu.ru'
DEPLOY_DEFAULT_REPOSITORY = 'https://pahaz@bitbucket.org/pahaz/developers.urfu.ru.git'

DEPLOY_USER = 'root'                            # change
DEPLOY_PASSWORD = 'qwer'                        # change
DEPLOY_HOSTS = ['192.168.174.131:22', ]         # change

Запускаем файл init.sh.cmd, вводим название нового проекта, после продолжительного создания, нас спросят, хотим ли мы создать приватный bitbucket репозиторий. (отвечаем на свое усмотрение)

Все новый проект готов.

Открываем его директорию src в PyCharme и пишем код.

После чего можем запустить деплой (если настройки PyCharm`a правильно скопировались, то деплой нам доступен в один клик; ну или руками запускаем файлик fabs.py в корне проекта)

Быстрого создания проектов тебе читатель.

PS. И да простят меня знатоки pep`a.

Автор: pahaz

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/django-2/24314

Ссылки в тексте:

[1] fabric: http://fabric.readthedocs.org

[2] подробнее о структуре Django проекта и requirements.txt: http://www.deploydjango.com/django_project_structure/index.html

[3] kmike: http://habrahabr.ru/post/113636/

[4] Nginx + uWSGI: http://habrahabr.ru/post/116894/

[5] DTemplate: http://habrahabr.ru/post/81050/

[6] Django и Fabric: http://habrahabr.ru/post/76619/

[7] Fabric: http://habrahabr.ru/post/141271/

[8] Полный вариант решения: https://bitbucket.org/pahaz/django-work-flow

[9] Источник: http://habrahabr.ru/post/165081/