Внедрение CI\CD для проекта Screen-Monitoring с помощью Travis-CI

Vlad Andreev
Mad Devs — блог об IT
8 min readDec 6, 2016

Это история о том, как мы настраивали Continuous delivery и Continuous integration для нашего Open Source проекта Screen-Monitoring

Основная концепция этого проекта — мониторинг для IT команды.

Screen-Monitoring должен уметь собирать “любую” информацию и показывать ее различными способами. Проект написан на языке GO и является абсолютно бесплатным.

Сейчас в архитектуре проекта есть две сущности:

  • Dashboard — центральная точка взаимодействия между пользователями и агентами
  • Agents — собственно агенты, которые собирают, агрегируют и показывают информацию юзеру через Dashboard

Я хочу осветить в этой статье как мы реализовали тесты, сборку и поставку на боевой сервер центральной части проекта Screen-Monitoring — Dashboard.

Для начала определимся, что же такое Continuous delivery и Сontinuous integration?

Continuous delivery или непрерывная поставка ПО — рекомендации, нацеленные на поставку ПО как можно быстрее и чаще в боевое окружение (на работающий сервер) стандартным и полностью автоматизированным способом.

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

Так как наш проект является Open Source и находится в открытом доступе, мы решили остановиться на бесплатном решении CI\CD — https://travis-ci.org/

Про Travis

Travis-ci очень любопытный проект для организации CI\CD, тесно интегрированный с github.com. Он позволяет организовать тестирование и сборку программного обеспечения, использующего Github в качестве хостинга исходного кода. Сервис поддерживает работу с большим количеством языков — C, C++, D, JavaScript, Java, PHP, Python и Ruby. Поддерживает большое количество сторонних программ и скриптов(git, docker, bash), а также множество возможностей для деплоя сборок на различные Cloud Services (AWS, Google Cloud Storage, Heroku, S3).

Еще немаловажная фича Travis-ci это поддержка шифрованных переменных и шифрованных файлов лежащих в репозитории проекта. Это может потребоваться для сборки или поставки приложения на сервер, чтобы не беспокоиться о том, что пароли от БД или другие секретные данные могут попасть не в те руки.

Подготовка

Для начала определимся с реализацией.

На входе у нас есть:

  • проект написанный на GO;
  • репозиторий на Github;
  • написанные тесты.

Что должно получиться:

  • готовые Docker Images на https://hub.docker.com/;
  • деплой успешной сборки на боевой сервер;

Для начала нужно получить в репо проекта роль «Admin», это необходимо для дальнейшей интеграции с Travis-ci.

Далее желательно завести в проекте пустой файл «.travis.yml» и задеплоить его на Github.

