Непрерывная интеграция с помощью Drone CI, Docker и Ansible

в 11:52, , рубрики: Ansible, automation, continuous deployment, continuous integration, docker, Drone, IT-стандарты, node.js, Разработка веб-сайтов

image

Можете представить, что Вам больше никогда не придется устанавливать зависимости и настраивать конфигурации вручную на вашем сервере непрерывной интеграции? А вы верите в то, что каждый шаг вашего билда может быть по-настоящему изолированным и работать исключительно в Docker контейнерах? В конце концов, хотели бы вы попробовать инструмент, который входит в топ 20 всех открытых проектов, написанных на Golang, и имеет 9k+ звездочек на Github?

В этой статье мы хотели бы рассказать о великолепном Drone CI, который уже помог нам упростить и сделать лучше нашу непрерывную интеграцию. Мы поделимся деталями установки Drone CI и покажем на примере небольшого проекта все детали использования. Если вы не любите много читать и хотите сразу попробовать, в конце статьи есть ссылки на Github репозитории, которые помогут с быстрым стартом.

Перед тем как перейти к основной теме, я хотел бы поблагодарить наших читателей за большое количество добрых отзывов о статье о docker-compose и, конечно же, Brad Rydzewski, автора Drone CI, без которого этой статьи не было бы. Это огромная мотивация для нас писать дальше!

Немного истории

Если вы уже знакомы с термином "Continuous Integration" (CI) и хотели бы узнать побольше — начните с замечательной статьи от Martin Fowler. Непрерывная интеграция уже давно стала для нас чем-то привычным, тем, что помогает нам выявлять неполадки и обеспечивает полную автоматизацию процесса развертывания приложения, сохраняя при этом огромное количество времени для всей команды.

Я использую CI серверы на протяжении уже 7 лет и, как многие из нас, начинал с Jenkins, позже TeamCity, а затем влюбился в Travis CI. Каждый из этих продуктов сделал очень многое для развития практики непрерывной интеграции. Одна из идей, которая заставляет меня менять инструменты время от времени — это возможность полной автоматизации любых процессов. У Jenkins и TeamCity очень развитый пользовательский интерфейс, позволяющий настроить непрерывную интеграцию для любого проекта, но это достаточно сложно поддается автоматизации. Travis — очень хороший иструмент и по-прежнему остается вариантом номер 1 для всех моих open-source начинаний, так как "Testing your open source project is 10000% free". Travis был первым инструментом, который позволял настроить непрерывную интеграцию с помощью одного файла .travis.yml.

Pipeline as a code

"Pipeline as a code" — это относительно новый подход, позволяющий настроить 'deployment pipeline' с помощью кода вместо ручной настройки запущенного CI сервиса. На сегодняшний день эта концепция очень популярна, и мне известно как минимум пять игроков в этом сегменте: LambdaCD, Concourse, Drone, GoCD и Travis CI. Этот подход позволяет не только проще автоматизировать непрерывную интеграцию и непрерывное развертывание, но также позволяет тестировать инфраструктуру для развертывания. Эта концепция заставила взглянуть на мир по-другому, но самое главное — это то, что она действительно позволяет использовать CI более эффективно и изящно.

Как мы непрерывно интегрируем

На сегодняшний день у нас в команде работает 6 человек, и наш подход к непрерывной интеграции и развертывания достаточно прост. Мы активно используем Github, Pull Requests, Code Review. Если вы хотите глубже познакомиться с "Continuous Delivery" — обратите внимание на книгу "Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation" от авторов Jez Humble и David Farley.

Основные шаги нашей непрерывной интеграции и непрерывного развертывания:

  1. Каждый член команды работает в отдельной ветке и создает Pull Request, как только задача сделана.
  2. Каждый коммит в Pull Request проверяется нашим CI сервером. Запускаются юнит, интеграционные, UI тесты и линтинг. Статус запуска отображается в Github: image
  3. Каждый Pull Request обязательно просматривается двумя членами команды. Один из них запускает код локально и проверяет все изменения в пользовательском интерфейсе. Если запуск тестов был успешен и ревью пройдено — код мержится в основную бранчу (master) и начинается процесс автоматического развертывания на pre-production среду.
  4. Развертывание состоит из двух шагов. Первым делом мы публикуем все наши Docker контейнеры на DockerHub, а затем запускаем небольшой Ansible скрипт, который разворачивает приложение на наших серверах.
  5. После небольшого ручного тестирования, все изменения подливаются в ветку "production", и запускается процесс автоматического развертывания уже в "production" среду.

