Используйте «make»!
Люди всегда искали способы облегчить свою работу. Спорный вопрос, являются ли использование инструментов и автоматизация отдельными человеческими чертами или общими с другими видами. Дело в том, что мы стараемся передать самые простые задачи машинам - и это здорово!
Зачем автоматизировать?
Повторение часто приводит к скуке и усталости. Скука - это первый шаг к выгоранию, а усталость - главный источник ошибок. Поскольку мы не хотим, чтобы наши коллеги (или мы сами) выгорели, так же как мы не хотим совершать дорогостоящие ошибки, мы стараемся автоматизировать повседневные задачи.
Похоже, растет количество программного обеспечения, предназначенного для автоматизации общих задач. В одной только экосистеме Node.JS есть (или были) такие решения, как сценарии Bower, Yeoman, Grunt, Gulp и NPM.
Но есть еще и хороший стандартный инструмент UNIX. Под стандартом я имею в виду, что у него действительно есть надежная документация, о которой многие другие инструменты, похоже, забыли. Я говорю о make. В частности, эта статья посвящена GNU make, доступной в macOS, Windows, Linux и большинстве других операционных систем.
make настолько стандартен, что вы уже установили его. Введите «make» в командную строку и проверьте себя. Эта программа вышла в 1977 году, а это значит, что она в значительной степени проверена в боевых условиях. Это необычно? Ага, даже по стандарту 70-х. Но он хорошо выполняет свою работу, и это все, что мы от него хотим.
Разве это не Make для кода C / C ++?
Когда вы читаете о make, вы, вероятно, вспоминаете, что тогда существовал такой инструмент для создания проектов C / C ++. Это было так ./configure && make && make install. Да, мы говорим об одном и том же инструменте - он не ограничивается компиляцией кода C / C ++. Если честно, он даже не может скомпилировать код.
Практически все, что понимает make, - это файлы. Он знает, существует файл или нет, и какой файл является более новым. Другая половина его функциональных возможностей заключается в поддержке графа зависимостей. Это немного, но эти две особенности и составляют его силу.
Чтобы make действительно что-то делал, вы пишете набор рецептов. Каждый рецепт состоит из цели, нуля или более зависимостей и нуля или более правил. Цели - это файлы, которые вы хотите получить. Зависимости - это файлы, необходимые для создания или обновления целей. Набор правил описывает процесс преобразования зависимостей в цели. Например, представьте, что вы хотите автоматизировать установку пакетов Node.js:
node_modules: package.json npm install
Это означает, что файл node_modules (да, каталоги тоже являются файлами) можно получить из файла package.json, запустив правило npm install. Все еще со мной?
Теперь эти зависимости могут быть и другими целями. Это означает, что мы можем связать другой набор правил и создать конвейеры. Например, мы можем сделать каталог результатов тестирования зависимым от каталога сборки, каталог сборки зависимым от каталога node_modules и node_modules зависимым от package.json. Черт возьми, мы можем даже создать package.json динамически, сделав его целью другого правила. Помните, когда я упомянул, что make отслеживает, какой файл является более новым? Это то, что экономит нам время. Без этой функции каждый раз, когда мы запускаем make test (следуя приведенному выше примеру), нам пришлось бы запускать всю цепочку с самого начала (npm install, сборка, затем проверка). Но если ничего не изменилось, зачем заново устанавливать пакеты? Зачем строить? Зачем проводить тесты?
Вот где make действительно сияет. Выясняя порядок выполнения заданий, он проверяет временные метки целей и зависимостей. Это следует правилу только в том случае, если:
- одна или несколько зависимостей более свежие, чем целевая
- цель не существует.
Еще кое-что! Поскольку make test на самом деле не будет создавать файл с именем test, нам нужно добавить эту цель как зависимость от .PHONY цели - другое соглашение. Нравится:
.PHONY: test test: build npm test build: node_modules npm build node_modules: package.json npm install
В нашем примере выше одно изменение package.json привело бы к созданию всего с нуля. Но если мы изменим код только одного из тестов, make пропустит все шаги, предшествующие тестам, при условии, что зависимости написаны правильно.
Но у языка, который я использую, уже есть собственная система сборки
Многие современные языки программирования и среды поставляются со своими собственными инструментами сборки. В Java есть Ant, Maven и Gradle, в Ruby есть Rake, в Python используются инструменты настройки. И если вы беспокоитесь, что я собираюсь забрать у вас эти игрушки и заменить их на make, вы ошибаетесь.
Посмотрите на это с другой стороны: сколько времени нужно, чтобы познакомить человека с вашей командой и сделать его продуктивным? Это означает настройку среды разработки, установку всех зависимостей, настройку каждой движущейся части, сборку проекта, запуск проекта, возможно, даже развертывание проекта в среде разработки.
Сможет ли новый сотрудник приступить к реальной работе в считанные часы? Дней? Надеюсь, не недели. Помните, что на настройку компании тратится не только время нового сотрудника. Кого-то также будет беспокоить множество вопросов, когда что-то пойдет не так (а обычно так и происходит).
Есть соглашение, которое я люблю использовать в своих проектах. Поскольку это обычное явление для нескольких проектов, люди могут свободно перемещаться между ними. Каждый новый проект или каждый новый представленный человек должен усвоить это соглашение, чтобы достичь желаемого результата. Это соглашение предполагает, что у проектов есть свои собственные Makefile с набором предопределенных целей:
make prepareустанавливает все необходимые внешние приложения. Обычно это делается с помощьюBrewfileи Homebrew / Linuxbrew. Поскольку этот шаг не является обязательным, кодировщики могут выбрать собственный метод установки на свой страх и риск.make devустанавливает локальную среду разработки. Обычно он создает и вызывает контейнеры Docker, но посколькуmakeдействует как оболочка, его можно легко заменить любым необходимым (например,npm serve).make deployразвертывает код в выбранной среде (по умолчаниюdevelopment). Под капотом обычно работает Ansible.make infrastructureэто предварительное условие дляmake deploy, поскольку он в первую очередь использует Terraform для создания указанных сред.make allпроизводит все артефакты, необходимые для развертывания.
Ты знаешь что это значит? Это означает, что обязательный README.md может сосредоточиться на бизнес-потребностях проекта и наметить некоторые процессы сотрудничества. Мы прилагаем приведенный выше список в конце, чтобы все знали, каковы эти цели. Это означает, что когда вы входите в новый проект, все, что вам нужно сделать, это make prepare и make dev. После нескольких циклов ЦП перед вами будет рабочий проект, и вы сможете приступить к взлому.
Для этого у меня есть конвейер непрерывной интеграции
На этом этапе некоторые люди могут заметить, к чему я клоню. Артефакты, шаги, развертывание, инфраструктура. Это то, что делает наш конвейер непрерывной интеграции / непрерывного развертывания. Я уверен, что да! Но помните, что CI / CD не только для запуска тестов каждый раз, когда появляется новый коммит.
Правильно реализованный CI / CD может помочь уменьшить количество отладок, облегчая воспроизведение проблемы и выполнение анализа первопричин. Как? Артефакты с поддержкой версий - одно из таких средств. Они могут помочь найти основную причину, но не обязательно устранить ее.
Чтобы исправить ошибку, вам нужно изменить код и создать свою собственную сборку. Вы понимаете, к чему я клоню? Если ваш конвейер CI / CD может быть зеркалирован локально, разработчики могут тестировать и развертывать крошечные изменения без необходимости фактического использования конвейера CI / CD, тем самым сокращая цикл. Самый простой способ сделать ваш конвейер CI / CD доступным локально - сделать его тонкой оболочкой вокруг make.
Допустим, у вас есть бэкэнд и фронтенд, и у вас также есть несколько тестов для них (если нет, вы с ума сошли, запуская CD без тестов!). Это сделало бы четыре различных задания CI. Их можно в значительной степени охарактеризовать как звонки make backend, make test-backend, make frontend, make test-frontend. Или любое другое соглашение, которому вы хотите следовать.
Таким образом, независимо от того, на локальной машине или на CI, код строится точно так же. Речь идет о точно таких же шагах. Чем меньше команд входит в ваш Jenkinsfile или .travis.yml (или что-то еще), тем меньше вы полагаетесь на Holy Build Machine.
Хорошо, но действительно ли кто-нибудь использует Make?
Оказывается, да. Если вы посмотрите вокруг, то найдете такие статьи, как «Пора возвращаться Makefiles» (Джейсона Олсона). «Сила Makefile» (Ахмад Фараг). «Переписываем наши инструменты развертывания: с Makefile на Bash и обратно» (Пол Дэвид). Или «Makefile для разработчиков Node.js» (от Патрика Хениза). Это все статьи прошлого года, а не какие-то воспоминания из прошлого века.
Да, я должен признать, что make неуклюжий. Я знаю его многочисленные недостатки и странные языковые особенности. Но покажите мне лучший инструмент для реальной автоматизации рабочего процесса разработки, и я буду рад перейти на него. А пока я буду смотреть на это в ROTFL:

