Laravel trên Serverless — Deploy lên AWS Lambda với Bref
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:workhay 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)