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 — это набор практик, которые должен применять к своему проекту любой грамотный разработчик. Чтобы каждый пуш в репозиторий превращался в развёрнутый отчёт по тестированию, метрикам кода, и приводил к автоматическому появлению новой версии продукта на продакшене, на благо пользователям.

2017-01-08

О статике

Веб меняется.
Web
Начиналось всё с рисования HTML ручками. Потом добавился CSS, чтобы красивее было, тоже писался ручками. Потом добавился JS, но использовался он исключительно для свистелок и перделок. Помнится, даже специальные гуёвые инструменты были, чтобы сайты делать.
Со стороны сервера эти самые HTML, CSS и JS валялись файлами. Прямо руками создавались файлы. Прямо в HTML или CSS разметке. И заливались через FTP на сервер. Это была исключительно статика.
Но конечно же очень скоро захотелось динамики. У нас же есть формы. А формы надо куда-то сохранять. И какие-нибудь результаты или даже отчёты надо отображать.
Динамика появилась сначала в виде CGI. Довольно быстро основным языком для написания CGI-скриптов стал Perl. А вскоре и PHP подоспел.
Быстро оказалось, что CGI — это не очень эффективно — плодить по процессу на каждый запрос. И самый популярный тогда веб сервер Apache быстренько обзавёлся модулями для интерпретации и Perl, и PHP, чтобы выполнять скрипты прямо в процессе веб сервера. Именно тогда появилась аббревиатура LAMP.
LAMP
CGI-скрипты были именно скриптами. По одному скрипту на каждый запрос. И такая структура была унаследована и в PHP. Бэкенд становился всё сложнее, требовались какие-то средства организации кода. В те времена PHP4 каждый пэхэпэшник рисовал свой фрэймворк, организующий .php файлы и загружающий только нужные.
Несколько организованней всё происходило в мире Java. Там с самого начала были сервлеты — отдельные классы для обработки запросов. И механизмы маппинга запросов на эти самые сервлеты. Можно было прилично всё организовать: классы бизнес-логики — на старой доброй Java, и всё это для веба слегка прикрыто сервлетами.
Правда, испугавшись популярности PHP, ребята запилили JSP. Все тогда пилили свои Server Pages, на которых можно было говнякать, как на PHP, смешивая в одних файлах и обращение к базе данных (о, я помню mysql-escape-string() и mysql-real-escape-string()), и бизнес-логику, и формирование HTML.
Важно, что вся эта динамика возвращалась с сервера в виде HTML. А так как на солидных порталах без динамики не может обойтись ни одна страница (имя залогиненного юзера в меню показывать), весь HTML генерировался бэкендом. Из статики остались только CSS и JS, ну и картинки, конечно.
Понятное дело, чтобы как-то организовать код на бэкенде, придумали много разных вещей. Например, MVC — Model-View-Controller, чтобы отделить то, что зависит от предметной области — модель, от того, что является спецификой веба — контроллер, и от того, что является исключительно представлением — вьюхой. Но вьюха по-прежнему создаёт HTML разметку.
MVC
Кстати, создатели HTML допустили фатальную ошибку, когда решили использовать угловые скобки для выделения тегов. Они ведь ни на одной клавиатуре без шифта не набираются. Понятно, что сильно многим надоело топтаться на шифте, и придумали уйму языков-шаблонов.
Самый известный сейчас, пожалуй, — это Markdown. В нём можно писать так, что и текст в простом тексте (т.е. «исходники» маркдауна), и текст в «богатом» варианте (например, в HTML) выглядят одинаково читабельно и красиво. Вот только в Markdown можно представить только простую разметку, для текста, даже на таблицы стандарта нет.
А недавно я познакомился с Pug, он же PugJS, ранее известный как Jade. Дурацкое название, что новое, что старое, гуглить приходится «pug/jade». Вот это уже полноценный язык шаблонов для иерархических структур, типа HTML или XML. Вложенность тегов изображаем вложенностью элементов. Да, Pug — это ещё один язык (разметки), чувствительный к отступам. Сразу предупреждаю.
Потом появился Node.js. Внезапно оказалось, что те, кто писал свистелки и перделки на JavaScript, могут сами для себя и сервер написать. Но с его приходом пока что мало что поменялось. Express — это, в чистом виде, такая же низкоуровневая штука, как сервлеты. Вот вам запросы, обрабатывайте их как хотите. Можно и HTML по шаблонам генерить.
Серьёзное изменение произошло с появлением AJAX, задолго до всяких Ноджээс. Оказалось, что не обязательно серверу возвращать всю динамику в виде целой HTML страницы. Сначала оказалось, что кусочки этой страницы можно подгружать отдельными запросами. Потом оказалось, что не обязательно возвращать HTML. Пусть приходит какой-нибудь JSON, а мы уж тут, в JavaScript, прямо в браузере, разберёмся, куда эти данные запихнуть в DOM дерево.
AJAX
И если раньше веб-разработчик занимался всем: и схему БД делал, и бизнес-логику писал, и HTML верстал, и CSS стили натягивал, и JS рисовал (как попало, до появления jQuery). То теперь между фронтендом и бэкендом пролегла чёткая граница.
Бэкенду осталась персистентность, работа с БД, и API для доступа к данным. Фронтенду досталась вся красота, видимая пользователям. Веб стал веб-приложениями, запускаемыми в браузере и общающимися с API.
Примечательно, что это API может быть использовано не только веб-приложениями, но и мобильными приложениями, десктоп приложениями (которые тоже всё чаще становятся внутри веб), и даже сторонними сервисами.
Также примечательно, что подразумевается чаще всего RESTful API. Происходит это от сосредоточенности на данных, ведь REST — это доступ к данным. Причём данным предметной области. Этим RESTом всё настолько пропитано, что инструменты документирования API, вроде Swagger, даже не подозревают, что через HTTP можно просто пересылать JSON или XML документы, без привязки типа документа к пути в URL.
REST
На самом деле, для веба, мобилки и десктопа в общем случае нужны разные API. Как минимум потому, что на одном экране мобилки помещается меньше данных, чем на одной веб-странице. В идеале хочется, чтобы на одну страницу был бы один запрос на сервер, который бы вернул все данные, необходимые для отображения данной страницы. А раз данные разные для мобилки и веба, то и запросы, и API должны быть разными. И REST тут тоже будет только мешать, ведь мы хотим в один запрос вместить совершенно разнообразные данные. Это должна быть просто пересылка сообщений через HTTP.
Так вот, у нас получается один сервер исключительно для динамики, для API. Но веб-приложение — это HTML, CSS и JavaScript. Это — статика. В том смысле, что для браузера это статичные файлы, пусть и создающие динамическое веб-приложение. Нужен ещё один сервер для раздачи статики.
Ну и саму эту статику, фронтенд веб-приложения, никто уже не пишет на голом HTML, CSS и JS. Для HTML есть шаблоны, тот же Pug или JSX. Для CSS есть всяческие препроцессоры и постпроцессоры: SASS и LESS — по сути отдельные языки (более удобные?) для описания стилей. Для JS тоже есть альтернативные представления, вроде TypeScript или CoffeeScript. К тому же стало модно транслировать популярные языки программирования общего назначения в JS представление, чтобы кодить и под веб.
Само собой, появились и фреймворки. Весьма мощные. Вы их знаете: Angular и React (упомянутый выше JSX есть часть ReactJS).
Angular vs. React
Настолько всё изменилось в мире фронтенда, что это дело понадобилось собирать. Тут, внезапно, снова пригодился Node.js. Всякие прелести стали паковать в NPM модули, даже CSS. Появился свой собственный инструмент сборки, работающий в Node. Да сильно не один: Grunt, Gulp, Webpack... Тысячи их. Хипстотометр зашкаливает.
В общем, статика рулит вебом. Никому не интересны эти ваши бэкенды и API. API можно и заглушкой заделать, или в Firebase мышкой натыкать. А вот на чём мы будем фронт делать? И чем это дело будем собирать?
С другой стороны, есть у меня сайт на PHP. Где из всех конструкций PHP используется только include. Зачем ему быть динамичным, если это всё статичный HTML? Ведь можно взять все эти шаблоны, генераторы, билдеры и сгенерировать статику. Нефиг руки HTML тегами марать.
Есть ещё сайты, в которых нет ни одной формы, одно только содержание. Вот я и побаловался. Ради снижения уровня хипстоты нарисовал генераторы страничек на Python. Pug для шаблонов (как ни странно, через Mako). Markdown для контента. Получилось. Даёшь генерацию статичных сайтов!