Коллега посоветовал классный бесплатный сервис http://semaphoreci.com/ в связке с bitbucket для простой и удобной организации непрерывной интеграции и автоматического деплоя.
До этого, если мне приходилось организовать что-то подобное, я просто поднимал TeamCity на том же сервере, где расположено приложение. По мержу в мастер происходила автоматическая сборка и деплой на сервер в случае удачи. Существенный минус этого способа - это то, что сборка тратит ресурсы боевого сервера, и вообще, в принципе может его положить. Если выносить все это на отдельный сервер, то это увеличивает расходы.
Если проект приносит деньги, то разумеется, оплатить дополнительные ресурсы это не проблема. Но если это не так, то приятно иметь бесплатную систему.
Бесплатный аккаунт Semaphoreci позволяет делать до 100 сборок в месяц. Он имеет встроенную интеграцию с bitbucket. Как только произошел мерж в master или просто настало время, он автоматически загружает актуальную версию кода к себе в рабочий диалог. Далее пользователю необходимо с нуля настроить окружение и затем сделать сборку.
На момент написания поста мне CI был не нужен, было интересно освоить сам механизм. Поэтому я пристроил CI к проекту Akademo с недавнего хакатона. Мне нужно было, чтобы при мерже в master выполнялись следующие операции:
До этого, если мне приходилось организовать что-то подобное, я просто поднимал TeamCity на том же сервере, где расположено приложение. По мержу в мастер происходила автоматическая сборка и деплой на сервер в случае удачи. Существенный минус этого способа - это то, что сборка тратит ресурсы боевого сервера, и вообще, в принципе может его положить. Если выносить все это на отдельный сервер, то это увеличивает расходы.
Если проект приносит деньги, то разумеется, оплатить дополнительные ресурсы это не проблема. Но если это не так, то приятно иметь бесплатную систему.
Бесплатный аккаунт Semaphoreci позволяет делать до 100 сборок в месяц. Он имеет встроенную интеграцию с bitbucket. Как только произошел мерж в master или просто настало время, он автоматически загружает актуальную версию кода к себе в рабочий диалог. Далее пользователю необходимо с нуля настроить окружение и затем сделать сборку.
На момент написания поста мне CI был не нужен, было интересно освоить сам механизм. Поэтому я пристроил CI к проекту Akademo с недавнего хакатона. Мне нужно было, чтобы при мерже в master выполнялись следующие операции:
- Сборка проекта в jar
- Загрузка jar на сервер
- Остановка предыдущей instance
- Старт новой intance
У semaphoreCI есть одна особенность: для каждой сборки выделяется чистая виртуальная машина, которая уничтожается после завершения сборки. Таким образом, необходимо не только с нуля настраивать машину при каждой сборке, но и надо думать где хранить артефакты после окончания сборки. Для всего этого была выбрана такая схема:
Для реализации этой схемы в корневой директории проекта я расположил скрипты, которые все это выполняет.
Данные скрипты выполняются на сервере semaphoreCI:
Таким образом, инструкция сборки у меня состоит из нескольких последовательных вызовов этих скриптов.
Теперь по шагам:
Данные скрипты выполняются на сервере semaphoreCI:
- Подготовка окружения
- Сборка проекта
- Загрузка jar
- Загрузка скриптов на боевой сервер и запуск одного из них.
А эти скрипты выполняются на боевой машине:
- Удаление предыдущего приложения
- Скачивание jar и его запуск.
Таким образом, инструкция сборки у меня состоит из нескольких последовательных вызовов этих скриптов.
Теперь по шагам:
Подготовка окружения:
Нужно установить gradle, npm, bower, gulp. В проекте применяется система сборки похожая на ту, которую я описал в этом посте.
#!/bin/bash sudo apt-get install -y gradle npm npm install gulp -g npm install bower -g
Сборка проекта:
В моем приложении это делается одной строкой. Я прокидываю дополнительный параметр чтобы в названии итогового билда было слово RELEASE.
#!/bin/bash gradle -PbuildType=RELEASE clean build
Загрузка jar:
Для этого скрипту нужно сделать следующее:
- Добавить к итоговому имени файла номер сборки, чтобы хоть как-то отличать сборки внутри одной версии.
- Загрузить полученный файл на bitbucket с помощью bitbucket API. Потом этот файл можно будет скачать в разделе downloads.
Чтобы понять, с каким файлом работать, необходимо подгрузить gradle.properties файл, который у меня выглядит вот так:
baseVersion=1.2 baseProjectName=akademo
А это сам скрипт:
#!/bin/bash . ./gradle.properties OLD_BUILD_FILE=build/libs/$baseProjectName-$baseVersion-RELEASE.jar NEW_BUILD_FILE=build/libs/$baseProjectName-$baseVersion-RELEASE-build-$SEMAPHORE_BUILD_NUMBER.jar mv $OLD_BUILD_FILE $NEW_BUILD_FILE echo Build is uploading... curl -s -u $BB_USER:$BB_PASS -X POST $BB_API -F files=@$NEW_BUILD_FILE echo Build is uploaded
Загрузка скриптов на сервер и запуск:
У меня было написано два скрипта: deploy.sh и kill.sh. Оба эти скрипта должны выполнятся на сервере. Первый скрипт отвечает за deploy, второй за остановку и удаление приложения. Как и в предыдущем случае мне нужен gradle.properties чтобы определить имя билда. Прежде чем скопировать новые скрипты нужно удалить старые.
#!/bin/bash . ./gradle.properties SCRIPT_DIR="~/akademo" BUILD_FILE=$baseProjectName-$baseVersion-RELEASE-build-$SEMAPHORE_BUILD_NUMBER.jar echo Removing old deploy.sh ... ssh -o StrictHostKeychecking=no $SERVER_USER@$SERVER_HOST rm $SCRIPT_DIR'/'deploy.sh echo Removing old kill.sh ... ssh -o StrictHostKeychecking=no $SERVER_USER@$SERVER_HOST rm $SCRIPT_DIR'/'kill.sh echo Copy new deploy.sh to server... scp -o StrictHostKeychecking=no deploy.sh $SERVER_USER@$SERVER_HOST:$SCRIPT_DIR echo Copy new deploy.sh to server... scp -o StrictHostKeychecking=no kill.sh $SERVER_USER@$SERVER_HOST:$SCRIPT_DIR echo Run delpoy.sh ... ssh -o StrictHostKeychecking=no $SERVER_USER@$SERVER_HOST bash $SCRIPT_DIR'/'deploy.sh -u $BB_LOGIN -p $BB_PASSWORD -h $BB_DOWNLOADS -b $BUILD_FILE -q 8082 echo Finish deploying
Выполнение скриптов на стороне сервера:
Скрипт deploy.sh принимает на вход параметры:
- Логин bitbucket
- Пароль bitbucket
- URL bitbucket для загрузки
- Имя билда
- Номер порта для запуска
Это позволяет с помощью него деплоить не только последнюю сборку, но и любую сборку - достаточно указать имя.
В начале работы скрипт запускает скрипт удаления предыдущего билда, затем скачивает актуальный и запускает. После запуска создается файл kill.properties. В нем храниться pid актуального запущенного процесса, имя и путь запущенного jar. Эти данные нужны будут скрипту, который будет этот билд удалять.
deploy.sh (убрал парсинг параметров для лучшего просмотра):
#!/bin/bash bash $SCRIPT_DIR'/'kill.sh echo "Loading build..." wget --user $BB_USER --password $BB_PASSWORD $BB_URL'/'$BUILD_FILE -O $DEPLOY_DIR'/'$BUILD_FILE echo "Running new build..." nohup java -jar $DEPLOY_DIR'/'$BUILD_FILE --server.port=$APP_PORT > $DEPLOY_DIR/ak.log 2>&1& echo 'OLD_PID='$! > $SCRIPT_DIR'/'kill.properties echo 'OLD_BUILD_FILE='$BUILD_FILE >> $SCRIPT_DIR'/'kill.properties echo 'DEPLOY_DIR='$DEPLOY_DIR >> $SCRIPT_DIR'/'kill.properties exit 0
kill.sh
#!/bin/bash SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [ ! -f $SCRIPT_DIR'/kill.properties' ] then echo "Nothing to kill" exit 0 fi . $SCRIPT_DIR'/kill.properties' echo 'kill pid ' $OLD_PID kill $OLD_PID echo "Waiting for stop..." while [ -d /proc/$OLD_PID ]; do sleep 1 done echo 'Deleting old build...' rm $DEPLOY_DIR'/'$OLD_BUILD_FILE rm $DEPLOY_DIR'/ak.log' echo 'Old build was deleted'
Что получилось в итоге:
Теперь достаточно сделать merge в мастер ветку и через некоторое время новая сборка будет поднята на сервере. Как видно, все настраивается самостоятельно, что придает гибкость системе и реализовать можно практически все что угодно.
Для интереса я подобрал диаграмму распределения времени двух разных билдов. Левый примерно самый худший, правый - лучший. Время показано в секундах. Основное время сборки уходит на deploy приложения, большую часть этого времени мой сервер качает актуальную сборку с bitbucket. Т.е. в моих силах уменьшить этот промежуток. (билд занимает около 40 мегабайт)
По тому, как сильно может меняться время загрузки билда (зеленый) можно понять, как сильно скачет скорость интернет соединения у semaphoreCI.
Также видно, что из-за того, что машина сначала сырая, примерно четверть суммарного времени тратиться на ее настройку. Надо заметить, что semaphoreCI имеет внутренний кэш, что позволяет стабилизировать время выполнения этого этапа.
Также благодаря этому кэшу билд выполняется за довольно стабильное время. (время загрузки зависимостей константное) Для сравнения, на машине Intel Pentium CPU G2120 3.1 GHz 8Gb ОЗУ он выполняется за 35 секунд.
Комментариев нет :
Отправить комментарий