A presentation at PHP Shropshire, April 2019 in in Telford, UK by Rob Allen
Introduction to Serverless PHP Rob Allen April 2019
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 Deployed to the cloud Runs when needed Scaled automatically 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 • PWA/Static site contact form, et al. Rob Allen ~ @akrabat
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
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
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
Serverless platforms Rob Allen ~ @akrabat
Serverless platforms with PHP support Rob Allen ~ @akrabat
Concepts Rob Allen ~ @akrabat
Rob Allen ~ @akrabat
Rob Allen ~ @akrabat
Hello world in PHP Rob Allen ~ @akrabat
Hello world in PHP Rob Allen ~ @akrabat
Upload your action $ wsk action update hello hello.php ok: updated action hello Rob Allen ~ @akrabat
Run your action $ wsk action invoke hello —result { “msg”: “Hello World” } 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
Rob Allen ~ @akrabat
AWS Lambda with PHP Only sensibly possible since November 2018 with the introduction of layers Rob Allen ~ @akrabat
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
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
Serverless Framework Infrastructure as code Rob Allen ~ @akrabat
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
Manage using Serverless Framework service: hello-lambdaphp provider: name: aws runtime: provided memorySize: 128 layers: php: path: layer/php Rob Allen ~ @akrabat
Manage using Serverless Framework service: hello-lambdaphp provider: name: aws runtime: provided memorySize: 128 layers: php: path: layer/php Rob Allen ~ @akrabat
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
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
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
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
Manage using Serverless Framework functions: hello: handler: handler.hello layers: - {Ref: PhpLambdaLayer} Rob Allen ~ @akrabat
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
Invoke using Serverless Framework $ sls invoke -f hello —data=’{“name”:”Rob”}’ { “msg”: “Hello Rob” } Rob Allen ~ @akrabat
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
Project 365 My photo-a-day 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Invalidate CloudFront $cft = new CloudFrontClient([ .. ]); $result = $cft->createInvalidation([ ‘DistributionId’ => $cloudFrontId, ‘InvalidationBatch’ => [ ‘CallerReference’ => date(‘YmdHis’), ‘Paths’ => [ ‘Items’ => [“$year.html”], ‘Quantity’ => 1, ], ], ]); Rob Allen ~ @akrabat
Invalidate CloudFront $cft = new CloudFrontClient([ .. ]); $result = $cft->createInvalidation([ ‘DistributionId’ => $cloudFrontId, ‘InvalidationBatch’ => [ ‘CallerReference’ => date(‘YmdHis’), ‘Paths’ => [ ‘Items’ => [“$year.html”], ‘Quantity’ => 1, ], ], ]); Rob Allen ~ @akrabat
Invalidate CloudFront $cft = new CloudFrontClient([ .. ]); $result = $cft->createInvalidation([ ‘DistributionId’ => $cloudFrontId, ‘InvalidationBatch’ => [ ‘CallerReference’ => date(‘YmdHis’), ‘Paths’ => [ ‘Items’ => [“$year.html”], ‘Quantity’ => 1, ], ], ]); Rob Allen ~ @akrabat
Invalidate CloudFront $cft = new CloudFrontClient([ .. ]); $result = $cft->createInvalidation([ ‘DistributionId’ => $cloudFrontId, ‘InvalidationBatch’ => [ ‘CallerReference’ => date(‘YmdHis’), ‘Paths’ => [ ‘Items’ => [“$year.html”], ‘Quantity’ => 1, ], ], ]); Rob Allen ~ @akrabat
The finished website Rob Allen ~ @akrabat
To sum up Rob Allen ~ @akrabat
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
Thank you! Rob Allen ~ @akrabat
APIs and microservices are how we build modern web applications and serverless technologies make this easy. This session will show you how serverless applications are built and how you can leverage your PHP skills to build APIs of all shapes and sizes. We will cover how to use your current knowledge to build applications in PHP within Apache OpenWhisk or AWS Lambda, leveraging the API Gateway to build robust APIs quickly and easily. By the end of the session, you’ll be well placed to design and build your own microservices that take full advantage of the power of serverless technologies.