Процесс имеет несколько недостатков и потребует некоторых изменений, когда наша команда станет больше. Но на сегодняшний день он нас полностью устраивает и, на наш взгляд, идеально подходит для небольших команд до 10 человек. К основным недостаткам можно отнести:

  1. Несмотря на большое количество тестов, некоторые изменения, попадающие в master, содержат дефекты. Это блокирует развертывание других Pull Requests до устранения дефектов.
  2. Когда наша master ветка находится не в "production ready" состоянии приходится отклоняться от процесса и исправлять проблемы, возникшие у наших клиентов прямо в "production" ветке, что добавляет необходимость обратного переноса этих изменений в master ветку.
  3. У нас не полностью отработан процесc отката изменений, если что-то пошло не так. Состоит он в откате ветки "production" в состояние до развертывания. Если требуются изменения в базе данных — они на данном этапе проводятся вручную.

Устранением этих недостатков мы займемся по мере роста нашей команды. На сегодняшний день мы уже можем почти безболезненно разворачивать наш проект несколько раз в день. Мы еще не разворачиваем приложение как Github, но первые шаги уже сделаны :)

Drone CI

После сравнения различных инструментов наш выбор пал на Drone CI и за последние три месяца мы полностью перешли на него. Drone CI имеет 9003 звездочек на Github (15 марта, 2017). Drone CI входит в top 20 приложений написанных на Go на Github. Канал в Gitter никогда не спит — там можно получить ответы на любые вопросы.

Установка

Drone CI представляет собой один Docker контейнер размером в 8 мегабайт. Этот контейнер содержит два сервиса:

  1. Drone UI — простой пользовательский интерфейс и сервер, который координирует работу агентов и отображает статусы сборки.
  2. Drone Agent — другой сервис, в котором и запускается сборка ваших проектов.

Все данные о прошедших и текущих билдах сохраняются в базу данных. По умолчанию используется Sqlite, но есть возможность использовать другие реляционные базы данных, такие как PostgreSQL и MySQL. Специально для этой статьи, мы подготовили Github репозиторий, который поможет вам установить Drone CI на локальном окружении или на вашем production окружении всего за несколько минут.

Начало работы

Для начала хотелось бы вкратце познакомить вас с принципом работы Drone CI. После того как вы установили и залогинились в Drone CI с помощью Вашего Github аккаунта, Drone автоматически отображает все ваши репозитории. Первым шагом Вам нужно включить репозитории, для которых вы хотите настроить непрерывную интеграцию:

image

На этом работа с пользовательским интерфейсом практически закончена. Дальше он будет нужен только для того, чтобы смотреть состояние ваших билдов.

Вся настройка шагов развертывания осуществляется в одном файле: drone.yml. Этот файл обычно находится в корне вашего репозитория и полностью описывает все, что происходит у вас на CI сервере. По сути, .drone.yml представляет собой произвольный набор шагов, каждый из которых запускается в отдельном, изолированном Docker контейнере. При каждом коммите, перед запуском шагов из .drone.yml, Drone автоматически клонирует наш репозиторий и добавляется его как Docker volume для каждого шага.

Чтобы стало понятней, давайте посмотрим на простую конфигурацию, которая запускает тесты, собирает образ докера, публикует его на Dockerhub и отправляет простую нотификацию в Slack, когда сборка закончена.

pipeline:
  run-tests:
    image: node:6.3.0
    commands:
      - cd ./api && npm i --quiet
      - npm test

  publish-api-docker:
    image: plugins/docker:1.12
    username: ${DOCKER_USERNAME}
    password: ${DOCKER_PASSWORD}
    email: ${DOCKER_EMAIL}
    repo: anorsich/ds-api
    tags:
      - latest
    dockerfile: ./api/Dockerfile
    context: ./api/

  slack-notification:
    image: plugins/slack
    webhook: https://hooks.slack.com/services/...
    username: drone-ci
    channel: andrew
    icon_emoji: ":rocket:"

Это все! Теперь при каждом коммите в репозиторий у вас будут запускаться тесты, собираться контейнеры и приходить нотификация в Slack.

