GithubHelp home page GithubHelp logo

laravel-bref's Introduction

Laravel Bref

This is an opiniated guide to deploy Laravel with Bref with several specific requirements. For example we use a separate CDK project to deploy pretty much anything else, from Transit Gateway to RDS.

Install serverless command

Install the serverless command via NPM:

npm install -g serverless

Reference: Installation - Bref

Install Bref and Laravel Bridge

Install bref/laravel-bridge and create initial serverless.yml

composer require bref/bref bref/laravel-bridge -W
php artisan vendor:publish --tag=serverless-config

Reference: Serverless Laravel applications - Bref

CloudFront and Custom Domain

Creating CloudFront 'manually' instead of using serverless-lift gives more flexibility.

Create mappings for CloudFront managed policies.

resources:
  Mappings: 
    CachePolicyIds:
      # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html
      Amplify:
        Id: 2e54312d-136d-493c-8eb9-b001f22f67d2
      CachingDisabled:
        Id: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
      CachingOptimized:
        Id: 658327ea-f89d-4fab-a63d-7e88639e58f6
      CachingOptimizedForUncompressedObjects:
        Id: b2884449-e4de-46a7-ac36-70bc7f1ddd6d
      Elemental-MediaPackage:
        Id: 08627262-05a9-4f76-9ded-b50ca2e3a84f
    OriginRequestPolicyIds:
      # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html
      AllViewer:
        Id: 216adef6-5c7f-47e4-b989-5492eafa07d3
      AllViewerAndCloudFrontHeaders-2022-06:
        Id: 33f36d7e-f396-46d9-90e0-52428a34d9dc
      AllViewerExceptHostHeader:
        Id: b689b0a8-53d0-40ab-baf2-68738e2966ac
      CORS-CustomOrigin:
        Id: 88a5eaf4-2fd4-4709-b370-b4c650ea3fcf
      Elemental-MediaTailor-PersonalizedManifests:
        Id: 775133bc-15f2-49f9-abea-afb2e0bf67d2
      UserAgentRefererHeaders:
        Id: acba4595-bd28-49b8-b9fe-13317c0390fa
    ResponseHeadersPolicyIds:
      # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-response-headers-policies.html
      CORS-and-SecurityHeadersPolicy:
        Id: e61eb60c-9c35-4d20-a928-2b84e02af89c
      CORS-With-Preflight:
        Id: 5cc3b908-e619-4b99-88e5-2cf7f45965bd
      CORS-with-preflight-and-SecurityHeadersPolicy:
        Id: eaab4381-ed33-4a86-88ca-d9558dc6cd63
      SecurityHeadersPolicy:
        Id: 67f7725c-6f97-4210-82d7-5512b31e9d03
      SimpleCORS:
        Id: 60669652-455b-4ae9-85a4-c4c02393f86c

Create CloudFront with custom domain and add record to Route53

resources:
  Resources:
    SiteCdn:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Enabled: true
          Aliases:
            - !Sub 'ken-site.${ssm:/ken/route53/zoneName}'
          ViewerCertificate:
            AcmCertificateArn: ${ssm:/ken/acm/usEast1Arn}
            MinimumProtocolVersion: TLSv1
            SslSupportMethod: sni-only
          PriceClass: PriceClass_100
          HttpVersion: http2
          Origins:
            - Id: HttpApi
              DomainName: !Sub '${HttpApi}.execute-api.${AWS::Region}.amazonaws.com'
              CustomOriginConfig: 
                OriginProtocolPolicy: https-only
          DefaultCacheBehavior:
            AllowedMethods: [GET, HEAD]
            TargetOriginId: HttpApi
            CachePolicyId: !FindInMap [ CachePolicyIds, CachingOptimized , Id ]
            OriginRequestPolicyId: !FindInMap [ OriginRequestPolicyIds, AllViewerExceptHostHeader, Id ]
            ResponseHeadersPolicyId: !FindInMap [ ResponseHeadersPolicyIds, CORS-with-preflight-and-SecurityHeadersPolicy, Id ]
            ViewerProtocolPolicy: redirect-to-https
    SiteDns:
      Type: 'AWS::Route53::RecordSet'
      Properties:
        HostedZoneId: ${ssm:/ken/route53/zoneId}
        Name: !Sub 'ken-site.${ssm:/ken/route53/zoneName}'
        Type: A
        AliasTarget:
          # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html
          HostedZoneId: Z2FDTNDATAQYW2
          DNSName: !GetAtt SiteCdn.DomainName

