О деплое

2017-01-21

Есть такая штука — плойка. Ею завивают волосы. Соответственно, где-то в природе существует штука — деплойка. Ею распрямляют волосы. Но айтишники для деплоя, т.е. deploy, т.е. развёртывания, явно пользуются чем-то другим.

Deploy!

Начиналось всё, как я уже писал, с простого FTP. По крайней мере для веба. Во времена PHP и статических сайтов всё, что нужно было сделать, это положить новые версии файлов в нужные места на сервере. FTP для этого вполне хватало.

Страшно представить, в те времена даже системами контроля версий мало кто пользовался. Хотя CVS и, прости Господи, Visual SourceSafe вполне уже были и работали. И даже модный Subversion уже подтягивался. А что, начал серьезные переделки, сохрани предыдущую версию в архив, на всякий случай. Вот такая деревенская система контроля версий.

Конечно, сейчас есть более интересные и секурные способы передачи файлов. SCP — просто копирование файлов, но через SSH. Значительно секурнее. А rsync — вообще пушка. Тоже работает через SSH, хотя имеет и своего демона. Жмёт файлы при передаче, что очень даже актуально для всяких скриптов, бинарников и разметок. Ну а самое главное, rsync — синхронизирует. Т.е. высчитывает контрольные суммы от содержимого файлов на удалённой стороне и передаёт только различающиеся файлы. Даже только различающиеся части файлов. Для инкрементного деплоя — ваще то, что надо.

А недавно я открыл для себя новые возможности ssh и rsync. Во-первых, ssh умеет ограничивать те команды, которые может запускать определённый даже не пользователь — ключ. Вы сталкивались с этим на примере ssh доступа к git репозиториям. Вроде все подключаются под одним юзером по имени git, но с разными ключами, и имеют доступ к разным репозиториям. В ~/.ssh/authorized_keys этого пользователя перед каждым ключём ещё указана директива command=.

Во-вторых, в rsync для Debian имеется команда rrsync. Технически это скрипт на Perl, работает не только в Debian но и во FreeBSD. Он позволяет для rsync через ssh ограничить доступ к определённому каталогу файловой системы. Ну примерно как в FTP, где каждый юзер может наложить файлы только в свой каталог. Только тут каждый ssh ключ можно снабдить ограничениями. Можно в authorized_keys прописать что-то вроде:

command="$HOME/bin/rrsync -ro ~/backups/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa AAA...vp Automated remote backup

И пожалуйста, по этому ssh ключу можно rsyncать только каталог backups, причем только читать из него.

Rsync minion

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

Систем сборки существует туева хуча. Замечу только, что уже окончательно произошёл переход от императивного описания сборки к декларативному. В императивном случае вы обязаны указать, что именно нужно сделать, какие шаги выполнить, какие команды и с какими параметрами запустить, чтобы получить очередной кусок проекта. В декларативном случае все шаги и команды уже подразумеваются неявно, и исходные файлы уже расположены в подразумевающихся по умолчанию местах. Вам лишь нужно задать некоторые параметры, которые не могут быть выяснены неявно. Как минимум это — имя проекта и его зависимости, т.е. другие проекты, которые он использует. Для явистов поясняю кратко: в начале был Ant, но теперь балом правит Maven, а также всякие штуки, использующие его инфраструктуру, вроде Gradle.

Ради сборки возникает CI — Continuous Integration — непрерывная интеграция. Идея такая. Какая бы ни была у нас система сборки, каждый разработчик всё равно собирает проект у себя локально. Как минимум, чтобы тесты погонять. Но откуда возьмётся релиз? Т.е. сборка продукта, которая пойдёт к пользователям. Либо в виде кода, залитого на сервер, либо в виде какого-то дистрибутива, доступного для скачивания и установки.

Либо нам нужно выделять отдельного человека для осуществления ответственной релизной сборки. Это — релиз-менеджер, была такая роль в проектах. Либо мы это дело автоматизируем. Ведь у нас уже есть система автоматизированной сборки. И мы умеем заливать файлы, куда надо.

Первым CI инструментом, с которым я столкнулся, был Hudson. Родился он в недрах Sun, которую, как вы знаете, со всеми потрохами купил Oracle. Ну и, как случилась и со многим другим наследием Sun, разработчики разругались с Oracle, хлопнули дверью и создали свой клон — Jenkins. А Hudson умер.

Сейчас набирает существенную популярность Teamcity от JetBrains. Он, конечно, стоит денег. Но для небольших команд хватит и бесплатной версии.

Jenkins и Teamcity работают очень похоже. Система CI мониторит систему контроля версий. Когда в релизной ветке появляются новые коммиты, запускается сборка. Сборка происходит на агентах, которыми могут быть даже Windows машины. Важно, что репозиторий, который мониторить, шаги сборки, которые запускать (ну то, какие параметры mvn передать), задаются в самом CI и хранятся в его БД. В особо запущенных случаях сборка проекта расползается на десятки шагов, с невнятными многостраничными shell скриптами на каждом шаге. Самое страшное в этом, что эти скрипты — за пределами контроля версий.

