Хабрахабр

ServerLess PHP

В этот раз познакомились с концепцией Serverless, поговорили о её реализации в AWS, разобрали принципы работы, сборки и запуска, а также построили простой TG-бот на PHP на базе AWS Lambda. Накануне запуска курса «Backend-разработчик на PHP» мы провели традиционный открытый урок.

Преподаватель — Александр Пряхин, технический директор компании Westwing Russia.

Краткий экскурс в историю

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

Например, процессор. Что же мы вообще обычно виртуализируем? Можно виртуализировать сеть VPN. Ещё можно виртуализировать память, выделив определённые области памяти и сделав их доступными для одних пользователей и недоступными для других. И так далее.

Но есть и минусы, например, в своё время были проблемы с совместимостью. Виртуализация хороша тем, что у нас лучше утилизируются ресурсы и повышается производительность. Однако сейчас практически нет архитектур, которые были бы несовместимы с современными виртуальными машинами.

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

Если мы возьмём с вами стандартную виртуалку, она будет выглядеть примерно так:

И поверх всего этого добра крутятся наши виртуалки, в которых есть гостевая ОС, библиотеки и приложения. Во-первых, у нас есть железный сервер, во-вторых — операционная система, на который будет крутиться наш гипервизор. Если рассуждать логично, то мы видим некоторый overhead в наличии гостевой ОС, ведь по факту тратим лишние ресурсы.

Отказавшись от виртуальных машин и поставив поверх основной операционной системы систему управления контейнерами. Как можно решить проблему оверхеда? Тогда библиотеки внутри контейнера станут использовать ядро хоста операционной системы. Разумеется, наиболее популярная сейчас система — Docker Engine.

Таким образом мы уберём оверхед, однако ведь и Docker неидеален, и у него имеются свои проблемы и особенности работы, которые не всем нравятся.

Docker — это не микровиртуалка, с которой можно работать, как с виртуальной машиной, ведь контейнер на то и контейнер. Главное, что следует уяснить, — Docker и виртуалка — это разные подходы, и не нужно их приравнивать. Зато контейнер позволяет нам обеспечивать гибкость и совершенно иной подход к Continuous delivery, когда мы доставляем вещи до production и понимаем, что они уже протестированы и работают.

Облачные технологии

Это неплохое решение, однако сразу стоит сказать, что облака — это не серебряная пуля и не панацея от всех бед. С дальнейшим развитием виртуализации начали развиваться и облачные технологии. Здесь нельзя не вспомнить одну известную цитату:

«When I hear someone touting the cloud as a magic-bullet for all computing problems, I silently replace “cloud” with “clown” and carry on with a zen-like smile».
Amy Rich

И многим компаниям держать свой дата-центр с таким же SLA будет гораздо дороже, чем обслуживаться в облаке. Однако для компаний средней руки, которые желают получать определённый уровень сервиса и отказоустойчивости без огромных финансовых вливаний, облака — вполне себе вариант. Например, возможность в несколько кликов поднять виртуальную машину или сеть.
Да, есть ограничения, например 152-й Федеральный закон, запрещающий хранить персональные данные за рубежом, поэтому тот же Amazon при аудите перестанет нам подходить. Кроме того, мы можем использовать облака для своих нужд, ведь какие-то вещи они предоставляют буквально в несколько кликов мышки, что очень удобно. Многие облачные решения не портируются между собой, хотя те же S3-совместимые хранилища поддерживаются большинством провайдеров. Не стоит забывать и про Vendor-lock.

Чем меньше нужно знаний, тем больше будем платить. Облака предоставляют нам возможность без узконаправленных знаний получать разные уровни сервиса. На рисунке ниже Вы можете посмотреть на пирамиду, где снизу вверх отображено, образно говоря, убывание требований к техническим знаниям при использовании облака:

Serverless и FaaS (Function As A Service)

