Cloud

무중단 배포 환경에서 버전 변경 중 발생할 수 있는 장애 대응 전략

dog-pawwer 2025. 3. 19. 01:13
반응형

배포 전략과 무중단 배포


1. 재생성 전략 (Recreate)

  • 재배포!
  • 디플로이먼트와 관련된 모든 포드를 중지 → 새로운 버전의 포드를 생성
    • 장점 : 쉽고 간편하다
    • 단점 : 서비스 다운타임이 발생한다.
      • 서비스에 영향이 있다.
    • 기능 테스트 배포에 적합하다.

2. 롤링 업데이트 전략 (Rolling Update)

  • Blue / Green 배포 방식과 유사
  • 기존 버전의 애플리케이션을 점진적으로 새로운 버전으로 교체하는 배포 방식
  • 트래픽을 기존 버전(V1.1)과 새 버전(V1.2)으로 분산하면서 배포
  • 사실 사용자 경험 측면에서는 서버가 내려갔다는 것도 못 느끼지만, 아주 희박한 환경에서의 에러가 난다면?
    • 기존 v1.1에 있던 사용자
      • 로그아웃
      • 심한 경우 500 에러
      • [그런데 결제 중이었다면?]
        • 실제 결제는 되었는데, 사용자는 v1.2로 옮겨가고 사이트에서는 재결제가 뜸
    • → ALB에서 Connection Draining(연결 지연 종료) 기술
      • 기존 V1.1에서 요청을 처리 중인 사용자가 있다면 해당 요청이 끝날 때까지 기다린다.
      • 모든 연결이 종료된 후, 완전히 전환
      • 세션이 끊길 때 대기하는 것
    • 완벽한 해결일까?
      • 기존 연결이 강제 종료되는 문제를 해결하지만, 세션 유지 문제는 해결하지 못함
      1. 세션 기반 애플리케이션 (로그인, 결제 등)은 해결되지 않음
      • Connection Draining은 진행 중인 요청만 유지해줄 뿐, 새 요청까지 보장하지 않음
      • 세션이 메모리에 저장되어 있다면, 서버 교체 시 세션이 사라질 가능성 있음
      • 결제 진행 중일 때 사용자가 새로운 버전(V1.2)으로 이동하면 세션이 끊기고 재로그인 요구
      1. 웹소켓, 장기 연결(Long-lived connections)에 취약
      • WebSocket 같은 경우, 클라이언트가 지속적으로 서버와 연결을 유지함
      • Connection Draining이 끝난 후에는 새로운 서버(V1.2)로 강제 이동해야 함
      1. 완전한 무중단 배포가 어려움
      • 기존 요청을 마친 후 서버를 종료하더라도, 세션을 공유하지 않으면 로그인 유지가 불가능
      • Sticky Session을 설정하면 해결 가능하지만, 서버가 교체되면 Sticky Session도 의미가 없음

3. 실무 해결책

  • ⇒ 실무 : Connection Draining + 세션 공유(redis 등) + Canary 배포 활용
    • 세션 공유
      • 세션을 서버에 저장하면 배포 시 세션이 사라지므로, 반드시 중앙 저장소 사용 필요
      • Redis, Memcached 같은 세션 스토리지
      • 레디스락..?
    • Canary 배포
      • 일부 사용자(예: 1~5%)만 새 버전으로 전환 후 안정성이 확인되면 전체 배포
      • 롤백 가능
      • 단점 : 리소스를 두 배로 사용… 비용 증가
    • 이중 트랜잭션 처리 → DB + 메시지 큐(Kafka, SQS)를 활용하여 트랜잭션 상태 유지
      • Idempotency Key 적용 → 중복 요청을 방지하여 결제가 두 번 이루어지지 않도록 함
      • 트랜잭션 스토리지 사용 (DynamoDB, Kafka, SQS 등)

4. 결론

  • ⇒ 그냥 이런 기능있네? 좋다! 도입하자!
  • 가 아닌 "서비스" 레이어의 WorkFlow를 생각하는 것이 아키텍쳐 / 기술 선택에서 중요하다.

해결 방식 딥다이브


https://techblog.woowahan.com/15236/

https://oliveyoung.tech/2024-06-25/oliveyoung-order-payment-part4/

https://tech.kakaopay.com/

https://techblog.woowahan.com/4886/

실무에서 많이 쓰는 배포 방식


1. Canary Deployment (카나리아 배포)

  • 일부 사용자(예: 1~5%)만 새 버전으로 전환 후 안정성이 확인되면 전체 배포
  • 롤백 가능 → 결제 오류 발생 시 즉시 이전 버전으로 되돌릴 수 있음
  • 진행 방법:
    • Ingress 컨트롤러(Nginx, Traefik)나 서비스 메시(Istio, Linkerd)를 활용
    • 트래픽을 5% → 10% → 50% → 100% 순서로 점진적 전환
## Istio 카나리아 배포 예제 (트래픽 90%: 기존 v1, 10%: 새로운 v2)
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
    - myapp.example.com
  http:
  - route:
    - destination:
        host: myapp-v1
      weight: 90
    - destination:
        host: myapp-v2
      weight: 10

2. Blue/Green Deployment

  • 기존 버전(V1)과 새로운 버전(V2)을 동시에 운영
  • 배포 완료 후 트래픽을 일괄적으로 새로운 버전(V2)로 스위칭
  • 배포 중 결제 오류 발생 시 즉시 이전 버전(V1)로 롤백 가능
  • 실무에서는 ALB나 Istio를 사용하여 Blue/Green 전환
## ALB Listener Rule을 사용하여 배포 전환 (AWS CLI)
aws elbv2 modify-listener --listener-arn <ALB_LISTENER_ARN> \
    --default-actions '[{"Type":"forward","TargetGroupArn":"<NEW_TARGET_GROUP_ARN>"}]'
  • "배포 단계에서 장애가 발생해도 기존 시스템은 그대로 유지됨"
  • 하지만 리소스를 두 배로 사용해야 하므로 비용이 증가할 수 있음

결제 문제 해결을 위한 실무적 접근 방식


1. 세션 관리 & 트랜잭션 일관성 유지

  • 세션을 서버에 저장하면 배포 시 세션이 사라지므로, 반드시 중앙 저장소 사용 필요
  • Redis, Memcached 같은 세션 스토리지 활용
  • JWT + Refresh Token 방식 사용 (Stateless한 방식)
  • Sticky Session 적용은 단기적 해결책이지만, 서버가 내려가면 의미 없음
## Kubernetes에서 Redis 기반 세션 스토리지 설정
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:latest

2. 결제 트랜잭션 안정성 확보

  • 이중 트랜잭션 처리 → DB + 메시지 큐(Kafka, SQS)를 활용하여 트랜잭션 상태 유지
  • Idempotency Key 적용 → 중복 요청을 방지하여 결제가 두 번 이루어지지 않도록 함
  • 트랜잭션 스토리지 사용 (DynamoDB, Kafka, SQS 등)
## Idempotency Key를 사용한 결제 요청 처리 (Python)
import hashlib
def generate_idempotency_key(user_id, amount, timestamp):
    return hashlib.sha256(f"{user_id}-{amount}-{timestamp}".encode()).hexdigest()

## 요청이 들어오면 중복 확인 후 처리
if not redis.exists(idempotency_key):
    process_payment()
    redis.set(idempotency_key, "processed", ex=3600)  # 1시간 동안 저장

3. 배포 중인 사용자의 요청 보호

  • ALB/NLB의 Connection Draining 활용 → 기존 연결을 즉시 끊지 않음
  • Graceful Shutdown 적용 → 현재 진행 중인 요청이 끝날 때까지 기다린 후 종료
  • WebSocket, gRPC 같은 장기 연결(LT connections) 보호
## Kubernetes에서 Graceful Shutdown 설정 (preStop Hook 사용)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 5"]  # 5초 동안 요청을 마칠 시간 부여

실무에서 많이 쓰는 결제 문제 해결 방식 정리

문제 해결 방법
세션 유지 문제 Redis/Memcached 같은 중앙 저장소 사용
배포 중 결제 요청 손실 Kafka/SQS로 트랜잭션 이벤트 저장 후 재처리
중복 결제 Idempotency Key 사용하여 중복 요청 방지
기존 사용자의 세션 끊김 Sticky Session 적용 (단기 해결), Redis 기반 세션 유지
배포 중 트래픽 이동 ALB Connection Draining + Graceful Shutdown
롤백 필요 시 Canary Deployment or Blue/Green Deployment

결론

  • 가장 많이 쓰이는 배포 방식 → Canary Deployment & Blue/Green Deployment
  • 세션 & 트랜잭션 유지 필수 → Redis 기반 세션 저장 & Idempotency Key 활용
  • 배포 중 결제 요청 손실 방지 → Kafka/SQS 사용하여 트랜잭션 상태 저장
  • 배포 중 연결 보호 → Connection Draining + Graceful Shutdown 적용
  • 실제 서비스에서는 여러 전략을 조합하여 안정성을 높임!
반응형