Автор: Клэр Лигуори

Проходя собеседование на свою должность в компании Amazon, я спросила одного из сотрудников: «Насколько часто вы развертываете решения в рабочей среде?» В то время я работала над продуктом, основные версии которого выпускались один или два раза в год, но иногда между этими важными этапами приходилось выпускать небольшие исправления. При выпуске каждого исправления я тратила много часов на тщательное развертывание. Затем я судорожно проверяла журналы и метрики, чтобы узнать, не вышло ли что-нибудь из строя в результате развертывания и не нужно ли вернуться к предыдущей версии.

Я читала, что в компании Amazon практикуется непрерывное развертывание, поэтому на собеседовании я хотела узнать, сколько времени уходит у разработчиков Amazon на управление развертываниями и наблюдение за ними. Сотрудник, проводивший собеседование, рассказал, что изменения автоматически развертываются в рабочей среде по несколько раз в день с помощью конвейеров непрерывного развертывания. Когда я спросила, сколько времени ежедневно уходит у него на тщательный контроль каждого из этих развертываний, а также проверку журналов и метрик на предмет последствий, он ответил: «Как правило, нисколько». Так как конвейеры выполняют эту работу автоматически, за большинством развертываний никто целенаправленно не наблюдает. «Ничего себе!» – сказала я. Когда я устроилась на работу в компании Amazon, мне не терпелось узнать, как именно работают эти «беспилотные» автоматизированные развертывания.

Безопасное непрерывное развертывание в Amazon

За время своей работы в Amazon я увидела собственными глазами, как в этой компании настраивают конвейеры для быстрого и безопасного непрерывного развертывания. Я по достоинству оценила то, как методики обеспечения безопасности непрерывного развертывания помогают разработчикам экономить время, освобождая их от работы над развертываниями. Как правило, отправив рабочий код в основную ветвь репозитория исходного кода для своего сервиса, я сразу забываю об этом и перехожу к следующей задаче, в то время как конвейер моей команды переносит изменение в рабочую среду. Выпуск изменения кода в рабочем сервисе полностью автоматизирован конвейером, то есть я или какой-либо другой разработчик в последний раз работаем с фрагментом кода, когда он объединяется в репозиторием исходного кода.
 
Моя команда настроила в этом конвейере автоматические действия для безопасного развертывания изменений в рабочей среде, чтобы нам не приходилось наблюдать за каждым развертыванием. Конвейер проводит ряд тестов и проверок безопасности развертывания новых изменений. Эти автоматические действия не дают дефектам, влияющим на клиентов, достичь рабочей среды и ограничивают последствия тех дефектов, которые все-таки попадут в нее. Как разработчик, я могу быть уверена, что конвейер осторожно и безопасно развернет изменение в рабочей среде, и мне не придется наблюдать на ним.

Переход на непрерывную доставку

Компания Amazon не с самого начала практиковала непрерывную доставку, и ее разработчикам приходилось тратить часы или даже дни на управление развертыванием кода в рабочей среде. Мы внедрили непрерывную доставку в масштабах компании, чтобы автоматизировать и стандартизировать развертывание программного обеспечения, а также сократить время переноса изменений в рабочую среду. Совершенствование процедуры выпуска происходило постепенно. Мы определили риски, связанные с развертыванием, и нашли способы снижения этих рисков благодаря новым автоматическим средствам обеспечения безопасности в конвейерах. Мы продолжаем совершенствовать процедуру выпуска, выявляя новые риски и новые способы повышения безопасности развертывания. Подробнее о том, как мы перешли на непрерывную доставку и как мы продолжаем совершенствовать эту процедуру, рассказывается в статье Builders' Library Непрерывная доставка – возможность работать быстрее.

Четыре этапа конвейера

В этой статье мы рассмотрим этапы, которые проходит код в конвейере Amazon до развертывания в рабочей среде. Работа типичного конвейера непрерывной доставки делится на четыре основных этапа: получение исходного кода, сборку, тестирование и развертывание в рабочей среде. Мы подробно изучим, что происходит на каждом из этих этапов с типичным сервисом AWS, и рассмотрим пример настройки типичного конвейера для сервиса AWS.

