왜 스모크 테스트 게이트가 필요한가

단위 테스트와 통합 테스트를 모두 통과해도 실제 배포 환경에서는 다른 문제가 터진다. 환경변수 누락, DB 커넥션 실패, 잘못된 시크릿, 이미지 태그 불일치 같은 문제는 코드 레벨 테스트로는 잡히지 않는다. 배포는 성공(exit 0)했지만 컨테이너가 부팅 직후 죽거나, 헬스체크는 통과하는데 핵심 API가 500을 반환하는 상황이 대표적이다.

스모크 테스트는 "배포된 실물"이 최소한의 기능을 수행하는지 확인하는 얕고 빠른 검증이다. 이걸 파이프라인의 게이트로 만들면, 실패 시 자동으로 롤백하거나 트래픽 전환을 막아 장애가 사용자에게 도달하기 전에 차단한다.

헬스체크와 스모크 테스트는 다르다

둘을 혼동하면 게이트가 무력화된다. 헬스체크는 프로세스 생존 여부를, 스모크 테스트는 비즈니스 경로의 동작을 본다.

항목헬스체크스모크 테스트
대상프로세스/포트실제 요청 경로
검증 깊이살아있음DB·의존성 포함 응답
실행 주체오케스트레이터배포 파이프라인
실패 시재시작배포 중단/롤백

/health가 200이라고 로그인 API가 정상이란 뜻은 아니다. 스모크 테스트는 인증, 주요 조회, 쓰기 경로 중 핵심 몇 개만 실제로 때려본다.

스모크 테스트 작성 원칙

  • 빠를 것: 전체 30초~2분 안에 끝나야 게이트로 쓸 수 있다.
  • 독립적일 것: 실행 순서·외부 데이터 상태에 의존하지 않는다.
  • 핵심만: 커버리지가 아니라 "서비스가 돌아가는가"를 본다. 5~15개면 충분하다.
  • 실환경 대상: 방금 배포한 URL을 외부에서 호출한다(블랙박스).

파이썬으로 스모크 테스트 구현

배포된 엔드포인트를 대상으로 재시도를 포함해 검증한다. 부팅 지연을 감안해 초기 대기 로직을 넣는 것이 실무 포인트다.

import sys, time, requests

BASE = sys.argv[1].rstrip("/")

def wait_ready(timeout=60):
    deadline = time.time() + timeout
    while time.time() < deadline:
        try:
            if requests.get(f"{BASE}/health", timeout=3).status_code == 200:
                return
        except requests.RequestException:
            pass
        time.sleep(2)
    raise SystemExit("service not ready")

def check(name, resp, want=200):
    ok = resp.status_code == want
    print(f"[{'OK' if ok else 'FAIL'}] {name} -> {resp.status_code}")
    return ok

wait_ready()
results = [
    check("home", requests.get(f"{BASE}/", timeout=5)),
    check("login", requests.post(f"{BASE}/api/login",
          json={"id": "smoke", "pw": "test"}, timeout=5)),
    check("items", requests.get(f"{BASE}/api/items?limit=1", timeout=5)),
]
if not all(results):
    sys.exit(1)
print("smoke passed")

CI/CD 파이프라인에 게이트로 연결

핵심은 스모크 실패가 곧 배포 실패여야 한다는 것이다. 아래는 GitHub Actions에서 배포 후 스모크를 돌리고, 실패하면 롤백까지 이어지는 구성이다.

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy new revision
        run: ./deploy.sh --env staging --tag ${{ github.sha }}

      - name: Smoke test gate
        id: smoke
        run: |
          python smoke_test.py "https://staging.example.com"

      - name: Rollback on failure
        if: failure() && steps.smoke.conclusion == 'failure'
        run: ./deploy.sh --rollback --env staging

블루/그린이나 카나리라면 스모크가 "그린 대상"만 통과한 뒤 트래픽을 전환한다. 스모크를 통과하지 못한 리비전에는 절대 실사용 트래픽이 흐르지 않게 하는 것이 게이트의 본질이다.

운영 시 주의점

  • 부작용 관리: 스모크가 쓰기 API를 호출하면 실데이터가 오염된다. 전용 테스트 계정·리소스를 쓰고, 생성한 데이터는 정리하거나 소프트 삭제 대상으로 격리한다.
  • 플래키 방지: 외부 결제·메일 같은 서드파티 의존 경로는 스모크에서 제외하거나 목(mock) 엔드포인트로 대체한다. 게이트가 자주 잘못 실패하면 팀이 게이트를 우회하기 시작한다.
  • 타임아웃 명시: 요청·전체 실행 모두 타임아웃을 걸어야 파이프라인이 멈추지 않는다.
  • 롤백 검증: 롤백 경로 자체도 정기적으로 실행해봐야 정작 필요할 때 동작한다.

스모크 게이트는 완벽한 품질 보증이 아니라 "명백한 장애를 사용자보다 먼저 발견"하는 안전장치다. 범위를 좁게, 실행은 빠르고 안정적으로 유지할 때 배포 파이프라인의 신뢰도를 실질적으로 끌어올린다.