Подходы *aaS, перечисленные на пирамиде выше, уже привычны: IaaS (EC2, VDS), PaaS (Shared Hosting), SaaS (Office 365, Tilda). Serverless — достаточно молодой способ запуска скриптов в облаках, например, таких как AWS (в терминах AWS сервер реализуется в Lambda). И заключается данный подход в предоставлении пользователю готовой платформы для разработки, запуска и управления определённой функциональностью без необходимости самостоятельной подготовки и настройки. Так вот, Serverless — это реализация подхода FaaS.

Спрашивается: зачем за неё платить днём? Представьте, что у вас есть машина, которая занимается ночной обработкой документов, выполняя задачи с 00:00 до 6:00, а в остальные часы она простаивает. Вот эта тяга к оптимизации и желание тратить деньги только на то, что реально используешь, и привела к появлению FaaS. И почему бы не использовать свободные ресурсы на что-нибудь другое?

Это не означает, что за нашим скриптом нет сервера — он есть, но у нас, по сути, нет какого-либо конкретно выделенного ресурса, на котором будет запускаться наша Lambda. Serverless — это ресурс для выполнения кода и не более того. Например, Вы ничего не можете хранить в этой среде, вам всё нужно выносить. Когда мы запускаем наш скрипт, под него сразу же разворачивается микроинфраструктура, и это не ваша проблема в принципе — вы думаете лишь о том, чтобы у вас выполнился код, а больше вам ни о чём думать не надо.
Это требует, конечно же, определённого подхода к разработке вашего кода. Благо, любой Serverless-провайдер предоставляет возможность соединения с внешними системами. Если это данные, то нужна внешняя БД, если это логи, то внешний сервис логов, если это файлы, то внешнее файловое хранилище.

Для того же мира PHP это означает, к примеру, что про стандартный механизм сессий можно забыть. У Вас есть только код, вы работаете в парадигме Stateless, у вас нет состояния. В принципе, Вы можете построить даже свой Serverless, и недавно на Хабре была статья на эту тему.

Всё ложится на плечи платформы, за что Вы, собственно говоря, и платите деньги. Главная идея Serverless — инфраструктура не требует поддержки со стороны команды. Из минусов — Вы не контролируете среду выполнение и не знаете, где что выполняется.

Итак, Serverless:

  • не означает физическое отсутствие сервера;
  • не убийца виртуалок и Докера;
  • не хайп «здесь и сейчас».
    Serverless стоит включать в стек осознанно и обдуманно. Например, если Вам нужно быстренько проверить какую-нибудь гипотезу, не привлекая половину команды. Таким образом вы и получите Function As A Service. Функция будет реагировать на какие-то события, а поскольку есть реакция на события, эти события должны чем-то вызываться — для этого в том же AWS есть множество триггеров.

Особенности FaaS:

  • инфраструктура не требует настройки;
  • событийная модель «из коробки»;
  • Stateless;
  • масштабирование производится очень легко и выполняется автоматически под нужды пользователя.

AWS Lambda

Если тезисно, то она имеет следующие особенности:
— доступна с 2014 года;
— поддерживает из коробки Java, Node.js, Python, Go и кастомные среды выполнения;
— мы платим за:
количество вызовов;
время выполнения.
AWS Lambda: зачем оно надо:
Утилизация. Первая и общедоступная реализация FaaS — AWS Lambda. Сама по себе лямбда поднимается и работает очень быстро.
Функционал. Вы платите только за то время, когда сервис работает.
Скорость. Положить лямбду довольно-таки сложно. Лямбда имеет много возможностей по интеграции с сервисами AWS.
Производительность. И при желании этот лимит можно повысить, написав в поддержку. Параллельно может выполняться в зависимости от региона максимально от 1000 до 3000 экземпляров.

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

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

Если вы работали с Докером, то Докер-слой очень похож на слой в лямбда — некое квази-хранилище, в котором размещается наша необходимая обвязка. Если у нас есть кастомная среда выполнения, придётся размещать код в слое. Там у нас лежит исполняемый файл среды (если речь идёт о PHP, следует заранее разместить скомпилированный бинарник PHP), Bootstrap-файл лямбды (находится по умолчанию) и непосредственно вызываемые скрипты, которые будут выполняться.