Исходный код и сборка

На приведенной ниже схеме представлен обзор этапов получения исходного кода и сборки в типичном конвейере для сервиса AWS.

Исходный код в конвейерах

Конвейеры в компании Amazon автоматически проверяют и безопасно развертывают в рабочей среде все изменения исходного кода, а не только изменения кода приложений. Они могут проверять и развертывать изменения таких компонентов, как статические ресурсы веб-сайтов, инструменты, тесты, инфраструктура, конфигурация и базовая операционная система (ОС) приложения. Версии всех этих изменений контролируются в отдельных репозиториях исходного кода. Зависимости исходного кода, например библиотеки, языки программирования и параметры (такие, как идентификаторы AMI), автоматически обновляются до новейшей версии как минимум раз в неделю.

Эти компоненты развертываются в отдельных конвейерах с использованием тех же механизмов защиты (например, автоматического отката), что и при развертывании кода приложений. Например, значения конфигурации для сервиса, которые могут меняться во время исполнения (такие, как ограничения на скорость API и флаги функций), автоматически развертываются в выделенном конвейере конфигурации. Изменения исходного кода автоматически откатываются, если они вызывают какие-либо проблемы в работе сервиса (например, невозможность проанализировать файл конфигурации).

Для типичного микросервиса могут использоваться конвейер кода приложения, конвейер инфраструктуры, конвейер исправлений для ОС, конвейер флагов функций или конфигурации и конвейер инструментов для операторов. Использование нескольких конвейеров для одного микросервиса помогает быстрее развертывать изменения в рабочей среде. Изменения кода приложений, которые не проходят проверки на интеграцию и блокируют конвейер приложений, не влияют на другие конвейеры. Например, они не мешают изменениям кода инфраструктуры попасть в рабочую среду через конвейер инфраструктуры. Как правило, все конвейеры для одного и того же микросервиса очень похожи между собой. Например, в конвейере флагов функций используются те же методики безопасного развертывания, что и в конвейере кода приложений, так как неправильное изменение конфигурации флагов функций может повлиять на рабочую среду так же сильно, как и изменение кода приложения.

Проверка кода

Все изменения, поступающие в рабочую среду, сначала проходят проверку кода и должны быть утверждены участником команды разработчиков, прежде чем они будут добавлены в главную ветвь (то, что обычно называют main или trunk), после чего конвейер автоматически запускается. Конвейер гарантирует, что все изменения, записанные в главную ветвь, проходят обязательную проверку кода и утверждаются участниками команды разработчиков сервиса, ответственными за этот конвейер. Конвейер запрещает развертывание всех непроверенных изменений.

Благодаря полностью автоматизированным конвейерам проверка кода и его утверждение ответственным инженером становятся последними действиями, выполняемыми вручную перед развертыванием в рабочей среде, так что этот этап крайне важен. Проверяющие оценивают правильность кода, а также определяют, можно ли безопасно развернуть это изменение в рабочей среде. Они проверяют, прошел ли код все необходимые тесты (модульные тесты, проверки на интеграцию и пробный запуск), предусмотрены ли надлежащие инструменты для мониторинга развертывания и можно ли будет выполнить безопасный откат изменения. В некоторых командах используются специальные контрольные списки, как в приведенном ниже примере. Они автоматически добавляются в каждую проверку кода соответствующей команды для явной проверки рисков, связанных с безопасностью развертывания.

Пример контрольного списка для проверки кода

## Testing
[ ] Did you write new unit tests for this change?
[ ] Did you write new integration tests for this change?

Include the test commands you ran locally to test this change:
```
mvn test && mvn verify
```

## Monitoring
[ ] Will this change be covered by our existing monitoring?
 (no new canaries/metrics/dashboards/alarms are required)
[ ] Will this change have no (or positive) effect on resources and/or limits?
 (including CPU, memory, AWS resources, calls to other services)
