Решение поставленной перед разработчиком задачи бывает найти нелегко. Но как только оно получено, автору сразу хочется поделиться им со всем миром, ведь это так здорово — «отгружать» код. Неиспользуемая программа — это не что иное, как цифровой мусор. Чтобы не тратить время на никому не нужный софт, современные разработчики поставляют функциональность небольшими порциями, разбивая процесс на короткие итерации.


Такой способ создания программного обеспечения используется в процессах Непрерывной интеграции (Continuous Integration) и Непрерывного развертывания (Continuous Deployment), или CI/CD-цепочке. В этой статье мы пройдем по всем шагам настройки такой цепочки, используя для ее построения бесплатные облачные сервисы.


Мы сделаем следующее:


  1. Напишем маленькую программу на Python (не Hello World).
  2. Создадим несколько автоматизированных тестов.
  3. Разместим код на GitHub.
  4. Настроим Travis CI на постоянное выполнение автоматизированных тестов.
  5. Настроим Better Code Hub на постоянную проверку качества кода.
  6. Превратим программу на Python в веб-приложение.
  7. Создадим Docker-образ для приложения.
  8. Разместим Docker-образ на Docker Hub.
  9. Развернем Docker-образ на Heroku.

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


Чтобы быть полностью честным по отношению к читателям, должен сообщить, что являюсь одним из разработчиков Better Code Hub.


Шаг № 1: пишем генератор модных фраз


Итак, нам нужна небольшая программка, которую мы прогоним через все этапы цепочки — от ноутбука до облака. С этой целью напишем на Python генератор модных CI/CD-фраз.


Создайте новую директорию ‘cicd-buzz’, внутри — еще одну под названием ‘buzz’, а в ней — файл ‘generator.py’, содержащий представленный ниже код.


import random

buzz = ('continuous testing', 'continuous integration',
    'continuous deployment', 'continuous improvement', 'devops')
adjectives = ('complete', 'modern', 'self-service', 'integrated', 'end-to-end')
adverbs = ('remarkably', 'enormously', 'substantially', 'significantly',
    'seriously')
verbs = ('accelerates', 'improves', 'enhances', 'revamps', 'boosts')

def sample(l, n = 1):
    result = random.sample(l, n)
    if n == 1:
        return result[0]
    return result

def generate_buzz():
    buzz_terms = sample(buzz, 2)
    phrase = ' '.join([sample(adjectives), buzz_terms[0], sample(adverbs),
        sample(verbs), buzz_terms[1]])
    return phrase.title()

if __name__ == "__main__":
    print generate_buzz()

Также создайте в этой директории пустой файл ‘init.py’. Теперь структура проекта должна выглядеть так:


cicd-buzz/
  buzz/
    __init__.py
    generator.py

Зайдите в директорию ‘buzz’ и выполните скрипт:


[cicd-buzz/buzz] $ python generator.py
End-To-End Devops Enormously Boosts Continuous Testing

Попробуйте несколько раз — это весело:


[cicd-buzz/buzz] $ python generator.py
Complete Continuous Improvement Enormously Improves Devops
[cicd-buzz/buzz] $ python generator.py
Modern Devops Remarkably Improves Continuous Testing

Шаг № 2: создаем автоматизированные тесты


Цепочки непрерывной поставки (Continuous Delivery) нужны лишь в том случае, когда написано много автоматизированных тестов, которые помогают избежать непрерывной отгрузки плохо работающего ПО. Для размещения unit-тестов создайте в корневой директории проекта папку ‘tests’. Представленный ниже код сохраните в файл ‘test_generator.py’:


import unittest

from buzz import generator

def test_sample_single_word():
    l = ('foo', 'bar', 'foobar')
    word = generator.sample(l)
    assert word in l

def test_sample_multiple_words():
    l = ('foo', 'bar', 'foobar')
    words = generator.sample(l, 2)
    assert len(words) == 2
    assert words[0] in l
    assert words[1] in l
    assert words[0] is not words[1]

def test_generate_buzz_of_at_least_five_words():
    phrase = generator.generate_buzz()
    assert len(phrase.split()) >= 5

Тесты мы будем запускать с помощью фреймворка ‘pytest’. Для его установки потребуется Python Virtual Environment (‘virtualenv’). Не беспокойтесь, сделать это легче, чем сказать. Сначала убедитесь, что у вас установлен virtualenv. Далее выполните следующую команду в корневой директории проекта:


