Introduction to Serverless PHP

A presentation at PHP Shropshire, April 2019 in April 2019 in Telford, UK by Rob Allen

Slide 1

Slide 1

Introduction to Serverless PHP Rob Allen April 2019

Slide 2

Slide 2

Platform options Rob Allen ~ @akrabat

Slide 3

Slide 3

Platform options Rob Allen ~ @akrabat

Slide 4

Slide 4

Platform options Rob Allen ~ @akrabat

Slide 5

Slide 5

Platform options Rob Allen ~ @akrabat

Slide 6

Slide 6

Platform options Rob Allen ~ @akrabat

Slide 7

Slide 7

Platform options Rob Allen ~ @akrabat

Slide 8

Slide 8

Platform options Rob Allen ~ @akrabat

Slide 9

Slide 9

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 10

Slide 10

FaaS • • • • • Your code Deployed to the cloud Runs when needed Scaled automatically Pay only for execution Rob Allen ~ @akrabat

Slide 11

Slide 11

Where are the servers? Rob Allen ~ @akrabat

Slide 12

Slide 12

Rob Allen ~ @akrabat

Slide 13

Slide 13

Rob Allen ~ @akrabat

Slide 14

Slide 14

Use-cases Rob Allen ~ @akrabat

Slide 15

Slide 15

Use-cases Synchronous Service is invoked and provides immediate response (HTTP requests: APIs, chat bots) Rob Allen ~ @akrabat

Slide 16

Slide 16

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 17

Slide 17

Benefits Rob Allen ~ @akrabat

Slide 18

Slide 18

Benefits • No need to maintain infrastructure Rob Allen ~ @akrabat

Slide 19

Slide 19

Benefits • No need to maintain infrastructure • Concentrate on application code Rob Allen ~ @akrabat

Slide 20

Slide 20

Benefits • No need to maintain infrastructure • Concentrate on application code • Pay only for what you use, when you use it Rob Allen ~ @akrabat

Slide 21

Slide 21

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 22

Slide 22

Challenges Rob Allen ~ @akrabat

Slide 23

Slide 23

Challenges • Start up latency Rob Allen ~ @akrabat

Slide 24

Slide 24

Challenges • Start up latency • Time limit Rob Allen ~ @akrabat

Slide 25

Slide 25

Challenges • Start up latency • Time limit • State is external Rob Allen ~ @akrabat

Slide 26

Slide 26

Challenges • • • • Start up latency Time limit State is external Different way of thinking Rob Allen ~ @akrabat

Slide 27

Slide 27

When should you use serverless? Rob Allen ~ @akrabat

Slide 28

Slide 28

When should you use serverless? • Responding to web hooks Rob Allen ~ @akrabat

Slide 29

Slide 29

When should you use serverless? • Responding to web hooks • PWA/Static site contact form, et al. Rob Allen ~ @akrabat

Slide 30

Slide 30

When should you use serverless? • Responding to web hooks • PWA/Static site contact form, et al. • Additional features without extending current platform Rob Allen ~ @akrabat

Slide 31

Slide 31

When should you use serverless? • • • • Responding to web hooks PWA/Static site contact form, et al. Additional features without extending current platform Variable traffic levels Rob Allen ~ @akrabat

Slide 32

Slide 32

When should you use serverless? • • • • • Responding to web hooks PWA/Static site contact form, et al. Additional features without extending current platform Variable traffic levels When you want your costs to scale with traffic Rob Allen ~ @akrabat

Slide 33

Slide 33

Serverless platforms Rob Allen ~ @akrabat

Slide 34

Slide 34

Serverless platforms with PHP support Rob Allen ~ @akrabat

Slide 35

Slide 35

Slide 36

Slide 36

Concepts Rob Allen ~ @akrabat

Slide 37

Slide 37

Rob Allen ~ @akrabat

Slide 38

Slide 38

Rob Allen ~ @akrabat

Slide 39

Slide 39

Hello world in PHP Rob Allen ~ @akrabat

Slide 40

Slide 40

Hello world in PHP Rob Allen ~ @akrabat

Slide 41

Slide 41

Upload your action $ wsk action update hello hello.php ok: updated action hello Rob Allen ~ @akrabat

Slide 42

Slide 42

Run your action $ wsk action invoke hello —result { “msg”: “Hello World” } Rob Allen ~ @akrabat

Slide 43

Slide 43

Under the hood Rob Allen ~ @akrabat

Slide 44

Slide 44

OpenWhisk’s architecture Rob Allen ~ @akrabat

Slide 45

Slide 45

Create an action Rob Allen ~ @akrabat