[ ] Can this change be deployed to Prod without triggering any alarms?

## Rollout
[ ] Can this change be merged immediately into the pipeline upon approval?
[ ] Are all dependent changes already deployed to Prod?
[ ] Can this change be rolled back without any issues after deployment to Prod?

Сборка и модульное тестирование

На этапе сборки код компилируется и проходит модульное тестирование. На разных языках и в разных командах могут использоваться различные инструменты и логика сборки. Например, разработчики могут выбирать платформы модульного тестирования, инструменты для контроля качества кода и средства статического анализа, наиболее подходящие для их команд. Кроме того, они могут выбирать конфигурацию этих инструментов, например минимальное допустимое покрытие кода на платформе модульного тестирования. Инструменты и типы проводимых тестов также зависят от типа кода, развертываемого в конвейере. Например, модульные тесты используются для кода приложений, а средства контроля качества – для шаблонов типа «инфраструктура как код». Все сборки запускаются без доступа к сети, чтобы изолировать их и способствовать их воспроизводимости. Как правило, модульные тесты имитируют (симулируют) все вызовы зависимостей (например, других сервисов AWS), совершаемые API-интерфейсами. Взаимодействие с настоящими (не имитированными) зависимостями испытывается в ходе интеграционных тестов на более позднем этапе конвейера. В отличие от тестов интеграции, модульные тесты с имитацией зависимостей дают возможность опробовать крайние случаи (например, непредвиденные ошибки, возвращаемые вызовами API) и обеспечить корректную обработку ошибок в коде. По завершении сборки скомпилированный код упаковывается и подписывается. 

Тестовые развертывания в промежуточных средах

Перед развертыванием в рабочей среде конвейер развертывает и проверяет изменения в нескольких промежуточных средах, например альфа-, бета- и гамма-среде. Альфа- и бета-среда используются для проверки работоспособности кода путем выполнения функциональных тестов API и комплексного тестирования интеграции. В гамма-среде проверяется не только работоспособность кода, но и возможность его безопасного развертывания в рабочей среде. Гамма-среда максимально приближена к рабочей среде. В частности, в ней используются те же конфигурация развертывания, средства мониторинга и оповещения, а также непрерывные процедуры пробного запуска, что и в рабочей среде. Кроме того, гамма-среда развертывается в нескольких регионах AWS, чтобы выявить все потенциальные проблемы, вызванные региональными различиями. 

Интеграционные тесты

Интеграционные тесты помогают нам испытывать сервис с точки зрения клиентов в рамках конвейера. Эти тесты задействуют весь комплекс сервиса, вызывая настоящие API, работающие на реальной инфраструктуре, на каждом промежуточном этапе для всех возможных сценариев работы клиентов. Цель интеграционного тестирования – выявить непредвиденное или некорректное поведение сервиса перед развертыванием в рабочей среде.

Для модульных тестов используются имитированные зависимости, а в ходе имитационного тестирования применяется промежуточная среда, которая вызывает реальные зависимости, проверяя предыдущие предположения о том, как они работают. Интеграционные тесты проверяют поведение отдельных API с разными входными данными. Кроме того, они полностью проверяют рабочие процессы, объединяющие несколько API, например создание, описание и последующее применение нового ресурса.

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

Проверка обратной совместимости и однокомпонентное тестирование

Перед развертыванием в рабочей среде необходимо убедиться, что новый код совместим с предыдущими версиями и его можно безопасно развернуть наряду с текущим кодом. Например, нам нужно убедиться, что новый код не записывает данные в формате, который не может быть расшифрован текущим кодом. На этапе однокомпонентного тестирования новый код внедряется в простейшую единицу развертывания в гамма-среде, например на одну виртуальную машину, в один контейнер или в небольшую долю вызовов функций AWS Lambda. При однокомпонентном развертывании в остальных частях гамма-среды на некоторое время (например, 30–60 минут) остается текущий код. Нет необходимости специально направлять трафик в тестируемый компонент. Вы можете добавить его к тому же балансировщику нагрузки или опрашивать ту же очередь, что и в других компонентах гамма-среды. Например, в гамма-среде, которая состоит из десяти контейнеров, обслуживаемых балансировщиком нагрузки, выбранный компонент получает 10 % трафика, создаваемого при пробном запуске. В однокомпонентном развертывании отслеживаются коэффициент успешного прохождения тестов и метрики сервиса, чтобы определить последствия такого развертывания или использования «смешанной» группы компонентов.

