Всем привет. Уже в понедельник состоится первое занятие в новой группе курса «Backend разработчик на PHP». В связи с этим мы продолжаем публиковать полезный материал по теме. Начнем.



Как и Саймон Вордли, я считаю, что бессерверные вычисления – это крайне интересная область, в первую очередь из-за гранулированной системы оплаты (платите только тогда, когда выполняется ваш код), и вам не нужно беспокоиться об облуживании и подготовке серверов и контейнеров. Настолько, что я работаю с открытым 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.

Ждем ваши комментарии, друзья!

Комментарии (10)


  1. lubezniy
    23.05.2019 16:51
    +1

    Правильно ли я понимаю, что с таким serverless-подходом (плата за выполнение кода), чтобы насолить какому-то сервису, его не надо даже ддосить, а просто организовать много запросов, чтобы счёт за хостинг стал астрономическим?


    1. el_kex
      23.05.2019 18:07

      Во-первых, стоимость идёт порядка 0.20USD за миллион запросов, либо 0,00001667 USD за превышение порога 400000 гигабайт-секунд (https://aws.amazon.com/ru/lambda/pricing/).
      Во-вторых, доступ извне к тем же лямбдам обычно пускают, например, через амазоновский Route 53, где есть такая прекрасная вещь, как AWS Shield. Да и без него, ЕМНИП, Амазон даст знать (но это неточно).

      Но, так или иначе, необлачный сервер при той же DDoS-атаке ляжет и перестанет предоставлять услугу. И тут тоже стоит подумать, что дороже: переплатить за лишние пару сотен миллионов вызовов или вообще не предоставлять сервис, теряя клиентов (деньги).


      1. lubezniy
        23.05.2019 19:12

        Насколько я понял, тарифицируются отдельно запросы, отдельно вычисления, и результаты расчётов следует суммировать.
        Попробуем посчитать просто для 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.

        Астрономической цену такую, конечно, вряд ли можно назвать. Но интересно, дороже или дешевле это обычного (так сказать, «серверного») хостинга по среднерыночным ценам?


        1. el_kex
          23.05.2019 22:55
          +1

          Насколько я понял, тарифицируются отдельно запросы, отдельно вычисления, и результаты расчётов следует суммировать.


          Не совсем. Вот выдержка из прайсинга:
          Lambda засчитывает запрос при каждом исполнении кода в ответ на вызов или оповещение о событии, при этом учитываются и тестовые вызовы с консоли


          Но даже по Вашим расчётам выходит что-то в рамках 100$ потерь за одну атаку. При этом:

          1. Обычный хостинг просто «ляжет», хотя будет дешевле, разумеется :)
          2. Скорее всего стоимость DDoS-атаки будет больше, чем потери от неё у «принимающей стороны»


          1. lubezniy
            25.05.2019 08:23

            100 запросов в секунду — это не DDoS-атака, а посещаемость относительно небольшого, но при этом раскрученного ресурса. Для DDoS количество запросов надо увеличить раз в 10 минимум.


            1. el_kex
              25.05.2019 12:09

              Хорошо, пусть даже отбивание атаки будет стоить порядка 1000 долларов. Если люди отбили эту атаку разово, это все равно оставит ресурс работающим за относительно небольшие деньги по меркам бизнеса, который конкуренты собрались ддосить. Дальше, имхо, если уж люди дошли до использования serverless, логично, что они, скорее всеоо, сделают выводы и подключат тот же экран Shield. Но в теории, при определенном стечении обстоятельств и вводных компанию таки можно разорить через Лямбды.


              Кстати, ещё надо знать, что из функционала отдаётся лямбдами, а что классическими серверными скриптами. Отдавать монолит на лямбды — такая себе идея.


              1. lubezniy
                25.05.2019 19:37

                Может быть. Но вообще я думал не только про DDoS, но и про стоимость такого хостинга по сравнению с обычными. Если ресурс нормально крутится на, условно говоря, 20-баксовом обычном хостинге, а здесь получается 50 (хотя на старте вряд ли столько будет), не потребуется ли со временем перейти снова на классику?


                1. el_kex
                  26.05.2019 12:59

                  Тут уже суровая экономика включается. Для своего блога и площадки под мелкие сайд-проекты достаточно простой виртуалки на хостинге или того же AWS EC2. Лямбды становятся выгодными в Enterprise-сегменте, где реально важной становится гибкость расширения, а также высокая отказоустойчивость. В моём понимании, лямбды хороши в быстро растущем бизнесе в случае, когда по каким-то причинам нет SRE или DevOps практик, но хочется в гибкость и не падать на очередной условной распродаже.


            1. Fortop
              27.05.2019 11:10

              Небольшого по каким критериям?

              100rps к ресурсам не за CloudFlare это очень недурной ресурс…
              Что-то типа Хабра или покрупнее.


              1. lubezniy
                27.05.2019 11:25

                Ну по меркам всего Интернета у Хабра далеко не последняя, но и при этом далеко не самая высокая посещаемость в сравнению, скажем, с тем же Reddit или даже Пикабу — просто потому, что ресурс относительно специализированный. А про грандов вроде Яндекса или Google — с ними и так всё понятно. Поэтому таки предлагаю считать, что 100 rps — это относительно немного.