AWS 모듈 3: Go와 Lambda
Source: Dev.to
Mac을 떠나지 않고 Linux용으로 컴파일 (비용 $0.06)
시리즈: AWS Zero to Architect - 모듈 3
읽는 시간: 20 분
구현 시간: 120 분
이전 모듈들에서 AWS, Terraform, IAM을 설정했습니다. 이제 재미있는 단계가 왔습니다: Go로 첫 Lambda 함수를 만들고, 비용은 센트 수준이며 Python보다 3배 빠릅니다.
🤔 우리 모두가 겪는 문제
일반적인 시나리오
“간단한 API가 있어요. 하루에 100 request만 받아도 매달 $50짜리 24/7 서버를 운영해야 할까요?”
답변: 아니오. Lambda를 사용하세요.
⚡ Lambda 설명: 푸드 트럭 vs 레스토랑
🏢 레스토랑 (EC2 – 전통 서버)
- Rentas local completo: $500/mes
- Pagas luz/agua/gas 24/7
- Staff de tiempo completo
- Si hay 3 clientes o 300, pagas lo mismo
- Si hay demanda, el local se satura
Costo fijo: $500/mes
🚚 푸드 트럭 (Lambda – 서버리스)
- Solo pagas cuando sirves un plato
- $0.20 por cada 1 millón de platos
- Sin staff fijo (AWS lo maneja)
- Escala automáticamente
- 3 clientes = $0.0006
- 300 clientes = $0.06
Costo variable: $0‑$100/mes
간헐적인 트래픽을 받는 API에 어느 것이 더 합리적일까요?
💰 실제 가격 (마케팅 과장 없이)
내 현재 Lambda
100,000 requests/mes
128 MB RAM
200 ms promedio de duración
Cálculo:
Requests: 100,000 ÷ 1,000,000 × $0.20 = $0.02
Compute: 0.128 GB × 0.2 s × 100,000 × $0.0000166667 = $0.04
Total: $0.06/mes
무료 티어
- 1 millón de requests/mes
- 400,000 GB‑segundos
번역: 개발 및 작은 앱에 무료.
🐹 왜 Go이고 Python/Node.js가 아닌가?
정직한 벤치마크 (내 직접 측정)
| Language | Cold Start (콜드 스타트) | Memory Used (사용 메모리) | Warm Invoke (웜 인보크) |
|---|---|---|---|
| Go | 156 ms | 48 MB | 45 ms |
| Node.js | 342 ms | 89 MB | 89 ms |
| Python | 478 ms | 127 MB | 134 ms |
| Java | 1,240 ms | 186 MB | 203 ms |
결과: Go는 콜드 스타트에서 3배 더 빠릅니다.
콜드 스타트란 무엇인가?
Primera invocación del día:
1. AWS crea un container → 100 ms
2. Carga tu runtime (Node.js/Python) → 200 ms
3. Carga tu código → 100 ms
Total: ~400 ms
Con Go:
1. AWS crea container → 100 ms
2. Ejecuta binario → 50 ms
Total: ~150 ms
최종 사용자에게: Go는 약 150 ms, Python은 약 400 ms에 응답합니다.
비용 장점
Python (512 MB): $0.0000083 por 100 ms
Go (128 MB): $0.0000021 por 100 ms
Ahorro: 75 %
월 100만 요청 기준
- Python: $83/월
- Go: $21/월
차이: $62/월 × 12개월 = $744/년
🏗️ 아키텍처: 서버리스 헥사고날
헥사고날 아키텍처란?
간단한 아이디어: 비즈니스 로직 AWS에 의존하면 안 된다.
// ❌ MAL (Acoplado a AWS)
func CreateSession(userID string) {
dynamodb.PutItem(...) // Directo a AWS
}
// ✅ BIEN (Desacoplado)
// Domain (puro Go, sin AWS)
type Session struct {
ID string
UserID string
}
func NewSession(userID string) *Session { ... }
// Adapter (traduce a AWS)
type DynamoDBRepo struct { ... }
func (r *DynamoDBRepo) Save(session *Session) { ... }
장점
- ✅ DynamoDB를 Postgres로 교체해도 도메인 코드는 건드릴 필요 없음
- ✅ AWS 없이 테스트 가능
- ✅ 비즈니스 로직이 명확함
내 구조
go-hexagonal-auth/
├── cmd/lambda/main.go # Lambda handler
├── internal/
│ ├── core/domain/
│ │ └── session.go # 비즈니스 로직
│ └── adapters/repository/
│ └── dynamodb_session.go # AWS 어댑터
└── terraform/
└── lambda.tf # 인프라
💻 중요한 코드
도메인 모델 (AWS 없음)
package domain
import (
"time"
"github.com/google/uuid"
)
type Session struct {
SessionID string
UserID string
ExpiresAt time.Time
CreatedAt time.Time
}
func NewSession(userID string, ttl time.Duration) *Session {
now := time.Now()
return &Session{
SessionID: uuid.New().String(),
UserID: userID,
CreatedAt: now,
ExpiresAt: now.Add(ttl),
}
}
func (s *Session) IsValid() bool {
return s.SessionID != "" && s.UserID != ""
}
주의: AWS import가 전혀 없습니다. 순수 로직.
DynamoDB 어댑터
package repository
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"go-hexagonal-auth/internal/core/domain"
)
type DynamoDBRepo struct {
client *dynamodb.Client
tableName string
}
func (r *DynamoDBRepo) Save(ctx context.Context, session *domain.Session) error {
item := map[string]interface{}{
"session_id": session.SessionID,
"user_id": session.UserID,
"expires_at": session.ExpiresAt.Unix(),
}
_, err := r.client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String(r.tableName),
Item: marshalMap(item),
})
return err
}
Lambda 핸들러
package main
import (
"context"
"encoding/json"
"time"
"github.com/aws/aws-lambda-go/lambda"
"go-hexagonal-auth/internal/core/domain"
"go-hexagonal-auth/internal/adapters/repository"
)
type APIGatewayRequest struct {
Body string `json:"body"`
}
type CreateSessionRequest struct {
UserID string `json:"user_id"`
}
type Response struct {
StatusCode int `json:"statusCode"`
Body string `json:"body"`
}
func handler(ctx context.Context, request APIGatewayRequest) (Response, error) {
// 1. Parse input
var req CreateSessionRequest
if err := json.Unmarshal([]byte(request.Body), &req); err != nil {
return Response{StatusCode: 400, Body: err.Error()}, err
}
// 2. Create session (domain logic)
session := domain.NewSession(req.UserID, 24*time.Hour)
// 3. Save (adapter)
repo := repository.NewDynamoDBRepo(ctx)
if err := repo.Save(ctx, session); err != nil {
return Response{StatusCode: 500, Body: err.Error()}, err
}
// 4. Response
body, _ := json.Marshal(session)
return Response{
StatusCode: 201,
Body: string(body),
}, nil
}
func main() {
lambda.Start(handler)
}
흐름: JSON 파싱 → 비즈니스 로직 (도메인) → 영속성 (어댑터) → 응답.
🔨 크로스 플랫폼 컴파일 (Go의 마법)
문제
Desarrollo en: macOS ARM64 (M1/M2/M3)
Lambda ejecuta: Linux ARM64
¿Cómo compilo para Linux sin salir de macOS?
Go의 해결책
# Un solo comando
GOOS=linux GOARCH=arm64 go build -o bootstrap ./cmd/lambda
그게 전부입니다. Docker, 가상 머신, CI/CD 빌드가 필요 없습니다; Go는 다른 플랫폼용으로 네이티브 컴파일합니다.
Makefile (자동화)
build:
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 \
go build -ldflags="-s -w" \
-o build/bootstrap ./cmd/lambda
zip: build
cd build && zip lambda.zip bootstrap
명령어
make build # Compila para Lambda
make zip # Crea lambda.zip
크기
build/bootstrap: 7.2 MB (sin comprimir)
build/lambda.zip: 2.8 MB (comprimido)
중요한 플래그
-ldflags="-s -w" # Reduce tamaño 30‑40 %
CGO_ENABLED=0 # Binario estático (sin dependencias C)
🚀 Terraform으로 배포
Lambda Configuration
resource "aws_lambda_function" "create_session" {
filename = "../build/lambda.zip"
function_name = "go-hexagonal-auth-dev-create-session"
role = aws_iam_role.lambda_exec.arn
handler = "bootstrap"
runtime = "provided.al2"
memory_size