На приведенной ниже схеме показано состояние гамма-среды после развертывания нового кода на этапе однокомпонентного тестирования, но до его развертывания в остальных компонентах гамма-среды: 

Нам также необходимо убедиться, что новый код имеет обратную совместимость с нашими зависимостями, например если изменение необходимо вносить в микросервисы в определенном порядке. Как правило, микросервисы в промежуточных средах вызывают рабочие адреса сервисов, принадлежащих другим командам, например Amazon Simple Storage Service (S3) или Amazon DynamoDB, но вызывают промежуточные адреса других микросервисов этой команды на том же этапе. Например, микросервис A вызывает микросервис B той же команды в гамма-среде, но вызывает рабочий адрес сервиса Amazon S3.

В некоторых конвейерах также повторно выполняются интеграционные тесты на отдельном этапе проверки обратной совместимости (это называется зета-тестированием). Для этого используется отдельная среда, в которой каждый микросервис вызывает только рабочие адреса, чтобы убедиться, что новые изменения совместимы с кодом разных микросервисов, развернутым в рабочей среде. Например, микросервис A в зета-среде вызывает рабочий адрес микросервиса B и рабочий адрес Amazon S3.

Описание стратегий записи и развертывания обратно-совместимых изменений представлено в статье Builders' Library Обеспечение безопасности отката во время развертывания

Рабочие развертывания

Наша главная цель для рабочих развертываний в компании AWS – предотвратить отрицательное влияние на несколько регионов одновременно и на несколько зон доступности в одном регионе. Ограничение области действия каждого развертывания по отдельности ограничивает потенциальное влияние неудачных развертываний на клиентов и предотвращает воздействие на несколько зон доступности или регионов. Чтобы ограничить область автоматического развертывания, мы разделили рабочий этап конвейера на несколько стадий и создаем несколько развертываний для отдельных регионов. Команды разделяют региональные развертывания на еще меньшие развертывания для отдельных зон доступности или для отдельных внутренних сегментов сервиса (так называемых ячеек) в конвейере, чтобы еще больше ограничить потенциальную область влияния неудачного развертывания в рабочей среде.

Распределенное по времени развертывание

Каждой команде необходимо поддерживать баланс между безопасностью небольших развертываний и скоростью доставки изменений клиентам во всех регионах. Поочередное развертывание изменений в 24 регионах или 76 зонах доступности через конвейер обеспечивает минимальный риск широкого влияния, но предоставление изменения клиентам по всему миру может занять несколько недель. Мы обнаружили, что объединение развертываний в «волны» увеличивающегося размера, как в рабочем конвейере из предыдущего примера, помогает достичь оптимального баланса между риском и скоростью развертывания. На каждой волне конвейера выполняется развертывание в группе регионов, и изменения передаются от волны к волне. Новые изменения могут поступать на рабочий этап конвейера в любой момент. Когда ряд изменений перейдет с первого на второй этап 1-й волны, следующий набор изменений из гамма-среды переводится на первый этап 1-й волны, чтобы у нас не оставалось больших пакетов изменений, ожидающих развертывания в рабочей среде.

Первые две волны в конвейере придают больше всего уверенности в изменении: на первой волне изменения развертываются в регионе с небольшим количеством запросов, чтобы ограничить возможное влияние первого развертывания нового изменения в рабочей среде. В этой волне развертывание выполняется только по одной зоне доступности (или ячейке) из этого региона, чтобы осторожно развернуть изменение во всем регионе. Во второй волне выполняется развертывание по одной зоне доступности (или ячейке) из этого региона с большим количеством запросов, где клиенты наверняка будут пользоваться всеми новыми ветвями кода, что позволяет должным образом оценить изменения.