Сторонние зависимости и изоляция шагов сборки

Каждый шаг в Drone выполняется в отдельном Docker контейнере, что позволяет вам не беспокоиться об установке и обновлении зависимостей на агентах сервера. Важным отличием является и то, что зависимости для каждого шага могут быть совершенно разными. В одном шаге вы можете запустить тесты для Node.JS, а уже в следующем спокойно запускать сборку приложения написанного на Go. Переход на новые версии платформ осуществляется одним лишь изменением версии контейнера. Например, мы легко можем добавить новый шаг, который запустит тесты и сборку проекта на последней версии Node.JS:

  run-tests-on-latest-node:
    image: node:7.7
    commands:
      - cd ./api && npm i --quiet
      - npm test

По сути, настраивать ваш CI сервер нужно лишь в одном случае — если вышла новая версия самого CI сервера. Все остальное настраивается прямо в репозитории в .drone.yml, без единого клика. Установил — и забыл :)

Важно отметить, что каждый шаг абсолютно изолирован от других, так как выполняется в отдельном контейнере. Больше никаких конфликтов версий!

Еще одна возможность, которую мы пока не используем, но хотелось бы упомянуть, — это поддержка матричных сборок, которая позволяет сразу тестировать ваш код на различных версиях платформ, баз данных и так далее.

Запуск по условию

Иногда есть необходимость запускать шаг в вашем билде только при определенных условиях. По умолчанию все шаги запускаются последовательно и, если один из шагов сломался, последующие не запускаются. Основные ограничения, которые мы используем это:

  1. Названию бранчи (master, production)
  2. Статус билда (success, failure)
  3. Событие Github (pull_request, push, tag, deployment)

Например мы хотим отправлять нотификацию в Slack когда билд выполнился успешно и когда сломался. Для этого мы используем секцию when и добавляем status:

  slack-notification:
    image: plugins/slack
    ...
    when:
      status: [ success, failure ]
      event: [ push, tag, deployment, pull_request ]

Более подробно с ограничениями можно познакомится здесь.

Еще одна интересная особенность, которую стоит упомянуть, — это возможность не запускать билд, добавив в сообщения коммита: [ci skip].

Плагины

Плагины — это подход Drone CI для интеграции со сторонними сервисами, такими как Amazon S3, Dockerhub, Slack. Полный список всех плагинов можно найти здесь. Каждый плагин представляет из себя отдельный Docker контейнер, который выполняет заранее определенную задачу. В нашем примере выше мы использовали два плагина:

  1. Docker плагин (plugins/docker) — для сборки и публикации образа Docker на Dockerhub.
  2. Slack плагин (plugins/slack) — для отправления уведомления в Slack.

На сегодняшний день плагины решают большинство типичных задач, но не все. Для запуска любой специфической для вашего проекта задачи Вам достаточно обернуть эту задачу в образ Docker — и Вы можете начать использовать ее в Drone CI. Вам доступны любые языки программирования, которые можно запустить в Docker. Непрерывную интеграцию для ваших специальных шагов можно организовать опять же с помощью Drone.

Консольная утилита

Консольная утилита Drone позволяет общаться с удаленным сервером и выполнять различные административные задачи. После установки вам нужно подключиться к вашему удаленному серверу. Для этого в терминале нужно экспортировать две переменные:

  1. export DRONE_SERVER=http://MY_DRONE_URL — URL вашего Drone сервера.
  2. export DRONE_TOKEN= — персональный токен, который создается после того, как вы авторизовались в Drone UI. Вы можете найти его на следующей странице: https://MY_DRONE_URL/account. Просто нажмите SHOW TOKEN и скопируйте.

Теперь консольная утилита должна быть настроена.

Секреты

У Drone есть очень удобные инструменты для безопасной работы с приватной информацией, такой как пароли и ssh ключи (одним словом — секреты). В примере выше мы использовали Docker плагин для публикации образа Docker на Dockerhub, которому нужен ваш пароль и имя пользователя.
В Github репозитории с примером организации непрерывной интеграции для Node.JS приложений с помощью Drone мы также используем Ansible. С его помощью мы запускаем Docker контейнеры на удаленном сервере. Для общения с удаленным сервером требуется отдельный ключ. Как известно, хранить секретную информацию в Github репозитории считается плохой практикой, которая может быть использована хакерами для компрометации вашего приложения. В Drone CI эта проблема решена.

