로컬 웹훅 테스트 환경

로컬 개발 환경에서 AWS를 이용하는 전체 웹훅 플로우를 테스트할 수 있는 환경을 구축함


개요

배경

운영 환경의 웹훅 처리 플로우:

1
Cafe24 → Django → SQS → EventBridge Pipes → Step Functions → Lambda → ElastiCache

이 플로우를 로컬에서 테스트하기 어려웠던 이유:

  • Lambda 함수가 ElastiCache(AWS)에만 접근 가능
  • LocalStack Free에서 EventBridge Pipes 미지원

해결 방법

LocalStack을 활용한 하이브리드 아키텍처:

  • 웹훅 처리 → LocalStack (Step Functions + Lambda)
  • 기타 AWS 서비스 (SQS, S3) → 실제 AWS 유지

💡 핵심 개념: LocalStack과 Endpoint URL

이 프로젝트는 하이브리드 클라우드 환경을 구성하기 위해 LocalStack과 endpoint_url 개념을 적극적으로 사용합니다.

1. LocalStack이란?

LocalStack은 내 컴퓨터(로컬 환경)에서 실행되는 AWS 클라우드 에뮬레이터입니다. 실제 AWS에 비용을 지불하거나 인터넷을 연결하지 않고도, 내 컴퓨터 안에서 S3, Lambda, DynamoDB 등의 AWS 서비스를 가짜로 띄워 테스트할 수 있게 해줍니다.

  • 이 프로젝트에서의 역할: 비용이 들고 구성이 복잡한 웹훅 처리 로직(Step Functions, Lambda)을 실제 AWS 대신 LocalStack 컨테이너 안에서 실행합니다.

2. Endpoint URL이란?

AWS SDK(Boto3 등)가 명령을 보낼 목적지 주소입니다.

  • 기본값 (Real AWS): 설정하지 않으면 https://sqs.ap-northeast-2.amazonaws.com 같은 실제 AWS 공용 주소로 요청을 보냅니다.
  • LocalStack 설정: endpoint_url을 LocalStack 주소로 강제 지정하면, 요청이 실제 AWS가 아닌 내 로컬 컨테이너로 향하게 됩니다.

3. 이 프로젝트의 하이브리드 구성 (Docker Network)

이 환경에서는 어떤 서비스냐에 따라 요청을 보내는 곳(Endpoint)을 다르게 설정합니다.

구분대상 서비스Endpoint URL 설정 값설명
실제 AWSSQS, S3None (기본값)실제 AWS 서버로 요청을 보냅니다.
LocalStackStep Functions, Lambdahttp://core-localstack:4566도커 네트워크 내부의 LocalStack 컨테이너로 요청을 가로챕니다.

⚠️ 주의: 로컬 PC(Host)에서 접속할 때는 localhost:4566을 쓰지만, 도커 컨테이너(core-backend) 내부끼리 통신할 때는 서비스명인 core-localstack:4566을 사용해야 합니다.


아키텍처

전체 구조

 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
┌─────────────────────────────────────────────────────────────────────────┐
│                           core-backend                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   [일반 AWS 작업]                    [웹훅 테스트]                        │
│   ┌─────────────────────┐           ┌─────────────────────┐             │
│   │ SQS_CLIENT          │           │ LOCALSTACK_         │             │
│   │ S3_CLIENT           │           │ STEPFUNCTIONS_      │             │
│   │ (Endpoint: Default) │           │ CLIENT              │             │
│   └──────────┬──────────┘           │ (Endpoint: Local)   │             │
│              │                      └──────────┬──────────┘             │
│              │                                 │                        │
└──────────────┼─────────────────────────────────┼────────────────────────┘
               │                                 │
               ▼                                 ▼
        ┌────────────┐                  ┌────────────────┐
        │  실제 AWS  │                  │   LocalStack   │
        │ (SQS, S3)  │                  │ (웹훅 전용)    │
        │ 계정: 341..│                  │ 계정: 000000.. │
        └────────────┘                  └────────────────┘
                                       ┌────────────────┐
                                       │  core-redis    │
                                       │ (토큰 + 결과)  │
                                       └────────────────┘

웹훅 처리 플로우 (로컬)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1. 웹훅 수신
              
              
2. Django  (views.py)
   - LOCAL=True 확인
   - SQS 대신 Step Functions 직접 호출 (endpoint_url: core-localstack)
              
              
3. LocalStack Step Functions
   - Core-System-Local-Webhook 상태 머신 실행
              
              
4. Token Lambda (LocalStack)
   - core-redis에서 OAuth 토큰 조회
   - access_token을 payload에 추가
              
              
5. 웹훅 종류별 Lambda (LocalStack)
   - Cafe24 API 호출 (실제 외부 통신)
   - 결과를 core-redis에 저장
              
              
6. 결과 확인
   docker exec core-redis redis-cli KEYS "webhook:*"