[cicd-buzz] $ virtualenv venv

В результате будет создана новая директория 'venv'. Чтобы начать использование этого окружения, выполните:


[cicd-buzz] $ source venv/bin/activate
(venv) [cicd-buzz] $

Далее создайте файл под названием ‘requirements.txt’ и пропишите там зависимость от pytest:


pytest==3.0.6

Для загрузки перечисленных в requirements.txt требований выполните команду ‘pip’:


(venv) [cicd-buzz] $ pip install -r requirements.txt

Корневая директория проекта должна выглядеть так:


cicd-buzz/
  buzz/
  requirements.txt
  tests/
  venv/

Теперь, используя виртуальное окружение, мы можем выполнить тесты из ‘test_generator.py’:


(venv) [cicd-buzz] $ python -m pytest -v tests/test_generator.py

Вывод команды должен выглядеть примерно так:


========== test session starts ==========
platform darwin -- Python 2.7.10, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- /Users/rob/projects/workspace/cicd-buzz/venv/bin/python
cachedir: .cache
rootdir: /Users/rob/projects/workspace/cicd-buzz, inifile:
collected 3 items
tests/test_generator.py::test_sample_single_word PASSED
tests/test_generator.py::test_sample_multiple_words PASSED
tests/test_generator.py::test_generate_buzz_of_at_least_five_words PASSED
========== 3 passed in 0.02 seconds ==========

Шаг № 3: размещаем код на GitHub


Войдите на GitHub (зарегистрируйтесь, если у вас еще нет аккаунта), и создайте новый публичный репозиторий ‘cicd-buzz’.


В корневой директории проекта создайте файл ‘.gitignore’, содержащий одну строку:


venv

Это нужно для того, чтобы git не добавил virtualenv в репозиторий. А теперь настало время локально инициализировать Git и отправить код на GitHub:


[cicd-buzz] $ git init
[cicd-buzz] $ git add *
[cicd-buzz] $ git commit -m "Initial commit"
[cicd-buzz] $ git remote add origin git@github.com:<YOUR_GITHUB_USERNAME>/cicd-buzz.git
[cicd-buzz] $ git push -u origin master

Шаг № 4: для выполнения тестов после каждого коммита подключаем Travis CI


Travis CI — это облачный сервис Непрерывной интеграции (Continuous Integration). Для публичных GitHub-репозиториев он бесплатен. Чтобы получить аккаунт на Travis CI, достаточно посетить https://travis-ci.org и войти, используя учетные данные GitHub.


Включить сборку по каждому Push- и Pull-запросам репозитория в Travis CI очень просто. Для этого нужно лишь передвинуть переключатель, расположенный напротив репозитория cicd-buzz (нажмите на ‘Sync account’, если репозитория еще нет в списке):



Последний шаг процедуры активации Travis CI заключается в добавлении файла ‘.travis.yml’ в корневую директорию проекта. Для нашего buzz-генератора он должен содержать:


language: python
script:
  - python -m pytest -v

Добавьте этот файл в Git, сделайте коммит и отправьте изменения на Github:


[cicd-buzz] $ git add .travis.yml
[cicd-buzz] $ git commit -m "Add Travis CI configuration"
[cicd-buzz] $ git push

Перейдите в панель инструментов Travis CI. В скором времени Travis оповестит вас об изменении кода и начнет сборку/тестирование кода. В журнале вывода можно увидеть результаты unit-тестов:




Шаг № 5: добавляем в цепочку Better Code Hub


Теперь, когда у нас есть хорошо смазанный механизм, который постоянно проверяет работоспособность кода с помощью автоматизированных тестов, очень хочется сфокусироваться на функциональности и забыть про качество кода. Better Code Hub — это облачный сервис, который проверяет качество кода согласно 10 рекомендациям по созданию поддерживаемого современного кода. Better Code Hub постоянно контролирует результаты разработки (буквально каждый push на GitHub) и сообщает о ситуациях, когда качество кода находится под угрозой.


Better Code Hub, как и Travis CI, бесшовно интегрируется с GitHub. Чтобы прикрепить его к репозиторию, зайдите на https://bettercodehub.com и выберите кнопку входа с надписью 'Free'.