Когда мы будем полностью уверены в безопасности изменения после развертывания первых волн конвейера, мы сможем параллельно развертывать изменения во все большем количестве регионов в рамках одной волны. Например, в предыдущем примере рабочий конвейер развертывает изменения в трех регионах во время 3-й волны, затем в 12 регионах во время 4-й волны, а потом во всех остальных регионах во время 5-й волны. Конкретное количество и выбор регионов в каждой из этих волн, а также количество волн в конвейере команды разработчиков сервиса зависят от способов использования и масштабов определенного сервиса. Дальнейшие волны конвейера также помогают нам предотвратить отрицательное влияние на несколько зон доступности в том же регионе. При параллельном развертывании в нескольких регионах для каждого из них применяется такой же осторожный подход, как и в первых волнах. На каждом этапе волны выполняется развертывание только в одной зоне доступности или ячейке из каждого соответствующего региона.

Однокомпонентные и последовательные развертывания

Развертывание каждой рабочей волны начинается с однокомпонентной реализации. Как и при однокомпонентном развертывании в гамма-среде, на каждом этапе рабочего однокомпонентного развертывания новый код внедряется в один элемент (одну виртуальную машину, один контейнер или небольшую долю вызовов функций Lambda) каждого региона или каждой зоны доступности из соответствующей волны. Однокомпонентное развертывание в рабочей среде сводит к минимуму потенциальное влияние изменений на волну, изначально ограничивая количество запросов, обслуживаемых новым кодом в этой волне. Как правило, тестируемый компонент обслуживает не более 10 % запросов для соответствующего региона или зоны доступности. Если изменение оказывает отрицательное влияние в одном компоненте, конвейер автоматически выполняет откат и не распространяет этого изменение на остальные этапы рабочего развертывания.

После однокомпонентного развертывания большинство команд выполняют последовательное развертывание в основной рабочей группе соответствующей волны. Последовательное развертывание гарантирует, что ресурсов сервиса хватает для обработки рабочей нагрузки в масштабах развертывания. Оно контролирует скорость внедрения нового кода в сервис (то есть начала обработки рабочего трафика), чтобы ограничить последствия изменений. При типичном последовательном развертывании в регионе не более 33 % компонентов сервиса в этом регионе (контейнеров, вызовов Lambda или программ, запущенных на виртуальных машинах) заменяется на новый код.

Во время развертывания система сначала выбирает исходную группу, включающую до 33 % компонентов, код которых будет заменен на новый. Во время замены как минимум 66 % ресурсов сервиса остаются работоспособными и обрабатывают запросы. Все сервисы масштабируются с расчетом на потерю зоны доступности в соответствующем регионе, поэтому мы можем быть уверены, что сервис по-прежнему может справиться с рабочей нагрузкой при текущей мощности. Когда система развертывания определит, что компонент из исходной группы прошел проверки работоспособности, можно заменить код в одном из оставшихся компонентов и так далее. При этом как минимум 66 % ресурсов сервиса всегда остаются доступными для обработки запросов. Чтобы еще больше ограничить последствия изменений, в некоторых конвейерах одновременно развертывается не более 5 % компонентов. Однако затем выполняется быстрый откат, при котором система заменяет по 33 % компонентов на старый код.

На приведенной ниже схеме показано состояние рабочей среды в процессе последовательного развертывания. Новый код был развернут на этапе однокомпонентного развертывания и в первой партии основной рабочей группы. Еще один пакет компонентов был удален из балансировщика нагрузки и останавливается для замены.

Мониторинг метрик и автоматический откат