ARM architecture

Use ARM, cheaper and better performance.

functions:
  web:
    architecture: arm64

Reference: PHP runtimes for AWS Lambda - Bref

VPC

To access resources in VPC (e.g., RDS), modify cdk-core to create 2 SSM StringList parameters for subnet IDs and security group IDs respectively.

  • /ken/bref/securityGroupIds: StringList containing security group IDs for lambda
  • /ken/bref/privateSubnetIds: StringList containing private subnet IDs for lambda

Then refer to them in serverless.yml by adding the following lines:

functions:
  web:
    vpc:
      securityGroupIds: ${ssm:/ken/bref/securityGroupIds}
      subnetIds: ${ssm:/ken/bref/privateSubnetIds}

Reference: Database - Bref

RDS

For RDS access, store RDS credentials in SSM. Modify cdk-core projects to also store the created credentials in Parameter Store.

  • /ken/rds/username: RDS username
  • /ken/rds/password: RDS password
  • /ken/rds/port: RDS port
  • /ken/rds/database: RDS database

For projects with separate read and write connections:

  • /ken/rds/readWriteHost: RDS writer endpoint (or RDS proxy read/write endpoint if you use it)
  • /ken/rds/readOnlyHost: RDS reader endpoint (or RDS proxy read-only endpoint if you use it)

Otherwise:

  • /ken/rds/host: RDS host

Serverless S3 sync

Sync assets to S3 and exclude them from lambda deployment package.

Exclude public, node_modules, resources/assets, storage, and tests directories from deployment package.

package:
  patterns:
    - '!public/**'
    - '!node_modules/**'
    - '!resources/assets/**'
    - '!storage/**'
    - '!tests/**'
    - 'public/index.php'
    - 'public/robots.txt'
    - 'public/favicon.ico'
    - 'app/**'
    - 'bootstrap/**'
    - 'config/**'
    - 'routes/**'
    - 'vendor/**'
    - 'artisan'

Install serverless-s3-sync plugin.

sls plugin install -n serverless-s3-sync

Add S3 bucket and CloudFront distribution resources to serverless.yml

