목차
1. 왜 API Gateway가 필요한가
1.1 마이크로서비스의 Cross-Cutting Concerns
마이크로서비스 아키텍처에서는 수십~수백 개의 서비스가 독립적으로 운영됩니다. 각 서비스마다 인증, 로깅, 레이트 리밋 등을 개별 구현하면 중복과 불일치가 발생합니다.
API Gateway 없는 세계:
[클라이언트]
│ │ │
│ │ └──── [서비스 A] (자체 인증, 자체 로깅, 자체 레이트 리밋)
│ └─────── [서비스 B] (자체 인증, 자체 로깅, 자체 레이트 리밋)
└────────── [서비스 C] (자체 인증, 자체 로깅, 자체 레이트 리밋)
↑
중복! 불일치! 관리 불가!
API Gateway가 있는 세계:
[클라이언트]
│
┌────┴─────────────────────────┐
│ API Gateway │
│ ┌─────────────────────────┐ │
│ │ 인증 / 인가 │ │
│ │ 레이트 리밋 │ │
│ │ 요청 변환 │ │
│ │ 캐싱 │ │
│ │ 로깅 / 모니터링 │ │
│ │ 서킷 브레이커 │ │
│ │ 로드 밸런싱 │ │
│ └─────────────────────────┘ │
└──────┬───────┬───────┬───────┘
│ │ │
[서비스A] [서비스B] [서비스C]
(비즈니스 (비즈니스 (비즈니스
로직만) 로직만) 로직만)
1.2 API Gateway 패턴
3가지 핵심 Gateway 패턴:
1. Routing Pattern (라우팅)
클라이언트 요청을 올바른 백엔드 서비스로 전달
/api/users → User Service
/api/orders → Order Service
/api/products → Product Service
2. Aggregation Pattern (집약)
여러 서비스의 응답을 하나로 합쳐서 반환
GET /api/dashboard →
User Service (프로필) +
Order Service (최근 주문) +
Notification Service (알림)
→ 단일 JSON 응답
3. Offloading Pattern (오프로딩)
공통 기능을 서비스에서 Gateway로 이전
인증, SSL 종료, 압축, 캐싱, CORS 등
→ 서비스는 비즈니스 로직에만 집중
1.3 API Gateway가 처리하는 기능
| 기능 | 설명 |
|------|------|
| 라우팅 | URL 패턴/헤더 기반으로 요청을 올바른 서비스로 전달 |
| 인증/인가 | OAuth2, JWT, API Key 검증 |
| 레이트 리밋 | API 남용 방지. 사용자/IP/엔드포인트별 제한 |
| 요청/응답 변환 | 헤더 추가/제거, 바디 변환, 프로토콜 변환 |
| 캐싱 | 응답 캐싱으로 백엔드 부하 감소 |
| 로드 밸런싱 | 트래픽을 여러 인스턴스에 분산 |
| 서킷 브레이커 | 장애 서비스 격리. 연쇄 장애 방지 |
| 로깅/모니터링 | 접근 로그, 메트릭 수집, 분산 트레이싱 |
| SSL/TLS 종료 | HTTPS 처리를 Gateway에서 수행 |
| CORS | Cross-Origin 요청 정책 관리 |
| 카나리/A-B 라우팅 | 트래픽 비율 기반 배포 |
| 웹소켓/gRPC | 다양한 프로토콜 지원 |
2. Kong Deep Dive
2.1 Kong 아키텍처
Kong Architecture:
[클라이언트]
│
┌────┴────────────────────────────────┐
│ Kong Gateway │
│ │
│ ┌──────────────────────────────┐ │
│ │ Kong Core (OpenResty) │ │
│ │ Nginx + LuaJIT │ │
│ └──────────┬───────────────────┘ │
│ │ │
│ ┌──────────┴───────────────────┐ │
│ │ Plugin Layer │ │
│ │ │ │
│ │ Auth │ Rate │ Logging │ ... │ │
│ │ │ Limit │ │ │ │
│ └──────────────────────────────┘ │
│ │ │
│ ┌──────────┴───────────────────┐ │
│ │ Data Store │ │
│ │ PostgreSQL │ Cassandra │ │
│ │ (또는 DB-less Mode) │ │
│ └──────────────────────────────┘ │
└──────┬──────────┬──────────┬────────┘
│ │ │
[Service A] [Service B] [Service C]
2.2 Kong DB-less Mode (Declarative Config)
kong.yml - DB-less Declarative Configuration
_format_version: "3.0"
_transform: true
services:
- name: user-service
url: http://user-service:8080
routes:
- name: user-routes
paths:
- /api/v1/users
methods:
- GET
- POST
- PUT
- DELETE
strip_path: false
plugins:
- name: rate-limiting
config:
minute: 100
hour: 5000
policy: local
- name: jwt
config:
uri_param_names:
- token
claims_to_verify:
- exp
- name: order-service
url: http://order-service:8080
routes:
- name: order-routes
paths:
- /api/v1/orders
strip_path: false
plugins:
- name: rate-limiting
config:
minute: 50
hour: 2000
- name: request-transformer
config:
add:
headers:
- "X-Request-Source:api-gateway"
- "X-Forwarded-Service:order-service"
- name: product-service
url: http://product-service:8080
routes:
- name: product-routes
paths:
- /api/v1/products
strip_path: false
plugins:
- name: proxy-cache
config:
response_code:
- 200
request_method:
- GET
content_type:
- "application/json"
cache_ttl: 300
strategy: memory
Global Plugins
plugins:
- name: correlation-id
config:
header_name: X-Request-ID
generator: uuid
echo_downstream: true
- name: prometheus
config:
per_consumer: true
- name: file-log
config:
path: /dev/stdout
reopen: true
2.3 Kong 주요 플러그인
Kong Plugin 카테고리:
Authentication:
├── jwt - JWT 토큰 검증
├── oauth2 - OAuth2 서버 내장
├── key-auth - API Key 인증
├── basic-auth - Basic 인증
├── hmac-auth - HMAC 서명 인증
├── ldap-auth - LDAP/AD 인증
└── openid-connect - OIDC (Enterprise)
Security:
├── cors - Cross-Origin 정책
├── ip-restriction - IP 화이트/블랙리스트
├── bot-detection - 봇 탐지
├── acl - Access Control Lists
└── mtls-auth - mTLS 인증
Traffic Control:
├── rate-limiting - 레이트 리밋
├── request-size-limiting - 요청 크기 제한
├── request-termination - 요청 차단/유지보수 모드
└── proxy-cache - 응답 캐싱
Transformations:
├── request-transformer - 요청 헤더/바디 변환
├── response-transformer - 응답 헤더/바디 변환
├── correlation-id - 요청 추적 ID
└── grpc-gateway - REST to gRPC 변환
Observability:
├── prometheus - Prometheus 메트릭
├── datadog - Datadog APM 통합
├── zipkin - 분산 트레이싱
├── file-log - 파일 로깅
├── http-log - HTTP 기반 로그 전송
└── opentelemetry - OTel 통합
2.4 Kong Custom Plugin (Lua)
-- custom-auth-plugin/handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local CustomAuthHandler = BasePlugin:extend()
CustomAuthHandler.VERSION = "1.0.0"
CustomAuthHandler.PRIORITY = 1000 -- 실행 순서
function CustomAuthHandler:new()
CustomAuthHandler.super.new(self, "custom-auth")
end
function CustomAuthHandler:access(conf)
CustomAuthHandler.super.access(self)
-- 1. API Key 추출
local api_key = kong.request.get_header("X-API-Key")
if not api_key then
return kong.response.exit(401, {
message = "Missing API Key"
})
end
-- 2. 외부 인증 서비스 호출
local httpc = require("resty.http").new()
local res, err = httpc:request_uri(conf.auth_service_url, {
method = "POST",
headers = {
["Content-Type"] = "application/json",
},
body = '{"api_key":"' .. api_key .. '"}',
timeout = conf.timeout or 5000,
})
if not res then
kong.log.err("Auth service error: ", err)
return kong.response.exit(503, {
message = "Auth service unavailable"
})
end
if res.status ~= 200 then
return kong.response.exit(403, {
message = "Invalid API Key"
})
end
-- 3. 인증 정보를 업스트림 헤더에 추가
local cjson = require "cjson.safe"
local body = cjson.decode(res.body)
if body then
kong.service.request.set_header("X-Consumer-ID", body.consumer_id or "")
kong.service.request.set_header("X-Consumer-Plan", body.plan or "free")
end
end
return CustomAuthHandler
-- custom-auth-plugin/schema.lua
local typedefs = require "kong.db.schema.typedefs"
return {
name = "custom-auth",
fields = {
{ consumer = typedefs.no_consumer },
{ protocols = typedefs.protocols_http },
{ config = {
type = "record",
fields = {
{ auth_service_url = {
type = "string",
required = true,
default = "http://auth-service:8080/validate"
}},
{ timeout = {
type = "number",
default = 5000
}},
{ cache_ttl = {
type = "number",
default = 60
}},
},
}},
},
}
3. Envoy Proxy Deep Dive
3.1 Envoy 아키텍처
Envoy Core Architecture:
┌──────────────────────────────────────────────┐
│ Envoy Proxy │
│ │
│ Listener (포트 바인딩) │
│ │ │
│ ▼ │
│ Filter Chain (필터 체인) │
│ ├── Network Filters (L3/L4) │
│ │ ├── TCP Proxy │
│ │ ├── TLS Inspector │
│ │ └── HTTP Connection Manager │
│ │ │
│ └── HTTP Filters (L7) │
│ ├── Router (라우팅) │
│ ├── CORS │
│ ├── JWT Auth │
│ ├── Rate Limit │
│ ├── WASM Filter (커스텀) │
│ └── ... │
│ │
│ Route Configuration │
│ │ │
│ ▼ │
│ Cluster (업스트림 서비스 그룹) │
│ ├── Endpoint Discovery │
│ ├── Load Balancing │
│ ├── Health Checking │
│ ├── Circuit Breaking │
│ └── Outlier Detection │
└──────────────────────────────────────────────┘
3.2 Envoy 정적 설정
envoy.yaml - Static Configuration
static_resources:
listeners:
- name: main_listener
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
log_format:
json_format:
timestamp: "%START_TIME%"
method: "%REQ(:METHOD)%"
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
status: "%RESPONSE_CODE%"
duration: "%DURATION%"
upstream: "%UPSTREAM_HOST%"
request_id: "%REQ(X-REQUEST-ID)%"
route_config:
name: local_route
virtual_hosts:
- name: api_service
domains: ["*"]
routes:
User Service Routes
- match:
prefix: "/api/v1/users"
route:
cluster: user_service
timeout: 10s
retry_policy:
retry_on: "5xx,connect-failure"
num_retries: 3
per_try_timeout: 3s
Order Service Routes
- match:
prefix: "/api/v1/orders"
route:
cluster: order_service
timeout: 15s
Product Service (with canary)
- match:
prefix: "/api/v1/products"
route:
weighted_clusters:
clusters:
- name: product_service_v1
weight: 90
- name: product_service_v2
weight: 10
http_filters:
- name: envoy.filters.http.cors
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
auth0:
issuer: "https://company.auth0.com/"
audiences:
- "https://api.company.com"
remote_jwks:
http_uri:
uri: "https://company.auth0.com/.well-known/jwks.json"
cluster: auth0_jwks
timeout: 5s
cache_duration: 600s
rules:
- match:
prefix: "/api/"
requires:
provider_name: "auth0"
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: user_service
connect_timeout: 5s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
health_checks:
- timeout: 3s
interval: 10s
unhealthy_threshold: 3
healthy_threshold: 2
http_health_check:
path: "/health"
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 1024
max_pending_requests: 1024
max_requests: 1024
max_retries: 3
load_assignment:
cluster_name: user_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: user-service
port_value: 8080
- name: order_service
connect_timeout: 5s
type: STRICT_DNS
lb_policy: LEAST_REQUEST
outlier_detection:
consecutive_5xx: 5
interval: 10s
base_ejection_time: 30s
max_ejection_percent: 50
load_assignment:
cluster_name: order_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: order-service
port_value: 8080
3.3 xDS API (동적 설정)
xDS API 종류:
┌──────────────────────────────────────┐
│ Control Plane │
│ (Istio Pilot / custom xDS server) │
└──────────┬───────────────────────────┘
│
┌────────┴──────────────────┐
│ │
▼ ▼
[Envoy A] [Envoy B]
xDS 유형:
├── LDS (Listener Discovery Service)
│ → 리스너 설정 동적 변경
├── RDS (Route Discovery Service)
│ → 라우팅 규칙 동적 변경
├── CDS (Cluster Discovery Service)
│ → 클러스터(업스트림) 동적 변경
├── EDS (Endpoint Discovery Service)
│ → 엔드포인트(IP:Port) 동적 변경
├── SDS (Secret Discovery Service)
│ → TLS 인증서 동적 변경
└── ECDS (Extension Config Discovery)
→ 필터 설정 동적 변경
3.4 Envoy WASM Filter
Envoy WASM Filter 설정 예시
http_filters:
- name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
name: "custom_rate_limiter"
root_id: "rate_limiter"
vm_config:
runtime: "envoy.wasm.runtime.v8"
code:
local:
filename: "/etc/envoy/wasm/rate_limiter.wasm"
allow_precompiled: true
configuration:
"@type": type.googleapis.com/google.protobuf.StringValue
value: |
{
"max_requests_per_second": 100,
"burst_size": 20,
"response_code": 429
}
4. AWS API Gateway
4.1 AWS API Gateway 유형 비교
AWS API Gateway 3가지 유형:
┌──────────────┬───────────────┬───────────────┬───────────────┐
│ │ REST API │ HTTP API │ WebSocket │
├──────────────┼───────────────┼───────────────┼───────────────┤
│ 프로토콜 │ REST │ REST │ WebSocket │
│ 레이턴시 │ 보통 │ 낮음 (~35%) │ 보통 │
│ 가격 │ 높음 │ 낮음 (~70%) │ 메시지당 │
│ 캐싱 │ O │ X │ X │
│ Usage Plans │ O │ X │ X │
│ API Keys │ O │ X │ X │
│ WAF 통합 │ O │ X │ X │
│ Request 검증 │ O │ Parameter만 │ X │
│ Custom Domain│ O │ O │ O │
│ Lambda 통합 │ O │ O │ O │
│ VPC Link │ O │ O │ X │
│ 권장 사용 │ 풀기능 필요 │ 단순 프록시 │ 실시간 통신 │
└──────────────┴───────────────┴───────────────┴───────────────┘
4.2 AWS REST API Gateway + Lambda
SAM Template - REST API Gateway
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Api:
Cors:
AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
AllowHeaders: "'Content-Type,Authorization,X-Api-Key'"
AllowOrigin: "'https://app.company.com'"
Resources:
ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Auth:
DefaultAuthorizer: CognitoAuth
Authorizers:
CognitoAuth:
UserPoolArn: !GetAtt UserPool.Arn
Identity:
Header: Authorization
ApiKeyRequired: true
UsagePlan:
CreateUsagePlan: PER_API
UsagePlanName: "StandardPlan"
Throttle:
BurstLimit: 100
RateLimit: 50
Quota:
Limit: 10000
Period: DAY
MethodSettings:
- HttpMethod: "*"
ResourcePath: "/*"
ThrottlingBurstLimit: 200
ThrottlingRateLimit: 100
CachingEnabled: true
CacheTtlInSeconds: 300
LoggingLevel: INFO
MetricsEnabled: true
User Function
UserFunction:
Type: AWS::Serverless::Function
Properties:
Handler: handlers/user.handler
Runtime: nodejs20.x
MemorySize: 256
Timeout: 10
Events:
GetUsers:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /api/v1/users
Method: GET
CreateUser:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /api/v1/users
Method: POST
Order Function
OrderFunction:
Type: AWS::Serverless::Function
Properties:
Handler: handlers/order.handler
Runtime: nodejs20.x
MemorySize: 512
Timeout: 15
Events:
GetOrders:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /api/v1/orders
Method: GET
CreateOrder:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /api/v1/orders
Method: POST
Cognito User Pool
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: api-user-pool
AutoVerifiedAttributes:
MfaConfiguration: "OPTIONAL"
Policies:
PasswordPolicy:
MinimumLength: 12
RequireUppercase: true
RequireLowercase: true
RequireNumbers: true
RequireSymbols: true
4.3 Lambda Authorizer (Custom Authorizer)
// authorizer.js - Lambda Custom Authorizer
const jwt = require('jsonwebtoken');
const JWKS_CACHE = {};
exports.handler = async (event) => {
try {
const token = extractToken(event.authorizationToken);
if (!token) {
throw new Error('Unauthorized');
}
// JWT 검증
const decoded = await verifyToken(token);
// IAM Policy 생성
const policy = generatePolicy(
decoded.sub,
'Allow',
event.methodArn,
{
userId: decoded.sub,
email: decoded.email,
role: decoded.role,
plan: decoded.plan || 'free'
}
);
return policy;
} catch (error) {
console.error('Authorization failed:', error.message);
throw new Error('Unauthorized');
}
};
function extractToken(authHeader) {
if (!authHeader) return null;
const parts = authHeader.split(' ');
if (parts.length !== 2 || parts[0] !== 'Bearer') return null;
return parts[1];
}
async function verifyToken(token) {
const decoded = jwt.decode(token, { complete: true });
if (!decoded) throw new Error('Invalid token');
// JWKS에서 공개키 가져오기
const publicKey = await getPublicKey(decoded.header.kid);
return jwt.verify(token, publicKey, {
algorithms: ['RS256'],
issuer: process.env.TOKEN_ISSUER,
audience: process.env.TOKEN_AUDIENCE,
});
}
function generatePolicy(principalId, effect, resource, context) {
const [arn, partition, service, region, accountId, apiId, stage] =
resource.split(/[:/]/);
return {
principalId,
policyDocument: {
Version: '2012-10-17',
Statement: [{
Action: 'execute-api:Invoke',
Effect: effect,
Resource: `arn:${partition}:${service}:${region}:${accountId}:${apiId}/${stage}/*`
}]
},
context: context || {}
};
}
5. Traefik
5.1 Traefik 아키텍처
Traefik Architecture:
[클라이언트]
│
┌────┴────────────────────────────────┐
│ Traefik Proxy │
│ │
│ EntryPoints (포트) │
│ ├── :80 (web) │
│ └── :443 (websecure) │
│ │ │
│ Routers (라우팅 규칙) │
│ ├── Host / Path / Header 매칭 │
│ ├── TLS 설정 │
│ └── Middleware 체인 │
│ │ │
│ Middlewares (중간 처리) │
│ ├── RateLimit │
│ ├── BasicAuth / ForwardAuth │
│ ├── Headers │
│ ├── Retry │
│ ├── CircuitBreaker │
│ └── StripPrefix │
│ │ │
│ Services (백엔드) │
│ ├── LoadBalancer │
│ ├── Weighted │
│ └── Mirroring │
│ │
│ Providers (설정 소스 - 자동 발견) │
│ ├── Docker │
│ ├── Kubernetes │
│ ├── File │
│ └── Consul / etcd │
└──────────────────────────────────────┘
5.2 Docker + Traefik 자동 발견
docker-compose.yml with Traefik
version: "3.8"
services:
traefik:
image: traefik:v3.0
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedByDefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=admin@company.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--metrics.prometheus=true"
- "--accesslog=true"
- "--accesslog.format=json"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
user-service:
image: user-service:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.users.rule=Host(`api.company.com`) && PathPrefix(`/api/v1/users`)"
- "traefik.http.routers.users.entrypoints=websecure"
- "traefik.http.routers.users.tls.certresolver=letsencrypt"
- "traefik.http.routers.users.middlewares=rate-limit,auth-forward"
- "traefik.http.services.users.loadbalancer.server.port=8080"
- "traefik.http.services.users.loadbalancer.healthcheck.path=/health"
- "traefik.http.services.users.loadbalancer.healthcheck.interval=10s"
order-service:
image: order-service:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.orders.rule=Host(`api.company.com`) && PathPrefix(`/api/v1/orders`)"
- "traefik.http.routers.orders.entrypoints=websecure"
- "traefik.http.routers.orders.tls.certresolver=letsencrypt"
- "traefik.http.routers.orders.middlewares=rate-limit,auth-forward"
- "traefik.http.services.orders.loadbalancer.server.port=8080"
Middleware 정의
Rate Limiting
rate-limit:
labels:
- "traefik.http.middlewares.rate-limit.ratelimit.average=100"
- "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
- "traefik.http.middlewares.rate-limit.ratelimit.period=1m"
volumes:
letsencrypt:
5.3 Kubernetes IngressRoute (CRD)
Traefik IngressRoute CRD
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: api-routes
namespace: production
spec:
entryPoints:
- websecure
routes:
- match: Host(`api.company.com`) && PathPrefix(`/api/v1/users`)
kind: Rule
services:
- name: user-service
port: 8080
weight: 100
middlewares:
- name: rate-limit
- name: jwt-auth
- name: cors-headers
- match: Host(`api.company.com`) && PathPrefix(`/api/v1/products`)
kind: Rule
services:
- name: product-service-v1
port: 8080
weight: 90
- name: product-service-v2
port: 8080
weight: 10
tls:
certResolver: letsencrypt
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: rate-limit
namespace: production
spec:
rateLimit:
average: 100
burst: 50
period: 1m
sourceCriterion:
ipStrategy:
depth: 1
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: jwt-auth
namespace: production
spec:
forwardAuth:
address: http://auth-service:8080/verify
authResponseHeaders:
- X-User-ID
- X-User-Role
6. API Gateway 비교표 (15+ 항목)
| 항목 | Kong | Envoy | AWS API GW | Traefik |
|------|------|-------|------------|---------|
| **기반** | Nginx/OpenResty | C++ (자체) | AWS 관리형 | Go (자체) |
| **라이선스** | Apache 2.0 / Enterprise | Apache 2.0 | 종량제 | MIT |
| **배포 방식** | Self-hosted / Cloud | Self-hosted (사이드카) | Serverless | Self-hosted |
| **성능** | 높음 (Nginx) | 매우 높음 | 높음 (관리형) | 높음 |
| **설정 방식** | Admin API / Declarative | YAML / xDS API | Console / CloudFormation | Labels / YAML / CRD |
| **DB-less 모드** | O | N/A (항상 stateless) | N/A | N/A |
| **플러그인** | Lua / Go | C++ / WASM | Lambda Authorizer | 내장 Middleware |
| **서비스 디스커버리** | DNS / Consul | EDS (xDS) | CloudMap / VPC Link | Docker / K8s / Consul |
| **L7 기능** | 풍부 | 매우 풍부 | 기본 | 풍부 |
| **gRPC** | O | O | O (HTTP/2) | O |
| **WebSocket** | O | O | O (별도 API) | O |
| **mTLS** | O (Enterprise) | O | O (VPC Link) | O |
| **레이트 리밋** | 내장 플러그인 | 외부 서비스 / WASM | 내장 (Usage Plans) | 내장 Middleware |
| **분산 트레이싱** | Zipkin/OTel 플러그인 | 내장 (Zipkin/OTel) | X-Ray | Jaeger/Zipkin |
| **대시보드** | Kong Manager | 없음 (Kiali 등 사용) | AWS Console | 내장 대시보드 |
| **학습 곡선** | 중간 | 높음 | 낮음 | 낮음 |
| **적합 대상** | 범용 API GW | Service Mesh / 사이드카 | AWS 서버리스 | Docker/K8s 환경 |
7. 인증 (Authentication)
7.1 JWT 검증 구현
JWT 검증 미들웨어 (Python/FastAPI 예시)
from fastapi import Request, HTTPException
from jose import jwt, JWTError, ExpiredSignatureError
from functools import lru_cache
class JWTAuthMiddleware:
def __init__(self, jwks_url: str, issuer: str, audience: str):
self.jwks_url = jwks_url
self.issuer = issuer
self.audience = audience
self._jwks_cache = None
async def get_jwks(self):
"""JWKS (JSON Web Key Set) 가져오기 및 캐시"""
if self._jwks_cache is None:
async with httpx.AsyncClient() as client:
response = await client.get(self.jwks_url)
self._jwks_cache = response.json()
return self._jwks_cache
async def verify_token(self, request: Request) -> dict:
"""요청에서 JWT를 추출하고 검증"""
1. Authorization 헤더에서 토큰 추출
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(
status_code=401,
detail="Missing or invalid Authorization header"
)
token = auth_header.split(" ")[1]
try:
2. 토큰 헤더에서 kid 추출
unverified_header = jwt.get_unverified_header(token)
kid = unverified_header.get("kid")
3. JWKS에서 해당 키 찾기
jwks = await self.get_jwks()
key = None
for jwk in jwks.get("keys", []):
if jwk["kid"] == kid:
key = jwk
break
if key is None:
캐시 갱신 후 재시도
self._jwks_cache = None
jwks = await self.get_jwks()
for jwk in jwks.get("keys", []):
if jwk["kid"] == kid:
key = jwk
break
if key is None:
raise HTTPException(status_code=401, detail="Unknown signing key")
4. JWT 검증
payload = jwt.decode(
token,
key,
algorithms=["RS256"],
audience=self.audience,
issuer=self.issuer,
)
return payload
except ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except JWTError as e:
raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}")
7.2 OAuth2 흐름
OAuth2 Authorization Code Flow + PKCE:
[브라우저/앱] [API Gateway] [Auth Server] [Backend]
│ │ │ │
│ 1. 로그인 요청 │ │ │
│─────────────────────────────▶│ │ │
│ │ │ │
│ 2. Auth Server로 리다이렉트 │ │ │
│◀─────────────────────────────│ │ │
│ │ │ │
│ 3. 인증 (ID/PW 또는 SSO) │ │ │
│──────────────────────────────────────────────────▶│ │
│ │ │ │
│ 4. Authorization Code 반환 │ │ │
│◀──────────────────────────────────────────────────│ │
│ │ │ │
│ 5. Code + PKCE verifier │ │ │
│─────────────────────────────▶│ │ │
│ │ 6. Code -> Token │ │
│ │───────────────────▶│ │
│ │ 7. Access Token │ │
│ │◀───────────────────│ │
│ │ │ │
│ 8. Access Token │ │ │
│◀─────────────────────────────│ │ │
│ │ │ │
│ 9. API 요청 + Bearer Token │ │ │
│─────────────────────────────▶│ │ │
│ │ 10. Token 검증 │ │
│ │───────────────────▶│ │
│ │ 11. 검증 결과 │ │
│ │◀───────────────────│ │
│ │ 12. 요청 전달 │ │
│ │───────────────────────────────────▶│
│ │ 13. 응답 │ │
│ │◀───────────────────────────────────│
│ 14. API 응답 │ │ │
│◀─────────────────────────────│ │ │
7.3 API Key 관리
API Key 관리 시스템
from datetime import datetime, timedelta
class APIKeyManager:
def __init__(self, db):
self.db = db
def generate_key(
self,
consumer_id: str,
plan: str = "free",
expires_in_days: int = 365
) -> dict:
"""새 API Key 생성"""
prefix + secret 형식 (sk_live_xxxx)
prefix = f"sk_{'live' if plan != 'free' else 'test'}"
raw_key = f"{prefix}_{secrets.token_urlsafe(32)}"
DB에는 해시만 저장
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
record = {
"key_hash": key_hash,
"key_prefix": raw_key[:12], # 식별용 prefix만 저장
"consumer_id": consumer_id,
"plan": plan,
"rate_limit": self._get_rate_limit(plan),
"created_at": datetime.utcnow().isoformat(),
"expires_at": (
datetime.utcnow() + timedelta(days=expires_in_days)
).isoformat(),
"is_active": True,
}
self.db.insert("api_keys", record)
return {
"api_key": raw_key, # 한 번만 반환, 이후 조회 불가
"prefix": raw_key[:12],
"plan": plan,
"expires_at": record["expires_at"],
}
def validate_key(self, raw_key: str) -> dict:
"""API Key 검증"""
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
record = self.db.find_one("api_keys", {"key_hash": key_hash})
if not record:
return {"valid": False, "error": "Invalid API key"}
if not record["is_active"]:
return {"valid": False, "error": "API key is deactivated"}
if datetime.fromisoformat(record["expires_at"]) < datetime.utcnow():
return {"valid": False, "error": "API key expired"}
return {
"valid": True,
"consumer_id": record["consumer_id"],
"plan": record["plan"],
"rate_limit": record["rate_limit"],
}
def _get_rate_limit(self, plan: str) -> dict:
limits = {
"free": {"rpm": 60, "rpd": 1000},
"starter": {"rpm": 300, "rpd": 10000},
"pro": {"rpm": 1000, "rpd": 100000},
"enterprise": {"rpm": 10000, "rpd": 1000000},
}
return limits.get(plan, limits["free"])
8. 레이트 리밋 알고리즘
8.1 Token Bucket
class TokenBucket:
"""Token Bucket 레이트 리미터
특징:
- 일정 속도로 토큰이 버킷에 추가됨
- 요청 시 토큰 1개 소비
- 토큰 없으면 요청 거부
- 버스트 허용 (버킷이 가득 찰 때까지)
"""
def __init__(self, rate: float, capacity: int):
self.rate = rate # 초당 토큰 생성 속도
self.capacity = capacity # 버킷 최대 크기 (버스트 허용량)
self.tokens = capacity # 현재 토큰 수
self.last_refill = time.monotonic()
self.lock = threading.Lock()
def allow_request(self) -> bool:
with self.lock:
now = time.monotonic()
elapsed = now - self.last_refill
토큰 보충
self.tokens = min(
self.capacity,
self.tokens + elapsed * self.rate
)
self.last_refill = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
def get_retry_after(self) -> float:
"""다음 토큰까지 남은 시간 (초)"""
if self.tokens >= 1:
return 0
return (1 - self.tokens) / self.rate
사용 예시
limiter = TokenBucket(rate=10, capacity=20)
rate=10: 초당 10개 토큰 생성
capacity=20: 최대 20개 토큰 저장 (버스트 20)
for i in range(25):
if limiter.allow_request():
print(f"Request {i}: ALLOWED")
else:
retry = limiter.get_retry_after()
print(f"Request {i}: DENIED (retry after {retry:.2f}s)")
8.2 Sliding Window Log
from collections import deque
class SlidingWindowLog:
"""Sliding Window Log 레이트 리미터
특징:
- 정확한 윈도우 기반 제한
- 메모리 사용량이 요청 수에 비례
- 경계 문제 없음 (Fixed Window 대비 장점)
"""
def __init__(self, max_requests: int, window_seconds: int):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.requests = deque() # 타임스탬프 로그
self.lock = threading.Lock()
def allow_request(self) -> bool:
with self.lock:
now = time.monotonic()
window_start = now - self.window_seconds
윈도우 밖의 오래된 요청 제거
while self.requests and self.requests[0] < window_start:
self.requests.popleft()
if len(self.requests) < self.max_requests:
self.requests.append(now)
return True
return False
def get_remaining(self) -> int:
with self.lock:
now = time.monotonic()
window_start = now - self.window_seconds
while self.requests and self.requests[0] < window_start:
self.requests.popleft()
return max(0, self.max_requests - len(self.requests))
사용 예시
limiter = SlidingWindowLog(max_requests=100, window_seconds=60)
60초 동안 최대 100개 요청
8.3 Fixed Window Counter
from collections import defaultdict
class FixedWindowCounter:
"""Fixed Window Counter 레이트 리미터
특징:
- 메모리 효율적 (카운터만 저장)
- 윈도우 경계에서 2배 버스트 가능 (단점)
- 구현이 단순함
"""
def __init__(self, max_requests: int, window_seconds: int):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.counters = defaultdict(int)
self.lock = threading.Lock()
def _get_window_key(self) -> int:
return int(time.time() // self.window_seconds)
def allow_request(self, client_id: str = "global") -> bool:
with self.lock:
window = self._get_window_key()
key = f"{client_id}:{window}"
이전 윈도우 정리
old_keys = [
k for k in self.counters
if not k.endswith(f":{window}")
]
for k in old_keys:
del self.counters[k]
if self.counters[key] < self.max_requests:
self.counters[key] += 1
return True
return False
8.4 분산 환경 레이트 리밋 (Redis)
class DistributedRateLimiter:
"""Redis 기반 분산 레이트 리미터 (Sliding Window Counter)"""
def __init__(self, redis_client: redis.Redis, prefix: str = "rl"):
self.redis = redis_client
self.prefix = prefix
def is_allowed(
self,
key: str,
max_requests: int,
window_seconds: int
) -> dict:
"""
Sliding Window Counter (Redis Sorted Set 사용)
"""
now = time.time()
window_start = now - window_seconds
redis_key = f"{self.prefix}:{key}"
pipe = self.redis.pipeline()
1. 윈도우 밖의 오래된 항목 제거
pipe.zremrangebyscore(redis_key, 0, window_start)
2. 현재 윈도우의 요청 수 조회
pipe.zcard(redis_key)
3. 현재 요청 추가
pipe.zadd(redis_key, {f"{now}:{id(object())}": now})
4. TTL 설정 (윈도우 크기 + 여유)
pipe.expire(redis_key, window_seconds + 1)
results = pipe.execute()
current_count = results[1]
if current_count < max_requests:
remaining = max_requests - current_count - 1
return {
"allowed": True,
"remaining": max(0, remaining),
"reset_at": int(now + window_seconds),
"limit": max_requests,
}
else:
방금 추가한 요청 제거
self.redis.zrem(redis_key, f"{now}:{id(object())}")
return {
"allowed": False,
"remaining": 0,
"reset_at": int(now + window_seconds),
"limit": max_requests,
"retry_after": window_seconds,
}
9. 요청/응답 변환 및 캐싱
9.1 Request/Response Transformation
Kong Request Transformer 예시
plugins:
- name: request-transformer
config:
add:
headers:
- "X-Gateway-Version:2.0"
- "X-Request-Start:$(now)"
querystring:
- "format:json"
rename:
headers:
- "X-Old-Header:X-New-Header"
remove:
headers:
- "X-Internal-Debug"
replace:
headers:
- "Host:internal-api.company.com"
- name: response-transformer
config:
add:
headers:
- "X-Response-Time:$(latency)"
- "X-RateLimit-Remaining:$(rate_limit_remaining)"
remove:
headers:
- "Server"
- "X-Powered-By"
- "X-Backend-Server"
replace:
headers:
- "Content-Security-Policy:default-src 'self'"
9.2 캐싱 전략
API Gateway 캐싱 전략:
1. Response Cache (응답 캐싱)
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 클라이언트│────▶│ Gateway │────▶│ Backend │
│ │ │ Cache │ │ │
│ │◀────│ (Hit!) │ │ │
└──────────┘ └──────────┘ └──────────┘
Cache Key = Method + Path + Query + Selected Headers
TTL: GET /products → 5분, GET /products/123 → 1분
2. 캐시 무효화 전략:
- TTL 기반: 시간이 지나면 자동 만료
- Event 기반: 데이터 변경 시 캐시 퍼지
- Stale-While-Revalidate: 만료 후에도 캐시 반환, 백그라운드 갱신
3. Cache-Control 헤더:
Cache-Control: public, max-age=300, s-maxage=600
ETag: "v1-product-123-hash"
Vary: Accept, Authorization
Kong Proxy Cache 설정
plugins:
- name: proxy-cache
config:
strategy: memory
memory:
dictionary_name: cache_dict
response_code:
- 200
- 301
request_method:
- GET
- HEAD
content_type:
- "application/json"
- "text/html"
cache_ttl: 300
vary_headers:
- Accept
- Accept-Encoding
vary_query_params:
- page
- limit
- sort
cache_control: true
storage_ttl: 600
10. 서킷 브레이커 및 카나리 배포
10.1 서킷 브레이커 패턴
Circuit Breaker 상태:
┌──────────┐ 실패율 초과 ┌──────────┐
│ CLOSED │──────────────────▶│ OPEN │
│ (정상) │ │ (차단) │
└──────────┘ └────┬─────┘
▲ │
│ 타임아웃 후 │
│ 성공 ┌─────────┴──┐
└────────────────────│ HALF-OPEN │
│ (테스트) │
└────────────┘
│
실패 시 → 다시 OPEN
Envoy Circuit Breaker 설정
clusters:
- name: order_service
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 1024 # 최대 동시 연결
max_pending_requests: 512 # 대기 중 최대 요청
max_requests: 2048 # 최대 동시 요청
max_retries: 3 # 최대 재시도
track_remaining: true
outlier_detection:
consecutive_5xx: 5 # 연속 5xx 5회 시 제거
interval: 10s # 분석 주기
base_ejection_time: 30s # 기본 제거 시간
max_ejection_percent: 50 # 최대 제거 비율
success_rate_minimum_hosts: 3 # 통계에 필요한 최소 호스트
success_rate_request_volume: 100 # 통계에 필요한 최소 요청
success_rate_stdev_factor: 1900 # 표준편차 기반 제거
10.2 카나리/A-B 라우팅
Envoy Weighted Routing (카나리 배포)
route_config:
virtual_hosts:
- name: api
domains: ["api.company.com"]
routes:
- match:
prefix: "/api/v1/products"
route:
weighted_clusters:
clusters:
- name: product_v1
weight: 90
- name: product_v2
weight: 10
retry_policy:
retry_on: "5xx"
num_retries: 2
Header 기반 A/B 라우팅
- match:
prefix: "/api/v1/checkout"
headers:
- name: "X-Feature-Flag"
exact_match: "new-checkout"
route:
cluster: checkout_v2
- match:
prefix: "/api/v1/checkout"
route:
cluster: checkout_v1
Kong Canary Release 플러그인
plugins:
- name: canary
config:
start: 0 # 시작 비율 (%)
target: 100 # 목표 비율 (%)
steps: 10 # 단계 수
duration: 3600 # 전환 시간 (초)
upstream_host: "product-v2:8080"
upstream_port: 8080
hash: "consumer" # consumer/ip/header 기반 일관된 라우팅
11. GraphQL Gateway
11.1 Apollo Router / Federation
GraphQL Federation Architecture:
[클라이언트]
│
┌────┴────────────────────────┐
│ Apollo Router │
│ (Supergraph Gateway) │
│ │
│ ┌───────────────────────┐ │
│ │ Query Planner │ │
│ │ → 어떤 서브그래프에 │ │
│ │ 어떤 쿼리를 보낼지 │ │
│ │ 최적 계획 수립 │ │
│ └───────────────────────┘ │
└──────┬──────┬──────┬────────┘
│ │ │
┌────┴──┐ ┌┴────┐ ┌┴────────┐
│ User │ │Order│ │ Product │
│Subgraph│ │Sub- │ │Subgraph │
│ │ │graph│ │ │
└───────┘ └─────┘ └──────────┘
User Subgraph Schema
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
orders: [Order]
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User]
}
Apollo Router 설정
router.yaml
supergraph:
listen: 0.0.0.0:4000
headers:
all:
request:
- propagate:
named: "Authorization"
- propagate:
named: "X-Request-ID"
traffic_shaping:
all:
timeout: 30s
subgraphs:
users:
timeout: 10s
orders:
timeout: 15s
limits:
max_depth: 15
max_height: 200
max_aliases: 30
max_root_fields: 20
telemetry:
exporters:
metrics:
prometheus:
enabled: true
listen: 0.0.0.0:9090
path: /metrics
tracing:
otlp:
enabled: true
endpoint: http://otel-collector:4317
cors:
origins:
- https://app.company.com
methods:
- GET
- POST
allow_headers:
- Authorization
- Content-Type
ratelimit:
global:
capacity: 1000
interval: 1m
12. API 버저닝 전략
12.1 버저닝 방식 비교
3가지 API 버저닝 전략:
1. URL Path Versioning
GET /api/v1/users
GET /api/v2/users
→ 가장 직관적, 널리 사용
→ 캐싱 친화적
→ URL이 바뀜 (Breaking)
2. Header Versioning
GET /api/users
Accept: application/vnd.company.v2+json
→ URL 깔끔
→ 브라우저에서 테스트 어려움
3. Query Parameter Versioning
GET /api/users?version=2
→ 간단
→ 캐싱 복잡
→ 쿼리 파라미터 오염
12.2 Gateway 레벨 버저닝 구현
Kong Route 기반 버저닝
services:
- name: user-service-v1
url: http://user-service-v1:8080
routes:
- name: users-v1
paths:
- /api/v1/users
strip_path: false
- name: user-service-v2
url: http://user-service-v2:8080
routes:
- name: users-v2
paths:
- /api/v2/users
strip_path: false
Header 기반 버저닝
- name: user-service-v2-header
url: http://user-service-v2:8080
routes:
- name: users-v2-header
paths:
- /api/users
headers:
X-API-Version:
- "2"
strip_path: false
디폴트 (최신 안정 버전)
- name: user-service-stable
url: http://user-service-v1:8080
routes:
- name: users-default
paths:
- /api/users
strip_path: false
13. 모니터링 및 옵저버빌리티
13.1 Prometheus 메트릭
API Gateway 핵심 메트릭
1. 4 Golden Signals
golden_signals:
latency:
- histogram: api_request_duration_seconds
labels: [method, route, status_code]
buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
traffic:
- counter: api_requests_total
labels: [method, route, status_code, consumer]
errors:
- counter: api_errors_total
labels: [method, route, error_type]
saturation:
- gauge: api_active_connections
- gauge: api_rate_limit_remaining
- gauge: api_circuit_breaker_state
2. 레이트 리밋 메트릭
rate_limiting:
- counter: api_rate_limit_hits_total
labels: [consumer, plan, route]
- gauge: api_rate_limit_remaining
labels: [consumer, route]
3. 캐시 메트릭
caching:
- counter: api_cache_hits_total
- counter: api_cache_misses_total
- gauge: api_cache_size_bytes
4. 업스트림 메트릭
upstream:
- histogram: api_upstream_latency_seconds
labels: [service, status]
- counter: api_upstream_errors_total
labels: [service, error_type]
- gauge: api_upstream_healthy_count
labels: [service]
Grafana 대시보드 쿼리 예시
panels:
- title: "Request Rate (RPS)"
query: |
sum(rate(api_requests_total[5m])) by (route)
- title: "P99 Latency"
query: |
histogram_quantile(0.99,
sum(rate(api_request_duration_seconds_bucket[5m])) by (le, route)
)
- title: "Error Rate"
query: |
sum(rate(api_requests_total{status_code=~"5.."}[5m]))
/
sum(rate(api_requests_total[5m]))
* 100
- title: "Rate Limit Hits"
query: |
sum(rate(api_rate_limit_hits_total[5m])) by (consumer)
- title: "Circuit Breaker Status"
query: |
api_circuit_breaker_state
13.2 분산 트레이싱
API Gateway 분산 트레이싱 흐름:
Request ID: abc-123-def
[Client] ─────────── [API Gateway] ──────── [User Service] ──── [DB]
│ │ │ │
│ Span: client-req │ Span: gateway │ Span: user-svc│ Span: db-query
│ trace_id: abc123 │ trace_id: abc123 │ trace_id: abc123
│ span_id: span-1 │ span_id: span-2 │ span_id: span-3
│ parent: none │ parent: span-1 │ parent: span-2
│ duration: 250ms │ duration: 200ms │ duration: 150ms
│ │ │
│ Tags: │ Tags: │ Tags:
│ http.method: GET │ gateway.auth: jwt │ db.type: postgres
│ http.url: /users │ gateway.cache: miss │ db.statement: SELECT
│ http.status: 200 │ consumer: user-123 │ db.duration: 50ms
14. 퀴즈
**답:**
1. **Routing Pattern (라우팅):** 클라이언트 요청을 URL 경로, 헤더 등을 기반으로 올바른 백엔드 서비스로 전달합니다.
2. **Aggregation Pattern (집약):** 여러 백엔드 서비스의 응답을 하나로 합쳐서 클라이언트에게 단일 응답으로 반환합니다. BFF(Backend for Frontend) 패턴과 관련됩니다.
3. **Offloading Pattern (오프로딩):** 인증, SSL 종료, 캐싱, 레이트 리밋 등 공통 관심사를 개별 서비스에서 Gateway로 이전하여 서비스가 비즈니스 로직에 집중할 수 있게 합니다.
**답:**
- **Kong:** 범용 API Gateway가 필요할 때. 풍부한 플러그인 생태계, DB-less 모드 지원. 인증/레이트 리밋/변환 등이 플러그인으로 즉시 사용 가능합니다.
- **Envoy:** Service Mesh의 데이터 플레인이나 사이드카 프록시로 사용할 때. xDS API를 통한 동적 설정, WASM 필터로 커스텀 확장. Istio와 통합 시 최적입니다.
- **AWS API Gateway:** AWS Lambda 기반 서버리스 아키텍처에서. 인프라 관리 불필요, Usage Plans과 API Key 관리가 내장되어 있습니다.
- **Traefik:** Docker/Kubernetes 환경에서 자동 서비스 발견이 필요할 때. 라벨/어노테이션만으로 라우팅 설정이 가능하고, Let's Encrypt 자동 인증서 관리를 지원합니다.
**답:**
- **Token Bucket:** 일정 속도로 토큰이 충전되고, 요청마다 토큰을 소비합니다. 버킷이 가득 차면 일시적인 버스트가 허용됩니다. 메모리 효율적이고 구현이 간단합니다.
- **Sliding Window Log:** 각 요청의 타임스탬프를 기록하고, 현재 시점에서 윈도우 크기만큼 이전까지의 요청을 카운트합니다. Fixed Window의 경계 문제(윈도우 시작/끝에서 2배 버스트)가 없어 정확하지만, 메모리 사용량이 요청 수에 비례합니다.
실무에서는 Redis Sorted Set을 사용한 Sliding Window Counter가 정확성과 메모리 효율의 균형이 좋아 널리 사용됩니다.
**답:**
GraphQL Federation은 여러 서비스가 각자의 GraphQL 스키마(서브그래프)를 정의하고, 이를 하나의 통합 스키마(슈퍼그래프)로 조합하는 아키텍처입니다.
**장점:**
- 서비스 자율성: 각 팀이 자신의 도메인 스키마를 독립적으로 관리
- 단일 엔드포인트: 클라이언트는 하나의 GraphQL 엔드포인트만 사용
- 타입 확장: 서비스 간 타입을 확장하여 연결 (User 타입에 Orders 필드 추가 등)
**Apollo Router:**
- 클라이언트 쿼리를 분석하여 어떤 서브그래프에 어떤 쿼리를 보낼지 Query Plan을 수립합니다.
- 각 서브그래프의 응답을 자동으로 합쳐 클라이언트에게 반환합니다.
- 레이트 리밋, 인증, 트레이싱, 캐싱 등 게이트웨이 기능을 제공합니다.
**답:** Google SRE에서 제안한 4가지 Golden Signal입니다:
1. **Latency (지연 시간):** 요청 처리에 걸리는 시간. P50, P95, P99 백분위 추적이 중요합니다.
2. **Traffic (트래픽):** 초당 요청 수(RPS). 엔드포인트별, 컨슈머별 트래픽 패턴을 파악합니다.
3. **Errors (에러율):** 전체 요청 대비 에러 비율. 5xx 서버 에러와 4xx 클라이언트 에러를 구분합니다.
4. **Saturation (포화도):** 시스템 자원 사용률. 동시 연결 수, 레이트 리밋 잔여량, 서킷 브레이커 상태 등입니다.
15. 참고 자료
1. [Kong Documentation](https://docs.konghq.com/)
2. [Envoy Proxy Documentation](https://www.envoyproxy.io/docs/envoy/latest/)
3. [AWS API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/)
4. [Traefik Documentation](https://doc.traefik.io/traefik/)
5. [Apollo Router Documentation](https://www.apollographql.com/docs/router/)
6. [GraphQL Federation Specification](https://www.apollographql.com/docs/federation/)
7. [NGINX Rate Limiting](https://www.nginx.com/blog/rate-limiting-nginx/)
8. [Token Bucket Algorithm (Wikipedia)](https://en.wikipedia.org/wiki/Token_bucket)
9. [Envoy xDS Protocol](https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol)
10. [Google SRE Book - Monitoring Distributed Systems](https://sre.google/sre-book/monitoring-distributed-systems/)
11. [OpenTelemetry Documentation](https://opentelemetry.io/docs/)
12. [Istio Service Mesh](https://istio.io/latest/docs/)
13. [RFC 6585 - Additional HTTP Status Codes (429)](https://datatracker.ietf.org/doc/html/rfc6585)
14. [Microsoft API Design Best Practices](https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design)
현재 단락 (1/1467)
마이크로서비스 아키텍처에서는 수십~수백 개의 서비스가 독립적으로 운영됩니다. 각 서비스마다 인증, 로깅, 레이트 리밋 등을 개별 구현하면 중복과 불일치가 발생합니다.