A presentation at PHPUGFFM, September 2021 in in 8700 Monrovia, Lenexa, KS 66215, USA by Rob Allen
The Serverless PHP Application Rob Allen PHPUGFFM, September 2021
Serverless? Rob Allen ~ @akrabat
Platform options Rob Allen ~ @akrabat
Platform options Rob Allen ~ @akrabat
Platform options Rob Allen ~ @akrabat
Platform options Rob Allen ~ @akrabat
Platform options Rob Allen ~ @akrabat
Platform options Rob Allen ~ @akrabat
Platform options Rob Allen ~ @akrabat
Serverless Serverless is all about composing software systems from a collection of cloud services. With serverless, you can lean on off-the-shelf cloud services resources for your application architecture, focus on business logic and application needs. Nate Taggart, CEO Stackery Rob Allen ~ @akrabat
FaaS Your code Rob Allen ~ @akrabat
FaaS Deployed to the cloud Rob Allen ~ @akrabat
FaaS Runs when needed Rob Allen ~ @akrabat
FaaS Scaled automatically Rob Allen ~ @akrabat
FaaS Pay only for execution Rob Allen ~ @akrabat
Where are the servers? Rob Allen ~ @akrabat
Rob Allen ~ @akrabat
Rob Allen ~ @akrabat
Use-cases Rob Allen ~ @akrabat
Use-cases Synchronous Service is invoked and provides immediate response (HTTP requests: APIs, chat bots) Rob Allen ~ @akrabat
Use-cases Synchronous Service is invoked and provides immediate response (HTTP requests: APIs, chat bots) Asynchronous Push a message which drives an action later (web hooks, timed events, database changes) Rob Allen ~ @akrabat
Benefits Rob Allen ~ @akrabat
Benefits • No need to maintain infrastructure Rob Allen ~ @akrabat
Benefits • No need to maintain infrastructure • Concentrate on application code Rob Allen ~ @akrabat
Benefits • No need to maintain infrastructure • Concentrate on application code • Pay only for what you use, when you use it Rob Allen ~ @akrabat
Benefits • • • • No need to maintain infrastructure Concentrate on application code Pay only for what you use, when you use it Language agnostic Rob Allen ~ @akrabat
Challenges Rob Allen ~ @akrabat
Challenges • Start up latency Rob Allen ~ @akrabat
Challenges • Start up latency • Time limit Rob Allen ~ @akrabat
Challenges • Start up latency • Time limit • State is external Rob Allen ~ @akrabat
Challenges • • • • Start up latency Time limit State is external Different way of thinking Rob Allen ~ @akrabat
When should you use serverless? Rob Allen ~ @akrabat
When should you use serverless? • Responding to web hooks Rob Allen ~ @akrabat
When should you use serverless? • Responding to web hooks • Additional features without extending current platform Rob Allen ~ @akrabat
When should you use serverless? • Responding to web hooks • Additional features without extending current platform • PWA/Static site contact form, et al. Rob Allen ~ @akrabat
When should you use serverless? • • • • Responding to web hooks Additional features without extending current platform PWA/Static site contact form, et al. Variable traffic levels Rob Allen ~ @akrabat
When should you use serverless? • • • • • Responding to web hooks Additional features without extending current platform PWA/Static site contact form, et al. Variable traffic levels When you want your costs to scale with traffic Rob Allen ~ @akrabat
It’s about value Rob Allen ~ @akrabat
Serverless platforms Rob Allen ~ @akrabat
Serverless languages Rob Allen ~ @akrabat
Serverless platforms with PHP support Rob Allen ~ @akrabat
Hello World AWS Lambda (Bref): <?php return function ($event) { $name = $event[‘name’] ?? ‘world’; return ‘Hello ’ . $name; }; Rob Allen ~ @akrabat
Hello World Apache OpenWhisk: <?php function main(array $args): array { $name = $args[‘name’] ?? ‘world’; return [“greeting” => ‘Hello ’ . $name]; } Rob Allen ~ @akrabat
Hello World OpenFAAS <?php class Handler { public function handle(string $data): void { $decoded = json_decode($data, true); $name = $decoded[‘name’] ?? ‘world’; return ‘Hello ’ . $name; } } Rob Allen ~ @akrabat
Hello World Google Cloud Functions (alpha) <?php use Psr\Http\Message\ServerRequestInterface as Request; function helloHttp(Request $request) { $name = $request->getQueryParams(‘name’) ?? ‘world’; return ‘Hello ’ . $name; } Rob Allen ~ @akrabat
Rob Allen ~ @akrabat
The anatomy of an action function main(array $args): array { // Marshall inputs from event parameters $name = $args[‘name’] ?? ‘world’; // Do the work $message = ‘Hello ’ . $name // Return result return [“body” => $message]; } Rob Allen ~ @akrabat
Hello World function main(array $args): array { // Marshall inputs from event parameters $name = $args[‘name’] ?? ‘world’; // Do the work $message = ‘Hello ’ . $name // Return result return [“body” => $message]; } Rob Allen ~ @akrabat
Hello World function main(array $args): array { // Marshall inputs from event parameters $name = $args[‘name’] ?? ‘world’; // Do the work $message = ‘Hello ’ . $name // Return result return [“body” => $message]; } Rob Allen ~ @akrabat
Hello World function main(array $args): array { // Marshall inputs from event parameters $name = $args[‘name’] ?? ‘world’; // Do the work $message = ‘Hello ’ . $name // Return result return [“body” => $message]; } Rob Allen ~ @akrabat
Hello World function main(array $args): array { // Marshall inputs from event parameters $name = $args[‘name’] ?? ‘world’; // Do the work $message = ‘Hello ’ . $name // Return result return [“body” => $message]; } Rob Allen ~ @akrabat
Deploy to OpenWhisk $ zip -q hello.zip hello.php Rob Allen ~ @akrabat
Deploy to OpenWhisk $ zip -q hello.zip hello.php $ wsk action update —kind php:7.4 hello hello.zip ok: updated action hello Rob Allen ~ @akrabat
Run it $ wsk action invoke hello —result —param name Rob Rob Allen ~ @akrabat
Run it $ wsk action invoke hello —result —param name Rob { “body”: “Hello Rob!” } Rob Allen ~ @akrabat
Under the hood Rob Allen ~ @akrabat
OpenWhisk’s architecture Rob Allen ~ @akrabat
Create an action Rob Allen ~ @akrabat
Invoke an action Rob Allen ~ @akrabat
Action container lifecycle • Hosts the user-written code • Controlled via two end points: /init & /run Rob Allen ~ @akrabat
Action container lifecycle • Hosts the user-written code • Controlled via two end points: /init & /run Rob Allen ~ @akrabat
Architecture Rob Allen ~ @akrabat
Monolith architecture Rob Allen ~ @akrabat
Serverless architecture Rob Allen ~ @akrabat
Serverless architecture pattern Rob Allen ~ @akrabat
Functions are key Rob Allen ~ @akrabat
Functions are the Unit of Deployment Rob Allen ~ @akrabat
Functions are the Unit of Scale Rob Allen ~ @akrabat
Functions are Stateless Rob Allen ~ @akrabat
Functions have Structure Rob Allen ~ @akrabat
Structure If it’s non-trivial, software engineering principles apply! • Use multiple methods Rob Allen ~ @akrabat
Structure If it’s non-trivial, software engineering principles apply! • Use multiple methods • Use multiple files Rob Allen ~ @akrabat
Structure If it’s non-trivial, software engineering principles apply! • Use multiple methods • Use multiple files • Integrate reusable dependencies Rob Allen ~ @akrabat
Rob Allen ~ @akrabat
AWS Lambda with PHP Relies on Lambda’s layers system Process: 1. Create a layer containing: 1. the PHP executable 2. a bootstrap script 2. Write the PHP function! Rob Allen ~ @akrabat
.
Bref PHP function index.php: <?php declare(strict_types=1); require DIR . ‘/vendor/autoload.php’; return function($event) { return ‘Hello ’ . ($event[‘name’] ?? ‘world’); } Rob Allen ~ @akrabat
serverless.yml service: helloapp provider: name: aws runtime: provided.al2 functions: hello: handler: index.php layers: - ${bref:layer.php-80} Rob Allen ~ @akrabat
serverless.yml service: helloapp provider: name: aws runtime: provided.al2 functions: hello: handler: index.php layers: - ${bref:layer.php-80} Rob Allen ~ @akrabat
serverless.yml service: helloapp provider: name: aws runtime: provided.al2 functions: hello: handler: index.php layers: - ${bref:layer.php-80} Rob Allen ~ @akrabat
Deploy $ serverless deploy Serverless: Packaging service… … Serverless: Stack update finished… Service Information service: helloapp stage: dev region: eu-west-2 stack: helloapp-dev functions: hello: helloapp-dev-hello Rob Allen ~ @akrabat
Run $ serverless invoke -f hello “Hello world” Rob Allen ~ @akrabat
Run $ serverless invoke -f hello “Hello world” $ serverless invoke -f hello -d ‘{“name”: “Rob”}’ “Hello Rob” Rob Allen ~ @akrabat
Run locally in Docker $ serverless invoke local —docker -f hello Serverless: Building Docker image… … REPORT RequestId: 6a653a94-ee51-1f3e-65c7-1f1954842f29 Init Duration: 265.93 ms Duration: 145.37 ms Billed Duration: 200 ms Memory Size: 1024 MB Max Memory Used: 27 MB “Hello world” Xdebug also works! Rob Allen ~ @akrabat
Add AWS API Gateway serverless.yml: functions: hello: handler: index.php … events: - http: “GET /hello” - http: “GET /hi/{name}” Rob Allen ~ @akrabat
Return a PSR-15 RequestHandler class HelloHandler implements RequestHandlerInterface { public function handle(ServerRequest $request): Response { $name = ($request->getQueryParams()[‘name’] ?? ‘world’); $body = ‘Hello ’ . $name; return new Response(200, [‘Content-Type’ => ‘text/plain’], $body); } } Rob Allen ~ @akrabat
Deploy $ serverless deploy Serverless: Packaging service… … Service Information service: helloapp stage: dev stack: helloapp-dev endpoints: GET - https://l1v6cz13zb.execute-api.eu-west-2 .amazonaws.com/dev/hello Rob Allen ~ @akrabat
Test $ curl -i https://l1v6cz…naws.com/dev/hello?name=Rob HTTP/2 200 content-type: text/plain content-length: 9 Hello Rob Rob Allen ~ @akrabat
Case study Project 365 photo website Rob Allen ~ @akrabat
Project 365 Static website to display my photo-a-day picture for each day of the year. • Hosted on S3 • CloudFront CDN • Lambda/PHP function Rob Allen ~ @akrabat
Lambda/PHP function Rob Allen ~ @akrabat
Infrastructure as code serverless.yml: functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat
Infrastructure as code functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat
Infrastructure as code functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat
Infrastructure as code functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat
Process 1. 2. 3. 4. 5. Gather credentials from environment Download photos from Flickr API Create HTML page Upload to S3 Invalidate CloudFront cache Rob Allen ~ @akrabat
main() return function ($event) { $year = $event[‘year’] ?? date(‘Y’); $photos = (new PhotoFetcher())->fetchForYear($year); $html = (new PageCreator())->create($year, $photos); $filename = “$year.html”; $uploader = new Uploader(); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidateCache($filename); } Rob Allen ~ @akrabat
main() return function ($event) { $year = $event[‘year’] ?? date(‘Y’); $photos = (new PhotoFetcher())->fetchForYear($year); $html = (new PageCreator())->create($year, $photos); $filename = “$year.html”; $uploader = new Uploader(); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidateCache($filename); } Rob Allen ~ @akrabat
main() return function ($event) { $year = $event[‘year’] ?? date(‘Y’); $photos = (new PhotoFetcher())->fetchForYear($year); $html = (new PageCreator())->create($year, $photos); $filename = “$year.html”; $uploader = new Uploader(); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidateCache($filename); } Rob Allen ~ @akrabat
main() return function ($event) { $year = $event[‘year’] ?? date(‘Y’); $photos = (new PhotoFetcher())->fetchForYear($year); $html = (new PageCreator())->create($year, $photos); $filename = “$year.html”; $uploader = new Uploader(); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidateCache($filename); } Rob Allen ~ @akrabat
main() return function ($event) { $year = $event[‘year’] ?? date(‘Y’); $photos = (new PhotoFetcher())->fetchForYear($year); $html = (new PageCreator())->create($year, $photos); $filename = “$year.html”; $uploader = new Uploader(); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidateCache($filename); } Rob Allen ~ @akrabat
The finished website Rob Allen ~ @akrabat
To sum up Rob Allen ~ @akrabat
Thank you! Rob Allen ~ @akrabat
Photo credits - Assembly line: https://www.flickr.com/photos/adiram/3886212918 - Under the hood: https://www.flickr.com/photos/atomichotlinks/7736849388 - Pantheon: https://www.flickr.com/photos/shawnstilwell/4335732627 - Watch mechanism: https://www.flickr.com/photos/shinythings/2168994732 - Holiday snaps: https://www.flickr.com/photos/kjgarbutt/5358075923 - Rocket launch: https://www.flickr.com/photos/gsfc/16495356966 - Stars: https://www.flickr.com/photos/gsfc/19125041621 Rob Allen ~ @akrabat
Serverless platforms are suited to a wide range of applications from IoT and chatbots through to microservices and web hooks. In this session, I’ll look at how serverless applications work and embrace cloud services to live in a world where there are many separate parts that make up the application. I’ll cover the architectural patterns we use to design a serverless application including handling their stateless nature and leveraging event processing to trigger them. I will then turn my attention to showing how these are put into practice by looking at how they can be implemented in Apache OpenWhisk or AWS Lambda.