Transcription
Привет, если вы здесь впервые, меня зовут Димма. Я фронтенд-разработчик с 8-летним опытом, живущий сейчас в Берлине. Сегодня мы поговорим о CI/CD. Мы рассмотрим, зачем это нужно, как это работает, а затем перейдем к практическому примеру, развернув реальное full stack приложение на AWS. [музыка] Я думаю, CI/CD является слепым пятном для многих фронтенд-разработчиков. Если вы мидл-уровень или выше и не можете объяснить, как работает CI/CD в вашей текущей компании, это обычно плохой знак. Никто не ожидает, что вы будете DevOps-инженером, но вы должны знать, как это работает. В этом видео я дам вам структуру для понимания того, как работает CI/CD, не только в вашей компании, но и в любой компании. После этого мы посмотрим, как вы можете сделать ваш пайплайн быстрее и дешевле. Улучшение CI/CD пайплайна — один из самых простых способов построить сильное обоснование для вашего следующего повышения или следующей работы. Немногие разработчики думают об этом, но оптимизировать пайплайн относительно легко, и это влияет на многие вещи. Скорость сборки, надежность развертывания, сколько времени тратит ваша команда в ожидании. Так что это хорошая вещь для вашего портфолио. Теперь перейдем к слайдам. Начнем с базового сценария. Вы разработчик. Вы пишете код, создаете приложение. С другой стороны, есть пользователь, который хочет использовать ваше приложение. Два вопроса. Во-первых, как вы убедитесь, что приложение действительно работает до его развертывания, что оно не падает, что функции делают то, что должны делать. Во-вторых, как доставить приложение с вашего ноутбука пользователю? Вот что на самом деле происходит. Вы нажимаете "слить пул-реквест", и затем каким-то волшебным образом пользователи могут открыть ваше приложение на своих телефонах или ноутбуках. Многие фронтенд-разработчики не задумываются о том, что происходит между этим, потому что у нас есть DevOps. Они занимаются этим. Но это неправильное мышление. Вам нужно понимать полный жизненный цикл вашего приложения от начала до конца. CI/CD — это ответ на оба этих вопроса. CI означает непрерывная интеграция (continuous integration). Вопрос, на который он отвечает: "Достаточно ли хорош этот код для развертывания?". Каждый раз, когда кто-то отправляет код, система автоматически проверяет его, запускает тесты, проверяет на ошибки, убеждается, что ничего не сломано. CD означает непрерывное развертывание (continuous deployment) или непрерывная доставка (continuous delivery). И это на самом деле разные вещи. Мы рассмотрим это различие через мгновение. Но вопрос, на который отвечает CD: "Как доставить этот код пользователям?". Как только код проходит все проверки, система развертывает его. Теперь, куда он развертывается? Это зависит. Он может сначала попасть на staging-среду, где команда QA протестирует его вручную. Он может попасть на тестовый сервер или напрямую на production-сервер. Разные компании имеют разные настройки. У некоторых есть несколько этапов, например, dev, staging, а затем production. Некоторые развертывают напрямую на production при каждом слиянии. Непрерывная часть означает, что это происходит автоматически каждый раз, когда код меняется. Не раз в неделю, не когда кто-то вспомнит. Каждый коммит должен запускать процесс. Вот таблица, показывающая, что обычно ожидается на разных уровнях. Это варьируется в зависимости от компании и команды, но дает вам представление. Младший разработчик должен уметь читать логи пайплайна и исправлять свои собственные неудачные сборки. На мидл-уровне от вас ожидается отладка сбоев пайплайна, понимание конфигурационных файлов, определяющих рабочие процессы, и изменение существующих пайплайнов при необходимости. Старшие инженеры пишут пайплайны с нуля, оптимизируют время сборки и управляют секретами, такими как API-ключи и пароли. Инженеры уровня Staff и выше занимаются масштабированием инфраструктуры как кода и продвинутыми стратегиями развертывания. В сегодняшнем видео мы сосредоточимся на мидл-уровне и старших инженерах. Вы поймете, как работают пайплайны, как их писать и как отлаживать, когда что-то идет не так. Вот что мы рассмотрим. Во-первых, основы CI/CD, строительные блоки автоматизированных развертываний. Затем GitHub Actions. Мы увидим, как мы можем настроить CI/CD с помощью GitHub. После этого практика. Мы развернем приложение React и NodeJS на AWS EC2. И, наконец, методы оптимизации: кэширование, параллельные задачи и сокращение времени сборки. Если вы хотите углубиться в CI/CD, стратегии развертывания, паттерны отката, мониторинг и многое другое, я освещаю эти темы в своем продвинутом курсе по фронтенду [музыка]. Я также преподаю фронтенд-архитектуру, управление состоянием, связь в реальном времени и тестирование. Если вы готовитесь к собеседованию на позицию старшего специалиста или пытаетесь получить повышение в своей текущей компании, ознакомьтесь с ним. Начнем с основ. Если вы посмотрите на изображение, вы увидите полный цикл того, как программное обеспечение попадает из вашего редактора кода на production-сервер. Все начинается с кода. Вы вносите изменение и коммитите его в git. Затем сборка для фронтенда. Это означает компиляцию TypeScript, Bable, React или Vue или любого другого веб-фреймворка. Затем тесты, затем релиз. Приложение упаковывается во что-то, что можно развернуть. Это называется артефактами. Файлы, готовые к отправке на сервер. Затем этап развертывания. Эти артефакты отправляются на сервер. Это может быть EC2-инстанс на AWS, который по сути является виртуальным компьютером, работающим в дата-центре Amazon. Или это может быть сервер на Digital Ocean, Google Cloud или любом другом облачном провайдере. Суть в том, что нам нужно разместить код на машине, доступной из Интернета. Затем эксплуатация. Приложение работает в production. Реальные пользователи теперь могут открыть его и использовать. Мониторинг. Вы отслеживаете производительность и время безотказной работы. Если что-то ломается, вы видите это на этом этапе. И планируете на основе того, что говорит вам мониторинг, например, ваши ошибки, медленные конечные точки, жалобы пользователей. Вы планируете, что исправить или улучшить дальше. И затем цикл повторяется. На чем мы сосредоточимся. Мы не будем охватывать весь цикл сегодня. Мониторинг, эксплуатация, планирование — это отдельные темы. Мы сосредоточимся на коде, сборке, релизе и развертывании. Это основное знание, которое вам нужно знать. Как только вы поймете, как код попадает с вашей машины на сервер, все остальное будет относительно легко понять. Ранее я упоминал, что CD может означать непрерывную доставку или непрерывное развертывание. Давайте проясним разницу, потому что вы будете слышать оба термина, и они не одно и то же. Непрерывная доставка означает, что каждое изменение автоматически собирается, тестируется и подготавливается к production. Код упакован и готов к отправке. Но, и это ключевой момент, человек все еще решает, когда фактически отправить его на production. Итак, представьте, что пайплайн выполняется, все тесты проходят, артефакт создан. Но вместо автоматического развертывания кто-то, например, тимлид или менеджер релизов или кто-то ответственный за это, нажимает кнопку, чтобы сказать: "Хорошо, разверните это сейчас". Возможно, они хотят подождать до конца рабочего дня. Возможно, они хотят объединить несколько функций. Суть в том, что автоматизация обрабатывает все до последнего шага, а человек выполняет последнее действие. Непрерывное развертывание делает то же самое, но автоматически. Каждое изменение, которое проходит все автоматические проверки, автоматически выпускается на production. Так в чем практическая разница? При непрерывной доставке у вас есть ворота. Кто-то проверяет и одобряет перед production. При непрерывном развертывании ворот нет. Если тесты проходят, код идет на production. Большинство компаний начинают с непрерывной доставки, потому что это безопаснее, потому что у вас есть этот человеческий контроль. Но по мере улучшения покрытия тестами и роста уверенности команды в автоматизации, некоторые команды переходят к непрерывному развертыванию. Для этого видео мы настроим пайплайн, который развертывается автоматически при отправке в основную ветку. Так что технически это непрерывное развертывание. Прежде чем мы перейдем к мета-развертываниям, я хочу показать вам, как это работало до появления CI/CD. Потому что если вы знаете только о существовании автоматизации, но никогда не видели ручного процесса, вы не поймете, какие проблемы она на самом деле решает. Но если вы посмотрите на то, как было раньше и как это работает сейчас, вы увидите прогресс. И если ваша компания по какой-то причине до сих пор выполняет ручные развертывания, вы точно поймете, чего вам не хватает и почему стоит это изменить. Итак, давайте посмотрим на старый способ развертывания. Он называется SSH-развертывание. SSH означает Secure Shell. Он позволяет подключаться к удаленному серверу и выполнять команды на нем, как будто вы сидите за этим компьютером. Процесс выглядит так: вы отправляете код в git, затем подключаетесь к серверу по SSH. Вы запускаете `git pull` для загрузки последнего кода. Вы запускаете `npm install` для получения зависимостей. Вы запускаете команду сборки, а затем Nginx обслуживает приложение пользователям. Код проходит через git, так что у вас есть история версий. Вы можете видеть, кто что изменил. Но само развертывание все еще ручное. Каждое развертывание означает подключение к серверу и выполнение одних и тех же команд. Если у вас, например, пять серверов, вам придется делать это пять раз. Проблема в том, что это медленно, это повторяется, и когда несколько разработчиков развертывают, возникают конфликты. Кто-то может выполнить `pull`, пока кто-то другой находится в процессе развертывания. Почему ручные релизы остались в прошлом? Ручное развертывание не масштабируется. Первая проблема: очередь развертывания. Когда несколько разработчиков должны развернуть, им приходится координироваться. Так один разработчик спрашивает команду: "Кто-нибудь сейчас развертывает?" и кто-то отвечает: "Подожди, я закончу первым". Это замедляет всех. Вторая проблема: человеческая ошибка. Ручные процессы зависят от того, что люди не совершают ошибок, но мы, люди, можем забывать шаги. Мы можем выполнять команды в неправильном порядке. Мы можем развернуть на неправильный сервер. Третье: нет автоматизированных проверок безопасности. Код идет прямо на production без проверок безопасности. Да, вы можете запускать тесты вручную перед развертыванием, но когда процесс ручной, люди могут просто пропустить этот шаг, потому что вы спешите и думаете, что, вероятно, все в порядке. Четвертое: медленные откаты. Если что-то ломается, вам приходится вручную выяснять, что изменилось, и отменять это. Это занимает время. И пятое: потерянное время разработчиков. Каждое ручное развертывание может занимать от 30 до 60 минут. Это время, потраченное на подключение к серверам, выполнение команд и ожидание. Умножьте это на несколько развертываний в день, и вы потеряете часы продуктивной работы каждую неделю. Итак, главная идея: если вы можете что-то автоматизировать, вы должны это сделать. Ручные процессы не масштабируются. Они медленные, подвержены ошибкам и тратят время разработчиков. Итак, мы переходим к автоматизированному развертыванию. Давайте посмотрим, что дает нам автоматизация. Эти цифры взяты из исследования "Accelerate: The Science of Lean Software and DevOps". В этом исследовании они измерили тысячи инженерных команд. Команды с хорошим CI/CD развертываются в 46 раз чаще, чем команды, выполняющие ручное развертывание. Риски снижаются, потому что процесс развертывания проще и быстрее. Вы можете развертывать чаще небольшими порциями. Вместо одного массивного релиза с сотнями изменений вы развертываете 10 небольших изменений. Если что-то ломается, вы точно знаете, какое изменение вызвало это. Время восстановления намного быстрее. Если у вас есть автоматизация, вы исправляете это за минуты или часы. Без нее это может занять дни. Вы тратите меньше времени на повторяющиеся задачи. Автоматизация обрабатывает сборку, тестирование и развертывание. Разработчики могут сосредоточиться на написании кода. И любой разработчик может безопасно развернуть. Вам не нужен специальный человек для развертывания. Вам не нужно передавать знания новым членам команды. Процесс документирован в коде. Любой может запустить развертывание, и автоматизация гарантирует, что оно будет выполнено правильно. Давайте посмотрим, как выглядит автоматизированная версия. После настройки процесса развертывания, что мы сделаем в разделе практики, разработчику нужно сделать только одно: отправить код в репозиторий. `git push` запускает CI/CD пайплайн. Пайплайн выполняется на виртуальной машине, которую GitHub предоставляет нам. Пайплайн компилирует TypeScript, собирает фронтенд и запускает тесты. Если все проходит успешно, он упаковывает приложение и создает артефакт развертывания. Затем он отправляет эти артефакты на ваш сервер. И, наконец, Nginx обслуживает приложение пользователям. Теперь, когда мы понимаем, что дает нам CI/CD, давайте поговорим о каждом шаге более подробно. Во-первых, вы отправляете код в GitHub. GitHub обнаруживает push в ветку. Это запускает пайплайн. Что такое пайплайн? Это последовательность шагов, которые выполняются автоматически. Он выполняется на виртуальной машине, которую GitHub создает для вас. Эта машина называется раннером (runner). Когда вы отправляете код, GitHub запускает свежую виртуальную машину. Это временный компьютер в облаке, который существует только для выполнения вашего пайплайна. GitHub предоставляет эту машину бесплатно до определенного лимита. Затем вы платите за дополнительное использование. Пайплайн начинается. Как мы говорили ранее, CI — это первый шаг в CI/CD. На этом этапе мы проверяем, достаточно ли хорош код для его развертывания. Это означает, что мы запускаем линтеры для проверки стиля кода. Мы запускаем тесты, чтобы убедиться, что ничего не сломано. Мы проверяем, что TypeScript компилируется без ошибок. Если какая-либо из этих проверок не проходит, пайплайн останавливается. Код не развертывается. Если все проверки пройдены, мы переходим к следующему шагу. Как мы обсуждали, после прохождения проверок нам нужно собрать наше приложение. Результатом является артефакт. Артефакт — это наше приложение, готовое к развертыванию. Например, для React-приложения или Vue-приложения это папка `build` с файлами HTML, CSS и JavaScript. Раннер клонирует ваш код из репозитория. Он устанавливает зависимости, запускает `npm install` для загрузки всех пакетов, необходимых вашему приложению. Он запускает тесты и собирает приложение. Затем раннер подключается к вашему фактическому серверу через SSH. Он копирует файлы сборки на сервер. Он дает команду серверу перезапустить приложение. Суть в том, что мы описываем эти шаги один раз, и GitHub Actions выполняет их для нас каждый раз. Так что нам не нужны ручные SSH-подключения, и нам не нужно запоминать команды. GitHub-раннер появляется, когда вы отправляете код. Он исчезает после завершения пайплайна. Раннер существует, возможно, 5-10 минут, в зависимости от того, как долго длится пайплайн, а затем он удаляется. GitHub взимает плату за время его работы, но бесплатного лимита обычно достаточно. Итак, как GitHub узнает, какие шаги выполнять? Мы говорим ему, что делать, в конфигурационном YAML-файле. Здесь мы описываем наши GitHub Actions. Что такое GitHub Actions? Это встроенная система CI/CD от GitHub. Она читает конфигурационные файлы из вашего репозитория и выполняет определенные вами шаги. Другие провайдеры имеют аналогичные системы, например, у GitLab есть CI, у Bitbucket есть Pipelines. Концепции те же. YAML — это формат конфигурации, который легко читать и писать. Файл находится в вашем репозитории в папке `.github/workflows`. Когда вы отправляете код, GitHub ищет файлы в этой папке и запускает их. Позвольте мне пройтись по тому, что говорит этот файл. Поле `name` — это просто имя вашего пайплайна. То, что вы увидите в интерфейсе GitHub. Раздел `on` определяет, когда запускать этот раздел. В данном случае, когда кто-то отправляет код в основную ветку, этот пайплайн запускается. Раздел `jobs` определяет, что делать. У нас есть одна задача под названием `deploy`, которая выполняется на `ubuntu-latest`. Это операционная система для раннера. Затем шаги. Первое действие `actions/checkout`. Это загружает ваш код на раннер. Затем `actions/setup-node`. Это устанавливает NodeJS. Затем команды `run`. `npm install` для получения зависимостей. `npm run build` для создания production-сборки. `npm test` для запуска тестов. Вы также можете запускать линтеры здесь. Проверка типов, сканирование безопасности, все, что нужно вашему проекту. И, наконец, шаг развертывания. Команда `scp` копирует файлы с раннера на ваш сервер. Затем SSH подключается к серверу, переходит в папку вашего приложения, извлекает последний код и использует PM2 для перезапуска приложения. PM2 — это менеджер процессов, который поддерживает запуск приложений. Мы настроим его в разделе практики. Таким образом, все необходимое для развертывания вашего приложения находится в этом файле в вашем рабочем проекте. Вы можете прочитать его и понять процесс развертывания. Далее, давайте проясним, какие две машины у нас есть и что они делают. Мы упоминали раннеров ранее, но теперь давайте посмотрим на полную картину. Первая — GitHub Runner. Это временная виртуальная машина, которую предоставляет GitHub. Она собирает ваш код, запускает тесты и отправляет файлы на ваш сервер. Она создается при запуске пайплайна и удаляется после его завершения. Вторая — ваш EC2-инстанс. Это ваш фактический сервер, который всегда работает. EC2 — это Amazon Elastic Compute Cloud, виртуальные машины. Вы арендуете компьютер в их дата-центре. На этом сервере у вас есть Nginx для обслуживания вашего React-фронтенда браузерам и маршрутизации API-запросов к вашему бэкенду. Это может быть любая другая виртуальная машина, но я выбрал AWS, потому что это самое популярное решение. Вы можете использовать Google Cloud, Microsoft Azure или что угодно еще. Итак, это теория. Теперь перейдем к практике. Позвольте мне показать, что мы будем развертывать сегодня. Это приложение "Список наблюдения за акциями" (Stock Watchlist). Вы вводите тикер, например, Apple, или ищете название компании, например, Tesla, и можете добавить его в свой список наблюдения, а цены будут обновляться в реальном времени. Когда рынок открыт, вы видите, как меняется цена. Наша цель — иметь это приложение, работающее на инстансе AWS с автоматизированными развертываниями через GitHub Actions. Итак, позвольте мне быстро описать, как работает приложение. Вам не нужно понимать каждую деталь здесь. Мы сосредоточимся на процессе развертывания, но знание структуры облегчит настройку сервера. У нас есть React-фронтенд, собранный с помощью Vite. На бэкенде два NodeJS-сервера. Первый — это Express REST API, работающий на порту 30001 для таких вещей, как поиск акций и управление списком наблюдения. Данные хранятся в MongoDB Atlas. Второй сервер — это WebSocket-сервер на порту 30002. Он подключается к FinHub API, поставщику финансовых данных, и передает цены акций в реальном времени в браузер. На сервере Nginx обслуживает наше приложение. Он обслуживает файлы React и маршрутизирует запросы к соответствующему бэкенду. API-запросы идут на один порт, а WebSocket-соединения — на другой. Мы настроим конфигурацию Nginx, когда доберемся до сервера. Итак, для этой практики нам нужны три вещи: учетная запись AWS, кластер MongoDB Atlas и ключ API FinHub. Теперь запустим EC2-инстанс. В консоли AWS перейдите в EC2 и нажмите "Launch instance" (Запустить инстанс). Для операционной системы выберите Ubuntu. Для типа инстанса выберите `t2.micro`. Он доступен в бесплатном тарифе. Вы можете создать пару ключей для SSH-доступа из вашего терминала, и она вам понадобится для GitHub Actions, но также в этом демо мы будем использовать встроенный интерфейс SSH AWS в браузере. Так что пара ключей вам нужна только для GitHub Actions. Далее, запустите инстанс и подождите, пока он запустится. Как только он будет работать, вы увидите всю информацию, а также публичный IP-адрес. Важно знать: этот IP-адрес меняется каждый раз, когда вы останавливаете и запускаете инстанс. Если вам нужен фиксированный IP, вы можете выделить Elastic IP в AWS и прикрепить его к вашему инстансу. Теперь давайте подключимся к серверу. В консоли AWS выберите ваш инстанс и нажмите "Connect" (Подключиться). Выберите вкладку "EC2 Instance Connect" и нажмите "Connect". Это откроет терминал в вашем браузере. Сначала обновите системные пакеты. Выполните `apt update` и `apt upgrade`. Установите NodeJS. Проще всего использовать менеджер пакетов. После установки проверьте с помощью `node -v`. Установите PM2 глобально с помощью npm. PM2 поддерживает работу ваших Node.js приложений в фоновом режиме. Если процесс падает, PM2 автоматически перезапускает его. Далее, установите Nginx. Nginx делает две вещи для нас. Он обслуживает статические файлы React и маршрутизирует API- и WebSocket-запросы к Node.js серверам. После установки Nginx давайте настроим его. Сначала нам нужно создать конфигурационный файл для нашего приложения. Первый блок `location` обрабатывает корневой путь. Когда кто-то открывает сайт, Nginx обслуживает React-приложение из `/var/www/stock-watchlist/client`. Директива `try_files` нужна для клиентской маршрутизации. Если пользователь обновляет страницу на пути `/watchlist`, например, Nginx не ищет файл `watchlist`. Он использует `index.html` и позволяет React обрабатывать маршрут. Второй блок `location` обрабатывает API-запросы. Они идут на Express-сервер на порту 30001. Третий блок `location` обрабатывает `WS` для WebSocket-соединений. Он проксирует на порт 30002 и включает заголовки `Upgrade` и `Connection`, которые нужны WebSocket. Итак, это все с конфигурацией Nginx. Далее нам нужно включить конфигурацию, создав символическую ссылку в `/etc/nginx/sites-enabled`. Затем мы протестируем ее с помощью `nginx -t` и затем перезагрузим Nginx. Теперь давайте автоматизируем развертывания. GitHub Actions запускает рабочие процессы на основе событий. Мы будем запускать их при отправке в `main` и разрешим ручные триггеры. Сначала давайте добавим секреты в ваш репозиторий. Перейдите в "Settings" (Настройки), затем "Secrets and variables" (Секреты и переменные), затем "Actions". Итак, нам нужно добавить эти пять секретов: `EC2_HOST` — это публичный IP-адрес нашего сервера. `EC2_SSH_KEY` — содержимое вашего `.pem`-файла, который вы создали на этапе создания EC2-инстанса. `MONGODB_URI` — ваша строка подключения из MongoDB Atlas. И `FINHUB_API_KEY` — ваш ключ API от FinHub. Теперь давайте посмотрим, что делает рабочий процесс. Сначала он собирает React-приложение. `checkout` кода, установка зависимостей и запуск `vite build`. Вывод идет в папку `dist`. Затем он развертывает с помощью `rsync` через SSH. `rsync` передает только измененные файлы. Так что после первого развертывания обновления будут быстрее. Копирует сборку React в папку, которую Nginx обслуживает. В нашем случае это `/var/www/stock-watchlist/client`. Это путь из нашей конфигурации Nginx. Эта задача также копирует код сервера. Затем она устанавливает соединение с нашим сервером через SSH и запускает `npm install` для зависимостей бэкенда. Наконец, она перезапускает процесс PM2. Помните, мы установили PM2 ранее. Так что теперь нам просто нужно перезапустить его. Рабочий процесс также создает файл `.env` на сервере с секретами. Так что нам не нужно коммитить его в репозиторий. Теперь время развертывать. Мы можем отправить что-то в `main` или вручную запустить рабочий процесс из вкладки "Actions". Так что мы можем наблюдать за логами на каждом шаге. Сборка занимает около минуты, а развертывание зависит от скорости вашего соединения. Как только оно завершится, ваше приложение должно быть доступно. Теперь давайте убедимся, что все работает. Откройте браузер и перейдите по IP-адресу вашего EC2-инстанса. Браузеры по умолчанию открывают HTTPS. Так что нам нужно вручную изменить его на HTTP, потому что мы еще не настроили HTTPS. Если все в порядке, вы должны увидеть загрузку React-приложения. Давайте попробуем ввести что-нибудь вроде "Apple" в поиск. Появляется ответ с соответствующими результатами. Далее, давайте добавим Apple в наш список наблюдения. Если рынок США открыт, мы увидим, как цены обновляются в реальном времени. Итак, мы развернули полное приложение на AWS EC2, настроили Nginx для обслуживания статических файлов и маршрутизации запросов, а также настроили GitHub Actions для автоматизированных развертываний. Вы можете сделать многое поверх этого. Вы можете заменить EC2 на любого VPS-провайдера. Вы могли бы использовать Docker вместо запуска Node.js напрямую. Вы могли бы заменить PM2 на systemd или любой другой менеджер процессов, но развертывание будет работать почти так же. Теперь, когда у нас есть рабочий пайплайн, давайте поговорим о том, как его улучшить. Наша текущая настройка работает нормально, но по мере роста вашего проекта и увеличения вашей команды вы начнете замечать проблемы. Сборки занимают слишком много времени. Вы платите больше, чем должны, за работающие виртуальные машины. Разработчики слишком долго ждут, пока будет развернута новая версия приложения. Итак, давайте рассмотрим пять оптимизаций, которые помогут нам его улучшить. Вот что мы рассмотрим. Во-первых, управление параллелизмом: отмена устаревших пайплайнов, чтобы вы не запускали сборки для устаревших изменений. Далее, кэширование зависимостей: сохранение `node_modules` между запусками, чтобы вам не приходилось каждый раз загружать все с нуля. Фильтрация по путям: полное пропуски пайплайна при изменении файлов, которые не влияют на сборку, таких как файлы README или лицензии. Очередь слияния (Merge Queue): автоматическое тестирование PR друг против друга перед слиянием, чтобы вам не приходилось вручную делать rebase все время. И параллелизация тестов: запуск тестов на нескольких машинах одновременно для получения обратной связи быстрее. Давайте пройдемся по каждому из них. И первое — управление параллелизмом. Вот случай. Вы отправляете коммит в свой пул-реквест. Пайплайн начинает выполняться. Через минуту вы замечаете небольшую проблему в своем последнем коммите. Так что вы отправляете еще один коммит. Теперь у вас выполняются два пайплайна для одного и того же PR, но первый уже устарел. Вас интересует только последний коммит. По умолчанию GitHub не знает об этом и запускает оба пайплайна. Почему это проблема? Две причины. Во-первых, деньги. GitHub Actions взимает плату за минуту. Каждый дополнительный выполняющийся пайплайн — это время, за которое вы платите. Во-вторых, задержки в очереди. Большинство планов GitHub имеют ограничения на количество задач, которые вы можете выполнять одновременно. Ваши устаревшие пайплайны занимают слоты, которые могли бы быть использованы для чего-то полезного, например, для запуска пайплайна для другого PR. Решение — сообщить GitHub, что если для того же PR начинается новый запуск, отмените старый. Итак, вот что происходит. Вы отправляете коммит A, пайплайн запускается. Вы отправляете коммит B, пайплайн A отменяется, и запускается пайплайн B. Затем вы отправляете коммит C, пайплайн B отменяется, и запускается пайплайн C. В любой момент выполняется только последний пайплайн. Все, что вам нужно сделать, это добавить блок `concurrency` в ваш файл рабочего процесса. Поле `group` создает уникальный идентификатор для каждого пул-реквеста. Таким образом, GitHub знает, какие запуски относятся к одному и тому же пул-реквесту, а поле `cancel-in-progress: true` указывает GitHub отменить любые выполняющиеся пайплайны в этой группе при запуске нового. Следующее — кэширование зависимостей. Каждый запуск пайплайна начинается на свежей машине. Это означает, что он каждый раз выполняет `npm install`, загружая все пакеты из реестра npm и устанавливая их, даже если ваши зависимости не изменились с момента последнего запуска. Почему это важно? Опять же, две причины. Во-первых, деньги. Вы платите за эти минуты и время. `npm install` обычно занимает от одной до трех минут. Это может показаться небольшим количеством времени, но умножьте это на каждый запуск пайплайна в вашей команде, и это будет значительное количество времени. Решение — кэшировать папку `node_modules` между запусками пайплайна. Когда зависимости не изменились, вы полностью пропускаете установку зависимостей. Вот как это работает. Ключ кэша основан на хэше вашего файла `package-lock.json`. Если этот файл не изменился, зависимости те же, и вы можете безопасно повторно использовать папку `node_modules` из кэша. При первом запуске кэша нет. Поэтому `npm ci` выполняется нормально и сохраняет `node_modules` в кэш. При каждом последующем запуске пайплайн восстанавливает `node_modules` из кэша и полностью пропускает команду `npm ci`. Чтобы включить кэширование в файле рабочего процесса, нам нужно добавить шаг кэширования перед шагом установки. Вы можете использовать действие `actions/cache`. Укажите на директорию `node_modules` и установите ключ кэша так, чтобы он включал хэш `package-lock.json`. Затем в вашем шаге установки вам нужно добавить условие. Запускайте `npm ci` только в том случае, если кэш не был найден. Если кэш был восстановлен, устанавливать нечего. Есть альтернативный подход. Вместо прямого кэширования `node_modules`, кэшируйте кеш загрузки npm в `/npm`. Разница в том, что `npm ci` выполняется каждый раз, но он читает пакеты с локального дискового кэша вместо загрузки их из сети. Это немного медленнее, чем кэширование `node_modules`, потому что `npm ci` должен обрабатывать и устанавливать пакеты. Но в некоторых ситуациях это безопаснее. Итак, когда использовать какой подход: кэшируйте папку npm, когда ваш проект имеет нативные бинарные файлы, такие как `node-gyp` или `node-sass`, которые должны быть скомпилированы для конкретной операционной системы. Также используйте его, если ваш пайплайн выполняется на разных операционных системах или если вы хотите, чтобы `npm ci` всегда проверял целостность пакетов. Кэшируйте `node_modules` напрямую, когда вы всегда работаете на одной и той же операционной системе и у вас нет нативных бинарных файлов. В этом случае полное пропуски `npm ci` дает вам наибольшую экономию времени. Для большинства фронтенд-проектов, работающих на `ubuntu-latest` в GitHub Actions, прямое кэширование `node_modules` работает хорошо и дает лучшую производительность. Следующее — фильтрация по путям. Прямо сейчас каждый push запускает полный пайплайн независимо от того, какие файлы изменились. Но не каждый файл влияет на сборку. Документация, файлы лицензий, изображения, конфигурации IDE не должны запускать тесты и новое развертывание. Если вы не фильтруете пайплайны по путям, вы платите за минуты пайплайнов, которые ничего полезного не делают. И эти запуски могут занимать слоты задач, которые могли бы запускать пайплайны для фактических изменений кода. Решение — сообщить GitHub, какие файлы должны или не должны запускать пайплайн. Есть два подхода. Во-первых, вы можете использовать `paths`, список шаблонов файлов, которые должны запускать пайплайн. Все остальное пропускается. Или вы можете использовать `path-ignore`, список шаблонов файлов, которые не должны запускать пайплайн. Все остальное по-прежнему запускает его. Для большинства проектов `path-ignore` — это более простой подход. Вы перечисляете такие вещи, как файлы markdown, папку `docs`, файлы лицензий, настройки кода, настройки IDE, и любой коммит, который затрагивает только эти файлы, не будет запускать пайплайн. Теперь давайте поговорим об очередях слияния (merge queues). Это немного более продвинуто. Вот проблема. У вас есть репозиторий с несколькими PR, готовыми к слиянию. PR1 готов. PR2 готов. PR3 готов. Все их CI-проверки прошли. PR1 сливается. Теперь `main` обновлен. Ветка PR2 теперь устарела. Она тестировалась на более старой версии `main`. Автору PR2 приходится обновлять свою ветку. Сделать rebase или слить `main` в нее и снова ждать, пока CI выполнится. PR2 проходит и сливается. Теперь PR3 устарел, и происходит то же самое. Если вы работали в команде, где много разработчиков постоянно отправляют изменения в один и тот же репозиторий, вы должны быть знакомы с этой ситуацией. Каждый автор PR должен вручную обновлять свою ветку, когда ветка `main` меняется. На оживленном репозитории с множеством PR в день это создает цикл постоянных rebase и повторных запусков CI. Почему это плохо? Это вызывает две проблемы. Во-первых, задержки развертывания. Цикл обновления веток и ожидания CI означает, что PR, которые были готовы к слиянию, могут занять час, чтобы фактически слиться, и ручная работа. Каждый автор должен следить за обновлениями `main`, делать rebase своей ветки и снова ждать. Очередь слияния делает то же самое. Ваша ветка должна быть актуальной с веткой `main` перед слиянием, но она автоматизирует процесс. Авторам PR больше не нужно вручную обновлять свои ветки. Когда вы добавляете PR в очередь слияния, GitHub берет на себя тестирование его против последней версии `main`, включая любые другие PR, которые находятся перед ним в очереди. Если тесты проходят, он автоматически сливается. Если они не проходят, он удаляет PR из очереди и уведомляет разработчика. Настройка очереди слияния состоит из двух частей. Во-первых, обновите ваш файл рабочего процесса, чтобы он запускался при событии `merge_group`. Таким образом, ваш CI-пайплайн запускается не только при push и пул-реквестах, но и когда GitHub тестирует PR в очереди слияния. Шаг второй: в настройках вашего репозитория перейдите в "Branches" (Ветки), найдите правила защиты веток и включите "Require merge queue" (Требовать очередь слияния). Итак, как это работает? Если вы посмотрите на диаграмму, вы увидите очередь. Вместо того, чтобы каждый разработчик вручную делал rebase и ждал, PRы входят в очередь. GitHub тестирует их по порядку и сливает каждый, как только он проходит. И последняя оптимизация, которую мы рассмотрим сегодня, — параллелизация тестов. Проблема проста. Ваш набор тестов выполняется последовательно на одной машине. Общее время завершения всех тестов зависит от количества тестов. Больше тестов означает более долгое ожидание, а тесты часто являются самой медленной частью пайплайна. Когда ваш набор тестов занимает 5, 10 или 15 минут, каждый push означает ожидание этого времени для обратной связи. Это особенно неприятно, если некоторые тесты не проходят, и вам нужно запустить их снова. Решение — разделить ваши тесты на несколько машин, работающих одновременно. Вместо одной машины, выполняющей 200 тестов последовательно, занимающей, например, 10 минут, у вас есть четыре машины, выполняющие по 50 тестов каждая, завершающиеся примерно за 2,5 минуты. Компромисс: больше шардов означает более быструю обратную связь, но более высокую стоимость. Каждый шард — это отдельный раннер, и GitHub взимает плату за каждый из них. Так что вы хотите найти золотую середину, где время настройки на шард примерно равно времени выполнения тестов на шард. За пределами этого момента добавление большего количества машин дает убывающую отдачу. Итак, что вам нужно сделать в GitHub Actions? Вы настраиваете это с помощью стратегии `matrix`. Вы определяете переменную `shard`, скажем, четыре шарда, и GitHub запускает четыре раннера параллельно. Каждый раннер получает разный индекс шарда. В вашей команде тестов вы передаете этот индекс шарда. Например, с помощью `jest` вы используете флаг `--shard`. `jest` автоматически распределяет ваши тесты по шардам на основе индекса. Если вы посмотрите на диаграмму, вы увидите разницу. Без параллелизации все тесты выполняются на одной машине последовательно. С четырьмя шардами тесты распределяются по четырем машинам, и общее время значительно меньше. Но, как вы можете видеть, каждому шарду требуется некоторое время для запуска. Добавление дополнительных раннеров не окажет существенного влияния. Давайте завершим раздел оптимизации. Я разделил эти оптимизации на две категории. Первая — быстрые победы. Вещи, которые вы можете добавить за несколько минут и которые дают немедленный эффект. Кэширование зависимостей — самое простое. Ваша команда сразу же заметит более быстрые сборки. Управление параллелизмом так же просто и экономит как деньги, так и время. Фильтрация по путям — небольшое улучшение, но его добавление занимает минуту. Затем идут более продвинутые оптимизации. Очередь слияния легко включить, но она меняет то, как ваша команда сливает код. Разработчики больше не могут сливать мгновенно. Им приходится проходить через очередь. Это изменение рабочего процесса. Так что оно требует согласия команды. И параллелизация тестов имеет смысл, когда ваши тесты занимают более 5 минут. До этого накладные расходы на запуск нескольких машин не оправдываются. На этом все на сегодня. Мы прошли от основ CI/CD до развертывания реального приложения на AWS с помощью GitHub Actions, а затем рассмотрели, как сделать ваш пайплайн быстрее и дешевле. Если вы сейчас работаете над своим пайплайном, начните с кэширования и управления параллелизмом. Их настройка занимает 5 минут, и вы сразу же увидите разницу. Все остальное добавляйте, когда оно вам действительно понадобится. Я освещаю более продвинутые темы CI/CD и другие фронтенд-темы, такие как архитектура, управление состоянием и системный дизайн, в своем курсе. Ссылка в описании, и увидимся в следующем видео.