CI: непрерывная интеграция за 5 минут

Delphinum 7 ноября 2017 в 13:18 3,2k

Дочитав статью до самого конца, вы догадаетесь, почему в качестве КДПВ выбран бобренок в коробке

Всем здоровья, товарищи хаброжители. Совсем недавно столкнулся с необходимостью поднять и настроить сервис «Непрерывной интеграции» (далее CI) на одном очень небольшом проекте, очень косвенно связанном с моей работой. Время не поджимало, потому решил попробовать что-то новенькое (ранее использовал только Travis и Jenkins). Главным критерием выбора была: «простота и скорость развертывания системы на интеграционном сервере».

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

Идея


Возможно я плохо искал, возможно мне хотелось принести жертву богу велосипедов, но мне не попался на глаза ни один сервис CI, который можно было просто «закинуть» на сервер, прописать в конфигурацию Github пару webhooks и забыть о DevOps. Потому я решил больше не тратить время на поиск (а потратил я около 1-2 часов), а написать что-то простое на Bash.

Идея заключалась в следующем: написать простейший CI-trigger на Bash, который будет проходить по цепочке (конвееру, pipe) функций, настраиваемых из другого (конфигурационного) скрипта.

Плюсы данного решения очевидны:

  • Написать его можно за десять минут
  • Оно будет очень гибким
  • Ничего для развертывания CI не потребуется, кроме переноса CI-trigger файла и конфигурации на сервер интеграции

Думаю о минусах этого решения вы уже и сами догадались. Я приведу лишь несколько:

  • Нет удобного для использования и настройки GUI
  • Нет готовых решений и плагинов, все придется писать на Bash или доступных ему для вызова
  • Требуются некоторые знания в области подготовки, развертывания и тестирования проекта

Реализация


Сам CI-trigger элементарен:

CI-trigger
config=`pwd`/ci.config # Адрес конфигурационного скрипта
log=`pwd`/ci.log # Адрес лог-файла

# Разбор аргументов
# ...

# Дефолтные реализации функций-интеграции
function ci_bootstrap {
  return 0
}

function ci_update {
  return 0
}

function ci_analyse {
  return 0
}

function ci_build {
  return 0
}

function ci_unit_test {
  return 0
}

function ci_deploy {
  return 0
}

function ci_test {
  return 0
}

function ci_archive {
  return 0
}

function ci_report {
  return 0
}

function ci_error {
  return 0
}

# Подключение конфигурации и выполнение интеграции
. $config &&\
  ( \
  ci_bootstrap &&\
  ci_update &&\
  ci_analyse &&\
  ci_build &&\
  ci_unit_test &&\
  ci_deploy &&\
  ci_test &&\
  ci_archive &&\
  ci_report || ci_error
  ) 1>>$log 2>&1


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

Как видите, все сводится к объявлению 9 функций-интеграции, вызываемых конвейерно для выполнения интеграции при запуске CI-trigger. Оба выходных потока конвейера объединяются в одном файле-лога, который выступает в качестве отчета о результатах интеграции.

Перед выполнением конвейера интеграции вызывается конфигурационный скрипт от имени CI-tirgger (. $config), позволяя ему переопределить любые функции. В этом и кроется вся «магия» решения. В связи с тем, что конфигурационный скрипт написан на Bash, мы можем использовать любую логику для выполнения интеграции, просто сгруппировав ее в функции.

Пример конфигурации
# Переход в каталог проекта
cd my-project

# Подготовка проекта к сборке
function ci_bootstrap {
    mysql -uadmin -pmy_pass -e "DROP DATABASE db; CREATE DATABASE db"
}

# Загрузка изменений исходных кодов
function ci_update {
    if test -d .git; then
        return git pull
    else  
        return git clone https://github.com/vendor/project ./
    fi
}

# Сборка
function ci_build {
    return npm install && npm run build
}

# Запуск модульных тестов
function ci_unit_test {
    return npm run unit_test
}

# Развертывание проекта
function ci_deploy {
    return mysql -uadmin -pmy_pass db < migration/schema.sql &&\
        mysql -uadmin -pmy_pass db < migration/data.sql
}

# Уведомление о результатах интеграции
function ci_report {
    return mail -s "CI report" my@mail.com < $log
}

# Уведомление об ошибке
function ci_error {
    echo "== Error =="
    return mail -s "CI report" my@mail.com < $log
}


Теперь нам остается только настроить логику вызова CI-trigger в соответствии с нашими требованиями.

Периодический вызов


Для этого достаточно настроить Cron, на пример так:

crontab
0 0 * * * /home/user/ci/trigger


Вызов при изменении


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

Прослушивание порта
while true; do
  { echo -ne "HTTP/1.0 200 OK\r\n\r\n"; } | nc -v -l -p 8000 && /home/user/ci/trigger
done


Теперь необходимо настроить используемую нами систему контроля версий для выполнения HTTP-запроса к этому порту при каждом push commits, на пример с помощью Curl:

.git/hooks/post-commit
curl -X POST http://ci-server.com:8000


Ссылки и все такое


Естественно данное решение далеко не идеально, вам придется много «поработать руками» для того, чтобы использовать его в большом проекте, но для быстрого запуска сервиса CI оно вполне подойдет (я так думаю!).

Проголосовать:
+3
Сохранить: