Laravel trên Serverless — Deploy lên AWS Lambda với Bref

· 10 min read

Bạn đã biết cách deploy Laravel lên EC2 (truyền thống) và quản lý hạ tầng bằng Terraform. Nhưng có một cách tiếp cận hoàn toàn khác: không quản lý server nào cả.

AWS Lambda cho phép bạn chạy code mà không cần provisioning hay quản lý server. Bạn chỉ trả tiền cho thời gian thực thi thực tế — không có traffic, không mất tiền.

Bref (tiếng Pháp: "ngắn gọn") là một open-source framework giúp chạy PHP trên AWS Lambda một cách dễ dàng. Nó cung cấp các PHP runtime layers cho Lambda và tích hợp sẵn với Laravel.

Serverless là gì? (Giải thích đơn giản)

Truyền thống (EC2):
┌──────────────────────────────┐
│  Bạn quản lý:               │
│  ✗ OS updates                │
│  ✗ Nginx config              │
│  ✗ PHP-FPM tuning            │
│  ✗ SSL certificates          │
│  ✗ Auto-scaling              │
│  ✗ Server monitoring         │
│  ✗ Security patches          │
│  → Trả tiền 24/7             │
└──────────────────────────────┘

Serverless (Lambda):
┌──────────────────────────────┐
│  AWS quản lý tất cả ở trên  │
│  Bạn chỉ cần:               │
│  ✓ Deploy code               │
│  ✓ Cấu hình .env             │
│  → Trả tiền theo request     │
└──────────────────────────────┘

Khi nào nên dùng Serverless?

Phù hợp Không phù hợp
Blog, landing page WebSocket real-time
API với traffic không đều Long-running processes (> 15 phút)
Cron jobs, webhook handlers Ứng dụng cần local file storage
MVP / Side projects Ứng dụng cần latency cực thấp (< 50ms)
Traffic burst (viral content)

Yêu cầu

# 1. Node.js (cho Serverless Framework)
node --version  # >= 18

# 2. Serverless Framework
npm install -g serverless

# 3. AWS credentials đã cấu hình
aws sts get-caller-identity

Cài đặt Bref cho Laravel

# Trong project Laravel đã có
composer require bref/bref bref/laravel-bridge

# Tạo file cấu hình serverless.yml
php artisan vendor:publish --tag=serverless-config

Cấu hình serverless.yml

Đây là file quan trọng nhất — nó khai báo toàn bộ kiến trúc serverless của bạn:

# serverless.yml
service: laravel-blog

provider:
  name: aws
  region: ap-southeast-1
  runtime: provided.al2023
  # Biến môi trường cho Lambda
  environment:
    APP_NAME: LaravelBlog
    APP_ENV: production
    APP_DEBUG: false
    APP_URL: https://your-domain.com
    # Database
    DB_CONNECTION: mysql
    DB_HOST: ${ssm:/laravel/db-host}
    DB_DATABASE: ${ssm:/laravel/db-name}
    DB_USERNAME: ${ssm:/laravel/db-username}
    DB_PASSWORD: ${ssm:/laravel/db-password}
    # Cache & Session dùng DynamoDB (không cần Redis server)
    CACHE_STORE: dynamodb
    SESSION_DRIVER: dynamodb
    DYNAMODB_CACHE_TABLE: !Ref CacheTable
    # Queue dùng SQS
    QUEUE_CONNECTION: sqs
    SQS_QUEUE: !Ref JobsQueue
    # Storage dùng S3
    FILESYSTEM_DISK: s3
    AWS_BUCKET: !Ref StorageBucket
    # Bref
    VIEW_COMPILED_PATH: /tmp/views
    LOG_CHANNEL: stderr

  # IAM permissions cho Lambda function
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:PutItem
            - dynamodb:GetItem
            - dynamodb:DeleteItem
            - dynamodb:Scan
          Resource:
            - !GetAtt CacheTable.Arn
        - Effect: Allow
          Action:
            - sqs:SendMessage
            - sqs:ReceiveMessage
            - sqs:DeleteMessage
          Resource:
            - !GetAtt JobsQueue.Arn
        - Effect: Allow
          Action:
            - s3:PutObject
            - s3:GetObject
            - s3:DeleteObject
          Resource:
            - !Sub '${StorageBucket.Arn}/*'

package:
  patterns:
    # Loại bỏ file không cần thiết
    - '!node_modules/**'
    - '!tests/**'
    - '!storage/**'
    - '!resources/js/**'
    - '!resources/css/**'
    - '!.env'
    - '!docker/**'

