Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Running your PHP site on AWS Lambda

nealio82
February 21, 2019

Running your PHP site on AWS Lambda

Want to have immediate & easy website scaling, but also get rid of costly servers as they’re sitting idle 80% of the time waiting for visitors? Heard something about PHP and layers on AWS Lambda but have no idea what it means? You're not the only one! In this session we’ll get ourselves up-and-running with a PHP website on Lambda.

PHPUK Conference 2019

nealio82

February 21, 2019
Tweet

More Decks by nealio82

Other Decks in Programming

Transcript

  1. EC2

  2. • You can use up to 5 different layers in

    a Lambda function • The total unzipped size of code & layers must be < 250 MB • 1,000 concurrent invocations per region
  3. What kind of lambda do you want to create? [0]

    PHP function [1] HTTP application [2] Console application >
  4. What kind of lambda do you want to create? [0]

    PHP function [1] HTTP application [2] Console application > 1
  5. AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: '' Resources: MyFunction: Type: AWS::Serverless::Function

    Properties: FunctionName: 'my-function' Description: '' CodeUri: . Handler: index.php Timeout: 30 # in seconds (API Gateway has a timeout of 30 se Runtime: provided Layers: - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fp Events: # The function will match all HTTP URLs HttpRoot: Type: Api Properties: Path: / Method: ANY HttpSubPaths: Type: Api Properties: Path: /{proxy+} hello-world.php template.yaml
  6. # ... Resources: MyFunction: Type: AWS::Serverless::Function Properties: FunctionName: 'my-function' Description:

    '' CodeUri: . Handler: index.php Timeout: 30 # in seconds Runtime: provided hello-world.php template.yaml
  7. # ... Resources: MyFunction: Type: AWS::Serverless::Function Properties: FunctionName: 'bref-hello-world' Description:

    '' CodeUri: . Handler: hello-world.php Timeout: 30 # in seconds Runtime: provided hello-world.php template.yaml
  8. # ... Layers: - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fpm:1' Events: # The function will

    match all HTTP URLs HttpRoot: Type: Api Properties: Path: / Method: ANY HttpSubPaths: Type: Api Properties: Path: /{proxy+} Method: ANY # ... hello-world.php template.yaml
  9. # ... Layers: - 'arn:aws:lambda:eu-west-1:209497400698:layer:php-73-fpm:1' Events: # The function will

    match all HTTP URLs HttpRoot: Type: Api Properties: Path: / Method: ANY HttpSubPaths: Type: Api Properties: Path: /{proxy+} Method: ANY # ... hello-world.php template.yaml
  10. # ... Layers: - 'arn:aws:lambda:eu-west-1:209497400698:layer:php-73-fpm:1' Events: # The function will

    match all HTTP URLs HttpRoot: Type: Api Properties: Path: / Method: ANY HttpSubPaths: Type: Api Properties: Path: /{proxy+} Method: ANY # ... hello-world.php template.yaml
  11. # ... Layers: - 'arn:aws:lambda:eu-west-1:209497400698:layer:php-73-fpm:1' Events: # The function will

    match all HTTP URLs HttpRoot: Type: Api Properties: Path: / Method: ANY HttpSubPaths: Type: Api Properties: Path: /{proxy+} Method: ANY # ... hello-world.php template.yaml
  12. Execute the following command to deploy the packaged template aws

    cloudformation deploy --template-file / Users/neal/www/hello-world/.stack.yaml --stack- name <YOUR STACK NAME>
  13. Failed to create/update the stack. Run the following command to

    fetch the list of events leading up to the failure aws cloudformation describe-stack-events -- stack-name bref-hello-world
  14. Error occurred while GetObject. S3 Error Code: PermanentRedirect. S3 Error

    Message: The bucket is in this region: eu-west-2.
  15. • Looked at how FaaS could simplify our architecture &

    save us money • Installed AWS CLI • Added Bref to a project • Explored the template.yaml file • Used SAM Local to test • Created a deployment bucket • Packaged and deployed our application • Explored the stack • Tested the application on Lambda • Seen the logs in Cloudwatch • Added a custom domain name
  16. • Only /tmp is writeable • The total unzipped size

    of code & layers must be < 250 MB
  17. What kind of lambda do you want to create? [0]

    PHP function [1] HTTP application [2] Console application > 1
  18. # ... Resources: MyFunction: Type: AWS::Serverless::Function Properties: FunctionName: 'bref-kittyquotes' Description:

    '' CodeUri: . Handler: public/index.php Timeout: 30 # in seconds Runtime: provided MemorySize: 1024 template.yaml bin public src templates config
  19. # ... Resources: WebApplication: Type: AWS::Serverless::Function Properties: FunctionName: 'bref-kittyquotes' Description:

    '' CodeUri: . Handler: public/index.php Timeout: 30 # in seconds Runtime: provided template.yaml bin public src templates config
  20. bin public src templates config template.yaml <?php namespace App; class

    Kernel extends BaseKernel { // ... public function getCacheDir() { // When on the lambda only /tmp is writeable if (getenv('LAMBDA_TASK_ROOT') !== false) { return '/tmp/cache/' . $this->environment; } return $this->getProjectDir().'/var/cache' . $this->environment; } Kernel.php
  21. bin public src templates config template.yaml <?php namespace App; class

    Kernel extends BaseKernel { // ... public function getLogDir() { // When on the lambda only /tmp is writeable if (getenv('LAMBDA_TASK_ROOT') !== false) { return '/tmp/log/'; } return $this->getProjectDir().'/var/log'; } Kernel.php
  22. # ... Resources: WebApplication: Type: AWS::Serverless::Function Properties: Environment: Variables: APP_ENV:

    prod DATABASE_URL: ‘mysql://db_user:db_pass@...’ # ... template.yaml bin public src templates config
  23. Failed to create/update the stack. Run the following command to

    fetch the list of events leading up to the failure aws cloudformation describe-stack-events --stack-name bref-kitty-quotes-app
  24. $ composer install --optimize-autoloader --no-dev $ rm -rf node_modules $

    rm -rf var/cache $ rm -rf .idea/* $ rm -rf .git/*
  25. cp -Rf ../bref-kitty-quotes-symfony/* . \ && rm -rf var/cache/* \

    && rm -rf node_modules \ && composer install --optimize-autoloader --no-dev \ && php bin/console cache:warmup --env=prod \ && sam package --output-template-file .stack.yaml --s3-bucket bref-kitty-quotes-bucket \ && sam deploy --template-file .stack.yaml --stack-name bref-kitty-quotes-app --capabilities CAPABILITY_IAM
  26. // ... if (Encore.isProduction()) { Encore.setPublicPath( 'https://s3-eu-west-1.amazonaws.com/' + ‘kittyquotes-site-assets’ );

    Encore.setManifestKeyPrefix('build/'); } webpack.config.js template.yaml bin public src templates config php
  27. API Gateway Data Store Lambda function Handler PHP Layer Bootstrap

    Sessions Handler PHP Layer Bootstrap Sessions
  28. API Gateway Data Store Sessions Sessions Lambda function Handler PHP

    Layer Bootstrap Handler PHP Layer Bootstrap
  29. bin config services: # ... Symfony\Component\HttpFoundation\Session \Storage\Handler\PdoSessionHandler: arguments: - !service

    { class: PDO, factory: > 'database_connection:getWrappedConnection' } - { lock_mode: 1 } services.yaml webpack.config.js template.yaml public src templates php
  30. bin config framework: # ... session: # ... handler_id: >

    Symfony\Component\HttpFoundation\Session \Storage\Handler\PdoSessionHandler packages framework.yaml webpack.config.js template.yaml public src templates php
  31. bin public src templates config final class Version20180828140534 extends AbstractMigration

    { public function up(Schema $schema): void { $this->addSql("CREATE TABLE `sessions` ( `sess_id` VARCHAR(128) NOT NULL PRIMARY KEY, `sess_data` BLOB NOT NULL, `sess_time` INTEGER UNSIGNED NOT NULL, `sess_lifetime` MEDIUMINT NOT NULL ) COLLATE utf8_bin, ENGINE = InnoDB;"); } public function down(Schema $schema): void { $this->addSql('DROP TABLE sessions'); } } Migrations Version2019____.php webpack.config.js template.yaml php
  32. Globals: Function: Environment: Variables: DATABASE_URL: ‘mysql://db_user:db_pass@...’ Resources: WebApplication: # ...

    Console: Type: AWS::Serverless::Function Properties: FunctionName: 'bref-kittyquotes-console' CodeUri: . Handler: bin/console # or `artisan` for Laravel Runtime: provided Layers: # PHP runtime - 'arn:aws:lambda:eu-west-1:209497400698:layer:php-73:1' # Console layer - 'arn:aws:lambda:eu-west-1:209497400698:layer:console:1' webpack.config.js template.yaml bin public src templates config php
  33. Globals: Function: Environment: Variables: DATABASE_URL: ‘mysql://db_user:db_pass@...’ Resources: WebApplication: # ...

    Console: Type: AWS::Serverless::Function Properties: FunctionName: 'bref-kittyquotes-console' CodeUri: . Handler: bin/console # or `artisan` for Laravel Runtime: provided Layers: # PHP runtime - 'arn:aws:lambda:eu-west-1:209497400698:layer:php-73:1' # Console layer - 'arn:aws:lambda:eu-west-1:209497400698:layer:console:1' webpack.config.js template.yaml bin public src templates config php
  34. Globals: Function: Environment: Variables: DATABASE_URL: ‘mysql://db_user:db_pass@...’ Resources: WebApplication: # ...

    Console: Type: AWS::Serverless::Function Properties: FunctionName: 'bref-kittyquotes-console' CodeUri: . Handler: bin/console # or `artisan` for Laravel Runtime: provided Layers: # PHP runtime - 'arn:aws:lambda:eu-west-1:209497400698:layer:php-73:1' # Console layer - 'arn:aws:lambda:eu-west-1:209497400698:layer:console:1' webpack.config.js template.yaml bin public src templates config php
  35. # ... Resources: WebApplication: Type: AWS::Serverless::Function Properties: Environment: Variables: APP_UPLOADS_BUCKET_NAME:

    kittyquotes-uploads webpack.config.js template.yaml bin public src templates config php
  36. bin public src templates config <!-- ... --> <img class="rounded"

    src="{{ kitty_uploads_bucket }}/{{ quote.kitty.image }}" alt="{{ quote.kitty.name }}” > <!-- ... --> template.yaml webpack.config.js quote quote-card.html.twig php
  37. Client Server Amazon S3 Get Pre-Signed URL Get Pre-Signed URL

    One-time use URL One-time use URL PUT file to One-time use URL 200 OK
  38. Client Server Amazon S3 Get Pre-Signed URL Get Pre-Signed URL

    One-time use URL One-time use URL PUT file to One-time use URL 200 OK Update record
  39. Client Server Amazon S3 Get Pre-Signed URL Get Pre-Signed URL

    One-time use URL One-time use URL PUT file to One-time use URL 200 OK Update record Delete previous file
  40. bin public src config <?php namespace App\Controller; class AdminController extends

    BaseAdminController { /** * @Route("/kitty/upload-image", name="upload_kitty_image") */ public function uploadKittyImageAction() { //... } /** * @Route("/kitty/upload-image-pre-signed-url/{filename}", name="upload_kitty_image_pre_signed_url", methods={"GET"}) */ public function getPresignedUrlAction($filename) { //... } } php Controller AdminController.php templates template.yaml webpack.config.js
  41. bin public src templates config getPreSignedUrl = function (files) {

    file = files[0]; $.ajax({ url: '/admin/kitty/upload-image-pre-signed-url/' + file.name, type: "GET", dataType: "json", cache: false }) .done(function (data) { preSignedUrl = data.url; document.getElementById('kitty_image_image').value = data.filename; }); } function uploadFile() { $.ajax({ url: preSignedUrl, type: "PUT", data: file, contentType: file.type, processData: false }).done(function () { $("form[name='kitty_image']").submit(); }); } uploader.html.twig template.yaml webpack.config.js php
  42. bin public src config Entity Kitty.php <?php namespace App\Entity; class

    Kitty { // ... /** * @Vich\UploadableField( * mapping="product_image", * fileNameProperty="imageName", * size=“imageSize" * ) * * @var File */ private $imageFile; /** * @ORM\Column(type="datetime") * * @var \DateTime */ private $updatedAt; templates template.yaml webpack.config.js php
  43. <?php namespace App\Entity; class Kitty { // ... /** *

    @param File|\ * Symfony\Component\HttpFoundation\File\UploadedFile $image */ public function setImageFile(?File $image = null): void { $this->imageFile = $image; if (null !== $image) { $this->updatedAt = new \DateTimeImmutable(); } } public function getImageFile(): ?File { return $this->imageFile; } } bin public src config Entity Kitty.php templates template.yaml webpack.config.js php
  44. • Added Bref to an existing site • Fixed assets

    by moving them to a CDN • Moved session storage to the database • Ran console commands by adding another layer • Used pre-signed URLs for image uploading
  45. What kind of lambda do you want to create? [0]

    PHP function [1] HTTP application [2] Console application >
  46. What kind of lambda do you want to create? [0]

    PHP function [1] HTTP application [2] Console application > 0
  47. runtime ./configure \ --build=x86_64-pc-linux-gnu \ --prefix=${INSTALL_DIR} \ --enable-option-checking=fatal \ --enable-maintainer-zts

    \ --with-config-file-path=${INSTALL_DIR}/etc/php \ --with-config-file-scan-dir=${INSTALL_DIR}/etc/php/conf.d:/var/tas --enable-fpm \ --disable-cgi \ --enable-cli \ --disable-phpdbg \ --disable-phpdbg-webhelper \ --with-sodium \ --with-readline \ --with-openssl \ --with-zlib=${INSTALL_DIR} \ --with-zlib-dir=${INSTALL_DIR} \ --with-curl \ --enable-exif \ --enable-ftp \ --with-gettext \ --enable-mbstring \ --with-pdo-mysql=shared,mysqlnd \ --enable-pcntl \ --enable-zip \ --with-pdo-pgsql=shared,${INSTALL_DIR} \ --enable-intl=shared \ --enable-opcache-file php php.Dockerfile
  48. • Documentation • Speed & stability • A recommended method

    of creating your own runtimes • Better framework integrations