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 Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. WE ARE HIRING!

    View Slide

  6. View Slide

  7. RUNNING YOUR
    LARAVEL PROJECT
    ON AWS LAMBDA

    View Slide

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

    View Slide

  9. View Slide

  10. View Slide

  11. WHY?

    View Slide

  12. View Slide

  13. EC2

    View Slide

  14. EC2 Data Store

    View Slide

  15. EC2 Data Store

    View Slide

  16. EC2 Data Store
    Load Balancer

    View Slide

  17. WTF?

    View Slide

  18. EC2 Data Store
    Load Balancer

    View Slide

  19. EC2 Data Store
    Load Balancer
    EC2
    EC2

    View Slide

  20. EC2 Data Store
    Load Balancer
    EC2
    EC2

    View Slide

  21. EC2 Data Store
    Load Balancer
    EC2
    EC2

    View Slide

  22. SAY
    HELLO
    TO
    FaaS

    View Slide

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

    View Slide

  24. … until you need it …

    View Slide

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

    View Slide

  26. View Slide

  27. 1 million requests FREE per month

    View Slide

  28. $0.20 per million requests thereafter

    View Slide

  29. View Slide

  30. Container

    View Slide

  31. Container
    Bootstrap Code

    View Slide

  32. Container
    Bootstrap Code

    View Slide

  33. Container
    Bootstrap Code
    Application Code

    View Slide

  34. Container
    Bootstrap Code
    Application Code

    View Slide

  35. Container
    Bootstrap Code
    Application Code

    View Slide

  36. LAYERS

    View Slide

  37. View Slide

  38. View Slide

  39. Lambda function
    Bootstrap
    PHP Layer
    Handler

    View Slide

  40. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  41. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  42. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  43. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  47. Maximum request body size is 6 MB

    View Slide

  48. RUNNING
    PHP ON
    LAMBDA

    View Slide

  49. IS
    A PITA
    RUNNING
    PHP ON
    LAMBDA

    View Slide

  50. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  51. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  52. https://bref.sh

    View Slide

  53. BREF AIMS TO MAKE
    RUNNING PHP APPS
    SIMPLE

    View Slide

  54. SIMPLIFY
    PROBLEMS
    BY REMOVING
    CHOICES

    View Slide

  55. PROVIDE SIMPLE
    AND FAMILIAR
    SOLUTIONS

    View Slide

  56. EMPOWER BY SHARING
    KNOWLEDGE

    View Slide

  57. WHAT
    DOES
    BREF ACTUALLY
    DO?

    View Slide

  58. View Slide

  59. View Slide

  60. View Slide

  61. SAM DEALS
    WITH
    DEPLOYING

    View Slide

  62. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  63. Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  64. API Gateway
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  65. API Gateway
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  66. View Slide

  67. View Slide

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

    View Slide

  69. $ composer require mnapoli/bref

    View Slide

  70. $ php vendor/bin/bref init

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  78. 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 Slide

  79. # ...
    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 Slide

  80. # ...
    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 Slide

  81. # ...
    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 Slide

  82. # ...
    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 Slide

  83. # ...
    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 Slide

  84. # ...
    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 Slide

  85. DEPLOYING

    View Slide

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

    View Slide

  87. View Slide

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

    View Slide

  89. make_bucket: bref-hello-world

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  93. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  100. 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 Slide

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

    View Slide

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

    View Slide

  103. View Slide

  104. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  109. View Slide

  110. View Slide

  111. API Gateway
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  112. View Slide

  113. View Slide

  114. View Slide

  115. View Slide

  116. RECAP

    View Slide

  117. Looked how FaaS can simplify architecture & save money

    View Slide

  118. Installed AWS CLI

    View Slide

  119. Added Bref to an existing project

    View Slide

  120. Explored the template file

    View Slide

  121. Created a deployment bucket

    View Slide

  122. Created a deployment bucket in the correct region

    View Slide

  123. Packaged & deployed the application

    View Slide

  124. Explored the stack

    View Slide

  125. Tested the PHP application running on Lambda

    View Slide

  126. REFACTORING
    A FULLY
    FEATURED
    WEBSITE

    View Slide

  127. View Slide

  128. View Slide

  129. View Slide

  130. View Slide

  131. REMEMBER

    View Slide

  132. The filesystem is read-only

    View Slide

  133. (apart from /tmp)

    View Slide

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

    View Slide

  135. $ composer require mnapoli/bref

    View Slide

  136. $ php vendor/bin/bref init

    View Slide

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

    View Slide

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

    View Slide

  139. # ...
    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 Slide

  140. # ...
    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 Slide

  141. # ...
    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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

  145. 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 Slide

  146. Unzipped size must be smaller than 155930325 bytes

    View Slide

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

    View Slide

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

    View Slide

  149. 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 Slide

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

    View Slide

  151. View Slide

  152. CLOUDWATCH
    HAS YOUR LOGS

    View Slide

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

    View Slide

  154. "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 Slide

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

    View Slide

  156. /*
    |-----------------------------------------------------------------------
    | 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 Slide

  157. /*
    |-----------------------------------------------------------------------
    | 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 Slide

  158. # ...
    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 Slide

  159. # ...
    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 Slide

  160. # ...
    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 Slide

  161. # ...
    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 Slide

  162. 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 Slide

  163. 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 Slide

  164. View Slide

  165. View Slide

  166. View Slide

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

    View Slide

  168. CONNECTING THE DATABASE

    View Slide

  169. API Gateway
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

  170. API Gateway
    Lambda function
    Handler
    PHP Layer
    Bootstrap

    View Slide

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

    View Slide

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

    View Slide

  173. # ...
    Globals:
    Function:
    Environment:
    Variables:
    # Laravel environment variables
    APP_STORAGE: '/tmp'
    DATABASE_URL: ‘mysql://db_user:[email protected]
    # ...
    app
    config
    database
    bootstrap
    template.yaml
    public
    resources
    routes
    storage

    View Slide

  174. View Slide

  175. View Slide

  176. # ...
    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 Slide

  177. # ...
    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 Slide

  178. View Slide

  179. CONTENT
    DELIVERY
    NETWORK

    View Slide

  180. $ composer require arubacao/asset-cdn

    View Slide

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

    View Slide

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

    View Slide

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

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

  185. 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 Slide

  186. # ...
    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 Slide






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

    View Slide






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

    View Slide

  189. View Slide

  190. BRINGING
    YOUR OWN
    DOMAIN
    NAME

    View Slide

  191. View Slide

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

    View Slide

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

    View Slide

  194. View Slide

  195. View Slide

  196. View Slide

  197. View Slide

  198. View Slide

  199. View Slide

  200. View Slide

  201. 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 Slide



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

    View Slide

  203. View Slide

  204. View Slide

  205. 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 Slide

  206. # ...
    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 Slide

  207. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  213. $ php artisan session:table

    View Slide

  214. # ...
    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 Slide

  215. $ php artisan migrate

    View Slide

  216. CONSOLE COMMANDS

    View Slide

  217. 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 Slide

  218. 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 Slide

  219. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

  223. Migrating: 2019_06_06_163613_create_sessions_table
    Migrated: 2019_06_06_163613_create_sessions_table

    View Slide

  224. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  229. /**
    * 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 Slide

  230. 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 Slide

  231. View Slide

  232. View Slide


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

    View Slide

  234. app
    config
    database
    bootstrap
    public
    resources

    views
    home.blade.php
    routes
    storage

    View Slide

  235. View Slide

  236. UPLOADING
    NEW IMAGES
    OF KITTIES

    View Slide

  237. Pre-signed URLs

    View Slide

  238. Client Server Amazon S3

    View Slide

  239. Client Server Amazon S3
    Get Pre-Signed URL

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  243. 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 Slide

  244. 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 Slide

  245. 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 Slide

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

    View Slide

  247. /**
    * 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 Slide

  248. /**
    * 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 Slide

  249. 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 Slide

  250. 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 Slide

  251. 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 Slide

  252. 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 Slide

  253. 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 Slide

  254. 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 Slide



  255. // ...

    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 Slide

  256. 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 Slide

  257. 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 Slide

  258. 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 Slide

  259. View Slide

  260. View Slide

  261. View Slide

  262. View Slide

  263. View Slide

  264. View Slide

  265. View Slide

  266. $ composer require nealio82/kittyfinder-s3filepicker

    View Slide

  267. View Slide

  268. RECAP

    View Slide

  269. Added Bref to an existing site

    View Slide

  270. Fixed assets by moving them to a CDN

    View Slide

  271. Centralised session storage

    View Slide

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

    View Slide

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

    View Slide

  274. View Slide

  275. PERFORMANCE

    View Slide

  276. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  281. View Slide

  282. SAVINGS

    View Slide

  283. View Slide

  284. DOES IT
    SCALE?

    View Slide

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

    View Slide

  286. Uses Phalcon PHP framework

    View Slide

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

    View Slide

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

    View Slide

  289. 40 million requests per day

    View Slide

  290. 90 ms average response time

    View Slide

  291. 2,500 concurrent invocations at peak times

    View Slide

  292. 25% cost saving compared to EC2*

    View Slide

  293. GOING FURTHER

    View Slide

  294. QUEUES
    &
    EVENTS

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  298. ROLLING
    YOUR
    OWN

    View Slide

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

    View Slide

  300. View Slide

  301. serverless framework

    View Slide

  302. bref/bref

    View Slide

  303. JOIN US

    View Slide

  304. WE ARE HIRING!

    View Slide

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

    View Slide

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

    View Slide