The Serverless PHP Application Rob Allen
PHPUGFFM, September 2021
Slide 2
Serverless?
Rob Allen ~ @akrabat
Slide 3
Platform options
Rob Allen ~ @akrabat
Slide 4
Platform options
Rob Allen ~ @akrabat
Slide 5
Platform options
Rob Allen ~ @akrabat
Slide 6
Platform options
Rob Allen ~ @akrabat
Slide 7
Platform options
Rob Allen ~ @akrabat
Slide 8
Platform options
Rob Allen ~ @akrabat
Slide 9
Platform options
Rob Allen ~ @akrabat
Slide 10
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
Slide 11
FaaS
Your code
Rob Allen ~ @akrabat
Slide 12
FaaS
Deployed to the cloud
Rob Allen ~ @akrabat
Slide 13
FaaS
Runs when needed
Rob Allen ~ @akrabat
Slide 14
FaaS
Scaled automatically
Rob Allen ~ @akrabat
Slide 15
FaaS
Pay only for execution
Rob Allen ~ @akrabat
Slide 16
Where are the servers?
Rob Allen ~ @akrabat
Slide 17
Rob Allen ~ @akrabat
Slide 18
Rob Allen ~ @akrabat
Slide 19
Use-cases
Rob Allen ~ @akrabat
Slide 20
Use-cases Synchronous Service is invoked and provides immediate response (HTTP requests: APIs, chat bots)
Rob Allen ~ @akrabat
Slide 21
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
Slide 22
Benefits
Rob Allen ~ @akrabat
Slide 23
Benefits • No need to maintain infrastructure
Rob Allen ~ @akrabat
Slide 24
Benefits • No need to maintain infrastructure • Concentrate on application code
Rob Allen ~ @akrabat
Slide 25
Benefits • No need to maintain infrastructure • Concentrate on application code • Pay only for what you use, when you use it
Rob Allen ~ @akrabat
Slide 26
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
Slide 27
Challenges
Rob Allen ~ @akrabat
Slide 28
Challenges • Start up latency
Rob Allen ~ @akrabat
Slide 29
Challenges • Start up latency • Time limit
Rob Allen ~ @akrabat
Slide 30
Challenges • Start up latency • Time limit • State is external
Rob Allen ~ @akrabat
Slide 31
Challenges • • • •
Start up latency Time limit State is external Different way of thinking
Rob Allen ~ @akrabat
Slide 32
When should you use serverless?
Rob Allen ~ @akrabat
Slide 33
When should you use serverless? • Responding to web hooks
Rob Allen ~ @akrabat
Slide 34
When should you use serverless? • Responding to web hooks • Additional features without extending current platform
Rob Allen ~ @akrabat
Slide 35
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
Slide 36
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
Slide 37
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
Slide 38
It’s about value
Rob Allen ~ @akrabat
Slide 39
Serverless platforms
Rob Allen ~ @akrabat
Slide 40
Serverless languages
Rob Allen ~ @akrabat
Slide 41
Serverless platforms with PHP support
Rob Allen ~ @akrabat
Slide 42
Hello World AWS Lambda (Bref): <?php return function ($event) { $name = $event[‘name’] ?? ‘world’; return ‘Hello ’ . $name; };
Rob Allen ~ @akrabat
Slide 43
Hello World Apache OpenWhisk: <?php function main(array $args): array { $name = $args[‘name’] ?? ‘world’; return [“greeting” => ‘Hello ’ . $name]; }
Rob Allen ~ @akrabat
Slide 44
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
Slide 45
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
Slide 46
Rob Allen ~ @akrabat
Slide 47
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
Slide 48
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
Slide 49
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
Slide 50
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
Slide 51
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
Slide 52
Deploy to OpenWhisk $ zip -q hello.zip hello.php
Rob Allen ~ @akrabat
Slide 53
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
Slide 54
Run it $ wsk action invoke hello —result —param name Rob
Rob Allen ~ @akrabat
Slide 55
Run it $ wsk action invoke hello —result —param name Rob { “body”: “Hello Rob!” }
Rob Allen ~ @akrabat
Slide 56
Under the hood
Rob Allen ~ @akrabat
Slide 57
OpenWhisk’s architecture
Rob Allen ~ @akrabat
Slide 58
Create an action
Rob Allen ~ @akrabat
Slide 59
Invoke an action
Rob Allen ~ @akrabat
Slide 60
Action container lifecycle • Hosts the user-written code • Controlled via two end points: /init & /run
Rob Allen ~ @akrabat
Slide 61
Action container lifecycle • Hosts the user-written code • Controlled via two end points: /init & /run
Rob Allen ~ @akrabat
Slide 62
Architecture
Rob Allen ~ @akrabat
Slide 63
Monolith architecture
Rob Allen ~ @akrabat
Slide 64
Serverless architecture
Rob Allen ~ @akrabat
Slide 65
Serverless architecture pattern
Rob Allen ~ @akrabat
Slide 66
Functions are key
Rob Allen ~ @akrabat
Slide 67
Functions are the Unit of Deployment
Rob Allen ~ @akrabat
Slide 68
Functions are the Unit of Scale
Rob Allen ~ @akrabat
Slide 69
Functions are Stateless
Rob Allen ~ @akrabat
Slide 70
Functions have Structure
Rob Allen ~ @akrabat
Slide 71
Structure If it’s non-trivial, software engineering principles apply! • Use multiple methods
Rob Allen ~ @akrabat
Slide 72
Structure If it’s non-trivial, software engineering principles apply! • Use multiple methods • Use multiple files
Rob Allen ~ @akrabat
Slide 73
Structure If it’s non-trivial, software engineering principles apply! • Use multiple methods • Use multiple files • Integrate reusable dependencies
Rob Allen ~ @akrabat
Slide 74
Rob Allen ~ @akrabat
Slide 75
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
Slide 76
.
Slide 77
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
Slide 78
serverless.yml service: helloapp provider: name: aws runtime: provided.al2 functions: hello: handler: index.php layers: - ${bref:layer.php-80}
Rob Allen ~ @akrabat
Slide 79
serverless.yml service: helloapp provider: name: aws runtime: provided.al2 functions: hello: handler: index.php layers: - ${bref:layer.php-80}
Rob Allen ~ @akrabat
Slide 80
serverless.yml service: helloapp provider: name: aws runtime: provided.al2 functions: hello: handler: index.php layers: - ${bref:layer.php-80}
Rob Allen ~ @akrabat
Slide 81
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
Slide 82
Run $ serverless invoke -f hello “Hello world”
Rob Allen ~ @akrabat
Slide 83
Run $ serverless invoke -f hello “Hello world” $ serverless invoke -f hello -d ‘{“name”: “Rob”}’ “Hello Rob”
Rob Allen ~ @akrabat
Slide 84
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
Slide 85
Add AWS API Gateway serverless.yml: functions: hello: handler: index.php … events: - http: “GET /hello” - http: “GET /hi/{name}”
Rob Allen ~ @akrabat
Slide 86
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
Slide 87
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
Slide 88
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
Slide 89
Case study Project 365 photo website
Rob Allen ~ @akrabat
Slide 90
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
Slide 91
Lambda/PHP function
Rob Allen ~ @akrabat
Slide 92
Infrastructure as code serverless.yml: functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *)
Rob Allen ~ @akrabat
Slide 93
Infrastructure as code functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *)
Rob Allen ~ @akrabat
Slide 94
Infrastructure as code functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *)
Rob Allen ~ @akrabat
Slide 95
Infrastructure as code functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *)
Rob Allen ~ @akrabat
Slide 96
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
Slide 97
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
Slide 98
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
Slide 99
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
Slide 100
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
Slide 101
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
Slide 102
The finished website
Rob Allen ~ @akrabat
Slide 103
To sum up
Rob Allen ~ @akrabat
Slide 104
Thank you!
Rob Allen ~ @akrabat
Slide 105
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