
Непрерывная интеграция Python сервиса с помощью git
Данная статья является продолжнием статьи о создании сервиса на Pyhon (opens new window). Итак мы реализовали некоторый сервис который запускается и в фоне выполняет некоторые задачи (следит за изменением цен на очредной девайс, ведет незатейливую переписку в Telegram, и т.п.). Мы разместили его на нашем сервере (например на RaspberyPi) где он и выполняет рутинные задачи. Но сервис должен разваться, мы вносим изменения в его код и каждый раз вручную приходится переносить код на наш сервер и выполнять его перезапуск, а кроме того перед запуском нам желательно протестировать новый код, хотелсь бы выполнять всю эту рутину автоматически. Тут мы и приходим к необходимости реализации непрерывной интеграции. Есть множество инструментов призванных упростить данную процедуру такие как GitLabCI (opens new window), Jenkins (opens new window), circleci (opens new window) и другие. Каждый из них достоен внимания, но мы реализуем наше решение с помощью инструментов git
и bash
, обойдя стороной docker
контейнеры и погрузившись немного в принципы работы той самой непрерывной интеграции.
Все операции указанные в данной статье выполнялись в окружении Linux Ubuntu 20, с установленным Python 3.8.
# Создаем репозиторий git
Т.к. мы планируем выполнять деплой на удаленный сервер нам потребуется сам удаленный сервер, в качестве которого могут выступить виртуальная машина с установленным Linux на базе debian и Python старше версии 3.6 и ssh, Raspberry Pi
либо другой компьютер подключенный по локальной сети.
В дальнейшем мы считаем удаленный сервер имеет IP:
192.168.0.100
, пользователь ssh локального копьютераuser
, пользователь git на удаленном сервереgit
.
Прежде всего нам потребуется настроить доступ по SSH на стороне сревера. Для авторизации пользователей используем метод authorized_keys
. На нашем сервере создадим пользователя git
и каталог .ssh:
sudo adduser git
su git
cd ~
mkdir .ssh && chmod 700 .ssh
touch .ssh/authorized_keys && chmod 600 .ssh/authorized_keys
Добавим открытые (.pub
) ключи разработчика в файл authorized_keys пользователя git. Данные ключи должны быть сгенерированы на локальном компьютере (обычно находятся в папке .ssh
). Данные ключи должны быть скопированы с локального компьютера на сервер например с помощью scp
(команда выполняется на локальном компьютере):
scp ~/.ssh/id_rsa.user.pub user@192.168.0.100:/home/user/
Добавляем скопированные ключи в .ssh/autrhorized_keys:
cat /home/user/id_rsa.user.pub >> ~/.ssh/authorized_keys
Создаем на нашем сервер пустой репозиторий без рабочего каталога --bare
:
cd /home/git
mkdir project.git
cd project.git
git init --bare
Вышеуказанные команды должны быть выполнены под пользователем
git
т.е. перед их выполнением должна быть выполнена командаsu git
(если она не была выполнена ранее). Если каталог /home/git отсутствует следует создать его и установить владельцаgit:git
:sudo mkdir /home/git
,sudo chown git:git /home/git
Теперь на локальном компьютере инициализируем репозиторий с проектом, устанавливаем для него только что созданный удаленный репозиторий:
mkdir porject
cd project
git init
touch README.md
git add README.md
git commit -m 'Initial commit'
git remote add origin git@192.168.0.100:/home/git/project.git
git push origin master
Теперь мы можем работать с удаленным репозиторием и отправлять коммиты на наш сервер командой git push origin
.
# Создаем хук git
Для того чтобы иметь возможность автоматически развренуть наше приложение на удаленном сервере нам необходимо знать когда произошли изменения в исходных файлах. Для этих целей git предоставляет хуки. Хуки - это файлы скрипты которые храняться в подкаталоге проекта .git/hooks имеют определенное имя и запускаются на определенной стадии обработки push
(в зависимости от имени). Нам потребуется хук post-recieve который вызывается после окончания выполнения операции push
.
Создадим данный хук на нашем сервере:
su git
cd /home/git/project.git/hooks
touch post-receive
vim post-receive
со следующим содержимым:
#!/bin/bash
deploy_path=/home/user/project # Путь куда будет деплоится проект
repo_path=/home/git/project.git # Путь к нашему репозиторию
# Здесь мы выполняем перехват сообщений деплоя и направляем их в файл deploy.log
# который будет находится в директории деплоя, так мы можем отслеживать состояние
# деплоя и возникновение ошибок
exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>"$deploy_path/deploy.log" 2>&1
# Зачитаем ветку в которую выполняется push и текст сообщения коммита, что позволит
# нам идентифицировать необходимость деплоя. Как в данном случае мы проверяем
# если ветка `master` и сообщение коммита начинается с `[deploy]` мы производим
# оперцию деплоя иначе деплой игнорируется (обновляется только код в репозитории)
while read oldrev newrev refname
do
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
if [ "master" = "$branch" ]; then
message=$(git show -s --format=%s)
if [[ "$message" == "[deploy]"* ]]; then
echo '############## Update sources ############################'
# Код из репозитория размещаем в директории deploy_path
git --work-tree=$deploy_path --git-dir $repo_path checkout -f master
echo '############## Deploy ####################################'
# Запускаем скрипт производящий операции деплоя (тестирование,
# обновление и перезапуск сервиса)
cd $deploy_path
# Важно указать sudo т.к. в скрипте deploy.sh мы выполняем команды
# с правами sudo, если здесь упуситить sudo получим ошибку.
sudo ./deploy/deploy.sh
fi
fi
done
Установим нашему хуку права на выполнение для этого необходимо выйти из пользователя git
и выполнить chmod +x
:
exit
chmod +x post-receive
После того как скрипт post-recieve
создан на сервере, каждый раз при выполнении git push origin
он проверит что ветку в которую выполнен push и сообщение коммита и если ветка master
и текст последнего коммита начинается с [deploy]
то запустит на выполнение скрипт deploy.sh размещенный в директории deploy нашего проекта. Таким образом нам осталось реализовать данный скрипт непосредственно в коде нашего проекта project/deploy/deploys.sh:
#!/bin/bash
# Проверяем есть ли виртуальное окружение. Если есть активируем, иначе создаем его.
if [[ -d "venv" ]]; then
echo 'Venv exists'
source venv/bin/activate
else
echo '################# Create venv ##############################'
python3 -m venv venv
fi
# Добавляем PYTHONPATH наш проект
export PYTHONPATH=${PYTHONPATH}:project
# При необходимости можем установить библитоеки из requirements.txt
# for line in $(cat requirements.txt)
# do
# pip install $line
# done
# Так же можем выполнить тестирование перед деплоем
# echo '#################### Run unit tests ##############################'
# pytest project/tests
# test_status=$?
# if [[ $test_status -ne 0 ]]; then
# echo '################## Tests falied ################################'
# echo Pytest exited vith code: $test_status
# exit 0
# fi
# Обновляем сервис
echo '#################### Update service ##############################'
sudo systemctl stop test_daemon
sudo sed "s|DEPLOY_PATH|$(pwd)|g" deploy/test_daemon_template.service > test_daemon.service
sudo mv test_daemon.service /etc/systemd/system/test_daemon.service
sudo systemctl daemon-reload
sudo systemctl start test_daemon
echo '################### Market analyser started ######################'
Теперь остается добавить наш скрипт в visudo, чтобы имень возможность запуска комманд под sudo
в нем:
sudo visudo
Добавить в конец файла где git
пользователь от которого выполняются команды, а также указан путь к директории деплоя:
git ALL=(ALL) NOPASSWD: /home/project/deploy/deploy.sh
Если требуется указать несколько скриптов который должны запускаться с правами sudo от одного пользователя они указываются через запятые:
git ALL=(ALL) NOPASSWD: /home/script_1.sh, /home/script_2.sh
# Деплой
Остается разместить исходный код из нашей предыдущей статьи (opens new window) в соответствующих директориях и можно деплоить:
project/
-- test_daemon.py
-- deploy/
----- deploy.sh
----- test_daemon_template.service
В файле test_daemon_template.service заменим путь к рабочей директории на шаблон, который будет подменен при деплое:
[Unit]
Description=Test daemon
After=syslog.target
[Service]
Type=simple
User=user
Group=user
WorkingDirectory=DEPLOY_PATH
ExecStart=DEPLOY_PATH/venv/bin/python test_daemon.py
[Install]
WantedBy=multi-user.target
Теперь можем выполнить коммит наших изменений и отправить на деплой на сервере с локального компьютера:
git add test_daemon.py
git add deploy/deploys.sh
git add deploy/test_datemon_template.service
git commit -m "[deploy] Implemented test_service"
git push origin master
Остается подключиться к удаленному серверу через ssh
и проверить запустился ли наш сервис:
systemctl -l status test_unit
Если сервис не запущен проверяем сообщения в лог файле нашего проекта на сервере:
tail /home/user/project/deploy.log
# Отладка
В нашем коде достаточно много частей которые должны корректно взаимодействовать между собой, но всегда возможны ошибки и необходимо их найти. В нашем случае стоит начать с проверки срабатывания post-receive
хука. При его запуске в директории которую мы указали в данном файле deploy_path
должен быть создан файл deploy.log
. Если после выполнения push origin master
коммита с сообщением в формате [deploy] message...
файл не создан, следует проверить имя данного файла, владельца и выставленные права на выполнение:
ls -l /home/git/project.git/hooks
-rwxr-xr-x 1 git git 736 июл 17 14:37 post-receive
Если права выставлены корректно а файл deploy.log
не создается вероятна проблема с путями либо правами на запись по указанному пути, проверить это можно запустив post-receive
от имени git
:
cd /home/git/project.git/hooks
su git
./post-receive
exit
Если есть ошибка вы увидите сообщение, иначе вывод просто застынет т.к. будет ждать на вводе сообщений которые должны прийти от git
но т.к. мы запустили его вручную он их не дождется, жмем ctrl + z
и выходим из процесса.
Если же файл deploy.log
создается мы можем наблюдать ошибки возникшие в процессе деплоя непосредственно в нем. Чтобы каждый раз не выполнять git push origin master
при отладке, можем запускать файл deploy.sh
с правами git
:
cd /home/project/deploy/
su git
./deploy.sh
exit
Ну и конечный результат выполнения нашего процесса можно смотреть выполнив:
sudo systemctl status -l test_daemon
Если ошибки возникают непосредственно в коде при выполнении вы увидите их вывод.
Вот и вся инструкция самостоятельной реализации процесса непрерывной интеграции.