Как правило, для автоматического развертывания в конвейере не требуется участие разработчика, который бы активно наблюдал за обновлением каждого компонента в рабочей среде, проверял метрики и вручную выполнял откат при возникновении проблем. Эти развертывания полностью автоматизированы. Система развертывания следит за оповещениями, чтобы знать, когда нужно автоматически выполнить откат развертывания. Откат восстанавливает в среде образ контейнера, пакет развертывания функции AWS Lambda или пакет внутреннего развертывания, который был установлен до этого. Наши пакеты внутреннего развертывания подобны образам контейнеров, так как они не подлежат изменению, и для проверки их целостности используется контрольная сумма.

Как правило, для каждого микросервиса в каждом регионе создается оповещение высокой важности, которое активируется по достижении пороговых значений метрик, влияющих на клиентов сервиса (например, частоты сбоев и высокой задержки), и метрик работоспособности системы (например, использования ЦП), как показано в приведенном ниже примере. Это оповещение высокой важности информирует дежурного инженера и активирует автоматический откат сервиса, если выполняется развертывание. Как правило, к тому моменту, когда дежурный инженер получает уведомление и приступает к изучению проблемы, откат уже выполняется.

Пример оповещения высокой важности для микросервиса

ALARM("FrontEndApiService_High_Fault_Rate") OR
ALARM("FrontEndApiService_High_P50_Latency") OR
ALARM("FrontEndApiService_High_P90_Latency") OR
ALARM("FrontEndApiService_High_P99_Latency") OR
ALARM("FrontEndApiService_High_Cpu_Usage") OR
ALARM("FrontEndApiService_High_Memory_Usage") OR
ALARM("FrontEndApiService_High_Disk_Usage") OR
ALARM("FrontEndApiService_High_Errors_In_Logs") OR
ALARM("FrontEndApiService_High_Failing_Health_Checks")

Изменения, внесенные при развертывании, могут влиять как на предыдущие, так и на последующие микросервисы. По этой причине система развертывания должна отслеживать оповещения высокой важности как для развертываемого микросервиса, так и для других микросервисов той же команды, чтобы знать, когда следует выполнить откат. Развернутые изменения также могут влиять на метрики непрерывного пробного запуска, поэтому системе развертывания также необходимо отслеживать неудачные пробные запуски. Для автоматического отката во всех этих зонах потенциального влияния разработчики создают сборные оповещения высокой важности, отслеживаемые системой развертывания. Сборные оповещения высокой важности объединяют состояние всех оповещений высокой важности для отдельных микросервисов команды и состояние оповещений о пробных запусках в единое состояние, как в приведенном ниже примере. Если какое-либо из оповещений высокой важности для микросервисов команды перейдет в аварийное состояние, выполняется автоматический откат всех текущих развертываний для всех микросервисов команды в этом регионе.

Пример сборного оповещения высокой важности для отката

ALARM("FrontEndApiService_High_Severity") OR
ALARM("BackendApiService_High_Severity") OR
ALARM("BackendWorkflows_High_Severity") OR
ALARM("Canaries_High_Severity")

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

Пример оповещения для отката однокомпонентного развертывания

ALARM("High_Severity_Aggregate_Rollback_Alarm") OR
ALARM("FrontEndApiService_OneBox_High_Fault_Rate") OR
ALARM("FrontEndApiService_OneBox_High_P50_Latency") OR
ALARM("FrontEndApiService_OneBox_High_P90_Latency") OR
ALARM("FrontEndApiService_OneBox_High_P99_Latency") OR
ALARM("FrontEndApiService_OneBox_High_Cpu_Usage") OR
ALARM("FrontEndApiService_OneBox_High_Memory_Usage") OR
ALARM("FrontEndApiService_OneBox_High_Disk_Usage") OR
ALARM("FrontEndApiService_OneBox_High_Errors_In_Logs") OR
ALARM("FrontEndApiService_OneBox_Failing_Health_Checks")

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

Время выпекания