Slide 46

Slide 46

Invoke an action Rob Allen ~ @akrabat

Slide 47

Slide 47

Action container lifecycle • Hosts the user-written code • Controlled via two end points: /init & /run Rob Allen ~ @akrabat

Slide 48

Slide 48

Action container lifecycle • Hosts the user-written code • Controlled via two end points: /init & /run Rob Allen ~ @akrabat

Slide 49

Slide 49

Rob Allen ~ @akrabat

Slide 50

Slide 50

AWS Lambda with PHP Only sensibly possible since November 2018 with the introduction of layers Rob Allen ~ @akrabat

Slide 51

Slide 51

AWS Lambda with PHP Only sensibly possible since November 2018 with the introduction of layers Process: 1. Create a layer containing: 1. the PHP executable 2. Write a bootstrap script 2. Write the PHP function! Full details: akrabat.com/lambdaphp Rob Allen ~ @akrabat

Slide 52

Slide 52

Bootstrap file // layer/php/boostrap.php while (true) { // get next event $eventPayload = $lambdaRuntime->getEventPayload(); // execute handler $data = $handlerFunction($eventPayload); // send result $lambdaRuntime->sendResult($data); } Rob Allen ~ @akrabat

Slide 53

Slide 53

Serverless Framework Infrastructure as code Rob Allen ~ @akrabat

Slide 54

Slide 54

Manage using Serverless Framework application manifest: serverless.yml service: hello-lambdaphp provider: name: aws runtime: provided memorySize: 128 layers: php: path: layer/php Rob Allen ~ @akrabat

Slide 55

Slide 55

Manage using Serverless Framework service: hello-lambdaphp provider: name: aws runtime: provided memorySize: 128 layers: php: path: layer/php Rob Allen ~ @akrabat

Slide 56

Slide 56

Manage using Serverless Framework service: hello-lambdaphp provider: name: aws runtime: provided memorySize: 128 layers: php: path: layer/php Rob Allen ~ @akrabat

Slide 57

Slide 57

Hello World in Lambda function hello($eventData) { $data = json_decode($eventData, true, 512, JSON_THROW_ON_ERROR); $name = $data[‘name’] ?? ‘World’; return json_encode([‘msg’ => “Hello $name”]); } Rob Allen ~ @akrabat

Slide 58

Slide 58

Hello World in Lambda function hello($eventData) { $data = json_decode($eventData, true, 512, JSON_THROW_ON_ERROR); $name = $data[‘name’] ?? ‘World’; return json_encode([‘msg’ => “Hello $name”]); } Rob Allen ~ @akrabat

Slide 59

Slide 59

Hello World in Lambda function hello($eventData) { $data = json_decode($eventData, true, 512, JSON_THROW_ON_ERROR); $name = $data[‘name’] ?? ‘World’; return json_encode([‘msg’ => “Hello $name”]); } Rob Allen ~ @akrabat

Slide 60

Slide 60

Hello World in Lambda function hello($eventData) { $data = json_decode($eventData, true, 512, JSON_THROW_ON_ERROR); $name = $data[‘name’] ?? ‘World’; return json_encode([‘msg’ => “Hello $name”]); } Rob Allen ~ @akrabat

Slide 61

Slide 61

Manage using Serverless Framework functions: hello: handler: handler.hello layers: - {Ref: PhpLambdaLayer} Rob Allen ~ @akrabat

Slide 62

Slide 62

Deploy using Serverless Framework $ sls deploy Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… … functions: hello: hello-lambdaphp-dev-hello layers: php: arn:aws:lambda:eu-west-2:661969457706:layer:php: Serverless: Removing old service artifacts from S3… Rob Allen ~ @akrabat

Slide 63

Slide 63

Invoke using Serverless Framework $ sls invoke -f hello —data=’{“name”:”Rob”}’ { “msg”: “Hello Rob” } Rob Allen ~ @akrabat

Slide 64

Slide 64

Bref Community maintained PHP runtime for AWS Lambda <?php require DIR.’/vendor/autoload.php’; lambda(function (array $event) { $name = $event[‘name’] ?? ‘World’; return [‘msg’ => “Hello $name”]; }); Rob Allen ~ @akrabat

Slide 65

Slide 65

Project 365 My photo-a-day website Rob Allen ~ @akrabat

Slide 66

Slide 66

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 67

Slide 67

Lambda/PHP function Rob Allen ~ @akrabat

Slide 68

Slide 68

Serverless configuration functions: update: handler: src/actions/update.main layers: - {Ref: PhpLambdaLayer} environment: FLICKR_API_KEY: ${self:custom.FLICKR_API_KEY} FLICKR_USER_ID: ${self:custom.FLICKR_USER_ID} events: - schedule: name: project365-build rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat

Slide 69

Slide 69

Serverless configuration functions: update: handler: src/actions/update.main layers: - {Ref: PhpLambdaLayer} environment: FLICKR_API_KEY: ${self:custom.FLICKR_API_KEY} FLICKR_USER_ID: ${self:custom.FLICKR_USER_ID} events: - schedule: name: project365-build rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat

Slide 70

Slide 70

Serverless configuration functions: update: handler: src/actions/update.main layers: - {Ref: PhpLambdaLayer} environment: FLICKR_API_KEY: ${self:custom.FLICKR_API_KEY} FLICKR_USER_ID: ${self:custom.FLICKR_USER_ID} events: - schedule: name: project365-build rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat

Slide 71

Slide 71

Serverless configuration functions: update: handler: src/actions/update.main layers: - {Ref: PhpLambdaLayer} environment: FLICKR_API_KEY: ${self:custom.FLICKR_API_KEY} FLICKR_USER_ID: ${self:custom.FLICKR_USER_ID} events: - schedule: name: project365-build rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat

Slide 72

Slide 72

Serverless configuration functions: update: handler: src/actions/update.main layers: - {Ref: PhpLambdaLayer} environment: FLICKR_API_KEY: ${self:custom.FLICKR_API_KEY} FLICKR_USER_ID: ${self:custom.FLICKR_USER_ID} events: - schedule: name: project365-build rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat

Slide 73

Slide 73

main() function main(array $eventData) : array { $apiKey = getEnvVar(‘P365_FLICKR_API_KEY’); $userId = getEnvVar(‘P365_FLICKR_USER_ID’); $year = $eventData[‘year’] ?? date(‘Y’); $pageCreator = new PhotoPageCreator($apiKey); $html = $pageCreator->update($year, $userId); $uploader = new Uploader($cloudFrontId); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidate([‘/’.$filename]); } Rob Allen ~ @akrabat

Slide 74

Slide 74

main() function main(array $eventData) : array { $apiKey = getEnvVar(‘P365_FLICKR_API_KEY’); $userId = getEnvVar(‘P365_FLICKR_USER_ID’); $year = $eventData[‘year’] ?? date(‘Y’); $pageCreator = new PhotoPageCreator($apiKey); $html = $pageCreator->update($year, $userId); $uploader = new Uploader($cloudFrontId); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidate([‘/’.$filename]); } Rob Allen ~ @akrabat

Slide 75

Slide 75

main() function main(array $eventData) : array { $apiKey = getEnvVar(‘P365_FLICKR_API_KEY’); $userId = getEnvVar(‘P365_FLICKR_USER_ID’); $year = $eventData[‘year’] ?? date(‘Y’); $pageCreator = new PhotoPageCreator($apiKey); $html = $pageCreator->update($year, $userId); $uploader = new Uploader($cloudFrontId); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidate([‘/’.$filename]); } Rob Allen ~ @akrabat

Slide 76

Slide 76

main() function main(array $eventData) : array { $apiKey = getEnvVar(‘P365_FLICKR_API_KEY’); $userId = getEnvVar(‘P365_FLICKR_USER_ID’); $year = $eventData[‘year’] ?? date(‘Y’); $pageCreator = new PhotoPageCreator($apiKey); $html = $pageCreator->update($year, $userId); $uploader = new Uploader($cloudFrontId); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidate([‘/’.$filename]); } Rob Allen ~ @akrabat

Slide 77

Slide 77

main() function main(array $eventData) : array { $apiKey = getEnvVar(‘P365_FLICKR_API_KEY’); $userId = getEnvVar(‘P365_FLICKR_USER_ID’); $year = $eventData[‘year’] ?? date(‘Y’); $pageCreator = new PhotoPageCreator($apiKey); $html = $pageCreator->update($year, $userId); $uploader = new Uploader($cloudFrontId); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidate([‘/’.$filename]); } Rob Allen ~ @akrabat

Slide 78

Slide 78

Fetch photos from Flickr $url = ‘?’ . http_build_query([ ‘api_key’ => $this->flickrApiKey, ‘user_id’ => $flickrUserId, ‘extras’ => ‘url_z, date_taken, owner_name’, ‘method’ => ‘flickr.photos.search’, ‘tags’ => $year, ]); $response = $this->client->get($url); $data = json_decode($response->getBody(), true); return $data[‘photos’]; Rob Allen ~ @akrabat

Slide 79

Slide 79