После того как вы вошли под учетными данными для GitHub, откроется список ваших GitHub-репозиториев. Найдите ‘cicd-buzz’ и нажмите кнопку 'Play'. Better Code Hub спросит, запустить ли анализ со стандартными настройками. Нажмите ‘Go’ и подождите несколько секунд, после чего на экране должен появиться отчет.



Верхняя часть отчета на Better Code Hub


Если вы хотите, чтобы Better Code Hub выполнял анализ после каждого Push- и Pull-запросов (как Travis CI), щелкните по иконке с изображением шестеренки и передвиньте переключатель:




Шаг № 6: Превращаем buzz-генератор в простое веб-приложение


Отлично! Теперь у нас есть цепочка непрерывной интеграции, проверяющая работоспособность и качество кода. Далее надо настроить непрерывное развертывание прошедшего тесты кода.


Поскольку мы будем разворачивать веб-приложение на Heroku, чтобы научить buzz-генератор принимать HTTP-запросы и выводить HTML, сделаем для него обертку на Python Flask. Добавьте в корневую директорию проекта файл ‘app.py’, содержащий такой код:


import os
import signal
from flask import Flask
from buzz import generator

app = Flask(__name__)

signal.signal(signal.SIGINT, lambda s, f: os._exit(0))

@app.route("/")
def generate_buzz():
    page = '<html><body><h1>'
    page += generator.generate_buzz()
    page += '</h1></body></html>'
    return page

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=os.getenv('PORT')) # port 5000 is the default

Также добавьте еще одну строку в ‘requirements.txt’:


pytest==3.0.6
Flask==0.12

И установите новую зависимость:


(venv) [cicd-buzz] $ pip install -r requirements.txt

Теперь веб-приложение может быть запущено следующей командой:


[cicd-buzz] $ python app.py
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Откройте в браузере ссылку http://localhost:5000 и насладитесь результатами своего труда. Чтобы немного повеселиться, обновите страничку несколько раз.




Наконец, не забудьте добавить коммит и отправить изменения на GitHub:


[cicd-buzz] $ git add app.py
[cicd-buzz] $ git add requirements.txt
[cicd-buzz] $ git commmit -m "Step 5"
[cicd-buzz] $ git push

Теперь можно с удовольствием наблюдать, как Travis CI и Better Code Hub подхватывают эти изменения и проверяют код.


Step 7: Контейнеризируем приложение с помощью docker


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


Если Docker у вас уже установлен, добавьте в корневую директорию проекта ‘Dockerfile’ с таким содержимым:


FROM alpine:3.5
RUN apk add --update python py-pip
COPY requirements.txt /src/requirements.txt
RUN pip install -r /src/requirements.txt
COPY app.py /src
COPY buzz /src/buzz
CMD python /src/app.py

Следуя этим инструкциям, docker возьмет базовый образ alpine, установит Python и pip, а также установит наше веб-приложение. Последняя строка инструктирует docker запускать веб-приложение при запуске контейнера.


Теперь Docker-образ должен без проблем собираться и запускаться:


[cicd-buzz] $ docker build -t cicd-buzz .
[cicd-buzz] $ docker run -p 5000:5000 --rm -it cicd-buzz

Полюбуйтесь результатами:



CI/CD buzz-генератор, работающий в Docker-контейнере


Опять же, не забудьте сделать коммит и отправить изменения на GitHub:


[cicd-buzz] $ git add Dockerfile
[cicd-buzz] $ git commmit -m "Step 6"
[cicd-buzz] $ git push

Шаг № 8: публикация на Docker Hub


Публикация в реестре Docker-образов, таком как Docker Hub, упрощает развертывание контейнеров в различных окружениях и их откат на предыдущие версии. Для выполнения этого шага вам потребуется учетная запись на https://docker.com, а также файл ‘deploy_dockerhub.sh’ в новой директории ‘.travis’ в корне проекта:


#!/bin/sh
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
if [ "$TRAVIS_BRANCH" = "master" ]; then
    TAG="latest"
else
    TAG="$TRAVIS_BRANCH"
fi
docker build -f Dockerfile -t $TRAVIS_REPO_SLUG:$TAG .
docker push $TRAVIS_REPO_SLUG