Он доступен на GitHub, если вы хотите его летать.
Круто, теперь покажи мне код
Вот несколько отрывков, чтобы показать вам, что возможно с make.
.PHONY: dev .stamps: @mkdir -p $@ .stamps/git-hooks.installed: | .stamps # This checks whether git-hooks is an executable and installs it with # Homebrew/Linuxbrew is possible. @if ! command -v git-hooks >/dev/null 2>&1; then \ if command -v brew >/dev/null 2>&1; then \ brew install git-hooks; \ else \ echo "You have to install https://github.com/icefox/git-hooks"; \ exit 1; \ fi; \ fi @touch $@ .git/hooks.old: | .stamps/git-hooks.installed git hooks --install dev: | .git/hooks.old pip install -e .[dev]
Этот фрагмент устанавливает простую среду разработки. Поскольку мы хотим, чтобы все разработчики использовали один и тот же набор хуков перед фиксацией при работе с Git, мы устанавливаем эти хуки для них.
Для этого нам нужно установить git-hooks (так называется менеджер хуков). Мы пользуемся тем, что git-hooks перемещает исходные хуки в .git/hooks.old, поэтому мы проверяем наличие такого файла, чтобы определить, хотим ли мы запустить git hooks install или нет.
Один из приемов, который мы здесь используем, - это | для обозначения зависимостей только по порядку. Если вы просто хотите убедиться, что что-то существует, а не то, что оно было изменено позже, чем цель, лучше всего подойдут зависимости только для порядка. Полагаю, пока все хорошо.
Теперь представьте, что мы хотим создать контейнер Docker, содержащий файл, который мы не можем распространять в нашем исходном коде.
WEBAPP_SOURCES = $(sort $(notdir $(wildcard webapp/**/*))) all: webapp .stamps: Makefile @mkdir -p $@ third-party/top_secret.xml: # WEB_USER and WEB_AUTH_TOKEN are variables that should contain credentials # required to obtain the file. @curl -u "$(WEB_USER):$(WEB_AUTH_TOKEN)" https://example.com/downloads/this_is_a_secret.xml -L -o $@ webapp: .stamps/webapp.stamp .stamps/webapp.stamp: .stamps webapp/Dockerfile third-party/top_secret.xml $(WEBAPP_SOURCES) docker build -t example/webapp -f webapp/Dockerfile webapp @touch $@ .PHONY: all webapp
Поскольку мы не можем использовать фактический файл, созданный Docker (поскольку у образов жесткие разрешения), мы делаем следующее. Мы создаем пустой файл, который указывает, что мы успешно запустили docker build в определенный момент времени.
Обычно такие файлы называются «штампами». Наш штамп образа Docker, очевидно, зависит от Dockerfile, от исходных файлов и от другой цели, которая запускает curl для получения файла, получая учетные данные из переменных среды.
Поскольку мы не хотим выводить наши учетные данные на вывод, мы ставим перед командой префикс @. Это означает, что само правило не выводится на экран. Однако вывод правила не заглушается. Имейте это в виду, если какая-либо из программ, которые вы хотите запустить, имеет тенденцию записывать конфиденциальную информацию в stdout или stderr.
Хорошо, мы можем настроить git-хуки и создать несколько образов Docker. Почему бы не позволить разработчикам создавать свои собственные среды в облаке и развертывать их в них?
# We include the previous Makefile so we can build the image
include previous.mk
.stamps/webapp_pushed.stamp: .stamps/webapp.stamp
docker push example/webapp
@touch $@
infrastructure: $(INFRASTRUCTURE_SOURCES)
cd deployment/terraform && terraform apply
deploy: all infrastructure
cd deployment && ansible-playbook -i inventories/hosts deploy.yml
.PHONY: infrastructure deploy
Фактическая инфраструктура как управление кодом и конфигурацией выходит за рамки этой статьи. Позвольте мне сказать вам, что terraform apply управляет облачными ресурсами, а ansible-playbook выполняет настройку на удаленных машинах. Вы, наверное, знаете, что делает docker push. Короче говоря, он отправляет локальный образ в Docker Hub (или любой другой реестр), чтобы вы могли получить к нему доступ из любого места. На этом этапе, я уверен, вы можете понять, что делает приведенный выше фрагмент.
Для кого предназначен этот инструмент?
Несмотря на то, что в последнее время ажиотаж вокруг DevOps растет, между разработчиками и операторами все еще много разницы. Некоторые инструменты используются исключительно Dev, некоторые - только Ops. Есть точки соприкосновения, но насколько далеко они достигнуты, зависит от команды.
Управление пакетами разработки, компоновка исходного кода и рекомендации по кодированию - все это области Dev. Инфраструктура как код, управление конфигурацией и оркестровка - это игрушки для операционных. Система сборки и конвейер непрерывной интеграции могут быть разделены между ними или принадлежать любой из сторон. Вы видите, как тонко растягиваются точки соприкосновения?
make меняет ситуацию, обеспечивая более широкое сотрудничество. Поскольку он служит целям как Dev, так и Ops, это общая почва. Каждый говорит на своем языке, и каждый может внести свой вклад. Но поскольку его так легко использовать, даже когда вы хотите выполнять сложные задачи (как в нашем примере выше), истинная сила DevOps предоставляется каждому человеку в команде. Каждый может запускать make test, и каждый может изменять его правила и зависимости. Каждый мог запустить make infrastructure и подготовить хороший кластер для разработки или производства. В конце концов, они документированы в одном коде!
Конечно, когда есть точки соприкосновения, хорошо знать, кто за какую часть отвечает. Меньше всего вам нужно, чтобы люди из Dev и Ops перезаписывали друг друга! Но отличная командная работа всегда зависит от отличного общения, так что это может происходить как с make, так и без него.
Поэтому не имеет значения, используете ли вы какие-либо модные технологии, связанные с DevOps. Возможно, вам не нужны и не нужны Docker, Cloud, Terraform или Travis. Вы можете писать настольные приложения, чего бы оно ни стоило, и тщательно написанный Makefile все равно будет способствовать развитию DevOps.