functions:
  # Function chính xử lý HTTP requests
  web:
    handler: public/index.php
    runtime: php-84-fpm
    timeout: 28
    memorySize: 1024
    layers:
      - ${bref-extra:gd-php-84}
    events:
      - httpApi: '*'
    # Giữ ấm Lambda để tránh cold start
    # warmup:
    #   enabled: true
    #   concurrency: 5

  # Function xử lý Artisan commands
  artisan:
    handler: artisan
    runtime: php-84-console
    timeout: 720
    memorySize: 512

  # Function xử lý Queue jobs
  worker:
    handler: Bref\LaravelBridge\Queue\QueueHandler
    runtime: php-84
    timeout: 60
    memorySize: 512
    events:
      - sqs:
          arn: !GetAtt JobsQueue.Arn
          batchSize: 1

  # Scheduler (chạy mỗi phút)
  scheduler:
    handler: artisan
    runtime: php-84-console
    timeout: 120
    memorySize: 256
    events:
      - schedule:
          rate: rate(1 minute)
          input: '"schedule:run"'

# Tài nguyên AWS bổ sung
resources:
  Resources:
    # DynamoDB cho cache & sessions
    CacheTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:service}-cache-${sls:stage}
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: key
            AttributeType: S
        KeySchema:
          - AttributeName: key
            KeyType: HASH
        TimeToLiveSpecification:
          AttributeName: expires_at
          Enabled: true

    # SQS Queue cho Laravel jobs
    JobsQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: ${self:service}-jobs-${sls:stage}
        VisibilityTimeout: 120
        RedrivePolicy:
          maxReceiveCount: 3
          deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn

    # Dead Letter Queue
    DeadLetterQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: ${self:service}-dlq-${sls:stage}
        MessageRetentionPeriod: 1209600  # 14 ngày

    # S3 bucket cho file uploads
    StorageBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:service}-storage-${sls:stage}

Giải thích từng phần:

provider.environment — Biến môi trường

Lambda không có file .env. Mọi thứ đều khai báo ở đây hoặc lấy từ AWS SSM Parameter Store (cú pháp ${ssm:/path}). SSM là cách an toàn để lưu secrets — bạn không bao giờ hardcode mật khẩu trong file config.

functions.web — HTTP Handler

  • runtime: php-84-fpm: Bref cung cấp một PHP-FPM layer được tối ưu cho Lambda. Nó hoạt động giống như PHP-FPM truyền thống nhưng chạy trên Lambda.
  • timeout: 28: API Gateway có timeout tối đa 29 giây. Đặt 28 để Lambda kịp trả về lỗi trước khi Gateway timeout.
  • memorySize: 1024: Lambda scale CPU theo memory. 1024MB ≈ 1 vCPU. Đủ cho hầu hết Laravel requests.

functions.worker — Queue Worker

  • Mỗi khi có message trong SQS, Lambda tự động invoke function này. Không cần chạy php artisan queue:work hay supervisor.
  • batchSize: 1: Xử lý 1 job tại 1 thời điểm. Tăng lên nếu jobs nhẹ.

resources — Infrastructure

  • DynamoDB thay thế Redis cho cache/session — không cần quản lý server, pay-per-request.
  • SQS thay thế Redis Queue — durable, scalable tự động.
  • Dead Letter Queue: Jobs fail 3 lần sẽ chuyển vào đây để debug sau.

Điều chỉnh Laravel cho Serverless

1. Storage — Lambda là Ephemeral

Lambda function không có persistent filesystem. Bạn chỉ có /tmp (512MB–10GB) và nó bị xóa khi container dừng.

// config/filesystems.php
'disks' => [
    // Dùng S3 thay vì local
    'default' => env('FILESYSTEM_DISK', 's3'),

    's3' => [
        'driver' => 's3',
        'key'    => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION'),
        'bucket' => env('AWS_BUCKET'),
    ],
],

2. Compiled Views — Dùng /tmp

// config/view.php
'compiled' => env('VIEW_COMPILED_PATH', storage_path('framework/views')),

Trong serverless.yml, ta đã set VIEW_COMPILED_PATH: /tmp/views. Blade views sẽ được compile vào /tmp thay vì storage/framework/views.

3. Logging — Dùng stderr

// config/logging.php
'channels' => [
    'stderr' => [
        'driver'    => 'monolog',
        'handler'   => StreamHandler::class,
        'formatter' => env('LOG_STDERR_FORMATTER'),
        'with'      => [
            'stream' => 'php://stderr',
        ],
        'level' => env('LOG_LEVEL', 'info'),
    ],
],

Logs trên stderr sẽ tự động xuất hiện trong CloudWatch Logs. Không cần cấu hình thêm gì.

4. Assets — CDN cho frontend