Для начала, давайте разберемся как мы можем добавить пароль и имя пользователя для Dockerhub (используется плагином plugins/docker):

drone global secret add DOCKER_USERNAME andrew
drone global secret add DOCKER_PASSWORD password
drone global secret add DOCKER_EMAIL email

Вы можете добавлять секреты только к конкретному репозиторию или для всех репозиториев в рамках вашего Drone CI с использование global. Если Вы хотите добавить секрет только для определенного репозитория, Вам нужно указать его имя и не использовать global:

drone secret add maqpie/drone-starter DOCKER_USERNAME andrew

Как только ваши секреты добавлены, вы можете использовать их с помощью простого синтаксиса ${DOCKER_USERNAME} прямо в .drone.yml. Вот небольшой пример:

publish-api-docker:
  image: plugins/docker:1.12
  username: ${DOCKER_USERNAME}
  password: ${DOCKER_PASSWORD}
  email: ${DOCKER_EMAIL}

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

drone global secret add SSH_KEY @/Users/andrew/.ssh/id_rsa-drone-demo

Защита и подпись секретов

Чтобы защитить ваши секреты, Drone использует простой механизм подписи вашего .drone.yml. При каждом изменении в .drone.yml, Вам нужно его переподписать. Drone проверяет подпись каждый раз перед запуском билдов. Если подпись не совпадает, секреты не будут публиковаться и не будут доступны.

Для подписи используется ваш личный авторизационный ключ, который вы добавили при настройке консольной утилиты drone:

drone sign maqpie/drone-starter

При подписи нужно указать имя репозитория, в котором находится .drone.yml. В результате этой команды должен появиться файл .drone.yml.sig, который должен комититься в репозиторий.

Важно: Перед тем как подписывать ваш .drone.yml для вашего репозитория, убедитесь, что этот репозиторий включен в Drone UI — иначе файл подписи не будет создан.

Несмотря на хороший механизм защиты секретов в Drone, у хакеров все же остается несколько возможностей получить к ним доступ. Например, если Вы используете баш скрипт в каком-то из шагов, хакер может использовать curl либо другой инструмент, чтобы получить доступ к вашему паролю или ssh ключу. Всегда применяйте общие принципы безопасности.

Сервисы

Сервисы в Drone UI позволяют запустить любой контейнер во время выполнения вашего билд процесса. Все сервисы находятся в одной подсети с контейнерами билд процесса. Такая возможность может быть очень полезной для различных типов интеграционного тестирования. Сервисы обычно объявляются в конце .drone.yml. Пример запуска базы данных MySQL выглядит следующим образом:

services:
  database:
    image: mysql
    environment:
      - MYSQL_DATABASE=test
      - MYSQL_ALLOW_EMPTY_PASSWORD=yes

На текущем этапе мы не используем сервисы, вместо этого мы используем более простой для нас подход с использованием docker-compose.

Запуск тестов с помощью docker-compose

В нашем предыдущей статье мы поделились нашей любовью к docker-compose. Хотелось бы немного остановиться на том, как мы запускаем наши тесты в Drone CI. Шаг в .drone.yml выглядит вот так:

  run-tests-in-compose:
    image: michalpodeszwa/docker-compose:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    commands:
      - ./bin/drone-run-tests.sh api-tests
      - ./bin/drone-run-tests.sh web-tests
    when:
      event: [pull_request]

/bin/drone-run-tests.sh запускает тесты с помощью небольшой обертки над docker-compose. Перед тем как углубиться в наш подход, хотел бы рассказать о компромиссах, на которые мы пошли. В первую очередь, мы отказались от изоляции контейнера вот в этой строчке:

volumes:
   - /var/run/docker.sock:/var/run/docker.sock

Если вкратце, то эта строчка фактически дает возможность этому контейнеру запустить любую команду, которую может запустить сервис docker, что позволяет запустить любую команду на хосте, так как docker сервис запускается под root пользователем. Более подробно со всеми последствиями Вы можете ознакомиться в этой статье. Такой способ однозначно не подходит для общих репозиториев.