Иногда отрицательные последствия развертывания становятся заметны не сразу. Это так называемое замедленное воздействие. То есть проблема не становится заметна сразу после развертывания, особенно если сервис при этом находится под низкой нагрузкой. К тому моменту, как последствия станут заметны в первом регионе, перенос изменения на следующий этап конвейера сразу после развертывания может повлиять уже на несколько регионов. Передача изменения на следующий этап рабочего развертывания откладывается на время выпекания, заданное для соответствующего этапа конвейера. В это время конвейер продолжает отслеживать сборное оповещение высокой важности для этой команды на предмет замедленного воздействия после завершения развертывания и до перехода на следующий этап.

Чтобы рассчитать время выпекания развертывания, необходимо учитывать баланс между риском масштабных последствий поспешного внесения изменений в нескольких регионах и скоростью предоставления этих изменений клиентам по всему миру. Мы обнаружили, что наилучший баланс достигается, если для ранних волн конвейера используется продолжительное время выпекания, в течение которого мы можем убедиться в безопасности изменения, а последующие волны внедряются быстрее. Наша цель – свести к минимуму риск воздействия на несколько регионов. Так как большинство развертываний не находятся под постоянным наблюдением участников команды, для типичного конвейера по умолчанию задается умеренное время выпекания, и развертывание изменений во всех регионах занимает четыре-пять рабочих дней. В более крупных или важных сервисах используется еще более умеренное время выпекания и развертывания изменений в конвейерах.

Типичный конвейер ожидает как минимум один час после каждого однокомпонентного развертывания, как минимум 12 часов после первой региональной волны и как минимум два-четыре часа после каждой из последующих региональных волн. Для отдельных регионов, зон доступности и ячеек в каждой волне устанавливается дополнительное время выпекания. Время выпекания включает требования к ожиданию определенного количества точек данных в метриках команды (например, «дождаться отправки как минимум 100 запросов к API создания»). Это гарантирует, что было совершено достаточно запросов для полного испытания кода. В течение всего времени выпекания, если сборное оповещение высокой важности для команды перейдет в аварийное состояние, выполняется автоматический откат развертывания.

В некоторых случаях (хотя они крайне редки) срочное изменение (например, исправление системы безопасности или устранение крупномасштабной проблемы, влияющей на доступность сервисов) требуется предоставить клиентам быстрее, чем обычно выполняются выпекание и развертывание изменений в конвейере. В таких случаях время выпекания для конвейера можно сократить, чтобы ускорить развертывание, но для этого требуется тщательная проверка изменения. В таких случаях мы задействуем главных инженеров организации. Участники команды должны проверить изменение кода, а также его срочность и риск отрицательных последствий, заручившись поддержкой высококвалифицированных разработчиков, специализирующихся на эксплуатационной безопасности. Изменение проходит те же этапы конвейера, что и обычно, но переход к следующему этапу происходит быстрее. Мы контролируем риск при быстром развертывании, ограничивая изменения, обрабатываемые в конвейере на данный момент, чтобы разрешить только минимум изменений кода, необходимых для устранения актуальной проблемы, и обеспечив активное наблюдение за развертыванием.

Оповещения и ограничения интервала времени

Конвейер предотвращает автоматическое развертывание в рабочей среде, если существует повышенный риск отрицательных последствий. Конвейер использует ряд «ограничителей», которые оценивают риск, связанный с развертыванием. Например, автоматическое развертывание нового изменения в рабочей среде при наличии актуальной проблемы в среде может усугубить последствия или сделать их более долговременными. Прежде чем приступать к новому развертыванию на каком-либо рабочем этапе, конвейер проверяет сборное оповещение высокой важности, чтобы определить наличие актуальных проблем. Если оповещение находится в аварийном состоянии, конвейер останавливает внедрение изменения. Конвейеры также могут проверять оповещения в масштабах организации, например крупномасштабное оповещение о событиях, которое указывает на масштабное влияние на системы другой команды и предотвращает запуск нового развертывания, которое могло бы усугубить общие последствия. Разработчики могут обходить эти ограничения развертывания, если требуется развернуть изменение в рабочей среде для восстановления после проблемы высокой важности.

