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

Running your Laravel project on AWS Lambda

Running your Laravel project on AWS Lambda

Ever find yourself deploying your Laravel app to your EC2 boxes and wondering if you’re using your resources wisely? You’re almost certainly paying for CPU time you’re not using, and scaling PHP applications on AWS can be tricky.

Wouldn’t it be great to pay only for the resources you actually use, and if your site could scale instantly without needing to configure auto-scaling-groups, alarms, and rules? AWS Lambda gives us these features but doesn't natively support PHP.

Until now making PHP run on AWS Lambda has required a lot of hacks and workarounds. However, evolving tools such as https://bref.sh have fixed a lot of the pain for you.

In this session we’ll walk through the steps required to set up a real Laravel website running on Lambda, served over HTTPS, and all without launching a single EC2 instance or ELB.

Finally we'll look at some of the other possibilities and considerations that are now open to us as PHP engineers in a serverless world.

nealio82

June 11, 2019
Tweet

More Decks by nealio82

Other Decks in Programming

Transcript

  1. RUNNING YOUR
    LARAVEL PROJECT
    ON AWS LAMBDA

    View full-size slide

  2. WE ARE HIRING!

    View full-size slide

  3. RUNNING YOUR
    LARAVEL PROJECT
    ON AWS LAMBDA

    View full-size slide

  4. (coupling yourself to AWS for fun & profit)
    COUPLING
    YOURSELF
    TO AWS

    View full-size slide

  5. EC2 Data Store

    View full-size slide

  6. EC2 Data Store

    View full-size slide

  7. EC2 Data Store
    Load Balancer

    View full-size slide

  8. EC2 Data Store
    Load Balancer

    View full-size slide

  9. EC2 Data Store
    Load Balancer
    EC2
    EC2

    View full-size slide

  10. EC2 Data Store
    Load Balancer
    EC2
    EC2

    View full-size slide

  11. EC2 Data Store
    Load Balancer
    EC2
    EC2

    View full-size slide

  12. SAY
    HELLO
    TO
    FaaS

    View full-size slide

  13. Think of FaaS as a server that doesn’t exist…

    View full-size slide

  14. … until you need it …

    View full-size slide

  15. … and goes away again once the job is done

    View full-size slide

  16. 1 million requests FREE per month

    View full-size slide

  17. $0.20 per million requests thereafter

    View full-size slide

  18. Container
    Bootstrap Code

    View full-size slide

  19. Container
    Bootstrap Code

    View full-size slide

  20. Container
    Bootstrap Code
    Application Code

    View full-size slide

  21. Container
    Bootstrap Code
    Application Code

    View full-size slide

  22. Container
    Bootstrap Code
    Application Code

    View full-size slide

  23. Lambda function
    Bootstrap
    PHP Layer
    Handler

    View full-size slide

  24. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  25. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  26. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  27. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  28. You can use up to 5 different layers in a Lambda function

    View full-size slide

  29. The total unzipped size of code & layers must be < 250 MB

    View full-size slide

  30. Default account limit of 1,000 concurrent
    invocations per AWS region

    View full-size slide

  31. Maximum request body size is 6 MB

    View full-size slide

  32. RUNNING
    PHP ON
    LAMBDA

    View full-size slide

  33. IS
    A PITA
    RUNNING
    PHP ON
    LAMBDA

    View full-size slide

  34. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  35. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  36. https://bref.sh

    View full-size slide

  37. BREF AIMS TO MAKE
    RUNNING PHP APPS
    SIMPLE

    View full-size slide

  38. SIMPLIFY
    PROBLEMS
    BY REMOVING
    CHOICES

    View full-size slide

  39. PROVIDE SIMPLE
    AND FAMILIAR
    SOLUTIONS

    View full-size slide

  40. EMPOWER BY SHARING
    KNOWLEDGE

    View full-size slide

  41. WHAT
    DOES
    BREF ACTUALLY
    DO?

    View full-size slide

  42. SAM DEALS
    WITH
    DEPLOYING

    View full-size slide

  43. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  44. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  45. API Gateway
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  46. API Gateway
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  47. echo 'Hello, World!';
    phpinfo();
    hello-world.php

    View full-size slide

  48. $ composer require mnapoli/bref

    View full-size slide

  49. $ php vendor/bin/bref init

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  52. In which AWS region do you want to deploy
    your application?
    >

    View full-size slide

  53. In which AWS region do you want to deploy
    your application?
    > eu-west-1

    View full-size slide

  54. [OK] Project initialized and ready to test or deploy.
    The files created were automatically added to git.

    View full-size slide

  55. echo 'Hello, World!';
    phpinfo();
    hello-world.php
    index.php
    template.yaml

    View full-size slide

  56. echo 'Hello, World!';
    phpinfo();
    hello-world.php
    template.yaml

    View full-size slide

  57. 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:eu-west-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

    View full-size slide

  58. # ...
    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

    View full-size slide

  59. # ...
    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

    View full-size slide

  60. # ...
    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

    View full-size slide

  61. # ...
    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

    View full-size slide

  62. # ...
    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

    View full-size slide

  63. # ...
    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

    View full-size slide

  64. $ brew upgrade && brew update
    $ brew tap aws/tap
    $ brew install aws-sam-cli

    View full-size slide

  65. $ aws s3 mb s3://bref-hello-world

    View full-size slide

  66. make_bucket: bref-hello-world

    View full-size slide

  67. $ sam package \
    --output-template-file .stack.yaml \
    --s3-bucket

    View full-size slide

  68. $ sam package \
    --output-template-file .stack.yaml \
    --s3-bucket bref-hello-world

    View full-size slide

  69. Uploading to cf39403efc50314711b61effd4f5c948
    2841371 / 2841371.0 (100.00%)
    Successfully packaged artifacts and wrote output
    template to file .stack.yaml.

    View full-size slide

  70. Execute the following command to deploy the
    packaged template
    aws cloudformation deploy --template-file /
    Users/neal/www/hello-world/.stack.yaml --stack-
    name

    View full-size slide

  71. $ aws cloudformation deploy --template-file /
    Users/neal/www/hello-world/.stack.yaml --stack-
    name

    View full-size slide

  72. $ aws cloudformation deploy --template-file /
    Users/neal/www/hello-world/.stack.yaml --stack-
    name bref-hello-world

    View full-size slide

  73. Waiter encountered a terminal failure state
    Status: FAILED. Reason: Requires capabilities :
    [CAPABILITY_IAM]

    View full-size slide

  74. $ sam deploy \
    --template-file .stack.yaml \
    --capabilities CAPABILITY_IAM \
    --stack-name

    View full-size slide

  75. $ sam deploy \
    --template-file .stack.yaml \
    --capabilities CAPABILITY_IAM \
    --stack-name bref-hello-world

    View full-size slide

  76. 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

    View full-size slide

  77. $ php vendor/bin/bref deployment \
    --region eu-west-1 -- bref-hello-world

    View full-size slide

  78. AWS::Lambda::Function Error occurred while GetObject.
    S3 Error Code: PermanentRedirect. S3 Error Message:
    The bucket is in this region: us-east-1.

    View full-size slide

  79. $ aws s3 mb s3://bref-hello-world --region eu-west-1

    View full-size slide

  80. $ sam package \
    --output-template-file .stack.yaml \
    --s3-bucket bref-hello-world

    View full-size slide

  81. $ sam deploy \
    --template-file .stack.yaml \
    --capabilities CAPABILITY_IAM \
    --stack-name bref-hello-world

    View full-size slide

  82. Successfully created/updated stack - bref-hello-world

    View full-size slide

  83. API Gateway
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  84. Looked how FaaS can simplify architecture & save money

    View full-size slide

  85. Installed AWS CLI

    View full-size slide

  86. Added Bref to an existing project

    View full-size slide

  87. Explored the template file

    View full-size slide

  88. Created a deployment bucket

    View full-size slide

  89. Created a deployment bucket in the correct region

    View full-size slide

  90. Packaged & deployed the application

    View full-size slide

  91. Explored the stack

    View full-size slide

  92. Tested the PHP application running on Lambda

    View full-size slide

  93. REFACTORING
    A FULLY
    FEATURED
    WEBSITE

    View full-size slide

  94. The filesystem is read-only

    View full-size slide

  95. (apart from /tmp)

    View full-size slide

  96. The total unzipped size of code & layers must be < 250 MB

    View full-size slide

  97. $ composer require mnapoli/bref

    View full-size slide

  98. $ php vendor/bin/bref init

    View full-size slide

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

    View full-size slide

  100. In which AWS region do you want to deploy
    your application?
    > eu-west-1

    View full-size slide

  101. # ...
    Resources:
    WebApplication:
    Type: AWS::Serverless::Function
    Properties:
    FunctionName: ‘bref-kittyfinder'
    Description: ''
    CodeUri: .
    Handler: public/index.php
    Timeout: 30 # in seconds
    Runtime: provided
    MemorySize: 1024
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  102. # ...
    Resources:
    WebApplication:
    Type: AWS::Serverless::Function
    Properties:
    FunctionName: ‘bref-kittyfinder'
    Description: ''
    CodeUri: .
    Handler: public/index.php
    Timeout: 30 # in seconds
    Runtime: provided
    MemorySize: 1024
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  103. # ...
    Resources:
    WebApplication:
    Type: AWS::Serverless::Function
    Properties:
    FunctionName: ‘bref-kittyfinder'
    Description: ''
    CodeUri: .
    Handler: public/index.php
    Timeout: 30 # in seconds
    Runtime: provided
    MemorySize: 1024
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  104. $ aws s3 mb s3://bref-kitty-finder-bucket

    View full-size slide

  105. $ sam package \
    --output-template-file .stack.yaml \
    --s3-bucket bref-kitty-finder

    View full-size slide

  106. $ sam deploy \
    --template-file .stack.yaml \
    --capabilities CAPABILITY_IAM \
    --stack-name bref-kitty-finder

    View full-size slide

  107. 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-finder

    View full-size slide

  108. Unzipped size must be smaller than 155930325 bytes

    View full-size slide

  109. $ composer install --optimize-autoloader --no-dev
    $ rm -rf node_modules

    View full-size slide

  110. $ composer install --optimize-autoloader --no-dev
    $ rm -rf node_modules
    $ rm -rf .idea/*
    $ rm -rf .git/*

    View full-size slide

  111. cp -Rf ../bref-kitty-finder/* . \
    cp ../bref-kitty-finder/.env . \
    && rm -rf node_modules \
    && composer install --optimize-autoloader --no-dev \
    && sam package --output-template-file .stack.yaml
    --s3-bucket bref-kitty-finder-bucket \
    && sam deploy --template-file .stack.yaml
    --stack-name bref-kitty-finder
    --capabilities CAPABILITY_IAM

    View full-size slide

  112. Successfully created/updated stack - bref-kitty-finder

    View full-size slide

  113. CLOUDWATCH
    HAS YOUR LOGS

    View full-size slide

  114. $ sam logs --name bref-kitty-finder

    View full-size slide

  115. "NOTICE: PHP message: PHP Fatal error: Uncaught
    ErrorException: file_put_contents(/var/task/
    storage/framework/views/
    a048d5da056b113da279d23ae842d71b174d2ae6.php):
    failed to open stream: Read-only file system
    in /var/task/vendor/laravel/framework/src/
    Illuminate/Filesystem/Filesystem.php:122"

    View full-size slide

  116. # ...
    Globals:
    Function:
    Environment:
    Variables:
    # Laravel environment variables
    APP_STORAGE: '/tmp'
    Resources:
    WebApplication:
    # ...
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  117. /*
    |-----------------------------------------------------------------------
    | Create The Application
    |-----------------------------------------------------------------------
    |
    | The first thing we will do is create a new Laravel application instanc
    | which serves as the "glue" for all the components of Laravel, and is
    | the IoC container for the system binding all of the various parts.
    |
    */
    $app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
    );
    /*
    * Allow overriding the storage path in
    * production using an environment variable.
    */
    $app->useStoragePath($_ENV['APP_STORAGE'] ?? $app->storagePath());
    app
    config
    database
    bootstrap
    app.php
    public
    resources
    routes
    storage

    View full-size slide

  118. /*
    |-----------------------------------------------------------------------
    | Create The Application
    |-----------------------------------------------------------------------
    |
    | The first thing we will do is create a new Laravel application instanc
    | which serves as the "glue" for all the components of Laravel, and is
    | the IoC container for the system binding all of the various parts.
    |
    */
    $app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
    );
    /*
    * Allow overriding the storage path in
    * production using an environment variable.
    */
    $app->useStoragePath($_ENV['APP_STORAGE'] ?? $app->storagePath());
    app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    app.php

    View full-size slide

  119. # ...
    VIEW_COMPILED_PATH=/tmp/storage/framework/views
    # We cannot store sessions to disk: if you don't need sessions (e.g. API
    # then use `array`, else store sessions in database or cookies
    SESSION_DRIVER=array
    # Logging to stderr allows the logs to end up in Cloudwatch
    LOG_CHANNEL=stderr
    # ...
    app
    config
    database
    bootstrap
    .env
    public
    resources
    routes
    storage

    View full-size slide

  120. # ...
    VIEW_COMPILED_PATH=/tmp/storage/framework/views
    # We cannot store sessions to disk: if you don't need sessions (e.g. API
    # then use `array`, else store sessions in database or cookies
    SESSION_DRIVER=array
    # Logging to stderr allows the logs to end up in Cloudwatch
    LOG_CHANNEL=stderr
    # ...
    app
    config
    database
    bootstrap
    .env
    public
    resources
    routes
    storage

    View full-size slide

  121. # ...
    VIEW_COMPILED_PATH=/tmp/storage/framework/views
    # We cannot store sessions to disk: if you don't need sessions (e.g. API
    # then use `array`, else store sessions in database or cookies
    SESSION_DRIVER=array
    # Logging to stderr allows the logs to end up in Cloudwatch
    LOG_CHANNEL=stderr
    # ...
    app
    config
    database
    bootstrap
    .env
    public
    resources
    routes
    storage

    View full-size slide

  122. # ...
    VIEW_COMPILED_PATH=/tmp/storage/framework/views
    # We cannot store sessions to disk: if you don't need sessions (e.g. API
    # then use `array`, else store sessions in database or cookies
    SESSION_DRIVER=array
    # Logging to stderr allows the logs to end up in Cloudwatch
    LOG_CHANNEL=stderr
    # ...
    app
    config
    database
    bootstrap
    .env
    public
    resources
    routes
    storage

    View full-size slide

  123. class AppServiceProvider extends ServiceProvider
    {
    // ...
    /**
    * Bootstrap any application services.
    *
    * @return void
    */
    public function boot()
    {
    }
    }
    app
    config
    database
    Providers
    bootstrap
    AppServiceProvider.php
    public
    resources
    routes
    storage

    View full-size slide

  124. class AppServiceProvider extends ServiceProvider
    {
    // ...
    /**
    * Bootstrap any application services.
    *
    * @return void
    */
    public function boot()
    {
    // Make sure the directory for compiled views exist
    if (!is_dir(config('view.compiled'))) {
    mkdir(config('view.compiled'), 0755, true);
    }
    }
    }
    app
    config
    database
    Providers
    public
    resources
    routes
    storage
    bootstrap
    AppServiceProvider.php

    View full-size slide

  125. php
    extension=pdo_mysql
    conf.d
    php.ini
    app
    config
    database
    public
    resources
    routes
    storage
    bootstrap

    View full-size slide

  126. CONNECTING THE DATABASE

    View full-size slide

  127. API Gateway
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  128. API Gateway
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  129. API Gateway
    API Gateway
    Data Store
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  130. API Gateway
    VPC
    API Gateway
    Data Store
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  131. # ...
    Globals:
    Function:
    Environment:
    Variables:
    # Laravel environment variables
    APP_STORAGE: '/tmp'
    DATABASE_URL: ‘mysql://db_user:db_pass@...’
    # ...
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  132. # ...
    Globals:
    Function:
    Environment:
    Variables:
    # Laravel environment variables
    APP_STORAGE: '/tmp'
    DATABASE_URL: ‘{{resolve:ssm:kittyfinder-database-url:1}}'
    # ...
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  133. # ...
    Globals:
    Function:
    Environment:
    Variables:
    # Laravel environment variables
    APP_STORAGE: '/tmp'
    DATABASE_URL: ‘{{resolve:ssm:kittyfinder-database-url:1}}'
    # ...
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  134. CONTENT
    DELIVERY
    NETWORK

    View full-size slide

  135. $ composer require arubacao/asset-cdn

    View full-size slide

  136. $ composer require league/flysystem-aws-s3-v3

    View full-size slide

  137. $ aws s3 mb s3://kittyfinder-site-assets

    View full-size slide

  138. app
    config
    database
    bootstrap
    filesystems.php
    return [
    'disks' => [
    // ...
    /*
    |-----------------------------------------------------------------
    | Assets CDN
    |-----------------------------------------------------------------
    |
    | This is where compiled static assets will be served from
    */
    'asset-cdn' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'bucket' => env(‘AWS_CDN_BUCKET'),
    ],
    ],
    ];
    public
    resources
    routes
    storage

    View full-size slide

  139. app
    config
    database
    bootstrap
    filesystems.php
    return [
    'disks' => [
    // ...
    /*
    |-----------------------------------------------------------------
    | Assets CDN
    |-----------------------------------------------------------------
    |
    | This is where compiled static assets will be served from
    */
    'asset-cdn' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'bucket' => env(‘AWS_CDN_BUCKET'),
    ],
    ],
    ];
    public
    resources
    routes
    storage

    View full-size slide

  140. return [
    'use_cdn' => env('USE_CDN', false),
    'cdn_url' => 'https://cdn.kittyfinder.net',
    'filesystem' => [
    'disk' => 'asset-cdn',
    'options' => [
    'ACL' => 'public-read',
    'CacheControl' => 'max-age=31536000, public’,
    // ...
    ],
    'files' => [
    'include' => [
    'paths' => [
    'js',
    'css'
    ],
    // ...
    app
    config
    database
    bootstrap
    asset-cdn.php
    public
    resources
    routes
    storage

    View full-size slide

  141. # ...
    Globals:
    Function:
    Environment:
    Variables:
    # Laravel environment variables
    APP_STORAGE: '/tmp'
    DATABASE_URL: ‘{{resolve:ssm:kittyfinder-database-url:1}}'
    USE_CDN: true
    # ...
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide






  142. app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    views
    layouts
    app.blade.php

    View full-size slide






  143. app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    views
    layouts
    app.blade.php

    View full-size slide

  144. BRINGING
    YOUR OWN
    DOMAIN
    NAME

    View full-size slide

  145. https://lzfret3054.execute-api.eu-west-1.amazonaws.com/Prod/

    View full-size slide

  146. https://lzfret3054.execute-api.eu-west-1.amazonaws.com/Prod/

    View full-size slide

  147. return [
    'use_cdn' => env('USE_CDN', false),
    'cdn_url' => 'https://cdn.kittyfinder.net',
    'filesystem' => [
    'disk' => 'asset-cdn',
    'options' => [
    'ACL' => 'public-read',
    'CacheControl' => 'max-age=31536000, public’,
    // ...
    ],
    'files' => [
    'include' => [
    'paths' => [
    'js',
    'css',
    'vendor'
    ],
    // ...
    app
    config
    database
    bootstrap
    asset-cdn.php
    public
    resources
    routes
    storage

    View full-size slide



  148. app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    views
    vendor
    nova
    layout.blade.php

    View full-size slide

  149. class AppServiceProvider extends ServiceProvider
    {
    // ...
    /**
    * Bootstrap any application services.
    *
    * @return void
    */
    public function boot()
    {
    // Make sure the directory for compiled views exist
    if (!is_dir(config('view.compiled'))) {
    mkdir(config('view.compiled'), 0755, true);
    }
    if($this->app->environment('production')) {
    URL::forceScheme('https');
    }
    }
    }
    app
    config
    database
    Providers
    public
    resources
    routes
    storage
    bootstrap
    AppServiceProvider.php

    View full-size slide

  150. # ...
    Globals:
    Function:
    Environment:
    Variables:
    # Laravel environment variables
    APP_STORAGE: '/tmp'
    DATABASE_URL: ‘{{resolve:ssm:kittyfinder-database-url:1}}'
    USE_CDN: true
    APP_ENV: production
    # ...
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  151. API Gateway
    Data Store
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  156. $ php artisan session:table

    View full-size slide

  157. # ...
    Globals:
    Function:
    Environment:
    Variables:
    # Laravel environment variables
    APP_STORAGE: '/tmp'
    DATABASE_URL: ‘{{resolve:ssm:kittyfinder-database-url:1}}'
    USE_CDN: true
    APP_ENV: production
    SESSION_DRIVER: database
    # ...
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  158. $ php artisan migrate

    View full-size slide

  159. CONSOLE COMMANDS

    View full-size slide

  160. Resources:
    WebApplication:
    # ...
    Console:
    Type: AWS::Serverless::Function
    Properties:
    FunctionName: 'bref-kittyfinder-console'
    CodeUri: .
    Handler: artisan
    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'
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  161. app
    config
    database
    bootstrap
    Resources:
    WebApplication:
    # ...
    Console:
    Type: AWS::Serverless::Function
    Properties:
    FunctionName: 'bref-kittyfinder-console'
    CodeUri: .
    Handler: artisan
    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'
    template.yaml
    public
    resources
    routes
    storage

    View full-size slide

  162. app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage
    Resources:
    WebApplication:
    # ...
    Console:
    Type: AWS::Serverless::Function
    Properties:
    FunctionName: 'bref-kittyfinder-console'
    CodeUri: .
    Handler: artisan
    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'

    View full-size slide

  163. $ php vendor/bin/bref cli bref-kittyfinder-console -- \
    migrate --force

    View full-size slide

  164. $ php vendor/bin/bref cli bref-kittyfinder-console -- \
    migrate --force

    View full-size slide

  165. $ php vendor/bin/bref cli bref-kittyfinder-console -- \
    migrate --force

    View full-size slide

  166. Migrating: 2019_06_06_163613_create_sessions_table
    Migrated: 2019_06_06_163613_create_sessions_table

    View full-size slide

  167. API Gateway
    Data Store
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  168. API Gateway
    Data Store
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  169. API Gateway
    Data Store
    File Store
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  170. # ...
    AWS_BUCKET=kittyfinder-uploads
    # ...
    app
    config
    database
    bootstrap
    .env
    public
    resources
    routes
    storage

    View full-size slide

  171. /**
    * Get the fields displayed by the resource.
    *
    * @param \Illuminate\Http\Request $request
    * @return array
    */
    public function fields(Request $request)
    {
    return [
    // ...
    Avatar::make('Avatar', 'profile_pic')->disk('s3'),
    // ...
    ];
    }
    app
    config
    database
    bootstrap
    Nova
    Kitty.php
    User.php
    public
    resources
    routes
    storage

    View full-size slide

  172. app
    config
    database
    bootstrap
    Nova
    Kitty.php
    User.php
    /**
    * Get the fields displayed by the resource.
    *
    * @param \Illuminate\Http\Request $request
    * @return array
    */
    public function fields(Request $request)
    {
    return [
    // ...
    Avatar::make('Avatar')->disk('s3'),
    // ...
    ];
    }
    public
    resources
    routes
    storage

    View full-size slide


  173. app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    views
    home.blade.php

    View full-size slide

  174. app
    config
    database
    bootstrap
    public
    resources

    views
    home.blade.php
    routes
    storage

    View full-size slide

  175. UPLOADING
    NEW IMAGES
    OF KITTIES

    View full-size slide

  176. Pre-signed URLs

    View full-size slide

  177. Client Server Amazon S3

    View full-size slide

  178. Client Server Amazon S3
    Get Pre-Signed URL

    View full-size slide

  179. Client Server Amazon S3
    Get Pre-Signed URL
    Get Pre-Signed URL

    View full-size slide

  180. Client Server Amazon S3
    Get Pre-Signed URL
    Get Pre-Signed URL
    One-time use URL

    View full-size slide

  181. Client Server Amazon S3
    Get Pre-Signed URL
    Get Pre-Signed URL
    One-time use URL
    One-time use URL

    View full-size slide

  182. 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

    View full-size slide

  183. 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

    View full-size slide

  184. 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

    View full-size slide

  185. $ php artisan nova:field kittyfinder/s3-filepicker

    View full-size slide

  186. /**
    * Get the fields displayed by the resource.
    *
    * @param \Illuminate\Http\Request $request
    * @return array
    */
    public function fields(Request $request)
    {
    return [
    ID::make()->sortable(),
    Text::make('Name')
    ->sortable()
    ->rules('required', 'max:255'),
    Avatar::make(‘Avatar', 'profile_pic'),
    Text::make('Favourite Toy'),
    Trix::make('Bio'),
    BelongsTo::make('User')
    ];
    }
    app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    Nova
    Kitty.php
    User.php
    nova-components

    View full-size slide

  187. /**
    * Get the fields displayed by the resource.
    *
    * @param \Illuminate\Http\Request $request
    * @return array
    */
    public function fields(Request $request)
    {
    return [
    ID::make()->sortable(),
    Text::make('Name')
    ->sortable()
    ->rules('required', 'max:255'),
    S3FilePicker::make('Image', 'profile_pic'),
    Text::make('Favourite Toy'),
    Trix::make('Bio'),
    BelongsTo::make('User')
    ];
    }
    app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    Nova
    Kitty.php
    User.php
    nova-components

    View full-size slide

  188. class ApiController extends Controller
    {
    /**
    * Get a pre-signed key
    *
    * @return Response
    */
    public function preSignedUrl(Request $request): Response
    {
    if (null === $request->user()) {
    throw new UnauthorizedHttpException("You're not allowed her
    }
    return response()->json([
    'url' => $this->generatePreSignedUrl($request),
    'filename' => $this->getNewFilename($request),
    'path' => 'https://' . env('AWS_BUCKET')
    . '.s3-eu-west-1.amazonaws.com/'
    . $this->getNewFilename($request)
    ]);
    }
    // ...
    app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    Http
    ApiController.php
    Controllers
    nova-components

    View full-size slide

  189. class ApiController extends Controller
    {
    /**
    * Get a pre-signed key
    *
    * @return Response
    */
    public function preSignedUrl(Request $request): Response
    {
    if (null === $request->user()) {
    throw new UnauthorizedHttpException("You're not allowed her
    }
    return response()->json([
    'url' => $this->generatePreSignedUrl($request),
    'filename' => $this->getNewFilename($request),
    'path' => 'https://' . env('AWS_BUCKET')
    . '.s3-eu-west-1.amazonaws.com/'
    . $this->getNewFilename($request)
    ]);
    }
    // ...
    app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    Http
    ApiController.php
    Controllers
    nova-components

    View full-size slide

  190. class ApiController extends Controller
    {
    // ...
    private function generatePreSignedUrl(Request $request): string
    {
    $s3Client = new S3Client([
    'region' => env('AWS_DEFAULT_REGION'),
    'version' => 'latest',
    ]);
    $cmd = $s3Client->getCommand('PutObject', [
    'Bucket' => env('AWS_BUCKET'),
    'Key' => $this->getNewFilename($request),
    'ContentType' => $request->get('filetype')
    ]);
    $response = $s3Client->createPresignedRequest(
    $cmd,
    '+20 minutes’
    );
    return (string)$response->getUri();
    }
    // ...
    app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    Http
    ApiController.php
    Controllers
    nova-components

    View full-size slide

  191. class ApiController extends Controller
    {
    // ...
    private function generatePreSignedUrl(Request $request): string
    {
    $s3Client = new S3Client([
    'region' => env('AWS_DEFAULT_REGION'),
    'version' => 'latest',
    ]);
    $cmd = $s3Client->getCommand('PutObject', [
    'Bucket' => env('AWS_BUCKET'),
    'Key' => $this->getNewFilename($request),
    'ContentType' => $request->get('filetype')
    ]);
    $response = $s3Client->createPresignedRequest(
    $cmd,
    '+20 minutes’
    );
    return (string)$response->getUri();
    }
    // ...
    app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    Http
    ApiController.php
    Controllers
    nova-components

    View full-size slide

  192. class ApiController extends Controller
    {
    // ...
    private function generatePreSignedUrl(Request $request): string
    {
    $s3Client = new S3Client([
    'region' => env('AWS_DEFAULT_REGION'),
    'version' => 'latest',
    ]);
    $cmd = $s3Client->getCommand('PutObject', [
    'Bucket' => env('AWS_BUCKET'),
    'Key' => $this->getNewFilename($request),
    'ContentType' => $request->get('filetype')
    ]);
    $response = $s3Client->createPresignedRequest(
    $cmd,
    '+20 minutes’
    );
    return (string)$response->getUri();
    }
    // ...
    app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    Http
    ApiController.php
    Controllers
    nova-components

    View full-size slide

  193. class ApiController extends Controller
    {
    // ...
    private function generatePreSignedUrl(Request $request): string
    {
    $s3Client = new S3Client([
    'region' => env('AWS_DEFAULT_REGION'),
    'version' => 'latest',
    ]);
    $cmd = $s3Client->getCommand('PutObject', [
    'Bucket' => env('AWS_BUCKET'),
    'Key' => $this->getNewFilename($request),
    'ContentType' => $request->get('filetype')
    ]);
    $response = $s3Client->createPresignedRequest(
    $cmd,
    '+20 minutes’
    );
    return (string)$response->getUri();
    }
    // ...
    app
    config
    database
    bootstrap
    public
    resources
    routes
    storage
    Http
    ApiController.php
    Controllers
    nova-components

    View full-size slide



  194. // ...

    ref="fileField"
    type="file"
    name="imageUpload"
    @change="fileChange"
    />
    type="text"
    :id="field.name"
    :dusk="field.attribute"
    name="field.name"
    v-model="value"
    />



    app
    config
    database
    nova-components
    bootstrap
    public
    resources
    routes
    storage
    resources/js/components
    S3FilePicker
    FormField.vue

    View full-size slide

  195. app
    config
    database
    nova-components
    bootstrap
    public
    resources
    routes
    storage
    resources/js/components
    S3FilePicker
    FormField.vue
    /**
    * Update the field's internal value.
    */
    async fileChange(event) {
    let path = event.target.value
    let fileName = path.match(/[^\\/]*$/)[0]
    this.fileName = fileName
    this.file = this.$refs.fileField.files[0]
    await axios.get('/nova/presignedurl?'
    + 'filename=' + this.fileName
    + '&filetype=' + this.file.type
    ).then(async response => {
    let options = {
    headers: {
    "Content-Type": this.file.type
    }
    };
    await axios.put(response.data.url, this.file, options).then(result
    document.getElementById(‘previewImage')
    .setAttribute(‘src', response.data.path);
    this.value = response.data.filename
    });
    })
    }

    View full-size slide

  196. app
    config
    database
    nova-components
    bootstrap
    public
    resources
    routes
    storage
    resources/js/components
    S3FilePicker
    FormField.vue
    /**
    * Update the field's internal value.
    */
    async fileChange(event) {
    let path = event.target.value
    let fileName = path.match(/[^\\/]*$/)[0]
    this.fileName = fileName
    this.file = this.$refs.fileField.files[0]
    await axios.get('/nova/presignedurl?'
    + 'filename=' + this.fileName
    + '&filetype=' + this.file.type
    ).then(async response => {
    let options = {
    headers: {
    "Content-Type": this.file.type
    }
    };
    await axios.put(response.data.url, this.file, options).then(result
    document.getElementById(‘previewImage')
    .setAttribute(‘src', response.data.path);
    this.value = response.data.filename
    });
    })
    }

    View full-size slide

  197. app
    config
    database
    nova-components
    bootstrap
    public
    resources
    routes
    storage
    resources/js/components
    S3FilePicker
    FormField.vue
    /**
    * Update the field's internal value.
    */
    async fileChange(event) {
    let path = event.target.value
    let fileName = path.match(/[^\\/]*$/)[0]
    this.fileName = fileName
    this.file = this.$refs.fileField.files[0]
    await axios.get('/nova/presignedurl?'
    + 'filename=' + this.fileName
    + '&filetype=' + this.file.type
    ).then(async response => {
    let options = {
    headers: {
    "Content-Type": this.file.type
    }
    };
    await axios.put(response.data.url, this.file, options).then(result
    document.getElementById(‘previewImage')
    .setAttribute(‘src', response.data.path);
    this.value = response.data.filename
    });
    })
    }

    View full-size slide

  198. $ composer require nealio82/kittyfinder-s3filepicker

    View full-size slide

  199. Added Bref to an existing site

    View full-size slide

  200. Fixed assets by moving them to a CDN

    View full-size slide

  201. Centralised session storage

    View full-size slide

  202. Added Bref’s console layer in order to run artisan commands

    View full-size slide

  203. Used pre-signed URLs to send file uploads directly to the CDN

    View full-size slide

  204. VPC
    API Gateway
    Data Store
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View full-size slide

  205. source: https://medium.freecodecamp.org/lambda-vpc-cold-starts-a-latency-killer-5408323278dd

    View full-size slide

  206. source: https://medium.freecodecamp.org/lambda-vpc-cold-starts-a-latency-killer-5408323278dd

    View full-size slide

  207. source: https://medium.freecodecamp.org/lambda-vpc-cold-starts-a-latency-killer-5408323278dd

    View full-size slide

  208. DOES IT
    SCALE?

    View full-size slide

  209. Custom runtime based upon Bref’s PHP-FPM layer

    View full-size slide

  210. Uses Phalcon PHP framework

    View full-size slide

  211. Added AWS application load balancer in-front of Lambda

    View full-size slide

  212. Uses heavy caching - responses served
    from Cloudfront where possible

    View full-size slide

  213. 40 million requests per day

    View full-size slide

  214. 90 ms average response time

    View full-size slide

  215. 2,500 concurrent invocations at peak times

    View full-size slide

  216. 25% cost saving compared to EC2*

    View full-size slide

  217. GOING FURTHER

    View full-size slide

  218. QUEUES
    &
    EVENTS

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  221. https://github.com/stechstudio/
    laravel-bref-bridge

    View full-size slide

  222. ROLLING
    YOUR
    OWN

    View full-size slide

  223. https://github.com/stechstudio/
    bref-extensions

    View full-size slide

  224. serverless framework

    View full-size slide

  225. WE ARE HIRING!

    View full-size slide

  226. https://bref.sh/docs/community

    View full-size slide

  227. @nealio82
    https://bref.sh/docs/community

    View full-size slide