Почему же мы пошли на такой риск? Есть несколько причин:

  1. У нас закрытый проект, которым занимается одна команда.
  2. Наш Drone CI запущен на отдельном сервере, и даже, если кто-то захочет его сломать, воспользовавшись отсутствием изоляции, — мы ничего не потеряем. На поднятие нового Drone CI у нас уйдет не более 5 минут с учетом обновления DNS.
  3. Самой главной причиной является то, что мы хотели эффективно использовать Docker кэш. Мы делаем около 20-100 коммитов в репозиторий каждый день, и каждый из них запускает наши тесты. Если бы мы не использовали Docker кэш, при каждом коммите контейнеры бы перестраивались заново, что занимает достаточно много времени.

Теперь, когда вы знаете о минусах в нашем подходе, давайте разберем сам подход. По сути, для запуска тестов мы просто используем docker-compose up --file docker-compose.drone-tests.yml. Таким же образом тесты запускаются и на рабочих окружениях. Единственная проблема, с которой мы столкнулись, запуская тесты через docker-compose, это то, что код выхода всегда у docker-compose был 0. Drone, как и любой другой CI, в таком случае считает, что билд был успешным и переходит к следующему шагу. Чтобы исправить эту ситуацию, мы написали небольшой скрипт, который анализирует коды выхода контейнеров после запуска тестов и, если хотя бы один из контейнеров вышел с ненулевым кодом, использует этот код для выхода. Содержимое скрипта /bin/drone-run-tests.sh, который вы видели выше, в шаге запуска тестов, выглядит следующим образом:

#!/bin/sh
# remove old containers
docker-compose --file docker-compose.drone-tests.yml rm -f 
# run tests
docker-compose --file docker-compose.drone-tests.yml up --build

echo "Inspecting exited containers:"
docker-compose --file docker-compose.drone-tests.yml ps
docker-compose --file docker-compose.drone-tests.yml ps -q | xargs docker inspect -f '{{ .State.ExitCode }}' | while read code; do
    if [ "$code" != "0" ]; then
       exit $code
    fi
done

Ссылки и подсказки

Как и в любом инструменте, в Drone CI есть некоторые вещи, которые находятся в разработке и не всегда работают так, как хотелось бы. К счастью, их оказалось всего несколько:

  1. Drone UI замерзает, если процесс вашего билда выводит слишком много информации. Во всех наших Docker файлах для Node.JS нам пришлось добавить --quiet при запуске npm install — стало выводиться меньше бесполезной информации. Не совсем понимаю, почему в npm эта опция не используется по умолчанию.
  2. Есть две версии Drone 0.4 и 0.5. Последняя сейчас находится в активной разработке, но достаточно стабильна (у нас не возникло проблем за 2 месяца) Различий с 0.4 очень много. При поиске различных ответов мы часто находили старые ответы, которые уже не актуальны.
  3. Документация для версии 0.5 находится по следующему адресу: http://readme.drone.io/0.5/. Документация, пожалуй, самая слабая сторона Drone на текущий момент. От себя могу сказать, что первая версия (почитайте комментарии внизу) документации Webpack тоже была не самой лучшей, но это никак не помешало ему стать стандартом для большинства веб-проектов.

Если у нас получилось убедить Вас попробовать Drone, ниже Вы можете найти список ссылок, которые могут быть полезными:

  1. Drone github
  2. Drone plugins github
  3. Drone 0.5 documentation
  4. Drone gitter
  5. Drone 0.4 documentation — некоторые части из этой документации еще полностью не перенесены в версию 0.5.

Заключение

Drone CI — это современное решение проблем непрерывной интеграции. Используя Drone, Вы больше никогда не будете настраивать ваши сервера, получите полностью изолированную среду для запуска ваших билдов и сможете масштабировать ваш CI сервер до бесконечности. За два месяца работы мы полностью в него влюбились.

Чтобы помочь Вам начать использовать Drone CI, мы подготовили два Github репозитория:

  1. Deploy Drone — упрощает процесс установки Drone CI для рабочих и production окружений.
  2. Drone Starter — непрерывная интеграция с помощью Drone CI, Ansible, Docker на примере простого трехсервисного Node.js приложения.

Делитесь Вашим опытом и задавайте вопросы!

И да, если статья понравилась вам хотя бы на 51%, или не понравилась всего на 49% — не стесняйтесь поставить нам звездочку на Github :)

Надеемся, что статья была полезной и поможет сделать Ваш продукт лучше!

Версию на английском, можно почитать здесь.

Автор: AndrewOrsich

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js