Для конвейера также задается набор временных интервалов, определяющих, когда разрешается начать развертывание. При настройке временных интервалов необходимо поддерживать баланс между двумя причинами риска, связанного с развертыванием. С одной стороны, очень короткие интервалы времени могут приводить к накоплению изменений в конвейере за остальное время. Это повышает вероятность того, что одно из изменений в следующем развертывании окажет отрицательное влияние, когда начнется новый временной интервал. С другой стороны, очень продолжительные интервалы, распространяющиеся на нерабочее время, повышают риск продолжительного воздействия неудачного развертывания. В нерабочее время сложнее связаться с дежурным инженером, чем в течение рабочего дня, когда он и другие участники команды находятся на своих рабочих местах. В обычное рабочее время можно быстрее связаться с участниками команды после неудачного развертывания, если потребуется восстановление вручную.

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

Конвейеры как код

Типичная команда разработчиков сервиса AWS работает со множеством конвейеров для развертывания различных микросервисов и исходного кода разных типов (кода приложений, кода инфраструктуры, исправлений для ОС и т. д.). Каждый конвейер включает множество этапов развертывания для постоянно растущего количества регионов и зон доступности. По этой причине команде приходится управлять множеством конфигураций в системах конвейеров, развертывания и оповещения, а также прилагать много усилий, чтобы следовать новейшим рекомендациям и обслуживать новые регионы и зоны доступности. За последние несколько лет мы перешли на модель «конвейеры как код», чтобы обеспечить удобную и согласованную настройку безопасных и актуальных конвейеров, моделируя эту конфигурацию в коде. Наш собственный инструмент для работы с конвейерами в виде кода использует централизованный список регионов и зон доступности, чтобы легко добавлять новые регионы и зоны доступности в конвейеры в масштабах всей компании AWS. Этот инструмент также дает командам возможность моделировать конвейеры путем наследования, определения общей конфигурации для всех конвейеров команды в родительском классе (например, списка регионов для разных волн и времени выпекания для каждой из них) и определения конфигурации конвейера для всех микросервисов в виде подкласса, наследующего всю общую конфигурацию.

Выводы

В компании Amazon мы постепенно совершенствуем методики автоматизированного развертывания с учетом баланса между безопасностью и скоростью развертывания. В то же время мы стремимся свести к минимуму затраты времени разработчиков на контроль развертывания. Автоматизация обеспечения безопасности развертывания в процессе выпуска при помощи комплексного промежуточного тестирования, автоматического отката и распределенного по времени развертывания в рабочей среде помогает свести к минимуму потенциальное влияние развертывания на рабочую среду. Это означает, что разработчикам не приходится целенаправленно наблюдать за развертыванием в рабочей среде.

Благодаря полностью автоматизированным конвейерам разработчики могут легко проверять свой код и подтверждать, что он готов к развертыванию в рабочей среде. После добавления изменения в репозиторий исходного кода разработчик может переходить к следующей задаче, забыв про развертывание, и быть уверен, что конвейер безопасно и осторожно доставит изменение в рабочую среду. Автоматизированный конвейер развертывает изменения в рабочей среде несколько раз в день, поддерживая баланс между безопасностью и скоростью. Благодаря моделированию методик непрерывной доставки с помощью кода разработчикам сервисов AWS стало проще, чем когда-либо, автоматически и безопасно развертывать изменения кода.

Дополнительные сведения

Подробнее о том, как Amazon увеличивает безопасность и доступность сервисов, при этом повышая удовлетворенность клиентов и продуктивность разработчиков, рассказывается в статье Непрерывная доставка – возможность работать быстрее

Описание стратегий записи и развертывания обратно-совместимых изменений представлено в статье Builders' Library Обеспечение безопасности отката во время развертывания 


Об авторе

Клэр Лигуори занимает должность директора по разработке ПО в AWS. В настоящее время ее внимание направлено на улучшение интерфейса разработчика в AWS Container Services, создание инструментов на пересечении контейнеров и оптимизацию жизненного цикла разработки программного обеспечения: локальной разработки, инфраструктуры как кода, CI/CD, наблюдаемости и операций.