문제가 발생했다. 결국 휴먼 에러가 컸지만, 개발 구축 배포를 모두 하다보니 문제를 잡기가 쉬운 상황이 아니었다.
로컬 환경에서는 완벽하게 작동하던 암호화폐 자동매매 시스템이 AWS EC2로 배포 되면서 마주친 장애물들 이 있었다.
로컬에서는 되는데 클라우드에선 왜 안됨 ㅡㅡ!
의 전형적인 문제였고 나의 문제였다. 각각이 서로 다른 계층의 문제였기에 체계적인 접근이 필요했다.
로컬 개발 환경에서는 Poetry로 의존성을 관리하고, 배포 환경에서는 Docker Compose를 사용하려다 발생한 복합적인 문제.
해결방법 local과 product 환경 모두 docker compose로 통합하고 git으로 형상관리 진행
# 문제가 된 코드 패턴
from dotenv import load_dotenv # Poetry 환경에서 추가됨
load_dotenv() # Docker 환경에서는 불필요
ACCESS_KEY = os.getenv('UPBIT_ACCESS_KEY') # None 반환
메모리 오버헤드: Poetry 런타임과 가상환경이 컨테이너당 80-100MB를 추가로 점유하여 t2.micro의 1GB RAM에서는 5개 서비스 운영이 불가능.
환경변수 충돌: Docker Compose의 env_file
과 Poetry의 load_dotenv()
가 충돌하며, 컨테이너 내부에서 환경변수가 제대로 로드되지 않았음.
빌드 캐시 무효화: Poetry의 pyproject.toml
과 Docker의 requirements.txt
가 서로 다른 의존성 버전을 참조하여 빌드 시마다 전체 재설치가 발생했.
1단계: 역할 분리
# 개발 단계 - Poetry로 의존성 고정
poetry export -f requirements.txt --output requirements.txt --without-hashes
2단계: Multi-stage Docker 빌드
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
FROM python:3.12-slim AS production
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
# Poetry 런타임 제거로 메모리 절약
3단계: 환경변수 통일
# load_dotenv() 제거
# Docker Compose의 env_file만 사용
ACCESS_KEY = os.getenv('UPBIT_ACCESS_KEY') # 정상 동작
4단계: docker compose 환경을 통일 그냥 다 엎어버리고 docker compose 환경으로 통일했다. 아주 속이 시원했다. local에서 test하고 git으로 push 한 뒤 ec2에서 clone하고 docker compose up --build -d 하니까 뚝딱 됐다. 같은 환경이 최고다.
로컬 환경에서는 완벽하게 동작하던 Upbit API 호출이 EC2 환경에서 계속 실패했다.
# 실패하는 API 호출
try:
balance = upbit.get_balances()
print(balance)
except Exception as e:
print(f"API 호출 실패: {e}")
# EC2에서만 "Unauthorized" 오류 발생
1차 시도: 인증 정보 검증
# API 키 존재 여부 확인
print(f"Access Key exists: {bool(os.getenv('UPBIT_ACCESS_KEY'))}")
print(f"Secret Key exists: {bool(os.getenv('UPBIT_SECRET_KEY'))}")
# 모두 True로 확인됨
2차 시도: 네트워크 연결 테스트
# EC2에서 Upbit API 서버 연결 확인
curl -I https://api.upbit.com/v1/market/all
# HTTP/1.1 200 OK - 네트워크는 정상
3차 시도: API 응답 상세 분석
import requests
response = requests.get('https://api.upbit.com/v1/accounts',
headers={'Authorization': f'Bearer {jwt_token}'})
print(f"Status Code: {response.status_code}")
print(f"Response: {response.text}")
# "IP address verification failed" 메시지 발견
문제: Upbit은 보안상 API 사용 시 허용된 IP 주소에서만 호출을 허용한다. 로컬 개발 환경의 IP는 등록되어 있었지만, EC2의 공인 IP는 등록되지 않았다.
해결:
# 해결 후 정상 동작
balance = upbit.get_balances()
krw_balance = [b for b in balance if b['currency'] == 'KRW'][0]
print(f"원화 잔고: {krw_balance['balance']} KRW")
Streamlit 대시보드를 8501 포트에서 실행했으나, 외부에서 http://공인IP:8501
접속이 불가능.
1단계: 애플리케이션 레벨 확인
# EC2 내부에서 로컬 접속 테스트
ubuntu@ec2:~$ curl http://localhost:8501
... # 정상 응답
2단계: 네트워크 바인딩 확인
# 포트 리스닝 상태 확인
ubuntu@ec2:~$ sudo ss -tulpn | grep 8501
tcp LISTEN 0 4096 0.0.0.0:8501 0.0.0.0:* users:(("docker-proxy",pid=15320,fd=7))
# 0.0.0.0으로 바인딩됨 - 정상
3단계: 운영체제 방화벽 확인
# Ubuntu 방화벽 상태 확인
ubuntu@ec2:~$ sudo ufw status
Status: inactive # 방화벽 비활성화 - 문제 없음
4단계: Docker 포트 매핑 확인
# Docker 컨테이너 포트 확인
ubuntu@ec2:~$ docker compose ps
PORTS: 0.0.0.0:8501->8501/tcp # 포트 매핑 정상
5단계: AWS 보안 그룹 확인 여기서 결정적인 문제를 발견.
문제의 핵심:
1단계: 현재 보안 그룹 확인 AWS 콘솔 → EC2 → 인스턴스 → 보안 탭에서 기본 보안 그룹만 연결되어 있음을 확인
2단계: 보안 그룹 추가
인스턴스 선택 → 작업 → 보안 → 보안 그룹 변경
→ 새로 생성한 보안 그룹(8501 포트 포함) 추가 선택
→ 저장
3단계: 즉시 적용 확인
# 브라우저에서 즉시 접속 가능
http://공인IP:8501 # Streamlit 대시보드 정상 표시
보안 그룹 관리 체크리스트:
이 세 가지 문제는 각각 다른 계층에서 발생했습니다:
각 문제는 독립적이었지만, 체계적인 디버깅 프로세스(내부 → 외부, 하위 → 상위 계층)를 통해 순차적으로 해결할 수 있었다. 특히 "로컬에서는 되는데 클라우드에서는 안 된다"는 문제의 90%는 네트워크 설정이나 환경 차이에서 비롯되므로, 환경별 차이점을 체계적으로 점검하는 것이 핵심.
앞으로 천천히 잘 확인하자. 뭔가 이상하다 싶으면 commit 하고 rollback 하고 다시 시작하자.
그게 제일 빠르다.