С доставкой всё не так радужно:

Совсем некруто, что такое предлагают в официальной документации Амазона. То есть нам предлагают брать файлы с кодом, заливать в zip-архив, заливать в слой и запускать наш код.

Благо, добрые люди постарались и сделали несколько фреймворков, поэтому мы будем использовать Serverless framework, разработанный на Node.js и позволяющий управлять приложениями на базе AWS Lambda. Конечно, это не соответствует современным реалиям, и в воздухе запахло двухтысячными. Кроме того, когда мы говорим о деплое и разработке, то, конечно, вручную деплоить не очень хочется, а есть желание сделать что-то гибкое и автоматизированное.

Эта библиотека ставится посредством composer, поэтому код будет совместим с любым фреймворком. Итак, нам понадобятся:
— AWS CLI — интерфейс командной строки для работы с сервисами AWS;
— уже упомянутый выше Serverless framework (версия для разработки бесплатна, а её функционала хватает за глаза);
— библиотека Bref, которая нужна для написания кода. Прекрасное решение, особенно если учесть, что AWS Lambda не поддерживает вызов PHP-скриптов из коробки.

Настраиваем окружение и AWS

AWS CLI

Консольная оболочка от AWS основана на Python 2. Начнём с создания аккаунта и установки AWS CLI. 4+. 7+ либо 3. Так как в AWS рекомендуют 3 версию Python, спорить не будем.
Примеры ниже приводятся для Ubuntu.

sudo apt-get -y install python3-pip

Потом устанавливаем непосредственно AWS CLI:

pip3 install awscli --upgrade --user

Проверяем установку:

aws --version

Можно использовать имеющийся логин и пароль, но будет лучше, если вы создадите отдельного пользователя через AWS IAM, определив ему только нужные права доступа. Теперь надо подключить AWS CLI к аккаунту. Вызов конфигурации не вызовет проблем:

aws configure

Их можно получить в ASW IAM во вкладке «Security credentials» (находится на странице нужного пользователя). Далее вам потребуются AWS Secret и AWS Access Key. Сохраните их у себя. Сгенерировать ключи доступа поможет кнопка «Create access key».

В результате вам вернётся токен, необходимый для соединения с вашим ботом. Чтобы зарегистрировать нового бота в Telegram, используем @BotFather и команду /newbot. Его также зафиксируйте.

Serverless Framework

Потребуется Node.js 6-й и выше версии. Чтобы установить Serverless Framework, понадобится аккаунт на https://serverless.com/.
После выполнения регистрации перейдём к установке у себя на рабочей станции.

sudo apt-get -y install nodejs

Чтобы обеспечить корректный запуск в нашей среде, выполняем рекомендованные шаги:

mkdir ~/.npm-global
export PATH=~/.npm-global/bin:$PATH
source ~/.profile npm config set prefix ‘~/.npm-global’

Также добавляем:

~/.npm-global/bin:$PATH

в файл /etc/environment.
Теперь ставим Serverless:

npm install -g serverless

AWS

Создаём в AWS Route 53 зону, DNS-запись, а также SSL-сертификат для неё.
Кроме того, потребуется ELB, который мы создаём в сервисе EC2 -> Load Balancers. Что же, пришла пора перейти в AWS-интерфейс и добавить доменное имя. Кстати, при создании ELB нужно пройти все шаги мастера, указав созданный сертификат.

Что касается балансировщика, то его можно создать через AWS CLI, используя следующую команду:

aws elb create-load-balancer --load-balancer-name my-load-balancer --listeners "Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80" "Protocol=HTTPS,LoadBalancerPort=443,InstanceProtocol=HTTP,InstancePort=80,SSLCertificateId=arn:aws:iam::123456789012:server-certificate/my-server-cert" --subnets subnet-15aaab61 --security-groups sg-a61988c3