Затем необходимо авторизоваться в Travis-CI через OAuth(github) и разрешить ему просматривать текущий Github аккаунт. В настройках github (https://github.com/settings/applicationsAuthorized applications) появится разрешенное приложение Travis-CI.

Дальше на главной странице https://travis-ci.org/ появляется список репозиториев, разрешенных организаций и твои собственные репозитории (но только в том случае если они не приватные, для приватных придется воспользоваться платной подпиской на https://travis-ci.com/).

Являясь участником организации в Github, ты можешь видеть активность добавленных в Travis-CI репозиториев этой организации, но не можешь на них повлиять если твои права ниже (Owner или Admin). Например добавить/удалить переменные, запустить билд.

В Github правами репозиториев можно рулить на уровне Organizations или Teams (если над проектом работает множество специалистов).

Как только участник группы получает уровень Owner/Admin, ему следует зайти в Travis-CI и нажать в настройках Sync Account чтобы применились настройки доступа.

Теперь можно интегрировать репозиторий проекта в Travis-CI. Чтобы забрать возможность правки проекта в Travis-CI нужно понизить роль у участника, либо удалить его из Организации.

Выбираем проект который хотим запустить в Travis-CI и включаем в “General Settings” следующее:

  • Build only if .travis.yml is present;
  • Build pushes;
  • Build pull requests.

Теперь, можно запустить первую сборку, но она пока еще проходить не будет, так как travis.yml у нас пока пуст.

Последний пункт в подготовке это создание аккаунта на hub.docker.com. С этим не должно возникнуть больших проблем. Я дополнительно создал компанию maddevsio и пригласил в нее своих коллег.

Настраиваем CI

На этапе “CI” после любого push в репо мы должны получить какой либо артефакт, это может быть отчет по пройденным тестам, билд программы или как в нашем случае Docker Image лежащий на https://hub.docker.com/r/maddevsio/sm-dashboard/.

Сборку Docker Image, нужно начинать после успешного прохождения Unit тестов и успешной сборки приложения.

Вот с этого и начнем, добавим в “.travis.yml” начальный конфиг:

# Здесь мы указываем нужный язык и
# по необходимости конкретные его версии
language: go
go:
- 1.7.3
- tip
# Настраиваем окружение
before_install:
- ". $HOME/.nvm/nvm.sh"
- nvm install stable
- nvm use stable
- npm install
# Запускаем шаг тестов и сборки, если на этом
# шаге что-то пойдет не так, Travis пометит сборку
# как Failed
script:
- go test -v ./dashboard/
- go build -v
- npm run build-production

Теперь после каждого push в репо у нас будут запускаться тесты в Travis-CI.

Однако кроме тестов и сборки проекта хотелось бы получить готовый Image для Docker.

Для этого мы создали файл сборки Image “Dockerfile_Travis” в репо:

FROM golang:1.7MAINTAINER Andreev Vlad <andreevlad@gmail.com>COPY ./screen-monitoring /screen-monitoring/
COPY ./dashboard /screen-monitoring/dashboard
COPY ./public /screen-monitoring/public
WORKDIR /screen-monitoringEXPOSE 8080CMD ["./screen-monitoring"]

И написали bash скрипт сборки, который положили в отдельный репо, так как рассчитываем использовать его для сборки других проектов: https://raw.githubusercontent.com/maddevsio/travis-push-to-docker/master/sm-push.sh

#!/bin/bashfunction tag_and_push {
if [ -n "$1" ] && [ -n "$IMAGE_NAME" ]; then
echo "Pushing docker image to hub tagged as $IMAGE_NAME:$1"
docker build -t $IMAGE_NAME:$1 -t $IMAGE_NAME -f Dockerfile_Travis .
docker push $IMAGE_NAME
docker push $IMAGE_NAME:$1
fi
}
VERSION_TAG=v.$TRAVIS_BUILD_NUMBERif [ "${TRAVIS_GO_VERSION}" = "${GO_FOR_RELEASE}" ]; thencat > ~/.dockercfg << EOF
{
"https://index.docker.io/v1/": {
"auth": "${HUB_AUTH}",
"email": "${HUB_EMAIL}"
}
}
EOF
tag_and_push $VERSION_TAG
else
echo "No image to build"
fi

Скрипт проверяет версию GO в текущей сборке, это нужно если есть необходимость тестирования на разных версиях языка, например — 1.7.3 и самой последней:

go:
- 1.7.3
- tip

Далее скрипт берет номер текущей сборки (VERSION_TAG=v.$TRAVIS_BUILD_NUMBER) и подставляет его в команду “docker build” в виде TAG метки названия Docker Image, получается примерно так:

docker build maddevsio/sm-dashboard:v.32
docker push maddevsio/sm-dashboard:v.32

Чтобы запушить Image на hub.docker.com нужно воспользоваться шифрованными переменными в Travis-CI и указать следующие переменные:

HUB_AUTH
HUB_EMAIL

Это данные аутентификации на hub.docker.com. Получить HUB_AUTH можно вызвав утилиту “docker login” и ввести свои данные от учетки с hub.docker.com. После этого появится файлик $HOME/.docker/config.json в котором лежат нужные нам данные.

Итоговый конфиг для Travis-CI получился таким:

language: go
go:
- 1.7.3
- tip
env:
global:
- GO_FOR_RELEASE=1.7.3
- IMAGE_NAME=maddevsio/sm-dashboard
services:
- docker
before_install:
- ". $HOME/.nvm/nvm.sh"
- nvm install stable
- nvm use stable
- npm install
script:
- go test -v ./dashboard/
- go build -v
- npm run build-production
- curl https://raw.githubusercontent.com/maddevsio/travis-push-to-docker/master/sm-push.sh | bash

Теперь после успешного прохождения тестов и сборки билда, запускается скрипт сборки Docker Image, куда копируется собранное ранее приложение и заливается в hub.docker.com

Настраиваем CD

На этом этапе нужно научить Travis-CI деплоить получившийся Docker Image на сервер.

В нашем случае сервер это обычная виртуалка с Debian Linux на борту и заранее установленным Docker.

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