Travis CI будет выполнять этот скрипт в конце каждой сборки, создавая таким образом ее Docker-образ. Обратите внимание на 3 переменные окружения. Их можно определить на вкладке ‘settings’ репозитория 'cicd-buzz' в Travis CI:



Переменные окружения для Docker в Travis CI


Чтобы Travis CI публиковал образ на Docker Hub после каждого коммита в репозиторий GitHub, измените ‘.travis.yml’ следующим образом:


sudo: required

services:
  - docker

language: python

script:
  - python -m pytest -v

after_success:
  - sh .travis/deploy_dockerhub.sh

После создания коммита и отправки этих изменений на GitHub (а также ожидания, пока Travis CI закончит свою работу) у вас появится возможность запустить контейнер с buzz-генератором прямо с Docker Hub:


[cicd-buzz] $ docker run -p5000:5000 --rm -it <YOUR_DOCKER_USERNAME>/cicd-buzz:latest

Шаг № 9: развертывание на Heroku


Heroku — это облачная платформа для размещения небольших масштабируемых веб-приложений. Сервис предлагает бесплатный тарифный план, поэтому переходите по ссылке https://signup.heroku.com и регистрируйтесь, если еще не сделали этого раньше.


Установите консольное приложение Heroku Toolbelt и в корневой директории проекта выполните следующие команды:


[cicd-buzz] $ heroku login
[cicd-buzz] $ heroku create
Creating app… done, ? fathomless-inlet-53225
https://fathomless-inlet-53225.herokuapp.com/ | https://git.heroku.com/fathomless-inlet-53225.git
[cicd-buzz] $ heroku plugins:install heroku-container-registry
[cicd-buzz] $ heroku container:login
[cicd-buzz] $ heroku container:push web
[cicd-buzz] $ heroku ps:scale web=1

После этого у вас должна появиться возможность получить доступ к приложению по URL, выведенному командой heroku create.



CI/CD buzz-генератор, работающий на Heroku


Обратите внимание, что команда heroku container:push web размещает на Heroku тот же самый контейнер, который мы опубликовали на Docker Hub.


Чтобы автоматизировать процесс развертывания каждой сборки ветки master нашего проекта, добавьте файл ‘deploy_heroku.sh’ в директорию ‘.travis’:


#!/bin/sh
wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh
heroku plugins:install heroku-container-registry
docker login -e _ -u _ --password=$HEROKU_API_KEY registry.heroku.com
heroku container:push web --app $HEROKU_APP_NAME

Также добавьте следующую строку в файл ‘.travis.yml’:


after_success:
  — sh .travis/deploy_dockerhub.sh
  — test “$TRAVIS_BRANCH” = “master” && sh .travis/deploy_heroku.sh

Наконец, добавьте еще две переменные окружения в Travis CI. Heroku API можно найти в ‘Account Settings’, а heroku App name — в выводе команды heroku create.




Создайте коммит и отправьте изменения на GitHub. Новый Docker-образ после успешной сборки должен сразу попасть на Docker Hub и Heroku.


Шаг № 10: CI/CD для победы!


Теперь, когда у нас настроена современная цепочка разработки, можно начинать поставлять функциональность небольшими порциями, выполняя короткие итерации. Например, если мы решим сделать landing-страницу более привлекательной, типовой рабочий процесс будет выглядеть следующим образом:


  1. Начните с создания новой задачи: https://github.com/robvanderleek/cicd-buzz/issues/1.
  2. Создайте для этой задачи feature-branch: https://github.com/robvanderleek/cicd-buzz/tree/issue-1.
  3. Здесь происходит магия кодирования.
  4. Следите за сообщениями от Travis CI и Better Code Hub: https://github.com/robvanderleek/cicd-buzz/commits/issue-1.
  5. Проверьте свое приложение локально, запустив самый свежий Docker-образ: docker run --rm -p5000:5000 -it robvanderleek/cicd-buzz:issue-1. Вы можете поделиться этим образом с другими людьми.
  6. Если вы довольны новой функциональностью, откройте Pull Request. Теперь ваш код готов быть доставленным в production по цепочке CI/CD: https://github.com/robvanderleek/cicd-buzz/pull/2.


Ветка репозитория buzz-генератора в локальном Docker-контейнере


Счастливого кодинга и отгрузки в production!


Ссылки:


  1. Оригинал: How to build a modern CI/CD pipeline
    using free and hosted services
    .
  2. Репозиторий проекта на GitHub.