При этом нужно направить на него запросы к нашему домену. Балансировщик понадобится после первого деплоя. В результате вы увидите выпадающий список, поэтому останется выбрать необходимую запись и сохранить её. Чтобы это реализовать, в настройках DNS-записи (поле «Alias target») начните вводить название созданного ELB.

Теперь переходим к коду.

Пишем код

Как упоминалось ранее, данная библиотека ставится посредством composer, поэтому код будет совместим с любым фреймворком. Для написания кода будем использовать Bref. Но нам желательно поработать на «голом» PHP — это поможет лучше понять суть.
Начинаем с зависимостей: Кстати, разработчики уже описали процесс использования Bref с Laravel и Symfony.

, "autoload": { "psr-4": { "App\": "src/" } }
}

2 и выше, а для работы с Telegram нам подойдёт вот эта оболочка к API — https://github.com/TelegramBot/Api. Писать будем на PHP 7. Что касается самого кода, то он будет размещён в директории src.

Потребуется HTTP-приложение, а с точки зрения Lambda это будет значить, что вызов скриптов станет выполняться аналогично тому, как это осуществляет Nginx. Итак, бессерверная среда собирается через консольный диалог. В целом это больше похоже на стандартный консольный вызов скрипта. Интерпретацию будем выполнять силами PHP-FPM. Это важный момент, ведь без учёта данной особенности скрипты посредством HTTP вызывать у нас не получится.
Выполняем:

vendor/bin/bref init

В диалоге выбираем пункт «HTTP application» и не забываем указывать регион, так как приложение должно работать в том же регионе, в котором работает балансировщик.
После инициализации появятся 2 новых файла:
index.php — вызываемый файл;
serverless.yml — файл настройки деплоя.
Папку .serverless сразу необходимо будет добавить в .gitignore (она появится после 1-й попытки деплоя).

Вот как это может выглядеть в нашей реализации: Раз у нас web-приложение, скинем index.php в папку public, сразу переключившись на serverless.yml.

# имя lambda-приложения
service: app # описание провайдера услуг
provider: name: aws # указываем регион балансировщика! region: eu-central-1 # среда выполнения нестандартная runtime: provided # вообще, для bref рекомендуют 1024. Но для простого скрипта столько и не надо memoryLimit: 256 # указываем окружение stage: dev # глобальные переменные окружения environment: BOT_TOKEN: ${ssm:/app/bot-token} # подключаем bref
plugins: - ./vendor/bref/bref # описание Lambda-функций
functions: # наша функция в итоге будет называться php-api-dev # service-function-stage api: handler: public/index.php description: '' # in seconds (API Gateway has a timeout of 29 seconds) timeout: 28 layers: - ${bref:layer.php-73-fpm} # возможные события вызова для API Gateway events: - http: 'ANY /' - http: 'ANY /{proxy+}' # локальные переменные окружения environment: MY_VARIABLE: ${ssm:/app/my_variable}

В большей степени нам нужны переменные окружения. Теперь разберём неочевидные строчки. п. Мы не хотим хардкодить подключения к БД, внешним API и т. И хранить этот токен в serverless.yml не рекомендуется, поэтому лучше отправим его в ssm-хранилище AWS: Если же мы подключаемся к Telegram, у нас будет свой токен, который получен от BotFather.

aws ssm put-parameter --region eu-central-1 --name '/app/my_variable' --type String --value 'ТОКЕН_БОТА_ОТ_BOTFATHER'

Если говорить о нашем примере, то давайте для простоты сохраним токен бота в глобальной области видимости. Кстати, как раз к нему мы и обращаемся в конфигурации.
Эти переменные доступны как переменные окружения, а получить к ним доступ в PHP можно посредством функции getenv. Также можем перенести токен в область видимости отдельно взятой функции, причём сам вызов от этого не изменится.

Давайте теперь создадим простой класс BotApp — он будет отвечать за генерацию ответа для бота и будет реагировать на команды. Идём дальше. Давайте для интереса добавим ещё одну команду. Разработчики Telegram рекомендуют для всех ботов добавлять поддержку команд /help и /start. Чтобы получить более сложную логику, архитектуру следует развивать и усложнять. Сам по себе класс довольно прост и даёт возможность реализовать в index.php Front Controller, не нагружая кодом сам файл вызова.

<?php namespace App; use TelegramBot\Api\Client;
use Telegram\Bot\Objects\Update; class BotApp
{ function run(): void{ $token = getenv('BOT_TOKEN'); $bot = new Client($token); // команда для start $bot->command('start', function ($message) use ($bot) { $answer = 'Добро пожаловать!'; $bot->sendMessage($message->getChat()->getId(), $answer); }); // команда для помощи $bot->command('help', function ($message) use ($bot) { $answer = 'Команды: /help - вывод справки'; $bot->sendMessage($message->getChat()->getId(), $answer); }); // тестовая команда $bot->command('hello', function ($message) use ($bot) { $answer = 'Да-да, я - бот, работающий в Serverless окружении'; $bot->sendMessage($message->getChat()->getId(), $answer); }); $bot->run(); }
}

А вот как выглядит листинг index.php:

<?php require_once('../vendor/autoload.php'); use App\BotApp; try{ $botApp = new BotApp(); $botApp->run();
}
catch (Exception $e){ echo $e->getMessage(); print_r($e->getTrace(), 1);
}

Давайте сделаем это, выполнив команду в папке с serverless.yml: Это может показаться странным, но у нас уже всё готово, чтобы уехать на Production.

sls deploy

В штатном режиме serverless упакует файлы в zip архивы, создаст S3-bucket, куда положит их, потом создаст либо обновит AWS Application, привязанный к Lambda, и положит код и среду выполнения в отдельный слой.

Также надо будет настроить вызов Lambda через ELB, для чего выбираем «Add trigger» в окне управления функцией и в выпадающем списке выбираем «Application Load Balancer». Во время 1-го запуска будет создан API Gateway (мы его оставили, чтобы было проще потестировать вызовы, однако потом его желательно удалить). В итоге ваша Lambda станет доступна по URL с указанием заданного пути. Нужно будет указать созданный ранее ELB, задать соединение через HTTPS, Host оставить пустым, а в Path указать путь, который будет вызывать Lambda (допустим, /lambda/mytgbot).

Для этого вызовите в браузере следующий URL, но не забудьте подставить в него собственные параметры: Теперь можно регистрировать ответную часть бота в Telegram, чтобы мессенджер понимал, откуда брать сообщения.

https://api.telegram.org/botТОКЕН_БОТА/setWebhook?url=https://my-elb-host.com/lambda/mytgbot

В итоге API вернёт ответ «OK», после чего бот станет доступен.

Тестируем бота на локали

Дело в том, что Serverless Framework поддерживает запуск на локали, применяя для этого Docker-контейнеры. Бота можно потестировать и до деплоя. Команда вызова:

sls invoke local --docker -f myFunction

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

sls invoke local --docker -f myFunction --env VAR1=val1

Логи

Тут же вы сможете почитать трейсы вызовов в случае отвала на стороне PHP. По умолчанию вывод вызова будет логироваться в CloudWatch — он доступен в панели Monitoring соответствующей Lambda-функции. Кроме того, можно подключить и расширенные сервисы мониторинга, однако они обойдутся в дополнительные несколько центов ежемесячно.

Итого

Да, Lambda не всегда выигрывает по сравнению со стандартными ВМ и контейнерами, однако есть ситуации, когда Serverless-приложение помогает «выстрелить» быстро и эффективно. В результате мы получили довольно быстрое, гибкое, скалируемое, а также относительно недорогое решение. И пример созданного бота это как раз демонстрирует.
Полезные материалы по теме:

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть