Как построить эффективный конвейер начального развертывания
Опубликовано: 2022-03-11Я люблю создавать вещи — какой разработчик не любит? Я люблю придумывать решения интересных задач, писать реализации и создавать красивый код. Однако, что мне не нравится, так это операции . Эксплуатация — это все, что не связано с созданием отличного программного обеспечения — от настройки серверов до отправки вашего кода в производство.
Это интересно, потому что как внештатному разработчику Ruby on Rails мне часто приходится создавать новые веб-приложения и повторять процесс выяснения аспектов DevOps. К счастью, после создания десятков приложений я наконец остановился на идеальном начальном конвейере развертывания. К сожалению, не все поняли это так, как я — в конце концов, это знание побудило меня сделать решительный шаг и задокументировать мой процесс.
В этой статье я познакомлю вас с моим идеальным конвейером для использования в начале вашего проекта. В моем пайплайне проверяется каждое нажатие, основная ветвь развертывается на промежуточной стадии со свежим дампом базы данных из рабочей среды, а версионные теги развертываются в рабочей среде с автоматическим резервным копированием и миграцией.
Обратите внимание, поскольку это мой конвейер, он также самоуверен и подходит для моих нужд; тем не менее, вы можете свободно менять все, что вам не нравится, и заменять тем, что вам больше нравится. Для моего конвейера мы будем использовать:
- GitLab для размещения кода.
- Почему: Мои клиенты предпочитают, чтобы их код оставался в секрете, и уровень бесплатного пользования GitLab прекрасен. Кроме того, интегрированная бесплатная CI — это здорово. Спасибо ГитЛаб!
- Альтернативы: GitHub, BitBucket, AWS CodeCommit и многие другие.
- GitLab CI для создания, тестирования и развертывания нашего кода.
- Зачем: интегрируется с GitLab и бесплатен!
- Альтернативы: TravisCI, Codeship, CircleCI, DIY с Fabric8 и многие другие.
- Heroku для размещения нашего приложения.
- Почему: он работает из коробки и является идеальной платформой для начала. Вы можете изменить это в будущем, но не каждое новое приложение нужно запускать в специально созданном кластере Kubernetes. Даже Coinbase начинала с Heroku.
- Альтернативы: AWS, DigitalOcean, Vultr, DIY с Kubernetes и многие другие.
Старая школа: создайте базовое приложение и разверните его на Heroku
Во-первых, давайте воссоздадим типичное приложение для тех, кто не использует какие-либо причудливые конвейеры CI/CD и просто хочет развернуть свое приложение.
Неважно, какое приложение вы создаете, вам потребуется Yarn или npm. В моем примере я создаю приложение Ruby on Rails, потому что оно поставляется с миграциями и интерфейсом командной строки, и у меня уже есть написанная для него конфигурация. Вы можете использовать любой фреймворк или язык, который вы предпочитаете, но вам понадобится Yarn для управления версиями, которое я сделаю позже. Я создаю простое приложение CRUD, используя всего несколько команд и без аутентификации.
И давайте проверим, работает ли наше приложение должным образом. Я пошел дальше и создал несколько сообщений, просто чтобы убедиться.
И давайте развернем его на Heroku, отправив наш код и запустив миграции.
$ heroku create toptal-pipeline Creating ⬢ toptal-pipeline... done https://toptal-pipeline.herokuapp.com/ | https://git.heroku.com/toptal-pipeline.git $ git push heroku master Counting objects: 132, done. ... To https://git.heroku.com/toptal-pipeline.git * [new branch] master -> master $ heroku run rails db:migrate Running rails db:migrate on ⬢ toptal-pipeline... up, run.9653 (Free) ...
Наконец, давайте проверим это в производстве
Вот и все! Как правило, именно здесь большинство разработчиков прекращают свою работу. В будущем, если вы внесете изменения, вам придется повторить шаги развертывания и миграции, описанные выше. Вы даже можете провести тесты, если не опаздываете на ужин. Это отлично подходит в качестве отправной точки, но давайте подумаем об этом методе немного подробнее.
Плюсы
- Быстро настроить.
- Развертывания просты.
Минусы
- Not DRY: требует повторения одних и тех же шагов при каждом изменении.
- Без версии: «Я откатываю вчерашнее развертывание до развертывания на прошлой неделе» — это не очень конкретное определение через три недели.
- Неплохой код-доказательство: вы знаете, что должны запускать тесты, но никто не смотрит, поэтому вы можете протолкнуть его, несмотря на случайные неработающие тесты.
- Неплохая защита от актеров: что, если недовольный разработчик решит сломать ваше приложение, вставив код с сообщением о том, что вы не заказываете достаточно пиццы для своей команды?
- Не масштабируется: предоставление каждому разработчику возможности развертывания даст им доступ к приложению на рабочем уровне, что нарушает принцип наименьших привилегий.
- Нет промежуточной среды: ошибки, характерные для производственной среды, не будут отображаться до производственной среды.
Идеальный конвейер начального развертывания
Сегодня я попробую кое-что другое: давайте проведем гипотетический разговор. Я собираюсь дать «вам» голос, и мы поговорим о том, как мы можем улучшить этот текущий поток. Давай, скажи что-нибудь.
Чего-чего? Подождите, я могу говорить?
Да, именно это я имел в виду, говоря о том, чтобы дать тебе голос. Как твои дела?
Я в порядке. Это странно
Я понимаю, но просто смирись с этим. Теперь давайте поговорим о нашем конвейере. Что больше всего раздражает в запуске развертываний?
О, это легко. Количество времени, которое я теряю. Вы когда-нибудь пробовали нажать на Heroku?
Да, наблюдать за загрузкой ваших зависимостей и созданием приложения в рамках git push
— это ужасно!
Я точно знаю? Это безумие. Хотел бы я, чтобы мне не пришлось этого делать. Есть также тот факт, что я должен запускать миграции *после* развертывания, поэтому я должен смотреть шоу и проверять, чтобы убедиться, что мое развертывание выполняется.
Хорошо, вы могли бы решить эту последнюю проблему, объединив две команды с помощью &&
, например git push heroku master && heroku run rails db:migrate
, или просто создав bash-скрипт и поместив его в свой код, но тем не менее, отличный ответ, время и повторение - настоящая боль.
Да, это действительно отстой
Что, если бы я сказал вам, что вы можете немедленно исправить этот момент с помощью конвейера CI/CD?
А что теперь? Что это такое?
CI/CD означает непрерывную интеграцию (CI) и непрерывную доставку/развертывание (CD). Мне было довольно сложно понять, что именно это было, когда я начинал, потому что все использовали расплывчатые термины, такие как «объединение разработки и эксплуатации», но выражаясь проще:
- Непрерывная интеграция: Убедитесь, что весь ваш код объединен в одном месте. Заставьте свою команду использовать Git, и вы будете использовать CI.
- Непрерывная доставка: убедитесь, что ваш код постоянно готов к отправке. Это означает быстрое создание версии вашего продукта для чтения и распространения.
- Непрерывное развертывание: беспроблемное получение продукта из непрерывной поставки и простое его развертывание на ваших серверах.
О, теперь я понял. Речь идет о том, чтобы мое приложение волшебным образом распространилось по всему миру!
Моя любимая статья, объясняющая CI/CD, написана Atlassian здесь. Это должно прояснить все ваши вопросы. Впрочем, вернемся к проблеме.
Да, вернемся к этому. Как избежать ручного развертывания?
Настройка конвейера CI/CD для развертывания с отправкой на master
Что, если бы я сказал вам, что вы можете исправить этот момент с помощью CI/CD? Вы можете нажать на свой пульт GitLab ( origin
), и компьютер будет создан, чтобы просто отправить этот код в Heroku.
Ни за что!
Да способ! Давайте снова вернемся к коду.
Создайте .gitlab-ci.yml
со следующим содержимым, заменив toptal-pipeline
на имя вашего приложения Heroku:
image: ruby:2.4 before_script: - > : "${HEROKU_EMAIL:?Please set HEROKU_EMAIL in your CI/CD config vars}" - > : "${HEROKU_AUTH_TOKEN:?Please set HEROKU_AUTH_TOKEN in your CI/CD config vars}" - curl https://cli-assets.heroku.com/install-standalone.sh | sh - | cat >~/.netrc <<EOF machine api.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN machine git.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN EOF - chmod 600 ~/.netrc - git config --global user.email "[email protected]" - git config --global user.name "CI/CD" variables: APPNAME_PRODUCTION: toptal-pipeline deploy_to_production: stage: deploy environment: name: production url: https://$APPNAME_PRODUCTION.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_PRODUCTION.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku run rails db:migrate --app $APPNAME_PRODUCTION only: - master
Поднимите это вверх и посмотрите, как он выйдет из строя на странице «Конвейеры» вашего проекта. Это потому, что в нем отсутствуют ключи аутентификации для вашей учетной записи Heroku. Однако исправить это довольно просто. Сначала вам понадобится ключ API Heroku. Получите его на странице «Управление учетной записью», а затем добавьте следующие секретные переменные в настройки CI/CD вашего репозитория GitLab:
-
HEROKU_EMAIL
: адрес электронной почты, который вы используете для входа в Heroku. -
HEROKU_AUTH_KEY
: ключ, который вы получили от Heroku.
Это должно привести к рабочему развертыванию GitLab для Heroku при каждом нажатии. Что касается того, что происходит:
- При нажатии на мастер
- Интерфейс командной строки Heroku устанавливается и аутентифицируется в контейнере.
- Ваш код отправлен в Heroku.
- Резервная копия вашей базы данных сохраняется в Heroku.
- Миграции запущены.
Вы уже можете видеть, что вы не только экономите время, автоматизируя все с помощью git push
, но и создаете резервную копию своей базы данных при каждом развертывании! Если что-то пойдет не так, у вас будет копия вашей базы данных, к которой вы сможете вернуться.
Создание промежуточной среды
Но подождите, быстрый вопрос, что происходит с вашими производственными проблемами? Что, если вы столкнетесь со странной ошибкой, потому что ваша среда разработки слишком отличается от производственной? Однажды я столкнулся с некоторыми странными проблемами SQLite 3 и PostgreSQL, когда запускал миграцию. Конкретика ускользает от меня, но это вполне возможно.

