Serverless Framework란?
Serverless Framework는 서버리스 애플리케이션 개발 및 배포를 간소화하는 오픈소스 프레임워크임.
단일 설정 파일(serverless.yml)로 Lambda 함수, API Gateway, DynamoDB, S3 등 모든 AWS 리소스를 정의하고, 명령어 하나로 전체 인프라를 배포할 수 있음.
왜 Serverless Framework를 사용해야 하는가?
수동 배포 방식의 문제점
AWS 콘솔에서 직접 배포:
1
2
3
4
5
6
7
8
9
10
| 1. Lambda 함수 생성
2. IAM 역할 생성 및 권한 설정
3. API Gateway 생성 및 설정
4. DynamoDB 테이블 생성
5. 환경 변수 설정
6. Lambda 함수 코드 업로드
7. API Gateway 배포
8. CloudWatch 로그 설정
9. 모든 설정을 문서화
10. dev/staging/prod 환경별로 반복
|
문제점:
- 시간 소모: 클릭 수십 번, 수십 분 소요
- 휴먼 에러: 설정 누락, 오타, 잘못된 ARN
- 재현 불가능: 동일한 환경 재구성 어려움
- 환경 관리: dev/prod 환경 불일치
- 팀 협업: 설정 공유 어려움
- 롤백: 이전 버전으로 되돌리기 복잡
Serverless Framework의 해결 방법
serverless.yml 하나로 모든 것 정의:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| service: my-api
provider:
name: aws
runtime: nodejs20.x
region: ap-northeast-2
environment:
TABLE_NAME: !Ref UsersTable
functions:
createUser:
handler: handler.create
events:
- http:
path: /users
method: post
resources:
Resources:
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
|
배포 명령:
1
| serverless deploy --stage prod
|
결과:
- ✅ Lambda 함수 자동 생성
- ✅ IAM 역할 자동 생성 (최소 권한)
- ✅ API Gateway 자동 설정
- ✅ DynamoDB 테이블 자동 생성
- ✅ 환경 변수 자동 주입
- ✅ CloudWatch 로그 그룹 자동 생성
- ✅ 버전 관리 자동
- ✅ 5분 내 완료
Serverless Framework 핵심 개념
1. serverless.yml 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
| # 서비스 식별자
service: my-service
# AWS 제공자 설정
provider:
name: aws
runtime: nodejs20.x
region: ap-northeast-2
stage: ${opt:stage, 'dev'}
memorySize: 512
timeout: 30
environment:
# 모든 함수에 적용되는 환경 변수
STAGE: ${self:provider.stage}
iam:
role:
statements:
# IAM 권한 정의
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
Resource: !GetAtt UsersTable.Arn
# Lambda 함수 정의
functions:
createUser:
handler: src/handlers/users.create
events:
- http:
path: /users
method: post
cors: true
environment:
# 이 함수에만 적용되는 환경 변수
FUNCTION_SPECIFIC_VAR: value
getUser:
handler: src/handlers/users.get
events:
- http:
path: /users/{id}
method: get
# CloudFormation 리소스
resources:
Resources:
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:service}-users-${self:provider.stage}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
Outputs:
UsersTableArn:
Value: !GetAtt UsersTable.Arn
Export:
Name: ${self:service}-${self:provider.stage}-UsersTableArn
|
2. 변수와 참조
환경 변수 참조:
1
2
3
| provider:
environment:
API_KEY: ${env:API_KEY} # .env 파일이나 시스템 환경 변수
|
stage 변수:
1
2
3
4
5
6
7
8
9
10
11
| provider:
stage: ${opt:stage, 'dev'} # CLI 옵션 또는 기본값 'dev'
resources:
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-app-${self:provider.stage}
# dev → my-app-dev
# prod → my-app-prod
|
CloudFormation 함수:
1
2
3
4
5
| provider:
environment:
TABLE_NAME: !Ref UsersTable
TABLE_ARN: !GetAtt UsersTable.Arn
QUEUE_URL: !Ref MyQueue
|
커스텀 변수:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| custom:
tableName: ${self:service}-users-${self:provider.stage}
s3Bucket: ${self:service}-uploads-${self:provider.stage}
provider:
environment:
TABLE_NAME: ${self:custom.tableName}
resources:
Resources:
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:custom.tableName}
|
실전 프로젝트 활용 사례
사례 1: REST API 서비스
프로젝트 구조:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| my-api/
├── serverless.yml
├── .env
├── package.json
├── src/
│ ├── handlers/
│ │ ├── users.js
│ │ ├── orders.js
│ │ └── products.js
│ ├── utils/
│ │ └── dynamodb.js
│ └── middlewares/
│ └── auth.js
└── node_modules/
|
serverless.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
| service: ecommerce-api
provider:
name: aws
runtime: nodejs20.x
region: ap-northeast-2
stage: ${opt:stage, 'dev'}
environment:
USERS_TABLE: !Ref UsersTable
ORDERS_TABLE: !Ref OrdersTable
iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:*
Resource:
- !GetAtt UsersTable.Arn
- !GetAtt OrdersTable.Arn
- !Sub "${UsersTable.Arn}/index/*"
- !Sub "${OrdersTable.Arn}/index/*"
# HTTP API (API Gateway v2 - 더 저렴)
functions:
# 사용자 관리
createUser:
handler: src/handlers/users.create
events:
- httpApi:
path: /users
method: post
getUser:
handler: src/handlers/users.get
events:
- httpApi:
path: /users/{id}
method: get
listUsers:
handler: src/handlers/users.list
events:
- httpApi:
path: /users
method: get
# 주문 관리
createOrder:
handler: src/handlers/orders.create
events:
- httpApi:
path: /orders
method: post
getOrder:
handler: src/handlers/orders.get
events:
- httpApi:
path: /orders/{id}
method: get
resources:
Resources:
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:service}-users-${self:provider.stage}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
OrdersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:service}-orders-${self:provider.stage}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: orderId
AttributeType: S
- AttributeName: userId
AttributeType: S
- AttributeName: createdAt
AttributeType: S
KeySchema:
- AttributeName: orderId
KeyType: HASH
GlobalSecondaryIndexes:
- IndexName: UserOrdersIndex
KeySchema:
- AttributeName: userId
KeyType: HASH
- AttributeName: createdAt
KeyType: RANGE
Projection:
ProjectionType: ALL
|
배포:
1
2
3
4
5
| # dev 환경
serverless deploy --stage dev
# prod 환경
serverless deploy --stage prod
|
사례 2: 이벤트 기반 처리
SQS + Lambda 패턴:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| functions:
orderProcessor:
handler: src/handlers/processOrder.handler
timeout: 60
reservedConcurrency: 10 # 최대 동시 실행 수
events:
- sqs:
arn: !GetAtt OrdersQueue.Arn
batchSize: 10
maximumBatchingWindowInSeconds: 5
resources:
Resources:
OrdersQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: ${self:service}-orders-${self:provider.stage}
VisibilityTimeout: 300
MessageRetentionPeriod: 345600 # 4일
RedrivePolicy:
deadLetterTargetArn: !GetAtt OrdersDLQ.Arn
maxReceiveCount: 3
OrdersDLQ:
Type: AWS::SQS::Queue
Properties:
QueueName: ${self:service}-orders-dlq-${self:provider.stage}
MessageRetentionPeriod: 1209600 # 14일
|
S3 + Lambda 패턴:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| functions:
imageProcessor:
handler: src/handlers/processImage.handler
timeout: 300
memorySize: 1024
events:
- s3:
bucket: ${self:custom.uploadsBucket}
event: s3:ObjectCreated:*
rules:
- prefix: uploads/
- suffix: .jpg
custom:
uploadsBucket: ${self:service}-uploads-${self:provider.stage}
resources:
Resources:
UploadsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.uploadsBucket}
|
사례 3: Step Functions 통합
serverless-step-functions 플러그인:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| plugins:
- serverless-step-functions
stepFunctions:
stateMachines:
orderWorkflow:
name: ${self:service}-order-workflow-${self:provider.stage}
definition:
Comment: 주문 처리 워크플로우
StartAt: ValidateOrder
States:
ValidateOrder:
Type: Task
Resource: !GetAtt ValidateOrderLambdaFunction.Arn
Next: ProcessPayment
Catch:
- ErrorEquals: ["States.ALL"]
Next: HandleError
ProcessPayment:
Type: Task
Resource: !GetAtt ProcessPaymentLambdaFunction.Arn
Next: SendNotification
Retry:
- ErrorEquals: ["PaymentError"]
MaxAttempts: 3
BackoffRate: 2.0
SendNotification:
Type: Task
Resource: !GetAtt SendNotificationLambdaFunction.Arn
End: true
HandleError:
Type: Task
Resource: !GetAtt HandleErrorLambdaFunction.Arn
End: true
functions:
validateOrder:
handler: src/workflow/validate.handler
processPayment:
handler: src/workflow/payment.handler
sendNotification:
handler: src/workflow/notify.handler
handleError:
handler: src/workflow/error.handler
|
환경별 설정 관리
Stage 변수 활용
serverless.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| custom:
stages:
dev:
memorySize: 512
timeout: 30
logRetention: 3
prod:
memorySize: 1024
timeout: 60
logRetention: 30
provider:
stage: ${opt:stage, 'dev'}
functions:
myFunction:
handler: handler.main
memorySize: ${self:custom.stages.${self:provider.stage}.memorySize}
timeout: ${self:custom.stages.${self:provider.stage}.timeout}
events:
- http:
path: /api
method: get
|
.env 파일
.env.dev:
1
2
3
| API_KEY=dev-api-key-12345
DB_ENDPOINT=https://dev-db.example.com
LOG_LEVEL=debug
|
.env.prod:
1
2
3
| API_KEY=prod-api-key-67890
DB_ENDPOINT=https://prod-db.example.com
LOG_LEVEL=info
|
serverless.yml:
1
2
3
4
5
6
7
| useDotenv: true
provider:
environment:
API_KEY: ${env:API_KEY}
DB_ENDPOINT: ${env:DB_ENDPOINT}
LOG_LEVEL: ${env:LOG_LEVEL}
|
배포 시 환경 변수 로드:
1
2
3
4
5
| # .env.dev 로드
serverless deploy --stage dev
# .env.prod 로드
serverless deploy --stage prod
|
플러그인 활용
serverless-offline (로컬 테스트)
1
| npm install --save-dev serverless-offline
|
1
2
3
4
5
6
7
| plugins:
- serverless-offline
custom:
serverless-offline:
httpPort: 3000
lambdaPort: 3002
|
1
2
3
4
5
| # 로컬에서 API 실행
serverless offline
# 이제 http://localhost:3000 에서 테스트 가능
curl http://localhost:3000/users
|
serverless-prune-plugin (버전 정리)
1
| npm install --save-dev serverless-prune-plugin
|
1
2
3
4
5
6
7
| plugins:
- serverless-prune-plugin
custom:
prune:
automatic: true
number: 3 # 최신 3개 버전만 유지
|
Lambda는 배포마다 새 버전을 생성하므로, 오래된 버전이 누적되어 Code Storage 한도(75GB)를 초과할 수 있음. 이 플러그인으로 자동 정리.
serverless-dotenv-plugin
1
| npm install --save-dev serverless-dotenv-plugin
|
1
2
3
4
5
6
| plugins:
- serverless-dotenv-plugin
custom:
dotenv:
path: .env.${self:provider.stage}
|
배포 전략
1. CI/CD 통합 (GitHub Actions)
.github/workflows/deploy.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| name: Deploy
on:
push:
branches:
- main
- develop
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Deploy to Dev
if: github.ref == 'refs/heads/develop'
env:
AWS_ACCESS_KEY_ID: $
AWS_SECRET_ACCESS_KEY: $
run: |
npx serverless deploy --stage dev
- name: Deploy to Prod
if: github.ref == 'refs/heads/main'
env:
AWS_ACCESS_KEY_ID: $
AWS_SECRET_ACCESS_KEY: $
run: |
npx serverless deploy --stage prod
|
2. 단일 함수 배포 (빠른 업데이트)
1
2
3
4
5
6
7
8
9
| # 전체 배포 (느림, 5-10분)
serverless deploy
# 함수만 배포 (빠름, 10초)
serverless deploy function -f createUser
# 여러 함수 배포
serverless deploy function -f createUser
serverless deploy function -f getUser
|
3. 롤백
1
2
3
4
5
| # 이전 배포로 롤백
serverless rollback --timestamp <timestamp>
# 타임스탬프 확인
serverless deploy list
|
모니터링 및 로그
CloudWatch Logs
1
2
3
4
5
6
7
8
| # 실시간 로그 스트리밍
serverless logs -f createUser --tail
# 특정 기간 로그
serverless logs -f createUser --startTime 1h
# 필터링
serverless logs -f createUser --filter "ERROR"
|
함수 실행
1
2
3
4
5
6
7
8
| # 로컬 실행
serverless invoke local -f createUser -d '{"body": "{\"name\": \"John\"}"}'
# AWS에서 실행
serverless invoke -f createUser -d '{"body": "{\"name\": \"John\"}"}'
# 로그 포함
serverless invoke -f createUser -l
|
비용 최적화
1. 적절한 메모리 크기
1
2
3
4
5
6
7
8
9
10
| functions:
lightTask:
handler: handler.light
memorySize: 128 # 가벼운 작업
timeout: 10
heavyTask:
handler: handler.heavy
memorySize: 1024 # CPU 집약적 작업
timeout: 60
|
AWS Lambda Power Tuning 도구로 최적 메모리 찾기.
2. Reserved Concurrency
1
2
3
4
| functions:
criticalFunction:
handler: handler.critical
reservedConcurrency: 10 # 항상 10개 인스턴스 예약 (콜드 스타트 방지)
|
예측 가능한 트래픽이 있을 때만 사용. 비용 증가 가능.
3. Provisioned Concurrency
1
2
3
4
| functions:
highTrafficFunction:
handler: handler.main
provisionedConcurrency: 5 # 미리 5개 워밍
|
콜드 스타트가 치명적인 API에만 사용.
Best Practices
1. 구조화된 프로젝트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| my-service/
├── serverless.yml
├── package.json
├── .env.dev
├── .env.prod
├── src/
│ ├── handlers/
│ │ ├── users/
│ │ │ ├── create.js
│ │ │ ├── get.js
│ │ │ └── list.js
│ │ └── orders/
│ │ ├── create.js
│ │ └── get.js
│ ├── services/
│ │ ├── dynamodb.js
│ │ └── s3.js
│ ├── utils/
│ │ ├── validation.js
│ │ └── response.js
│ └── config/
│ └── constants.js
└── tests/
└── handlers/
└── users.test.js
|
2. 공통 레이어 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
| layers:
commonDeps:
path: layers/commonDeps
name: ${self:service}-common-deps-${self:provider.stage}
description: 공통 라이브러리
compatibleRuntimes:
- nodejs20.x
functions:
myFunction:
handler: handler.main
layers:
- !Ref CommonDepsLambdaLayer
|
layers/commonDeps/nodejs/package.json:
1
2
3
4
5
6
| {
"dependencies": {
"aws-sdk": "^3.0.0",
"lodash": "^4.17.21"
}
}
|
3. IAM 최소 권한
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| provider:
iam:
role:
statements:
# ✅ 특정 리소스만
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
Resource: !GetAtt UsersTable.Arn
# ❌ 와일드카드 지양
# - Effect: Allow
# Action:
# - dynamodb:*
# Resource: "*"
|
4. 환경 변수 보안
1
2
3
4
5
6
7
8
9
10
| provider:
environment:
# ❌ 민감한 정보 직접 저장
# API_KEY: "my-secret-key-12345"
# ✅ Systems Manager Parameter Store
API_KEY: ${ssm:/my-service/${self:provider.stage}/api-key}
# ✅ AWS Secrets Manager
DB_PASSWORD: ${ssm:/aws/reference/secretsmanager/my-db-password~true}
|
문제 해결
1
2
3
4
5
6
7
8
9
| # 스택 상태 확인
aws cloudformation describe-stacks --stack-name my-service-dev
# 이벤트 확인
aws cloudformation describe-stack-events --stack-name my-service-dev
# 강제 삭제 후 재배포
serverless remove --stage dev
serverless deploy --stage dev
|
문제 2: Lambda 함수 타임아웃
1
2
3
4
| functions:
myFunction:
handler: handler.main
timeout: 300 # 5분으로 증가 (기본 6초)
|
문제 3: Cold Start 느림
1
2
3
4
5
6
| functions:
myFunction:
handler: handler.main
provisionedConcurrency: 2 # 미리 워밍
# 또는
# reservedConcurrency: 5
|
또는 Provisioned Concurrency 스케줄링:
1
2
3
4
5
6
7
8
9
10
| plugins:
- serverless-plugin-warmup
custom:
warmup:
default:
enabled: true
events:
- schedule: rate(5 minutes)
concurrency: 1
|
Serverless Framework vs 다른 도구
vs AWS SAM (Serverless Application Model)
| 특성 | Serverless Framework | AWS SAM |
|---|
| 지원 클라우드 | AWS, Azure, GCP 등 | AWS만 |
| 문법 | YAML (간결) | YAML/JSON (상세) |
| 플러그인 | 풍부한 생태계 | 제한적 |
| 로컬 테스트 | serverless-offline | sam local |
| 학습 곡선 | 낮음 | 중간 |
| 특성 | Serverless Framework | Terraform |
|---|
| 초점 | 서버리스 앱 | 모든 인프라 |
| 추상화 | 높음 (간단) | 낮음 (상세) |
| Lambda 배포 | 자동 | 수동 처리 필요 |
| 상태 관리 | AWS에서 관리 | 별도 백엔드 필요 |
마치며
Serverless Framework는 AWS 서버리스 애플리케이션 개발의 필수 도구임.
Serverless Framework를 사용해야 할 때:
- Lambda 기반 API 구축
- 이벤트 기반 처리 시스템
- 여러 환경 관리 (dev/staging/prod)
- 팀 협업 프로젝트
- 빠른 프로토타이핑
핵심 장점:
- 단일 파일 설정: serverless.yml로 모든 것 정의
- 빠른 배포: 5분 내 전체 인프라 배포
- 환경 관리: stage 변수로 쉬운 환경 분리
- IAM 자동화: 최소 권한 자동 생성
- 풍부한 플러그인: offline, prune, warmup 등
실전 활용:
- REST API: HTTP API + Lambda + DynamoDB
- 비동기 처리: SQS + Lambda
- 이벤트 처리: S3 + Lambda
- 워크플로우: Step Functions + Lambda
프로젝트 경험을 통해 Serverless Framework는 개발 생산성을 10배 이상 향상시키고, 인프라 관리 부담을 제거하며, 팀 협업을 원활하게 만드는 것을 확인했음.
단일 명령어로 복잡한 서버리스 아키텍처를 배포하고, Git으로 인프라를 버전 관리하며, 환경별로 일관된 배포를 보장할 수 있음.
도움이 되셨길 바랍니다! 😀