resources:
  Resources:
    CloudFrontOriginAccessControl:
      Type: AWS::CloudFront::OriginAccessControl
      Properties: 
        OriginAccessControlConfig:
          Description: Default Origin Access Control
          Name: !Ref AWS::StackName
          OriginAccessControlOriginType: s3
          SigningBehavior: always
          SigningProtocol: sigv4
    AssetsBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ken-laravel-static-assets
    AssetsCdn:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Enabled: true
          Aliases:
            - !Sub 'ken-assets.${ssm:/ken/route53/zoneName}'
          ViewerCertificate:
            AcmCertificateArn: ${ssm:/ken/acm/usEast1Arn}
            MinimumProtocolVersion: TLSv1
            SslSupportMethod: sni-only
          PriceClass: PriceClass_100
          HttpVersion: http2
          Origins:
            - Id: AssetsBucket
              DomainName: !GetAtt AssetsBucket.RegionalDomainName
              S3OriginConfig: {}
              OriginAccessControlId: !GetAtt CloudFrontOriginAccessControl.Id
          DefaultCacheBehavior:
            AllowedMethods: [GET, HEAD]
            TargetOriginId: AssetsBucket
            CachePolicyId: !FindInMap [ CachePolicyIds, CachingOptimized , Id ]
            OriginRequestPolicyId: !FindInMap [ OriginRequestPolicyIds, AllViewerExceptHostHeader, Id ]
            ResponseHeadersPolicyId: !FindInMap [ ResponseHeadersPolicyIds, CORS-with-preflight-and-SecurityHeadersPolicy, Id ]
            ViewerProtocolPolicy: redirect-to-https
    AssetsBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref AssetsBucket
        PolicyDocument:
          Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub ${AssetsBucket.Arn}/*
            Principal:
              Service: cloudfront.amazonaws.com
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${AssetsCdn}
    AssetsDns:
      Type: 'AWS::Route53::RecordSet'
      Properties:
        HostedZoneId: ${ssm:/ken/route53/zoneId}
        Name: !Sub 'ken-assets.${ssm:/ken/route53/zoneName}'
        Type: A
        AliasTarget:
          # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html
          HostedZoneId: Z2FDTNDATAQYW2
          DNSName: !GetAtt AssetsCdn.DomainName

Sync public directory to the assets S3 bucket

custom:
  s3Sync:
    # Sync css directory to the assets S3 bucket
    - bucketName: ken-laravel-static-assets
      bucketPrefix: css/
      localDir: public/css/
      deleteRemoved: true

Configure ASSET_URL environment variables

provider:
  environment:
    ASSET_URL: !Sub 'https://${AssetsCdn.DomainName}'

Notes:

  • Alternatively, you can use serverless-lift as recommended by Bref, but we don't use it here because some of our deployments are behind VPN access and some use a different domain for assets.
  • For API, skip this step.

Reference: Serverless S3 Sync

IAM

Create role named lambda-laravel-role with the following permission (this is not the minimum required permission):

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "cloudfront:CreateInvalidation",
                "cloudwatch:GetMetricStatistics",
                "ec2:CreateNetworkInterface",
                "ec2:DeleteNetworkInterface",
                "ec2:DescribeNetworkInterfaces",
                "ec2:AssignPrivateIpAddresses",
                "ec2:UnassignPrivateIpAddresses"
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:FilterLogEvents",
                "logs:PutLogEvents",
                "kms:Decrypt",
                "secretsmanager:GetSecretValue",
                "ssm:GetParameters",
                "ssm:GetParameter",
                "lambda:invokeFunction",
                "s3:*",
                "ses:*",
                "sqs:*",
                "dynamodb:*",
                "route53domains:*"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

Use the newly created role by adding the following lines:

provider:
  iam:
    role: !Sub 'arn:aws:iam::${AWS::AccountId}:role/lambda-laravel-role'

Reference: Serverless Framework - IAM Permissions For Functions

Cache and Session

Use DynamoDB for cache and session.

Modify cdk-core project to create a DynamoDB table with partition key called key and TTL attribute called expires_at. And modify the following lines in your .env file:

CACHE_DRIVER=dynamodb
SESSION_DRIVER=dynamodb
SESSION_STORE=dynamodb

Queue

TODO: Experiment with serverless-lift

Create scheduler:

functions:
  artisan:
      handler: artisan
      runtime: php-81-console
      architecture: arm64
      timeout: 720 # in seconds
      # Uncomment to also run the scheduler every minute
      vpc:
        securityGroupIds: ${ssm:/ken/bref/securityGroupIds}
        subnetIds: ${ssm:/ken/bref/privateSubnetIds}
      events:
        - schedule:
            rate: rate(30 minutes)
            input: '"schedule:run"'

Create SQS queue resource:

resources:
  Resources:
    Queue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: ken-laravel-queue
        ReceiveMessageWaitTimeSeconds: 20
        VisibilityTimeout: 120

Automatic pruning

Serverless deployment creates deployment package and uploads it to lambda. Each deployment creates a new version without deleting the old ones. This can be problematic for accounts with many lambdas (especially monolithic lambdas like this one) because lambda has a code storage limit of 75GB. Use serverless-prune-plugin plugin delete old versions.

Install serverless-prune-plugin plugin.

sls plugin install -n serverless-prune-plugin

Add plugin configuration to serverless.yml

custom:
  prune:
    automatic: true
    number: 3

Reference: serverless-prune-plugin

Notes

laravel-bref's People

Contributors

kensasongko avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.