Я строго использую PostgreSQL в разработке, никогда не допускаю несоответствий между движками баз данных и тщательно отслеживаю свой стек на предмет потенциальных несовместимостей.
Что ж, это утомительная работа, и я приветствую вашу дисциплину. Лично я слишком ленив для этого. Однако можете ли вы гарантировать такой уровень усердия всем потенциальным будущим разработчикам, соавторам или участникам?
Эрррр- Да, нет. Тут ты меня подловил. Другие люди все испортят. Но что ты хочешь сказать?
Я хочу сказать, что вам нужна промежуточная среда. Это как производство, но не так. Промежуточная среда — это среда, в которой вы репетируете развертывание в рабочей среде и заранее обнаруживаете все свои ошибки. Мои промежуточные среды обычно отражают производственную среду, и я создаю дамп копии рабочей базы данных при промежуточном развертывании, чтобы никакие надоедливые угловые случаи не испортили мои миграции. С промежуточной средой вы можете перестать относиться к своим пользователям как к морским свинкам.
Это имеет смысл! Итак, как мне это сделать?
Вот где становится интересно. Мне нравится развертывать master
непосредственно в staging.
Подождите, а не там ли мы сейчас развертываем производство?
Да, но теперь вместо этого мы будем выполнять развертывание в промежуточной среде.
Но если master
развертывается в промежуточной среде, как мы развертываем ее в рабочей среде?
Используя то, что вы должны были делать много лет назад: версионирование нашего кода и добавление тегов Git.
Git-теги? Кто использует теги Git?! Это начинает казаться большой работой.
Так и было, но, к счастью, я уже проделал всю эту работу, и вы можете просто выгрузить мой код, и он заработает.
Во-первых, добавьте блок о промежуточном развертывании в файл .gitlab-ci.yml
Я создал новое приложение Heroku под названием toptal-pipeline-staging
:
… variables: APPNAME_PRODUCTION: toptal-pipeline APPNAME_STAGING: toptal-pipeline-staging deploy_to_staging: stage: deploy environment: name: staging url: https://$APPNAME_STAGING.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_STAGING.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku pg:backups:restore `heroku pg:backups:url --app $APPNAME_PRODUCTION` --app $APPNAME_STAGING --confirm $APPNAME_STAGING - heroku run rails db:migrate --app $APPNAME_STAGING only: - master - tags ...
Затем измените последнюю строку производственного блока, чтобы она выполнялась с семантически версионными тегами Git вместо главной ветки:
deploy_to_production: ... only: - /^v(?'MAJOR'(?:0|(?:[1-9]\d*)))\.(?'MINOR'(?:0|(?:[1-9]\d*)))\.(?'PATCH'(?:0|(?:[1-9]\d*)))(?:-(?'prerelease'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(?:\+(?'build'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$/ # semver pattern above is adapted from https://github.com/semver/semver.org/issues/59#issuecomment-57884619
Запустить это прямо сейчас не удастся, потому что GitLab достаточно умен, чтобы разрешать доступ к нашим секретным переменным только «защищенным» веткам. Чтобы добавить теги версии, перейдите на страницу настроек репозитория вашего проекта GitLab и добавьте v*
к защищенным тегам.
Давайте подытожим, что происходит сейчас:
- При отправке на мастер или отправке помеченной фиксации
- Интерфейс командной строки Heroku устанавливается и аутентифицируется в контейнере.
- Ваш код отправлен в Heroku.
- Резервная копия вашей базы данных сохраняется в Heroku.
- Резервная копия сбрасывается в промежуточной среде.
- Миграции выполняются в промежуточной базе данных.
- При отправке коммита с семантически помеченной версией
- Интерфейс командной строки Heroku устанавливается и аутентифицируется в контейнере.
- Ваш код отправлен в Heroku.
- Резервная копия вашей базы данных сохраняется в Heroku.
- Миграции выполняются в производственной базе данных.
Вы чувствуете себя сильным сейчас? Я чувствую себя сильным. Помню, когда я впервые зашел так далеко, я позвонил жене и объяснил весь этот конвейер в мучительных подробностях. И она даже не техническая. Я был очень впечатлен собой, и вы должны быть тоже! Отличная работа зашла так далеко!
Проверка каждого нажатия
Но это еще не все, так как компьютер в любом случае делает все за вас, он также может выполнять все, что вам лень делать: тесты, анализ ошибок, почти все, что вы хотите сделать, и если что-то из этого не удается, они выигрывают. Не переходите к развертыванию.
Мне нравится иметь это в своем пайплайне, это делает мои обзоры кода увлекательными. Если запрос на слияние проходит все мои проверки кода, он заслуживает проверки.
Добавьте test
блок:
test: stage: test variables: POSTGRES_USER: test POSTGRES_PASSSWORD: test-password POSTGRES_DB: test DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSSWORD}@postgres/${POSTGRES_DB} RAILS_ENV: test services: - postgres:alpine before_script: - curl -sL https://deb.nodesource.com/setup_8.x | bash - apt-get update -qq && apt-get install -yqq nodejs libpq-dev - curl -o- -L https://yarnpkg.com/install.sh | bash - source ~/.bashrc - yarn - gem install bundler --no-ri --no-rdoc - bundle install -j $(nproc) --path vendor - bundle exec rake db:setup RAILS_ENV=test script: - bundle exec rake spec - bundle exec rubocop
Давайте подытожим, что происходит сейчас:
- При каждом нажатии или мерж-реквесте
- Ruby и Node установлены в контейнере.
- Зависимости установлены.
- Приложение протестировано.
- При отправке на мастер или отправке помеченной фиксации и только в том случае, если все тесты пройдены
- Интерфейс командной строки Heroku устанавливается и аутентифицируется в контейнере.
- Ваш код отправлен в Heroku.
- Резервная копия вашей базы данных сохраняется в Heroku.
- Резервная копия сбрасывается в промежуточной среде.
- Миграции выполняются в промежуточной базе данных.
- При отправке коммита с семантически помеченной версией и только в том случае, если все тесты пройдены
- Интерфейс командной строки Heroku устанавливается и аутентифицируется в контейнере.
- Ваш код отправлен в Heroku.
- Резервная копия вашей базы данных сохраняется в Heroku.
- Миграции выполняются в производственной базе данных.
Сделайте шаг назад и поразитесь уровню автоматизации, которого вы достигли. С этого момента все, что вам нужно сделать, это написать код и нажать. Протестируйте свое приложение вручную на стадии подготовки, если вам это нравится, и когда вы почувствуете себя достаточно уверенно, чтобы представить его миру, пометьте его семантической версией!
Автоматическое семантическое управление версиями
Да, все идеально, но чего-то не хватает. Мне не нравится искать последнюю версию приложения и явно помечать ее. Это требует нескольких команд и отвлекает меня на несколько секунд.
Ладно, чувак, стой! Достаточно. Вы просто переусложняете это сейчас. Это работает, это блестяще, не испортите хорошую вещь, переборщив.
Хорошо, у меня есть веская причина сделать то, что я собираюсь сделать.
Молю, просвети меня.
Я был таким, как ты. Я был доволен этой установкой, но потом я испортил. git tag
перечисляет теги в алфавитном порядке, v0.0.11
выше v0.0.2
. Однажды я случайно пометил релиз и продолжал делать это примерно с полдюжины релизов, пока не увидел свою ошибку. Вот тогда я решил автоматизировать и это.
Это снова мы
Итак, к счастью, в нашем распоряжении есть мощь npm, поэтому я нашел подходящий пакет: запустите yarn add --dev standard-version
и добавьте следующее в ваш файл package.json
:
"scripts": { "release": "standard-version", "major": "yarn release --release-as major", "minor": "yarn release --release-as minor", "patch": "yarn release --release-as patch" },
Теперь вам нужно сделать еще одну вещь — настроить Git для отправки тегов по умолчанию. На данный момент вам нужно запустить git push --tags
, чтобы подтолкнуть тег вверх, но автоматически сделать это в обычном git push
так же просто, как запустить git config --global push.followTags true
.
Чтобы использовать новый конвейер всякий раз, когда вы хотите создать запуск выпуска:
-
yarn patch
для выпусков патчей -
yarn minor
для второстепенных выпусков -
yarn major
для крупных релизов
Если вы не знаете, что означают слова «основной», «второстепенный» и «исправление», узнайте больше об этом на сайте семантического управления версиями.
Теперь, когда вы, наконец, завершили свой конвейер, давайте вспомним, как его использовать!
- Напишите код.
- Зафиксируйте и отправьте его для тестирования и развертывания на промежуточной стадии.
- Используйте
yarn patch
, чтобы пометить выпуск патча. -
git push
, чтобы запустить его в производство.
Резюме и дальнейшие шаги
Я лишь поверхностно коснулся возможностей конвейеров CI/CD. Это довольно упрощенный пример. Вы можете сделать гораздо больше, заменив Heroku на Kubernetes. Если вы решите использовать GitLab CI, прочитайте документацию yaml, потому что вы можете сделать гораздо больше, кэшируя файлы между развертываниями или сохраняя артефакты!
Еще одно огромное изменение, которое вы можете внести в этот конвейер, — это ввести внешние триггеры для запуска семантического управления версиями и выпуска. В настоящее время ChatOps является частью их платного плана, и я надеюсь, что они перейдут на бесплатные планы. Но представьте, что вы можете запустить следующее изображение с помощью одной команды Slack!
В конце концов, когда ваше приложение станет сложным и потребует зависимостей на системном уровне, вам может понадобиться использовать контейнер. Когда это произойдет, ознакомьтесь с нашим руководством: Начало работы с Docker: упрощение DevOps.
Этот пример приложения действительно работает, и вы можете найти его исходный код здесь.