Деплоить с помощью Docker можно несколькими путями:

  1. Через ssh под каким-либо пользователем передавать команды демону Dockerd. Схема такая — Travis соединяется с хостом через ssh и выполняет команды (docker run\rm …). Минус этого способа — необходимость передачи Тревису id_rsa ключ, в этом случае на помощь придет возможность шифровать файлы. Однако шифрованный ключ должен храниться в репозитории проекта с которым работает Travis, что для крупных и закрытых проектов может быть неприемлемо.
  2. Прокинуть TCP socker демона Dockerd наружу, например через nginx, DNAT или указать демону Dockerd внешний IP. В этом случае встает вопрос защиты Dockerd. Можно зарезать по IP, но в нашем случае мы не знаем с каких IP может прийти соединение. Еще можно попробовать воспользоваться TLS, на хосте с Dockerd создаются crt ключи, указывается директория хранения ключей и необходимость их использования для аутентификации. Дальше этими же сертификатами подписывается сертификат клиента, которые передаются Travis-CI. Тут все усложняется необходимостью работы с сертификатами, но в целом думаю это наилучший и наиболее правильный сценарий для работы с Dockerd удаленным клиентам.
  3. У Dockerd есть HTTP_API. Можно включить HTTP_API, кинуть порт на loopback интерфейс и получить к нему доступ через nginx с HTTP_AUTH. Есть два минуса, HTTP_AUTH недостаточно надежен, а запросы которые придется передавать с консоли получаются очень громоздкими, хотя в целом если один раз написать билд план и скрипты к нему, этот вариант тоже вполне можно использовать.

Я решил использовать первый вариант как самый быстрый в реализации.

На сервере нужно создать пользователя (в нашем случае имя пользователя будет — sm-docker) для деплоев:

adduser --home /srv/docker/sm-docker sm-docker
usermod -a -G docker sm-docker
ssh-keygen
cp id_rsa.pub authorized_keys
chmod 600 authorized_keys
change authorized_keys << command="docker $SSH_ORIGINAL_COMMAND",no-port-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EA..... .ssh/id_rsa

С помощью директивы command в файле authorized_keys я ограничил этому пользователю запуск каких либо команд через ssh кроме утилиты docker, которая принимает в качестве аргументов любые данные введенные с удаленного хоста, например команда:

ssh -o StrictHostKeyChecking=no -i ./sm-docker-key sm-docker@sm.maddevs.io "rm -f screen-monitoring"

запустит на удаленном хосте:

docker rm -f screen-monitoring

Созданный id_rsa ключ для пользователя sm-docker я добавил в репо проекта предварительно зашифровав его утилитой travis. При шифровании утилита сама поправила файл .travis.yml, добавив туда функцию расшифровки файла, а также добавила переменные для расшифровки в build plan нашего проекта на travis-ci.org

travis encrypt-file  ./sm-docker-key --add

После этой процедуры оригинальный файл ключа надо удалить.

Для самого деплоя воспользуемся шагом Deploy в Travis-CI:

language: go
go:
- 1.7.3
- tip
env:
global:
- GO_FOR_RELEASE=1.7.3
- IMAGE_NAME=maddevsio/sm-dashboard
services:
- docker
before_install:
- openssl aes-256-cbc -K $encrypted_abfc74a8332b_key -iv $encrypted_abfc74a8332b_iv -in sm-docker-key.enc -out ./sm-docker-key -d
- ". $HOME/.nvm/nvm.sh"
- nvm install stable
- nvm use stable
- npm install
script:
- go test -v ./dashboard/
- go build -v
- npm run build-production
- curl https://raw.githubusercontent.com/maddevsio/travis-push-to-docker/master/sm-push.sh | bash
deploy:
provider: script
skip_cleanup: true
script: chmod 600 sm-docker-key &&
ssh -o StrictHostKeyChecking=no -i ./sm-docker-key sm-docker@sm.maddevs.io "pull $IMAGE_NAME:v.$TRAVIS_BUILD_NUMBER" &&
ssh -o StrictHostKeyChecking=no -i ./sm-docker-key sm-docker@sm.maddevs.io "rm -f screen-monitoring" || true &&
ssh -o StrictHostKeyChecking=no -i ./sm-docker-key sm-docker@sm.maddevs.io "run -d --restart=always --name=screen-monitoring -p 127.0.0.1:9080:8080 -v /srv/docker/sm-docker/screen-monitoring/screen_monitoring.db:/screen-monitoring/screen_monitoring.db $IMAGE_NAME:v.$TRAVIS_BUILD_NUMBER"
on:
go: '1.7.3'

Теперь после каждого push в репо на github запускается build plan в — Travis-ci. При успешном прохождении тестов и сборки docker image запускается шаг Deploy. В шаге Deploy на хост sm.maddevs.io пулится image с номером текушей сборки, удаляется старый контейнер screen-monitoring и запускается новый.

Благодаря Travis-CI получилось организовать легкую и бесплатную реализацию CI\CD.

Полезные ссылки

Если интересно поглубже изучить применяемые в статье технологии, можно почитать следующее:

Хочу выразить отдельную благодарность за помощь с интеграцией Кареву Геннадию

--

--