컨테이너 구성

컨테이너포트용도비고
core-backend8000Django 서버웹훅 수신
core-localstack4566AWS 에뮬레이션Step Functions, Lambda
core-redis6379OAuth 토큰 + 웹훅 결과 저장통합 Redis
core-db5432PostgreSQL기존 DB

핵심 코드 변경

1. AWS 클라이언트 분리 (core/config/aws.py)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 기본 클라이언트는 항상 실제 AWS 사용 (endpoint_url 미지정 = 실제 AWS)
_AWS_DEFAULT_ENDPOINT = "https://sqs.ap-northeast-2.amazonaws.com"
SQS_CLIENT = boto3.resource(
    "sqs",
    region_name=REGION,
    endpoint_url=_AWS_DEFAULT_ENDPOINT if AWS_ENDPOINT_URL else None,
)

# LocalStack 전용 클라이언트 (웹훅 테스트용)
# AWS_ENDPOINT_URL 환경변수가 'http://core-localstack:4566'으로 설정됨
LOCALSTACK_STEPFUNCTIONS_CLIENT = None
if LOCAL and AWS_ENDPOINT_URL:
    LOCALSTACK_STEPFUNCTIONS_CLIENT = boto3.client(
        "stepfunctions",
        region_name=REGION,
        endpoint_url=AWS_ENDPOINT_URL, # 여기서 LocalStack을 바라보게 됨
        ...
    )

3. Lambda SQS/Step Functions 호환성 (webhook-*/lambda_function.py)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def lambda_handler(event: dict, context: Any) -> None:
    # SQS 형태(Records)와 Step Functions 직접 호출(Flat JSON) 모두 처리
    if "Records" in event:
        # SQS를 통해 트리거된 경우
        body = event["Records"][0]["body"]
        if isinstance(body, str):
            body = json.loads(body)
    else:
        # Step Functions에서 직접 호출된 경우 (Local 테스트 환경 포함)
        body = event

    # platform_id와 platform_ids 모두 지원 (호환성)
    platform_id = body.get("platform_id") or body.get("platform_ids")
    ...

4. Lambda Redis SSL 분기

1
2
3
4
5
6
7
8
REDIS_SSL = os.getenv("REDIS_SSL", "true").lower() == "true"

r = Redis(
    host=CORE_REDIS_HOST,
    port=CORE_REDIS_PORT,
    ssl=REDIS_SSL,  # 로컬에서는 false
    decode_responses=True,
)

트러블슈팅

Lambda 함수가 생성되지 않음

1
2
3
4
5
6
7
8
# LocalStack 로그 확인
docker logs core-localstack | tail -50

# 수동으로 초기화 스크립트 실행
docker exec core-localstack bash /etc/localstack/init/ready.d/init.sh

# Lambda 함수 확인 (AWS CLI 사용 시 endpoint 필수)
aws --endpoint-url=http://localhost:4566 lambda list-functions

SQS 큐를 찾을 수 없음 오류

증상: The specified queue does not exist

원인: AWS_ENDPOINT_URL 환경변수가 전역으로 설정되어 boto3가 SQS 요청마저 LocalStack으로 보내버림

해결: core/config/aws.py에서 SQS_CLIENT 생성 시 endpoint_url을 명시적으로 지정하여 실제 AWS를 바라보도록 수정

1
2
3
4
5
6
SQS_CLIENT = boto3.resource(
    "sqs",
    region_name=REGION,
    # AWS_ENDPOINT_URL이 있어도 강제로 실제 AWS 주소를 사용하거나 None으로 설정
    endpoint_url="https://sqs.ap-northeast-2.amazonaws.com" if AWS_ENDPOINT_URL else None,
)

Redis 연결 오류 (Lambda)

증상: Lambda에서 Redis 연결 실패

원인: 로컬 Redis는 SSL을 사용하지 않음

해결: Lambda 환경변수에 REDIS_SSL=false 설정 (init.sh에서 자동 설정됨)

Step Functions 실행 실패

1
2
3
4
5
6
7
# 실행 이력 확인
aws --endpoint-url=http://localhost:4566 stepfunctions list-executions \
  --state-machine-arn arn:aws:states:us-east-1:000000000000:stateMachine:Core-System-Local-Webhook

# 특정 실행 상세 정보
aws --endpoint-url=http://localhost:4566 stepfunctions describe-execution \
  --execution-arn <EXECUTION_ARN>

core-backend 시작 실패 (Secrets Manager 오류)

증상: Service 'secretsmanager' is not enabled

원인: LOCAL 환경에서 Secrets Manager 호출 시도 (Endpoint가 LocalStack으로 잡혀있으나 LocalStack Free버전이거나 초기화 안됨)

해결: core/config/database.py에서 LOCAL 환경일 때 환경변수에서 직접 DB 정보 로드


관련 문서