Тем приятнее свежий подход, применяемый в Travis, Bitbucket Pipelines и GitLab CI. Здесь шаги CI прописываются в специальном файле, размещённом прямо в репозитории с исходным кодом. Как правило, в публичном репозитории, соответственно, на GitHub для Travis, в Bitbucket для Pipelines, в GitLab для GitLab CI. Получается, что вся конфигурация CI тоже под контролем версий.

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

Заметьте, системы контроля версий слились с системами непрерывной интеграции. Если сюда ещё добавить среду для выполнения ваших программ, (чтобы не пришлось куда-то отдельно копировать файлы :) то получится, простите, Heroku.

DevOps loop

В некотором смысле эти наши хероки, это уже следующий уровень развития облаков. А до этого облака были просто виртуальными машинами, с голой ОС, на которые можно ставить всё, что угодно. Это ещё называется VPS. Ну и обычные железные сервера, либо где-то арендованные, либо стоящие рядом на полочке (чтобы теплее и уютнее), никто не отменял. И эти сервера, ещё до того, как на них деплоить ваши приложения, надо как-то настроить.

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

Эту проблему назвали Configuration Management. И сначала подключились математики. Идея была такая. Правильно настроенный сервер находится в некотором состоянии. На нём установлены определённые пакеты, присутствуют определённые файлы конфигурации и запущены определённые сервисы. Задачей (математической) является доведение серверов до нужного состояния. А какое состояние должно быть у каждого сервера, пусть будет записано на некоем центральном сервере. Там же пусть хранятся и рецепты по переходу из состояния в состояние, т.е. по установке и настройке сервисов. А на самих серверах пусть будет запущен некий агент, который будет проверять, в каком состоянии находится этот сервер, сверяться с центральным сервером, и выполнять рецепты, если состояние нужно изменить.

Именно так работают CFEngine (ведущий родословную аж с 1993 года), Puppet и Chef. Существенная разница между ними, в общем-то, только в формате представления рецептов. Ну и в CFEngine больше всего математики. Puppet и Chef почему-то написаны на Ruby. Питонистам стало обидно и они родили SaltStack.

Но сейчас для задачи настройки кучи серверов всё чаще выбирают Ansible. Его делали инженеры. И им было лень заводить отдельный сервер и разворачивать целую инфраструктуру для такой скучной задачи, которую ещё и спокойно можно сделать руками. Поэтому Ansible — это, по большому счёту, лишь инструмент удалённого выполнения команд. Всё что ему нужно — доступ на удалённые сервера по ssh и наличие там Python 2. Команды, которые он может выполнять, чаще всего написаны на Python. И они, чаще всего, идемпотентны (ой, всё-таки математика). Команды, прежде чем что-то сделать, проверяют, а нужно ли это делать. Результат получается тем же самым. Если мы упорно будет пытаться поставить пакет на N серверов, он таки, рано или поздно, будет установлен на всех N серверах. Задача решена.

Справедливости ради стоит заметить, что серьёзные облачные провайдеры, вроде AWS, обзаводились собственными, ни с чем не совместимыми, инструментами конфигурирования и развёртывания ещё до того, как всякие Chef и Puppet становились популярными.

Lego VMs

И кстати, в облаках у нас живут исключительно виртуальные машины. А виртуальные машины возникают из образов. А в образ, помимо операционной системы, можно поместить и нужную среду выполнения. И даже поместить всё ваше (сетевое) приложение. И когда придёт время выпустить новый релиз, мы просто соберём новые образы и запустим новые виртуалки. А старые просто удалим. Это называется Immutable Infrastructure. И специализируется на этом подходе компания HashiCorp, которая подарила Миру такие замечательные инструменты как Vagrant и Packer.

А что, если пойти дальше? Пусть эти образы и виртуальные машины будут насколько лёгкими и простыми в использовании, что разработчики смогут запускать их на своих компьютерах. И тестировщики будут тестировать образы, созданные разработчиками. И те же самые образы будут деплоиться на реальных серверах. Это — идеология Docker. А Docker, как мы уже видели, может быть инфраструктурой не только самого приложения, но и частью инфраструктуры сборки приложения.

Всё становится довольно сложно. Нужно автоматически собирать. Нужно автоматически разворачивать. Нужно автоматически настраивать. Нужно ещё и мониторить. Для этой кучи задач придумали даже своё название: Continuous Delivery. (Забавно, что в русском «обеспечении» — тот же корень: «liver» — «печень».) А для людей, занимающихся этими задачами, тоже придумали название: DevOps.

Впрочем, тут справедливо заметили, да и Википедия пишет, что DevOps — вовсе не профессия, не должность, и даже не роль. DevOps — это набор практик, которые должен применять к своему проекту любой грамотный разработчик. Чтобы каждый пуш в репозиторий превращался в развёрнутый отчёт по тестированию, метрикам кода, и приводил к автоматическому появлению новой версии продукта на продакшене, на благо пользователям.