Всем привет. Уже в понедельник состоится первое занятие в новой группе курса «Backend разработчик на PHP». В связи с этим мы продолжаем публиковать полезный материал по теме. Начнем.
Как и Саймон Вордли, я считаю, что бессерверные вычисления – это крайне интересная область, в первую очередь из-за гранулированной системы оплаты (платите только тогда, когда выполняется ваш код), и вам не нужно беспокоиться об облуживании и подготовке серверов и контейнеров. Настолько, что я работаю с открытым PHP Runtime для Apache OpenWhisk, коммерческая версия которого доступна как одна из функций IBM Cloud.
Есть и другие бессерверные провайдеры, и AWS Lambda является лидером рынка, однако до недавнего времени поддержка PHP в нем была крайне громоздкой и неказистой. Это изменилось в конце 2018 года с новым runtime API от Lambda и поддержкой слоев.
Давайте посмотрим на практические аспекты бессерверного PHP на Lambda с Serverless Framework.
Исходный код простого Hello World находится в моем lambda-php репозитории на Github. Перейдите в раздел Notes, и мы можем продолжать.
API среды выполнения позволяет использовать любой runtime с Lambda. В некотором смысле это похоже на работу OpenWhisk, поскольку между бессерверной платформой и рантаймом есть HTTP API. Есть одно большое отличие, которое заключается в том, что с Lambda рантайм отправляет запрос платформе, чтобы получить данные вызова, тогда как OpenWhisk вызывает конечную точку, которую должен обеспечивать рантайм. Более подробная информация содержится в статье Майкла Муссы в блоге AWS, которая и вдохновила меня на эту работу.
Чтобы приступить к работе, нам понадобится среда выполнения PHP для Lambda. Она будет состоять из исполняемого файла PHP, кода на PHP для вызова бессерверной функции и файла
Все файлы мы положим в директорию
Нам нужен исполняемый файл PHP, который будет запускаться внутри контейнеров Lambda. Самый простой способ это сделать – это скомпилировать его на той же платформе, что и Lambda, поэтому мы будем использовать ЕС2. Статья Майкла поясняет как это сделать, и эти команды я обернул в скрипт compile_php.sh, чтобы потом скопировать его в экземпляр ЕС2, запустить и скопировать исполняемый файл обратно на свой компьютер:
Этот подход сделает его хорошо воспроизводимым, и, надеюсь, его будет просто обновлять до новых версий PHP.
Поскольку мы используем runtime API, нам понадобится
По сути нам нужно находиться в цикле и вызывать конечную точку
AWS предоставляет пример в BASH с использованием curl:
Мы хотим сделать то же самое в PHP, и хотя я мог бы написать это самостоятельно, но Парикшит Агнихотри уже опередил меня в PHP-Lambda-Runtime/runtime.php, поэтому мы просто скопируем это в
Файл
Вот и все. Теперь у нас есть три файла в layer/php:
В итоге все это станет PHP Layer (слоем) в нашем Lambda приложении.
Serverless Framework обеспечивает повторяемую конфигурацию и разворачивание бессерверного приложения. Я поклонник этой концепции и мне хочется использовать больше таких инструментов. Мы будем использовать Serverless Framework для нашего PHP Hello World.
Поскольку удобного шаблона для приложений на PHP в Serverless Framework нет, мы просто создадим файл
Для начала, самое базовое:
Мы назовем наше приложение
Runtime обычно является языком, на котором вы хотите, чтобы ваша функция выполнялась. Чтобы использовать
А еще вам понадобится
Поскольку в
Дальше давайте добавим наш слой к
Это создаст AWS слой и даст ему имя
Напишем функцию
Теперь мы можем написать нашу бессерверную PHP-функцию. Это необходимо вписать в файл
Функция берет информацию о событии и возвращает ассоциативный массив.
Чтобы сообщить Serverless Framework о развертывании нашей функции, нужно добавить следующее в файл
Serverless Framework поддерживает несколько функций для одного приложения. Каждая из них имеет имя , в данном случае
Наконец, мы также сообщаем функции о нашем слое PHP, чтобы он мог выполнять код на PHP.
Чтобы развернуть нашу функцию с её слоем, мы запускаем следующую команду:
При возможно продолжительном выполнении команды будет получен вывод, похожий на этот:
После развертывания, мы можем вызвать функцию используя команду:
И все готово!
Благодаря новым слоям и API среды выполнения теперь можно легко запускать бессерверные функции PHP в Lambda. Это хорошая новость для PHP разработчиков, привязанных к AWS.
Ждем ваши комментарии, друзья!
Как и Саймон Вордли, я считаю, что бессерверные вычисления – это крайне интересная область, в первую очередь из-за гранулированной системы оплаты (платите только тогда, когда выполняется ваш код), и вам не нужно беспокоиться об облуживании и подготовке серверов и контейнеров. Настолько, что я работаю с открытым PHP Runtime для Apache OpenWhisk, коммерческая версия которого доступна как одна из функций IBM Cloud.
Есть и другие бессерверные провайдеры, и AWS Lambda является лидером рынка, однако до недавнего времени поддержка PHP в нем была крайне громоздкой и неказистой. Это изменилось в конце 2018 года с новым runtime API от Lambda и поддержкой слоев.
Давайте посмотрим на практические аспекты бессерверного PHP на Lambda с Serverless Framework.
TL;DR
Исходный код простого Hello World находится в моем lambda-php репозитории на Github. Перейдите в раздел Notes, и мы можем продолжать.
PHP Runtime
API среды выполнения позволяет использовать любой runtime с Lambda. В некотором смысле это похоже на работу OpenWhisk, поскольку между бессерверной платформой и рантаймом есть HTTP API. Есть одно большое отличие, которое заключается в том, что с Lambda рантайм отправляет запрос платформе, чтобы получить данные вызова, тогда как OpenWhisk вызывает конечную точку, которую должен обеспечивать рантайм. Более подробная информация содержится в статье Майкла Муссы в блоге AWS, которая и вдохновила меня на эту работу.
Чтобы приступить к работе, нам понадобится среда выполнения PHP для Lambda. Она будет состоять из исполняемого файла PHP, кода на PHP для вызова бессерверной функции и файла
bootstrap
, как того требует платформа. Из этих трех вещей мы собираем слой. Слои могут переиспользоваться в разных аккаунтах, поэтому я удивлен, что AWS не предоставляет нам аккаунт PHP. Невероятно, но факт, они не используют PHP 7.3, поэтому нам придется построить свой собственный. Все файлы мы положим в директорию
layer/php
в нашем проекте. Построение исполняемого файла PHP
Нам нужен исполняемый файл PHP, который будет запускаться внутри контейнеров Lambda. Самый простой способ это сделать – это скомпилировать его на той же платформе, что и Lambda, поэтому мы будем использовать ЕС2. Статья Майкла поясняет как это сделать, и эти команды я обернул в скрипт compile_php.sh, чтобы потом скопировать его в экземпляр ЕС2, запустить и скопировать исполняемый файл обратно на свой компьютер:
$ export AWS_IP=ec2-user@{ipaddress}
$ export SSH_KEY_FILE=~/.ssh/aws-key.rsa
$ scp -i $SSH_KEY_FILE compile_php.sh $AWS_IP:doc/compile_php.sh
$ ssh -i $SSH_KEY_FILE -t $AWS_IP "chmod a+x compile_php.sh && ./compile_php.sh 7.3.0"
$ scp -i $SSH_KEY_FILE $AWS_IP:php-7-bin/bin/php layer/php/php
Этот подход сделает его хорошо воспроизводимым, и, надеюсь, его будет просто обновлять до новых версий PHP.
Bootstrapping
Поскольку мы используем runtime API, нам понадобится
bootstrap
файл. Такое имя задавать файла требует сама Lambda, он отвечает для вызов функции, соответствующим образов вызывая API в цикле. По сути нам нужно находиться в цикле и вызывать конечную точку
/next
, чтобы понимать, что вызывать следующим, вызвать ее, а затем отправить ответ в конечную точку /response
.AWS предоставляет пример в BASH с использованием curl:
while true
do
HEADERS="$(mktemp)"
# Get an event
EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
# Execute the handler function from the script
RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")
# Send the response
curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE"
done
Мы хотим сделать то же самое в PHP, и хотя я мог бы написать это самостоятельно, но Парикшит Агнихотри уже опередил меня в PHP-Lambda-Runtime/runtime.php, поэтому мы просто скопируем это в
layer/php/runtime.php
. В своей версии я сделал несколько изменений, добавил json_encoding и улучшил обработчик ошибок. Файл
layer/php/bootstrap
очень простой, и все что от него требуется, это запускать исполняемый файл PHP с этим файлом:#!/bin/sh
cd $LAMBDA_TASK_ROOT
/opt/php /opt/runtime.php
Вот и все. Теперь у нас есть три файла в layer/php:
php
– исполняемый файл PHP;runtime.php
– Рабочий файл runtime API;bootstrap
– требуемый Lambda, файл.
В итоге все это станет PHP Layer (слоем) в нашем Lambda приложении.
Настройка Serverless Framework
Serverless Framework обеспечивает повторяемую конфигурацию и разворачивание бессерверного приложения. Я поклонник этой концепции и мне хочется использовать больше таких инструментов. Мы будем использовать Serverless Framework для нашего PHP Hello World.
Поскольку удобного шаблона для приложений на PHP в Serverless Framework нет, мы просто создадим файл
serverless.yml
в каталоге с нашим проектом.Для начала, самое базовое:
service: php-hello-world
provider:
name: aws
runtime: provided
region: eu-west-2
memorySize: 128
Мы назовем наше приложение
php-hello-world
и используем AWS в качестве провайдера. Поскольку я в Великобритании, я установил регион Лондон. Нам не нужно много памяти, поэтому 128 МБ будет достаточно.Runtime обычно является языком, на котором вы хотите, чтобы ваша функция выполнялась. Чтобы использовать
runtime API
, который будет выполнять наш bootstrap
файл, вы установите это поле в provided.А еще вам понадобится
.gitignore
файл, содержащий:.serverless
Поскольку в
git
нам эта директория не нужна.Дальше давайте добавим наш слой к
serverless.yml
, добавив:layers:
php:
path: layer/php
Это создаст AWS слой и даст ему имя
PhpLambdaLayer
, на которое мы сможем ссылаться в нашей функции.Напишем функцию
Hello World
Теперь мы можем написать нашу бессерверную PHP-функцию. Это необходимо вписать в файл
handler.php
: <?php
function hello($eventData) : array
{
return ["msg" => "hello from PHP " . PHP_VERSION];
}
Функция берет информацию о событии и возвращает ассоциативный массив.
Чтобы сообщить Serverless Framework о развертывании нашей функции, нужно добавить следующее в файл
serverless.yml
: functions:
hello:
handler: handler.hello
layers:
- {Ref: PhpLambdaLayer}
Serverless Framework поддерживает несколько функций для одного приложения. Каждая из них имеет имя , в данном случае
hello
, и, в нашем случае, обработчик, который представляет собой имя файла без расширения, за которым следует точка, а затем имя функции в этом файле. Таким образом, обработчик handler.hello
означает, что мы запустим функцию hello()
в handler.php
.Наконец, мы также сообщаем функции о нашем слое PHP, чтобы он мог выполнять код на PHP.
Развертывание в Lambda
Чтобы развернуть нашу функцию с её слоем, мы запускаем следующую команду:
$ sls deploy
При возможно продолжительном выполнении команды будет получен вывод, похожий на этот:
Выполнение нашей функции
После развертывания, мы можем вызвать функцию используя команду:
$ sls invoke -f hello -l
И все готово!
Подведем итоги
Благодаря новым слоям и API среды выполнения теперь можно легко запускать бессерверные функции PHP в Lambda. Это хорошая новость для PHP разработчиков, привязанных к AWS.
Ждем ваши комментарии, друзья!
lubezniy
Правильно ли я понимаю, что с таким serverless-подходом (плата за выполнение кода), чтобы насолить какому-то сервису, его не надо даже ддосить, а просто организовать много запросов, чтобы счёт за хостинг стал астрономическим?
el_kex
Во-первых, стоимость идёт порядка 0.20USD за миллион запросов, либо 0,00001667 USD за превышение порога 400000 гигабайт-секунд (https://aws.amazon.com/ru/lambda/pricing/).
Во-вторых, доступ извне к тем же лямбдам обычно пускают, например, через амазоновский Route 53, где есть такая прекрасная вещь, как AWS Shield. Да и без него, ЕМНИП, Амазон даст знать (но это неточно).
Но, так или иначе, необлачный сервер при той же DDoS-атаке ляжет и перестанет предоставлять услугу. И тут тоже стоит подумать, что дороже: переплатить за лишние пару сотен миллионов вызовов или вообще не предоставлять сервис, теряя клиентов (деньги).
lubezniy
Насколько я понял, тарифицируются отдельно запросы, отдельно вычисления, и результаты расчётов следует суммировать.
Попробуем посчитать просто для 100 запросов с разными параметрами (чтобы они shield проходили) в секунду. Примем, что каждый запрос выполняется 10 мс, а памяти при этом заказано 128 МБайт (1/8 гига). Сумму платежа будем считать для упрощения за 30 дней. Соответственно в 30 днях у нас 30 * 24 * 3 600=2 592 000 секунд.
Сначала тарифицируем запросы. Умножаем количество секунд на 100, получаем 259 200 000 запросов. Вычитаем бесплатный (по приведённой ссылке) миллион запросов в месяц и считаем:
258 200 000 * 0,2 / 1 000 000 = 51,64 USD
Теперь считаем гигабайт-секунды. Для 259 200 000 запросов по 10 мс у нас получается 2 592 000 секунды; делим на 8 (у нас не гиг, а 128 Мб) и получаем 324 000 ГБ-секунд, что ещё укладывается в бесплатный лимит 400 тысяч; для 256 МБайт у меня получилась доплата порядка 4 USD.
Астрономической цену такую, конечно, вряд ли можно назвать. Но интересно, дороже или дешевле это обычного (так сказать, «серверного») хостинга по среднерыночным ценам?
el_kex
Не совсем. Вот выдержка из прайсинга:
Но даже по Вашим расчётам выходит что-то в рамках 100$ потерь за одну атаку. При этом:
lubezniy
100 запросов в секунду — это не DDoS-атака, а посещаемость относительно небольшого, но при этом раскрученного ресурса. Для DDoS количество запросов надо увеличить раз в 10 минимум.
el_kex
Хорошо, пусть даже отбивание атаки будет стоить порядка 1000 долларов. Если люди отбили эту атаку разово, это все равно оставит ресурс работающим за относительно небольшие деньги по меркам бизнеса, который конкуренты собрались ддосить. Дальше, имхо, если уж люди дошли до использования serverless, логично, что они, скорее всеоо, сделают выводы и подключат тот же экран Shield. Но в теории, при определенном стечении обстоятельств и вводных компанию таки можно разорить через Лямбды.
Кстати, ещё надо знать, что из функционала отдаётся лямбдами, а что классическими серверными скриптами. Отдавать монолит на лямбды — такая себе идея.
lubezniy
Может быть. Но вообще я думал не только про DDoS, но и про стоимость такого хостинга по сравнению с обычными. Если ресурс нормально крутится на, условно говоря, 20-баксовом обычном хостинге, а здесь получается 50 (хотя на старте вряд ли столько будет), не потребуется ли со временем перейти снова на классику?
el_kex
Тут уже суровая экономика включается. Для своего блога и площадки под мелкие сайд-проекты достаточно простой виртуалки на хостинге или того же AWS EC2. Лямбды становятся выгодными в Enterprise-сегменте, где реально важной становится гибкость расширения, а также высокая отказоустойчивость. В моём понимании, лямбды хороши в быстро растущем бизнесе в случае, когда по каким-то причинам нет SRE или DevOps практик, но хочется в гибкость и не падать на очередной условной распродаже.
Fortop
Небольшого по каким критериям?
100rps к ресурсам не за CloudFlare это очень недурной ресурс…
Что-то типа Хабра или покрупнее.
lubezniy
Ну по меркам всего Интернета у Хабра далеко не последняя, но и при этом далеко не самая высокая посещаемость в сравнению, скажем, с тем же Reddit или даже Пикабу — просто потому, что ресурс относительно специализированный. А про грандов вроде Яндекса или Google — с ними и так всё понятно. Поэтому таки предлагаю считать, что 100 rps — это относительно немного.