Делай как вGoogle
Скачать 5.77 Mb.
|
Расширение системы сборки. В Bazel есть цели по умолчанию для нескольких попу- лярных языков программирования, но инженеры всегда будут стремиться к больше- му — одним из преимуществ систем на основе задач является их гибкость в поддержке любого процесса сборки, и было бы странно отказываться от этого в системах сборки на основе артефактов. К счастью, Bazel позволяет расширять поддерживаемые типы целей и разрешает инженерам добавлять собственные правила ( https://oreil.ly/Vvg5D ). Чтобы определить правило в Bazel, нужно объявить входные данные, необходимые для правила (в форме атрибутов в файле BUILD ), и фиксированный набор выходных данных, которые создает правило. Также необходимо определить действия, которые будут выполняться правилом. Для каждого действия должны быть объявлены свои входные и выходные данные, конкретный выполняемый файл для запуска или определенная строка для записи в файл. Действия могут быть связаны с другими действиями через свои входные и выходные данные. То есть действия являются компонуемыми единицами самого нижнего уровня в системе сборки — действие может делать все, что потребуется, при условии, что оно использует только объ- явленные входные и выходные данные, а о планировании действий и кешировании их результатов позаботится Bazel. Такая система не является абсолютно надежной, учитывая невозможность помешать разработчику внедрить в действие недетерминированный процесс. Но на практике такое случается нечасто, а перенос возможностей для злоупотреблений на уровень действий значительно снижает вероятность ошибок. Правила, поддерживающие рас- пространенные языки и инструменты, широко доступны в интернете, и в большинстве проектов не требуется определять собственные правила. Но даже в случаях, когда это необходимо, правила должны быть определены в одном месте в репозитории, а это означает, что большинство инженеров смогут использовать эти правила, не заботясь об их реализации. Изоляция окружения. На первый взгляд кажется, что действия могут столкнуться с теми же проблемами, что и задачи в других системах, потому что действия могут записывать результаты в один и тот же файл и конфликтовать друг с другом. В Bazel 380 Глава 18. Системы и философия сборки такие конфликты невозможны, потому что она использует прием изоляции ( https:// oreil.ly/lP5Y9 ). В поддерживаемых системах все действия изолированы друг от друга посредством изоляции файловой системы. По сути, каждое действие видит только ограниченное представление файловой системы, которое включает объявленные входные данные и созданные выходные данные. Это обеспечивается такими систе- мами, как LXC (Linux Containers) в Linux, реализующими ту же технологию, что и Docker. Это означает, что действия не могут конфликтовать друг с другом, потому что не могут читать файлы, которые они не объявляли, а любые выходные файлы, создаваемые ими, но не объявленные, будут уничтожены после завершения действия. Bazel также использует прием изоляции, чтобы ограничить сетевые взаимодействия между действиями. Детерминизация внешних зависимостей. Остается еще одна проблема: системы сборки часто загружают зависимости (инструменты или библиотеки) из внешних источников, не создавая их напрямую. Примером может служить зависимость @com_google_common_guava_guava//jar , которая загружает файл JAR из Maven. Использование файлов, находящихся за пределами текущего рабочего пространства, сопряжено с риском. Эти файлы могут измениться в любой момент, что требует от системы сборки постоянной проверки их актуальности. Если удаленный файл из- менится, а в исходном коде в рабочем пространстве никаких изменений не будет, это тоже может привести к невоспроизводимым результатам сборки: сборка может безупречно выполниться в один день и завершиться ошибкой — в другой из-за не- заметного изменения зависимости. Наконец, внешняя зависимость, принадлежа- щая третьей стороне, может представлять огромный риск для безопасности 1 : если злоумышленнику удастся проникнуть на сторонний сервер, он сможет подменить файл зависимости чем-то своим и получить полный контроль над средой сборки и ее результатом. Основная проблема заключается в том, что система сборки вынуждена получать внешние файлы не из VCS. Обновление зависимости должно производиться осознанно и централизованно, а не даваться на откуп отдельным инженерам или автоматической системе. Это связано с тем, что даже при использовании «главной ветви» мы по-прежнему хотим, чтобы сборки сохраняли детерминированность. То есть, извлекая исходный код, зафиксированный в репозитории на прошлой неделе, мы ждем, что и зависимости будут такими, какими они были тогда, а не сейчас. Bazel и некоторые другие системы сборки решают эту проблему, требуя наличия файла объявления рабочей области, в котором определяется криптографический хеш для каждой внешней зависимости в рабочей области 2 . Хеш — это краткий способ однозначно представить файл без его передачи в VCS. Чтобы сослаться на 1 Такие атаки на «цепочки поставки ПО» ( https://oreil.ly/bfC05 ) становятся все более распро- страненными. 2 Недавно в Go была добавлена предварительная поддержка модулей, использующая ту же систему ( https://oreil.ly/lHGjt ). Современные системы сборки 381 новую внешнюю зависимость из рабочего пространства, в файл объявления не- обходимо вручную или автоматически добавить хеш этой зависимости. В момент запуска сборки Bazel проверит фактический хеш кешированной зависимости на соответствие ожидаемому, указанному в объявлении, и повторно загрузит файл, только если хеш отличается. Если хеш загружаемого артефакта отличается от хеша, указанного в файле объявле- ния, сборка завершится ошибкой. Изменить хеш в объявлении можно автоматически, но это изменение должно быть одобрено и зарегистрировано в VCS до того, как си- стема сборки примет новую зависимость. Это означает, что всегда есть возможность узнать, когда зависимость была обновлена, а внешняя зависимость не может изме- ниться без соответствующего изменения в объявлении рабочего пространства. Это также означает, что при извлечении из репозитория более старой версии исходного кода для сборки гарантированно будут использоваться те же зависимости, которые использовались в момент, когда эта версия была зафиксирована (или потерпит не- удачу, если необходимые зависимости окажутся больше недоступными). Конечно, может возникнуть проблема, если удаленный сервер станет недоступен или начнет посылать поврежденные данные — это может вызвать сбой всех ваших сборок, если у вас нет другой копии этой зависимости. Чтобы избежать этой про- блемы, мы рекомендуем для любого нетривиального проекта зеркалировать все его зависимости на подконтрольных вам серверах или службах, которым вы доверяете. В противном случае вы рискуете оказаться во власти третьей стороны в смысле до- ступности для вашей системы сборки, даже несмотря на то, что зарегистрированные хеши гарантируют ее безопасность. Распределенная сборка База кода в Google огромна — в ней хранится более двух миллиардов строк кода, а цепочки зависимостей могут быть очень глубокими. Даже простые двоичные файлы в Google часто зависят от десятков тысяч целей сборки. В таком масштабе просто невозможно завершить сборку за разумный промежуток времени на одной машине: никакая система сборки не может обойти фундаментальные законы физики, которым подчиняется аппаратное обеспечение. Единственный способ выполнить эту работу — использовать систему сборки, которая поддерживает распределенный режим работы, в котором единицы работы распределяются между произвольным и масштабируемым количеством машин. Если мы разобьем работу на достаточно маленькие части (подробнее об этом позже), то сможем завершить любую сборку любого размера настолько быстро, насколько позволят вложенные средства. Мас- штабируемость — это чаша Грааля, которую мы стремились заполучить, создавая систему сборки на основе артефактов. Удаленное кеширование Самый простой тип распределенной сборки — с использованием только удаленного кеширования (рис. 18.2). 382 Глава 18. Системы и философия сборки Сервер непрерывной интеграции Рабочая станция разработчика Рабочая станция разработчика Общий кеш Рис. 18.2. Распределенная сборка с удаленным кешированием Все системы, выполняющие сборку, включая рабочие станции разработчиков и систе- мы непрерывной интеграции, пользуются общей службой удаленного кеширования. Роль этой службы может играть быстрое и локальное хранилище, такое как Redis, или облачная служба, такая как Google Cloud Storage. Всякий раз, когда пользователь хочет выполнить сборку артефакта, непосредственно или как зависимости, система сначала ищет этот артефакт в удаленном кеше и в случае успеха загружает его. В про- тивном случае система сама производит сборку артефакта и выгружает результат в кеш. Благодаря этому сборка низкоуровневых зависимостей, которые меняются редко, может выполняться один раз, а ее результат — использоваться множеством пользователей. В Google многие артефакты извлекаются из кеша, что значительно увеличивает эффективность нашей системы сборки. Чтобы система удаленного кеширования приносила пользу, система сборки должна гарантировать воспроизводимость ее результатов — они должны позволять опреде- лить набор входных данных для любой цели и получить один и тот же результат на любой машине. Это единственный способ гарантировать, что результаты загрузки артефакта совпадут с результатами сборки на локальной машине. К счастью, Bazel дает такую гарантию и поддерживает удаленное кеширование ( https://oreil.ly/D9doX ), которое привязывает каждый артефакт в кеше к цели и к хешу входных данных. Только так разные инженеры смогут создавать разные модификации одной и той же цели, а удаленный кеш будет хранить все полученные артефакты и обслуживать их без конфликтов. Конечно, для получения выгоды от удаленного кеша загрузка артефакта должна происходить быстрее, чем его сборка. Однако это не всегда так, особенно если сер- вер кеша находится далеко от машины, выполняющей сборку. Компьютерная сеть и система сборки в Google настроены для быстрой загрузки результатов сборки. При настройке удаленного кеширования в своей организации учитывайте задержки в сети и опытным путем убедитесь, что кеширование действительно улучшает про- изводительность. Удаленное выполнение Удаленное кеширование — это еще не настоящая распределенная сборка. Если дан- ные в кеше потеряются или изменится некоторый низкоуровневый компонент, что потребует повторно собрать все зависимости, вам все равно придется выполнить сборку от начала до конца на локальной машине. Истинная цель распределенной Современные системы сборки 383 сборки — обеспечить возможность удаленного выполнения, когда фактическую работу по сборке можно распределить между любым числом рабочих машин (рис. 18.3). Инструмент сборки, выполняющийся на машине каждого пользователя (где поль- зователи — это инженеры или автоматизированные системы сборки), отправляет запросы главному серверу сборки. Главный сервер разбивает запросы на операции и распределяет их между рабочими машинами. Каждая рабочая машина выполняет требуемые от нее действия с входными данными, которые передал пользователь, и возвращает полученные артефакты. Затем эти артефакты совместно используются другими машинами, которым необходимы эти артефакты для выполнения своих действий. И так до тех пор, пока окончательный результат не будет получен и от- правлен пользователю. Рабочая машина Рабочая машина Рабочая машина Главный сервер сборки Рабочая станция разработчика Сервер непрерывной интеграции Рабочая станция разработчика Рис. 18.3. Система с поддержкой удаленного выполнения Самое сложное в реализации такой системы — управление взаимодействиями между рабочими машинами, главным сервером и локальной машиной пользователя. Одни рабочие машины могут ждать завершения сборки промежуточных артефак- тов другими рабочими машинами, чтобы окончательный результат можно было отправить обратно на локальный компьютер пользователя. Чтобы преодолеть эти сложности, в основу системы можно положить распределенный кеш, описанный выше, и заставить каждую рабочую машину выводить свои результаты в кеш и из- влекать зависимости из кеша. Главный сервер может блокировать рабочие машины до завершения сборки необходимых им зависимостей, чтобы они могли извлечь входные данные из кеша. Конечный продукт тоже можно поместить в кеш, что позволит локальному компьютеру загрузить его оттуда. Обратите внимание, что также необходимо предусмотреть возможность экспорта локальных изменений из дерева исходных кодов пользователя, чтобы рабочие машины могли применить эти изменения перед сборкой. Чтобы такая система на основе артефактов заработала, необходимо соединить все ее части, описанные выше. Среда сборки должна полностью описывать себя, чтобы рабочие машины могли действовать без участия человека. Сами процессы сборки должны быть полностью автономными, поскольку каждый шаг может выполняться 384 Глава 18. Системы и философия сборки на разных машинах. Результаты должны быть полностью детерминированными, чтобы каждая рабочая машина могла доверять результатам, полученным от других. Такие гарантии чрезвычайно сложно обеспечить в системе, основанной на задачах, что практически не позволяет создать на ее основе надежную систему удаленного выполнения. Распределенная система сборки в Google. С 2008 года в Google была применена распределенная система сборки, которая использовала и удаленное кеширование, и удаленное выполнение (рис. 18.4). Удаленный кеш в Google называется ObjFS. Он состоит из серверной части, которая хранит результаты сборки в хранилище Bigtables ( https://oreil.ly/S_N-D ), распределен- ном по всему нашему парку производственных машин, и внешнего демона FUSE с именем objfsd , который выполняется на каждой машине разработчика. Демон FUSE позволяет просматривать результаты сборки, как если бы они были обычными фай- лами, хранящимися на рабочей станции, но загружает содержимое файлов только по требованию пользователя. Такое обслуживание содержимого файлов значительно сокращает использование сети и дисков, и система может выполнять сборку в два раза быстрее ( https://oreil.ly/NZxSp ) по сравнению с вариантом, когда все результаты сборки сохраняются на локальном диске разработчика. Blaze ObjFS Bigtable ObjFS Кеш операций Планировщик Forge Рабочая станция разработчика Исполнители Forge Очередь операций Брокер Forge Рис. 18.4. Распределенная система сборки в Google Система удаленного выполнения в Google называется Forge. Клиент Forge в Blaze, называемый брокером (distributor), посылает планировщику (scheduler) запросы на выполнение операции в наших центрах обработки данных. Планировщик поддержи- вает кеш результатов выполнения операций, что позволяет ему немедленно вернуть ответ, если операция уже была создана любым другим пользователем системы. В противном случае операция помещается в очередь. Большой пул исполнителей заданий постоянно извлекает операции из этой очереди, выполняет их и сохраняет результаты непосредственно в кеше ObjFS Bigtables. Эти результаты могут исполь- зоваться исполнителями при выполнении операций в будущем или загружаться конечным пользователем с помощью objfsd Современные системы сборки 385 Такая система масштабируется для эффективной поддержки всех сборок, выполня- емых в Google. Каждый день в Google выполняются миллионы сборок, запускаются миллионы тестов и производятся петабайты результатов сборки из миллиардов строк исходного кода. Такая система не только позволяет нашим инженерам быстро создавать сложные кодовые базы, но и внедрять огромное количество автоматизи- рованных инструментов и систем, опирающихся на нашу систему сборки. На раз- работку этой системы мы потратили много лет, но в настоящее время доступны инструменты с открытым исходным кодом, используя которые любая организация сможет реализовать аналогичную систему. Для развертывания такой системы сборки нужны время и энергия, но конечный результат будет стоить затраченных усилий. Время, масштаб и компромиссы Главная задача системы сборки — упростить длительную работу с большими базами кода. Как и в любом другом аспекте программной инженерии, при выборе системы сборки необходимо учитывать возможные компромиссы. Подход «сделай сам» с ис- пользованием сценариев командной оболочки подходит только для самых маленьких проектов, в которых долго не изменяется код или используются такие языки, как Go, имеющие встроенную систему сборки. Выбор системы сборки на основе задач вместо самодельных сценариев значительно улучшает масштабируемость проекта, позволяя автоматизировать сложные сборки и легче добиваться воспроизводимых результатов на разных машинах. Недостаток такого выбора заключается в том, что он заставляет инженера структурировать сбор- ку и высчитывать затраты на написание файлов сборки (без автоматизированных инструментов). Для большинства проектов это вполне приемлемый компромисс, но в тривиальных проектах (например, весь код которых содержится в одном файле) затраты могут не окупиться. С увеличением проекта системы сборки на основе задач начинают сталкиваться с неко- торыми фундаментальными проблемами. Преодолеть эти проблемы можно с помо- щью системы сборки на основе артефактов. Такие системы сборки раздвигают горизонты масштабирования, позволяя распределить сборку между множеством машин и давая уверенность тысячам инженеров, что их сборки будут выполнены согласованным и воспроизводимым способом. Но эти системы недостаточно гибкие: они не позволяют описывать обобщенные задачи на настоящем языке программиро- вания и проводят сборку в рамках своих ограничений. Обычно это не проблема для проектов, которые изначально создавались с прицелом на использование системы на основе артефактов. Но миграция системы, основанной на задачах, может породить значительные сложности и потребовать затрат сил и времени, которые могут не окупиться, особенно если используемая система сборки на основе задач продолжает удовлетворять требованиям к скорости или безошибочности. Замена системы сборки проекта может стоить дорого, и эта стоимость увеличивается с увеличением проекта. Вот почему в Google считают, что почти каждый новый проект |