- PVSM.RU - https://www.pvsm.ru -
В этой статье я опишу настройку автоматического развёртывания веб-приложения на стеке Django + uWSGI + PostgreSQL + Nginx из репозитория на сервисе GitLab.com [1]. Изложенное также применимо к кастомной инсталляции GitLab. Предполагается, что читатель располагает опытом в создании веб-приложений на Django, а так же опытом администрирования Linux-систем.
Развёртывание реализуем с помощью Fabric [2], Docker [3] и docker-compose [4], а осуществлять его будет сервис непрерывной интеграции, встроенный в GitLab, под названием GitLab CI [5].
Развёртывание будет происходить следующим образом:
master
), то после успешной сборки и тестирования Gitlab CI с помощью Fabric развернёт собранный Docker-образ на сервер с указанным нами IP-адресом.Приватные данные, необходимые для развёртывания — закрытые ключи, SECRET_KEY
для Django, токены сторонних сервисов и т.д. — хранить открытым текстом в репозитории определённо не стоит, поэтому для их хранения воспользуемся механизмом GitLab Secret Variables [7]:
При таком подходе конфиденциальные данные доступны открытым текстом лишь в двух местах: в настройках проекта на GitLab.com и на сервере, на который осуществляется развёртывание. В свою очередь, на сервере конфиденциальные данные будут храниться в переменных окружения (читай: будут видны любому, кто может на него зайти по SSH).
Следующие переменные необходимы для работы механизма развёртывания:
DEPLOY_KEY
— приватная часть SSH-ключа, который используется для входа на сервер;DEPLOY_ADDR
— его IP-адрес;SECRET_KEY
— соответствующая [8] настройка Django.Кроме того, в файле settings.py
Django-проекта определим SECRET_KEY
следующим образом:
SECRET_KEY = os.getenv('SECRET_KEY') or sys.exit('SECRET_KEY environment variable is not set.')
В первую очередь, создадим Dockerfile
для запуска Django и uWSGI на основе легковесного образа Alpine Linux [9]:
FROM python:3.5-alpine
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN apk add --no-cache --virtual .build-deps gcc musl-dev linux-headers pkgconf
autoconf automake libtool make postgresql-dev postgresql-client openssl-dev &&
apk add postgresql-libs postgresql-client &&
# Предотвращаем неудачную компиляцию uWSGI внутри Docker, см. https://git.io/v1ve3
(while true; do pip --no-cache-dir install uwsgi==2.0.14 && break; done)
COPY requirements.txt /usr/src/app/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /usr/src/app
RUN SECRET_KEY=build ./manage.py collectstatic --noinput &&
./manage.py makemessages &&
apk del .build-deps
Предполагается, что зависимости нашего веб-приложения, как это принято в мире Python, хранятся в файле requirements.txt
.
Далее, для оркестрации Docker-контейнеров стека нам понадобится docker-compose
.
Теоретически, можно было бы обойтись и без него, но тогда файл с инструкциями для CI стал бы раздутым и нечитаемым (см. для примера здесь [10]).
Итак, в корневой директории репозитория создадим файл docker-compose.yml
следующего содержания:
version: '2'
services:
web:
# TODO: Смените username и project на подходящие вам значения.
image: registry.gitlab.com/username/project:${CI_BUILD_REF_NAME}
build: ./web
ports:
# открытые наружу порты
- "8000:8000"
environment:
# переменные окружения, значения которых пробрасываются
# в контейнер из сервера
- SECRET_KEY
command: uwsgi /usr/src/app/uwsgi.ini
volumes:
- static:/srv/static
restart: unless-stopped
test:
# TODO: Смените username и project на подходящие вам значения.
image: registry.gitlab.com/username/project:${CI_BUILD_REF_NAME}
command: python manage.py test
restart: "no"
postgres:
image: postgres:9.6
ports:
# открытые наружу порты
- "5432:5432"
environment:
# переменные окружения: пользователь и база данных
- POSTGRES_USER=root
- POSTGRES_DB=database
volumes:
# хранилище данных
- data:/var/lib/postgresql/data
restart: unless-stopped
nginx:
image: nginx:mainline
ports:
# открытые наружу порты
- "80:80"
- "443:443"
volumes:
# хранилища конфигов и статических файлов
- ./nginx:/etc/nginx:ro
- static:/srv/static:ro
depends_on:
- web
restart: unless-stopped
Приведённый файл отвечает следующей структуре проекта:
repository
├── nginx
│ ├── mime.types
│ ├── nginx.conf
│ ├── ssl_params
│ └── uwsgi_params
├── web
│ ├── project
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── app
│ │ ├── migrations
│ │ │ └── ...
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── models.py
│ │ ├── tests.py
│ │ └── views.py
│ ├── Dockerfile
│ ├── manage.py
│ ├── requirements.txt
│ └── uwsgi.ini
├── docker-compose.yml
└── fabfile.py
Теперь весь стек запускается одной командой docker-compose up
, а внутри Docker-контейнеров стека доступ к другим запущенным контейнерам происходит по DNS-именам, соответствующим записям в файле docker-compose.yml
. Так, релевантная часть конфига Nginx будет выглядеть следующим образом:
upstream django {
server web:8000;
}
… а настройки доступа Django к БД — следующим:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'database',
'HOST': 'postgres',
}
}
Благодаря настройке restart: unless-stopped
при перезагрузке сервера все контейнеры в нашем стеке автоматически перезапускаются с теми параметрами, с которыми они были запущены изначально, т.е. никаких дополнительных действий при перезапуске сервера совершать не требуется.
Создадим в корне репозитория файл .gitlab-ci.yml
с инструкциями для GitLab CI:
# Сообщаем Gitlab CI, что мы будем использовать Docker при сборке.
image: docker:latest
services:
- docker:dind
# Описываем, из каких ступеней будет состоять наша непрерывная интеграция:
# - сборка Docker-образа,
# - прогон тестов Django,
# - выкат на боевой сервер.
stages:
- build
- test
- deploy
# Описываем инициализационные команды, которые необходимо запускать
# перед запуском каждой ступени.
# Изменения, внесённые на каждой ступени, не переносятся на другие, так как запуск
# ступеней осуществляется в чистом Docker-контейнере, который пересоздаётся каждый раз.
before_script:
# установка pip
- apk add --no-cache py-pip
# установка docker-compose
- pip install docker-compose==1.9.0
# логин в Gitlab Docker registry
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
# Сборка Docker-образа
build:
stage: build
script:
# собственно сборка
- docker-compose build
# отправка собранного в registry
- docker-compose push
# Прогон тестов
test:
stage: test
script:
# вместо повторной сборки, забираем собранный на предыдущей ступени
# готовый образ из registry
- docker-compose pull test
# запускаем тесты
- docker-compose run test
# Выкат на сервер
deploy:
stage: deploy
# выкатываем только ветку master
only:
- master
# для этой ступени другие команды инициализации
before_script:
# устанавливаем зависимости Fabric, bash и rsync
- apk add --no-cache openssh-client py-pip py-crypto bash rsync
# устанавливаем Fabric
- pip install fabric==1.12.0
# добавляем приватный ключ для выката
- eval $(ssh-agent -s)
- bash -c 'ssh-add <(echo "$DEPLOY_KEY")'
- mkdir -p ~/.ssh
- echo -e "Host *ntStrictHostKeyChecking nonn" > ~/.ssh/config
script:
- fab -H $DEPLOY_ADDR deploy
Стоит отметить, что Docker-runner'ы GitLab CI, которые мы используем, в качестве основы используют всё тот же образ Alpine Linux [9], что создаёт ряд трудностей — из коробки нет bash, непривычный пакетный менеджер apk [11], непривычная стандартная библиотека musl-libc [12] и др. Трудности компенсируются тем, что образы на основе Apline Linux получаются действительно легковесными; так, официальный образ python:3.5.2-alpine
весит [13] всего 27.6 MB.
Для выката приложения на сервер нужно в корневой же директории репозитория создать файл fabfile.py
, как минимум содержащий следующее:
#!/usr/bin/env python2
from fabric.api import hide, env, settings, abort, run, cd, shell_env
from fabric.colors import magenta, red
from fabric.contrib.files import append
from fabric.contrib.project import rsync_project
import os
env.user = 'root'
env.abort_on_prompts = True
# TODO: Смените на путь на сервере, по которому будут скопированы файлы приложения
PATH = '/srv/mywebapp'
ENV_FILE = '/etc/profile.d/variables.sh'
VARIABLES = ('SECRET_KEY', )
def deploy():
def rsync():
exclusions = ('.git*', '.env', '*.sock*', '*.lock', '*.pyc', '*cache*',
'*.log', 'log/', 'id_rsa*', 'maintenance')
rsync_project(PATH, './', exclude=exclusions, delete=True)
def docker_compose(command):
with cd(PATH):
with shell_env(CI_BUILD_REF_NAME=os.getenv(
'CI_BUILD_REF_NAME', 'master')):
# прячем прогресс-бар, см. https://git.io/vXH8a
run('set -o pipefail; docker-compose %s | tee' % command)
# Сохраняем переменные на сервере
variables_set = True
for var in VARIABLES + ('CI_BUILD_TOKEN', ):
if os.getenv(var) is None:
variables_set = False
print(red('ERROR: environment variable ' + var + ' is not set.'))
if not variables_set:
abort('Missing required parameters')
with hide('commands'):
run('rm -f "%s"' % ENV_FILE)
append(ENV_FILE,
['export %s="%s"' % (var, val) for var, val in zip(
VARIABLES, map(os.getenv, VARIABLES))])
# Fabric перечитывает переменные из профиля при каждом вызове run(),
# поэтому нет смысла делать это явно. см. http://stackoverflow.com/q/38024726/1336774
# Логинимся в registry
run('docker login -u %s -p %s %s' % (os.getenv('REGISTRY_USER',
'gitlab-ci-token'),
os.getenv('CI_BUILD_TOKEN'),
os.getenv('CI_REGISTRY',
'registry.gitlab.com')))
# Выполняем начальную установку, если нужно
with settings(warn_only=True):
with hide('warnings'):
need_bootstrap = run('docker ps | grep -q web').return_code != 0
if need_bootstrap:
print(magenta('No previous installation found, bootstrapping'))
rsync()
docker_compose('up -d')
# Включаем заглушку "технические работы", см. https://habr.ru/post/139968
run('touch %s/nginx/maintenance && docker kill -s HUP nginx_1' % PATH)
rsync()
docker_compose('pull')
docker_compose('up -d')
# Убираем заглушку
run('rm -f %s/nginx/maintenance && docker kill -s HUP nginx_1' % PATH)
Вообще говоря, копировать rsync
'ом весь репозиторий необязательно, для запуска было бы достаточно файла docker-compose.yml
и содержимого директории nginx
.
Код приложения хранится на сервере на случай, если вдруг понадобится внести срочные изменения "наживую". На бесплатных аккаунтах gitlab.com для запуска CI используется сравнительно слабое виртуализированное железо, поэтому сборка, тесты и выкат, как правило, происходят за 5-10 минут.
(правда, бывает, что они до этого ещё в очереди торчат целую вечность)
Однако бывают случаи, когда каждая секунда на счету — для таких случаев мы и оставляем лазейку в виде полных исходников приложения. Для применения изменений, внесённых "наживую", достаточно перейти в директорию /srv/mywebapp
и сказать в консоли
docker-compose build
docker-compose up -d
Таким образом, мы реализовали непрерывную интеграцию веб-приложения с помощью сервиса GitLab.
Теперь все изменения будут прогоняться через батарею автоматических тестов (которые, разумеется, тоже нужно написать), а изменения в главной ветке будут автоматически разворачиваться на боевой сервер с околонулевым временем простоя.
За рамками статьи остались следующие вопросы:
Оставим их пытливому читателю в качестве самостоятельного упражнения.
» GitLab CI: Учимся деплоить [14]
» GitLab CI Quick Start [15]
» GitLab Container Registry [16]
» Django на production. uWSGI + nginx. Подробное руководство [17]
» Fabric documentation [18]
Автор: prefrontalCortex
Источник [19]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/django-2/213745
Ссылки в тексте:
[1] GitLab.com: https://gitlab.com
[2] Fabric: http://fabfile.org
[3] Docker: https://docker.io
[4] docker-compose: https://docs.docker.com/compose
[5] GitLab CI: https://about.gitlab.com/gitlab-ci
[6] GitLab container registry: https://about.gitlab.com/2016/05/23/gitlab-container-registry
[7] GitLab Secret Variables: https://docs.gitlab.com/ce/ci/variables/README.html#user-defined-variables-secure-variables
[8] соответствующая: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SECRET_KEY
[9] Alpine Linux: https://hub.docker.com/_/alpine
[10] здесь: https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/
[11] apk: https://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management
[12] musl-libc: https://www.musl-libc.org
[13] весит: https://microbadger.com/images/python
[14] GitLab CI: Учимся деплоить: https://habrahabr.ru/company/softmart/blog/310502
[15] GitLab CI Quick Start: https://docs.gitlab.com/ce/ci/quick_start/README.html
[16] GitLab Container Registry: https://gitlab.com/help/user/project/container_registry
[17] Django на production. uWSGI + nginx. Подробное руководство: https://habrahabr.ru/post/226419
[18] Fabric documentation: http://www.fabfile.org
[19] Источник: https://habrahabr.ru/post/316054/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.