Поделиться с друзьями
-->

Комментарии (16)


  1. fishca
    13.06.2017 13:40
    +1

    Спасибо.


  1. dem0n3d
    13.06.2017 14:28

    Я правильно понимаю, что при таком подходе образ на Docker Hub не будет значиться как Automated Build? Для открытых проектов это как бы важно. Мне кажется, можно научить трэвис инициировать автосборку на хабе.


    1. RobVanDerLeek
      15.06.2017 17:33

      Здравствуйте. Это разумеется возможно (например один из комментаторов ниже предложил пропустить DockerHub). Тогда Хероку будет сам собирать образ после Тревиса. Есть и другие утилиты — например Wercker и CodeFresh позволяют автоматизировать процессы сборки и заливки.


  1. whitedemon9
    13.06.2017 16:30

    а в чем различие между Docker HUB и реестром самого Heroku?


    1. RobVanDerLeek
      14.06.2017 23:47
      +2

      Здравствуйте, я Роб фан дер Леек (автор оригинального поста на medium).
      В оригинальном посте я использовал Докер Хаб так как в этом случае проще расшарить образы с другими разработчиками.
      Конечно, вы всегда можете залить образ напрямую на Хероку и пропустить Докер Хаб.


      1. whitedemon9
        15.06.2017 13:01

        еще такой вопрос, где лучше хранить разные connection strings, ключи к различным апи и прочую информацию? В самом тревисе и как-то прокидывать в docker image, или хранить как переменные окружения в самом хероку?


        1. RobVanDerLeek
          15.06.2017 17:15
          +1

          Хороший вопрос. Это конечно зависит в большинстве случаев от выбранной платформы — Heroku предоставляет возможность хранения переменных внутри (https://devcenter.heroku.com/articles/config-vars).
          Разумеется есть много других различных способов, главное избегать хранить переменные в вашем исходном коде.


          1. whitedemon9
            15.06.2017 22:11

            не планируете написать об этом статью? в интернете много статей о том как настроить CI/CD, но мало кто пишет про то, как хранить переменные


  1. Roquie
    13.06.2017 23:15
    +2

    Для opensource проектов действительно все быстро собирается, нежели для приватных. Хотелось бы увидеть нечто подобное для связки GitLab + дедик.


    1. Roquie
      16.06.2017 16:47

      Нашелся pet-project для экспериментов. Сделал по гайду для JavaScript-проекта.
      https://github.com/roquie/source-concater
      https://srcc.herokuapp.com/

      Из проблем — Heroku не хочет работать внутри контейнера из под рута, пришлось немного пошаманить.


  1. r-moiseev
    14.06.2017 11:35
    +1

    Для коммерческого проекта с закрытым кодом это схема стоит килотонны денег.

    Есть же GitLab, который один делает все означенное.

    А если вы уже используете докер, то вам и хероку не нужен, берите сервак на ДО и разворачивайте там сварм.


    1. RobVanDerLeek
      15.06.2017 17:23
      +1

      Спасибо! Это действительно так. Есть очень много вариантов как можно построить подобную конфигурацию. Своей статьёй я хотел продемонстрировать насколько легко и удобно это сделать в наше время с помощью современных (и бесплатных) облачных сервисов.


  1. mmxdesign
    14.06.2017 11:35
    +1

    Очень классный перевод и сама статья!
    Все четко, кратко без лишний воды, и самое главное показан весь цикл от А до Я.


    1. RobVanDerLeek
      15.06.2017 17:18
      +1

      Спасибо! Я автор оригинального поста на medium.
      Если вам интересны подобные публикации, мы регулярно пишем статьи на эти темы (https://medium.com/bettercode)


  1. JCHouse
    14.06.2017 11:36

    А смысл докера после тиревиса? Не легче самими тревисом собирать и выливать?


    1. RobVanDerLeek
      15.06.2017 17:16
      +1

      Здравствуйте, я Роб фан дер Леек (автор оригинального поста на medium).
      В посте я просто привожу пример цепочки. Конечно есть гораздо более различных способов собрать подобные цепочки.
      В тексте я лишь хотел показать насколько просто это сделать в современных реалиях и с современными технологии.

      А так — пожалуйста экспериментируйте и стройте то что подходит под ваши нужды!