Lambda chỉ phục vụ PHP. File tĩnh (CSS, JS, images) phải đặt trên S3 + CloudFront:

# Upload assets lên S3
aws s3 sync public/ s3://my-assets-bucket/ \
    --exclude "index.php" \
    --exclude ".htaccess" \
    --cache-control "max-age=31536000"
// config/app.php hoặc .env
ASSET_URL=https://d1234xyz.cloudfront.net

Deploy

# Deploy lên AWS
serverless deploy

# Chạy migrate
serverless bref:cli -- migrate --force

# Xem logs
serverless logs -f web --tail

# Xem thông tin
serverless info

Output sau khi deploy:

Service Information
service: laravel-blog
stage: production
region: ap-southeast-1
endpoints:
  ANY - https://abc123.execute-api.ap-southeast-1.amazonaws.com
functions:
  web: laravel-blog-production-web
  artisan: laravel-blog-production-artisan
  worker: laravel-blog-production-worker
  scheduler: laravel-blog-production-scheduler

Xử lý Cold Start

Cold start là thời gian Lambda mất để khởi tạo container lần đầu (hoặc sau khi idle). Với PHP/Laravel, thời gian này thường 800ms–2s.

Cách giảm cold start:

1. Giữ package size nhỏ:

# Kiểm tra kích thước package
serverless package
ls -lh .serverless/*.zip
# Mục tiêu: < 50MB

2. Optimize autoloader:

composer install --no-dev --optimize-autoloader --classmap-authoritative

3. Cache config & routes:

php artisan config:cache
php artisan route:cache
php artisan view:cache

Thêm vào script deploy:

# serverless.yml
custom:
  scripts:
    hooks:
      'before:deploy:deploy':
        - php artisan config:cache
        - php artisan route:cache
        - php artisan view:cache

4. Provisioned Concurrency (trả thêm tiền):

functions:
  web:
    # ...
    provisionedConcurrency: 5  # Giữ 5 instances luôn ấm

Chi phí: ~$15/tháng cho 5 provisioned instances. So sánh: EC2 t3.small ≈ $15/tháng nhưng chỉ 1 instance.

So sánh chi phí

Ví dụ: Blog với 100,000 pageviews/tháng

Hạng mục EC2 Serverless
Compute $15/tháng (t3.small) ~$0.50 (Lambda)
Database $15 (RDS t3.micro) $15 (RDS t3.micro)
Cache $13 (ElastiCache) ~$1 (DynamoDB)
CDN $5 (CloudFront) $5 (CloudFront)
Tổng ~$48/tháng ~$21.50/tháng

Lưu ý: Chi phí Serverless tăng tuyến tính theo traffic. Nếu bạn có 10 triệu requests/tháng, EC2 có thể rẻ hơn.

CI/CD cho Serverless

# .github/workflows/deploy-serverless.yml
name: Deploy Serverless

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'

      - name: Install Composer deps
        run: composer install --no-dev --optimize-autoloader

      - name: Cache Laravel
        run: |
          php artisan config:cache
          php artisan route:cache
          php artisan view:cache

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Serverless
        run: npm install -g serverless

      - name: Build assets
        run: |
          npm ci
          npm run build

      - name: Upload assets to S3
        run: |
          aws s3 sync public/build/ s3://${{ secrets.ASSETS_BUCKET }}/build/ \
            --cache-control "max-age=31536000"
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ap-southeast-1

      - name: Deploy
        run: serverless deploy --stage production
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Run migrations
        run: serverless bref:cli --stage production -- migrate --force
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Giới hạn cần biết

Giới hạn Giá trị Ảnh hưởng
Timeout tối đa 15 phút (API: 29 giây) Không dùng cho export/import lớn
Package size 250MB (unzipped) Cần optimize dependencies
/tmp storage 512MB – 10GB Upload files lớn phải stream trực tiếp S3
Concurrent executions 1000 (default) Request thêm AWS nếu cần
Payload size 6MB (sync), 256KB (async) Upload file lớn dùng pre-signed URL

Kết luận

Serverless không phải silver bullet, nhưng nó là lựa chọn tuyệt vời cho:

  • Blog/portfolio cá nhân
  • API với traffic không đều
  • Side projects mà bạn không muốn lo server

Với Bref, Laravel chạy trên Lambda gần như không cần thay đổi code. Bạn dành thời gian viết features thay vì SSH vào server fix Nginx config.

Quy tắc đơn giản:

  • Traffic thấp/trung bình + không muốn quản lý server → Serverless
  • Traffic cao ổn định + cần WebSocket/long-running processes → EC2/ECS
  • Muốn cả hai → Hybrid (Lambda cho API, EC2 cho WebSocket)

Bình luận