Fetch photos from Flickr $url = ‘?’ . http_build_query([ ‘api_key’ => $this->flickrApiKey, ‘user_id’ => $flickrUserId, ‘extras’ => ‘url_z, date_taken, owner_name’, ‘method’ => ‘flickr.photos.search’, ‘tags’ => $year, ]); $response = $this->client->get($url); $data = json_decode($response->getBody(), true); return $data[‘photos’]; Rob Allen ~ @akrabat

Slide 80

Slide 80

Fetch photos from Flickr $url = ‘?’ . http_build_query([ ‘api_key’ => $this->flickrApiKey, ‘user_id’ => $flickrUserId, ‘extras’ => ‘url_z, date_taken, owner_name’, ‘method’ => ‘flickr.photos.search’, ‘tags’ => $year, ]); $response = $this->client->get($url); $data = json_decode($response->getBody(), true); return $data[‘photos’]; Rob Allen ~ @akrabat

Slide 81

Slide 81

Upload to S3 $s3 = new S3Client([ ‘version’ => ‘latest’, ‘region’ => getenv(‘AWS_DEFAULT_REGION’) ]); $s3->putObject([ ‘Bucket’ => $bucketName, ‘ACL’ => ‘public-read’, ‘Key’ => $filename, ‘Body’ => $data, ‘ContentType’ => ‘text/html’, ]); Rob Allen ~ @akrabat

Slide 82

Slide 82

Upload to S3 $s3 = new S3Client([ ‘version’ => ‘latest’, ‘region’ => getenv(‘AWS_DEFAULT_REGION’) ]); $s3->putObject([ ‘Bucket’ => $bucketName, ‘ACL’ => ‘public-read’, ‘Key’ => $filename, ‘Body’ => $data, ‘ContentType’ => ‘text/html’, ]); Rob Allen ~ @akrabat

Slide 83

Slide 83

Upload to S3 $s3 = new S3Client([ ‘version’ => ‘latest’, ‘region’ => getenv(‘AWS_DEFAULT_REGION’) ]); $s3->putObject([ ‘Bucket’ => $bucketName, ‘ACL’ => ‘public-read’, ‘Key’ => $filename, ‘Body’ => $data, ‘ContentType’ => ‘text/html’, ]); Rob Allen ~ @akrabat

Slide 84

Slide 84

Invalidate CloudFront $cft = new CloudFrontClient([ .. ]); $result = $cft->createInvalidation([ ‘DistributionId’ => $cloudFrontId, ‘InvalidationBatch’ => [ ‘CallerReference’ => date(‘YmdHis’), ‘Paths’ => [ ‘Items’ => [“$year.html”], ‘Quantity’ => 1, ], ], ]); Rob Allen ~ @akrabat

Slide 85

Slide 85

Invalidate CloudFront $cft = new CloudFrontClient([ .. ]); $result = $cft->createInvalidation([ ‘DistributionId’ => $cloudFrontId, ‘InvalidationBatch’ => [ ‘CallerReference’ => date(‘YmdHis’), ‘Paths’ => [ ‘Items’ => [“$year.html”], ‘Quantity’ => 1, ], ], ]); Rob Allen ~ @akrabat

Slide 86

Slide 86

Invalidate CloudFront $cft = new CloudFrontClient([ .. ]); $result = $cft->createInvalidation([ ‘DistributionId’ => $cloudFrontId, ‘InvalidationBatch’ => [ ‘CallerReference’ => date(‘YmdHis’), ‘Paths’ => [ ‘Items’ => [“$year.html”], ‘Quantity’ => 1, ], ], ]); Rob Allen ~ @akrabat

Slide 87

Slide 87

Invalidate CloudFront $cft = new CloudFrontClient([ .. ]); $result = $cft->createInvalidation([ ‘DistributionId’ => $cloudFrontId, ‘InvalidationBatch’ => [ ‘CallerReference’ => date(‘YmdHis’), ‘Paths’ => [ ‘Items’ => [“$year.html”], ‘Quantity’ => 1, ], ], ]); Rob Allen ~ @akrabat

Slide 88

Slide 88

The finished website Rob Allen ~ @akrabat

Slide 89

Slide 89

To sum up Rob Allen ~ @akrabat

Slide 90

Slide 90

Resources • https://akrabat.com • https://www.martinfowler.com/articles/serverless.html • https://github.com/akrabat/ow-php-todo-backend • https://github.com/akrabat/project365-photos-website • http://www.openwhisk.org • https://aws.amazon.com/lambda/ • https://bref.sh Rob Allen ~ @akrabat

Slide 91

Slide 91

Thank you! Rob Allen ~ @akrabat