PR을 병합했습니다. CI는 성공적으로 통과했습니다. 배포는 로그에 단 하나의 오류도 없이 완료되었습니다. 20분 후, 고객 일부에게 결제 엔드포인트가 500 오류를 반환한다는 지원 티켓이 들어오기 시작했습니다. 파이프라인에서 아무것도 실패하지 않았기 때문에 그 이유를 전혀 알 수 없습니다.
이것이 바로 카나리 테스트가 메워주는 공백입니다. 단위 테스트와 통합 테스트는 예상했던 것과 비교하여 코드를 확인합니다. 하지만 실제 환경(운영 트래픽, 실제 데이터베이스, 지난 화요일에 조용히 속도 제한을 변경한 타사 API 등)에 대해서는 코드를 확인할 수 없습니다. 카나리 테스트는 새로운 릴리스를 실제 트래픽의 작은 부분에 먼저 배포하고, 동작 방식을 관찰한 다음, 신호가 정상으로 보일 때만 롤아웃을 확대합니다. 만약 문제가 발생하더라도, 1시간 동안 100%의 사용자에게 영향을 미치는 대신 2분 동안 2%의 사용자에게만 문제가 발생합니다.
특히 API의 경우, 대시보드를 지켜보며 희망하는 것보다 더 나은 방법이 있습니다. 카나리가 가동되는 즉시 실제 테스트 스위트를 실행하여 상태 코드, 응답 스키마, 지연 시간을 확인하고, 그 결과에 따라 롤아웃을 제어할 수 있습니다. 이 가이드는 이러한 워크플로우를 안내하며, 기존 CI/CD 파이프라인 내에서 전체 작업이 실행될 수 있도록 Apidog와 그 커맨드라인 러너를 사용하여 이 모든 것을 엔드투엔드로 연결할 것입니다.
카나리 테스트는 정확히 무엇인가?
이 이름은 탄광 속 카나리에서 유래했습니다. 광부들은 유독 가스에 인간보다 훨씬 일찍 반응하기 때문에 새를 새장에 넣어 지하로 데려갔습니다. 새가 노래를 멈추면 대피했습니다. 카나리 릴리스도 같은 방식으로 작동합니다. 작고 희생 가능한 샘플이 먼저 위험을 감수하여 나머지 사용자는 그럴 필요가 없도록 합니다.

실제로는 카나리 배포는 서비스의 두 가지 버전을 동시에 실행하는 것을 의미합니다.
- Stable (스테이블): 현재 운영 버전으로, 대부분의 트래픽을 처리합니다.
- Canary (카나리): 새로운 버전으로, 적은 비율(시작 시 보통 1%~5%)의 트래픽을 처리합니다.
로드 밸런서, 서비스 메시 또는 인그레스 컨트롤러가 이들 간에 트래픽을 분할합니다. 안정적인 기준선과 비교하여 카나리의 오류율, 지연 시간 및 비즈니스 지표를 관찰합니다. 카나리가 정상적으로 작동하면, 트래픽을 점진적으로 카나리로 전환하여 100%를 처리하고 새로운 안정 버전이 됩니다. 만약 성능이 저하되면, 모든 트래픽을 다시 안정 버전으로 되돌리고, 잘못된 릴리스는 대부분의 사용자에게 도달하지 않게 됩니다.
카나리 테스트는 이 루프의 능동적인 절반입니다. 자연 트래픽이 버그를 드러내기를 기다리는 대신, 의도적인 API 요청 스위트를 카나리에 실행하고 응답을 확인합니다. 수동 모니터링은 사용자가 문제가 발생한 후에 알려주지만, 능동적인 카나리 테스트는 영향 범위가 넓어지기 전에 무엇인가 잘못되었음을 알려줍니다.
카나리 테스트 vs. 이미 수행 중인 테스트
카나리 테스트는 다른 테스트를 대체하지 않습니다. 이는 체인의 마지막 단계에 위치하며 다른 종류의 실패를 포착합니다.
| 테스트 유형 | 대상 | 감지하는 문제 | 놓치는 문제 |
|---|---|---|---|
| 단위 테스트 | 격리된 함수 | 논리 버그 | 실제 I/O 관련 모든 것 |
| 통합 테스트 | 연결된 구성 요소 | 서비스 간 계약 불일치 | 운영 환경 설정, 실제 데이터 형태 |
| 스모크 테스트 | 배포된 빌드 | "작동하는가?"와 같은 기본적인 실패 | 미묘한 동작 회귀 |
| 카나리 테스트 | 실제 인프라에서의 라이브 릴리스 | 잘못된 구성, 환경 드리프트, 성능 회귀, 부분적인 서비스 중단 | 전체 규모에서만 나타나는 버그 |
카나리 테스트가 제자리를 차지하는 이유: 운영 환경 사고의 상당 부분은 사전 프로덕션 환경에서 완전히 재현할 수 없는 것들에서 발생합니다. 누락된 환경 변수, 오래된 연결 풀 설정, 스테이징에는 있지만 운영에는 없는 데이터베이스 인덱스, 실제 인증 하에서 다르게 동작하는 다운스트림 종속성 등이 그 예입니다. 코드는 정확하지만, 코드를 둘러싼 환경이 그렇지 않은 것입니다. 카나리 테스트는 새로운 릴리스가 그러한 환경을 처음으로 만나는 순간이며, 모든 트래픽이 아닌 2%의 트래픽으로 이를 만나기를 원할 것입니다.
이것이 어디에 적합한지에 대한 더 넓은 맥락을 원한다면, CI/CD에서 API 테스트를 자동화하는 방법에 대한 저희 가이드가 상위 단계를 다루고, 스모크 테스트 vs 회귀 테스트는 카나리가 가장 많이 의존하는 두 가지 테스트 유형을 설명합니다.
카나리에서 측정할 것
카나리는 "정상"이 무엇인지 알 때만 유용합니다. 적은 수의 신호를 선택하고, 절대적인 수치가 아닌 안정적인 기준선과 카나리를 비교하십시오. 1.2%의 오류율이 서비스에 정상적일 수 있습니다. 중요한 것은 현재 카나리가 안정 버전보다 의미 있게 나쁜지 여부입니다.
네 가지 신호가 대부분의 경우를 다룹니다.
- 오류율: 5xx 응답의 비율과, 인증 변경 후 갑작스러운 401 오류 급증과 같이 발생해서는 안 되는 4xx 오류도 포함됩니다. 이것은 단일 가장 중요한 지표입니다.
- 지연 시간: 평균이 아닌 p95 및 p99 값입니다. 평균은 실제 사용자가 고통을 느끼는 느린 구간을 숨깁니다. 평균이 괜찮아 보여도 p99에서 40ms 더 느린 카나리는 경고 신호입니다.
- 응답 정확성: 응답 본문이 여전히 스키마와 일치하는가? 잘못된 형태를 반환하는 200 OK는 모니터링이 플래그하지 않기 때문에 500 오류보다 더 나쁩니다.
- 비즈니스 신호: 결제 성공, 로그인 성공, 장바구니에 항목 추가 등입니다. 이는 기술적으로 "성공적인" HTTP 응답이지만 논리적 회귀를 잡아냅니다.
처음 세 가지는 API 테스트에서 직접 확인할 수 있습니다. 이 부분을 자동화할 것입니다.
단계별 카나리 테스트 워크플로우
다음은 자동화된 API 테스트로 제어되는 카나리 롤아웃의 형태입니다. 각 단계는 파이프라인에서 실행할 수 있는 작업입니다.
- 새로운 버전을 안정 버전과 함께 카나리로 배포합니다.
- 적은 양의 트래픽(예: 5%)을 카나리로 라우팅합니다.
- 카나리 엔드포인트에 대해 자동화된 API 테스트 스위트를 실행합니다.
- 짧은 대기 기간 동안 오류율과 지연 시간을 모니터링합니다.
- 게이트: 테스트가 통과하고 지표가 예산 범위 내에 있다면 더 많은 트래픽을 전환합니다. 그렇지 않다면 롤백합니다.
- 트래픽 증량(5%에서 25%, 50%, 100%로)을 반복하고, 각 단계에서 다시 테스트합니다.
- 카나리를 안정 버전으로 승격하고 이전 버전을 폐기합니다.
주목할 만한 두 부분은 3단계(테스트 스위트)와 5단계(게이트)입니다. 이 두 가지를 올바르게 구현하면 나머지는 플랫폼이 이미 제공하는 기반 작업에 불과합니다.
Apidog에서 테스트 스위트 구축
테스트 스위트는 카나리 테스트의 핵심이며, 대부분의 팀이 여기서 지름길을 택하는 부분입니다. 단순히 /health를 핑하고 200 응답을 확인하는 카나리 "테스트"는 프로세스가 시작되었음을 알려줄 뿐입니다. 실제 엔드포인트가 작동하는지 여부에 대해서는 아무것도 알려주지 않습니다.
실제 카나리 스위트는 중요한 경로를 실행합니다: 인증, 읽기, 쓰기, 그리고 각 응답 형태 검증. Apidog의 테스트 시나리오는 이러한 요청들을 연결하고, 데이터를 주고받으며, 글루 코드(glue code)를 작성하지 않고도 결과에 대해 확인할 수 있게 해줍니다.
전자상거래 API를 위한 견고한 카나리 시나리오는 다음과 같습니다.
- 1단계, 인증. 테스트 계정으로
POST /auth/login을 호출합니다.200을 확인하고, 응답에서 토큰을 변수로 추출합니다. - 2단계, 읽기. 토큰을 사용하여
GET /products?limit=10을 호출합니다.200을 확인하고, 응답이 배열인지 확인하며, 각 항목에id,name,price가 있는지 확인합니다. - 3단계, 쓰기. 알려진 제품으로
POST /cart를 호출합니다.201을 확인하고, 반환된 장바구니 총액이 예상 값과 일치하는지 확인합니다. - 4단계, 상태 확인.
GET /cart를 호출합니다. 방금 추가한 항목이 존재하는지 확인합니다.
Apidog에서는 각 요청을 한 번만 구축한 다음 시각적으로 확인을 추가합니다. 스키마 검사의 경우, 이미 설계한 OpenAPI 스키마에 대해 응답을 검증할 수 있으므로, 변경된 응답 본문은 자동으로 테스트를 실패하게 합니다. 인증 토큰 전달을 위해, 1단계 응답에서 토큰을 추출하고 이후 단계에서 변수로 참조할 수 있습니다. 일반적인 경우에는 스크립팅이 필요 없으며, 사용자 지정 로직이 필요할 때 JavaScript 후처리기를 사용할 수 있습니다.
그 결과, 이 동일한 시나리오는 하나의 정의에서 세 가지 방식으로 실행됩니다: 구축하는 동안 수동으로, 라이브 상태가 되면 합성 모니터링으로 예약 실행, 그리고 카나리 파이프라인 내에서 커맨드 라인을 통해 실행됩니다. 확인은 한 번만 작성하면 됩니다.
커맨드 라인에서 스위트 실행하기
배포를 제어하려면 CI에서 스위트를 헤드리스(headless)로 실행해야 합니다. Apidog은 정확히 이를 위한 CLI를 제공합니다. 빌드 에이전트에 설치하세요.
npm install -g apidog-cli
Apidog에서 테스트 시나리오 데이터를 CLI 형식 파일로 내보내거나, 접근 토큰을 사용하여 ID로 시나리오를 러너에 지정한 다음 실행하십시오.
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t "$CANARY_SCENARIO_ID" \
-e "$CANARY_ENV_ID" \
-r cli,html,junit
카나리 작업에 알아두면 좋은 몇 가지 플래그:
-t, --test-scenario는 ID로 특정 시나리오를 실행합니다. 전체 시나리오 폴더를 실행하려면-f, --test-scenario-folder를 사용하세요.-e, --environment는 런타임 환경을 선택합니다. 기본 URL이 카나리 엔드포인트인 환경을 지정하여, 하나의 값만 변경함으로써 동일한 테스트가 카나리, 스테이징 또는 운영 환경에 대해 실행될 수 있도록 합니다.-r, --reporters는 출력을 제어합니다.cli는 콘솔에 출력하고,html은 공유 가능한 보고서를 생성하며,junit는 GitHub Actions, GitLab, Jenkins 및 대부분의 CI 대시보드가 각 테스트의 통과/실패를 보여주기 위해 기본적으로 구문 분석하는 XML을 내보냅니다.-d, --iteration-data는 CSV 또는 JSON 파일의 각 행에 대해 스위트를 한 번 실행합니다. 한 번의 실행으로 여러 사용자 프로필 또는 제품 ID로 카나리를 테스트하는 데 유용합니다.--upload-report는 실행 요약을 Apidog으로 푸시하여 팀이 앱에서 카나리 기록을 볼 수 있도록 합니다.
확인에 실패하면 CLI는 0이 아닌 코드로 종료됩니다. 이 종료 코드가 전체 게이팅 메커니즘입니다. 파이프라인은 이미 실패한 단계에서 중단하는 방법을 알고 있으므로, 실패한 카나리 테스트는 롤아웃을 자동으로 중지합니다.
파이프라인에서 Apidog을 실행하는 방법에 대한 더 심층적인 내용은 GitHub Actions에서 API 테스트를 자동화하는 방법과 Jenkins 통합 가이드에서 해당 플랫폼들을 자세히 다룹니다.
CI/CD에 연결하기
다음은 카나리를 배포하고 테스트하며, 성공 시에만 승격하는 간소화된 GitHub Actions 작업입니다. 이 구조는 약간의 구문 변경으로 GitLab CI, CircleCI 또는 Jenkins로도 적용할 수 있습니다.
name: canary-release
on:
push:
branches: [main]
jobs:
canary:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy canary (5% traffic)
run: ./deploy.sh --canary --weight 5
- name: Install Apidog CLI
run: npm install -g apidog-cli
- name: Test the canary
run: |
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t "$CANARY_SCENARIO_ID" \
-e "$CANARY_ENV_ID" \
-r cli,junit
env:
APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}
CANARY_SCENARIO_ID: ${{ vars.CANARY_SCENARIO_ID }}
CANARY_ENV_ID: ${{ vars.CANARY_ENV_ID }}
- name: Bake and watch (2 min)
run: sleep 120 && ./check-metrics.sh --service canary --max-error-rate 1.0
- name: Promote canary to 100%
run: ./deploy.sh --promote
- name: Roll back on any failure
if: failure()
run: ./deploy.sh --rollback
이를 단순한 배포가 아닌 카나리로 만드는 논리는 순서에 있습니다. 테스트가 실행되기 전에 카나리가 트래픽의 일부를 가져가므로, 테스트는 이미 실제 요청을 처리하고 있는 릴리스에 대해 실행됩니다. if: failure() 단계는 안전망입니다. 테스트 스위트가 0이 아닌 값으로 종료되거나 지표 검사가 실패하면, 작업이 실패하고 트래픽이 5%를 넘기 전에 롤백이 실행됩니다.
CANARY_ENV_ID가 기본 URL이 카나리를 대상으로 하는 Apidog 환경을 가리키도록 유지하십시오. 나중에 동일한 스위트를 배포 후 운영 스모크 테스트로 실행하려면, 운영 환경 ID를 바꿔 넣고 다른 것은 아무것도 변경하지 않습니다. 이러한 재사용이 핵심입니다: 하나의 스위트, 여러 단계에서 사용합니다.
카나리 테스트를 무용지물로 만드는 흔한 실수
- 잘못된 엔드포인트를 테스트하는 것. 테스트가 로드 밸런싱된 공개 URL을 때리면, 요청이 카나리 대신 안정적인 인스턴스에 도달할 수 있습니다. 메시가 라우팅하는 헤더, 전용 카나리 호스트 이름 또는 기본 URL이 카나리 주소인 환경을 통해 테스트를 명시적으로 카나리로 라우팅하세요.
- 대기 기간(bake period)이 0인 경우. 일부 실패는 지속적인 부하 하에서만 나타납니다: 메모리 누수, 연결 풀 고갈, 캐시 가득 참. 테스트를 실행한 다음, 승격하기 전에 몇 분 동안 관찰하십시오. 즉시 통과하고 10초 만에 승격되는 카나리는 거의 카나리라고 할 수 없습니다.
- 자동 롤백 없음. 사람이 실패를 알아차리고 롤백을 클릭해야 한다면, 사고 대응에서 가장 느린 부분을 유지하는 것입니다. 파이프라인이 스스로 롤백하는 것이 전체 가치입니다. 롤백을 실패 조건에 연결하고 롤백이 작동하는지 테스트하십시오.
- 비교 대신 절대 임계값 사용. "오류율이 1%를 초과하면 실패"는 기준 오류율이 실제로 1.5%인 날에는 문제가 됩니다. 카나리를 현재 안정 버전과 비교하고, 몇 달 전에 선택한 숫자를 넘어설 때가 아니라 카나리가 의미 있게 더 나빠질 때 게이트를 작동시키십시오.
- 부실한 확인. 형식이 잘못된 본문을 가진 200 응답은 상태 코드만 확인하는 검사를 통과하지만 사용자에게는 실패입니다. 코드뿐만 아니라 응답 스키마에 대해서도 확인하십시오. 이는 API 계약을 먼저 설계하고 스키마에 대해 응답을 검증하는 것이 직접적으로 효과를 발휘하는 지점입니다. 카나리 테스트는 계약을 무료로 상속받습니다.
카나리는 얼마나 넓게, 그리고 얼마나 오래 유지해야 할까?
보편적인 답은 없지만, 대부분의 팀에 적용 가능한 기본값은 다음과 같습니다.
- 트래픽의 5%에서 시작. 피해를 제한할 만큼 작고, 바쁜 서비스에서 몇 분 내에 실제 신호를 얻을 만큼 충분히 커야 합니다. 트래픽이 적은 API는 충분한 요청을 모으기 위해 더 긴 기간이 필요할 수 있습니다.
- 단계적으로 증량: 5%에서 25%, 50%, 100%로. 각 단계에서 테스트 스위트를 다시 실행합니다. 5%에서는 숨어있던 회귀가 연결 풀이 포화될 때 50%에서 나타나는 경우가 있습니다.
- 각 단계마다 최소 몇 분 동안 대기. 천천히 발생하는 실패가 드러날 만큼 충분히 길고, 모든 릴리스를 1시간 동안 지연시키지 않을 만큼 충분히 짧게 유지합니다.
트래픽이 많은 서비스는 신호를 빠르게 축적하므로 더 빠르게 진행할 수 있습니다. 초당 수천 건의 요청을 처리하는 결제 API는 1분 안에 카나리를 판단할 충분한 데이터를 가집니다. 시간당 몇 건의 요청만 발생하는 내부 관리 API는 결정을 내리기 위해 더 긴 대기 시간 또는 더 많은 합성 테스트 부하가 필요합니다.
릴리스 전략에서 카나리 테스트의 위치
카나리 테스트는 기능 플래그 및 블루-그린 배포와 자연스럽게 짝을 이루며, 그 차이점을 명확히 아는 것이 중요합니다. 블루-그린은 한 전체 환경에서 다른 환경으로 모든 트래픽을 한 번에 전환합니다. 롤백은 빠르지만 점진적인 노출은 없습니다. 기능 플래그는 재배포 없이 선택된 사용자를 위한 동작을 토글합니다. 카나리 릴리스는 실제 트래픽을 점진적으로 전환하고 라이브 신호를 기반으로 제어합니다.
대부분의 성숙한 팀은 세 가지 모두를 사용합니다. 인프라 교체를 위한 블루-그린, 자동화된 게이트를 통한 점진적인 트래픽 증가를 위한 카나리, 그리고 코드가 라이브 상태일 때 세부적인 제어를 위한 기능 플래그입니다. 공통점은 이들 중 어느 것도 운영 환경에 대해 릴리스를 테스트할 필요성을 없애주지 않는다는 것입니다. 이들은 테스트를 수행하는 동안 얼마나 많은 사용자가 노출되는지를 제어합니다.
이것이 진정한 핵심입니다. 카나리 테스트는 구매하는 도구가 아니라, 하나의 원칙입니다. 작게 배포하고, 실제 확인을 통해 라이브 릴리스를 테스트하며, 신호를 관찰하고, 그 결과에 따라 롤아웃을 제어하는 것입니다. 각 단계를 자동화하기 위한 도구가 존재합니다. Apidog을 사용하면 테스트 스위트를 한 번 구축하고, 어떤 파이프라인 내에서든 CLI로 실행하며, 종료 코드가 릴리스 진행 여부를 결정하게 할 수 있습니다. 잘못된 릴리스는 5%의 트래픽에서 중단되고, 사용자는 500 오류를 전혀 보지 못하게 됩니다.
Apidog을 다운로드하여 첫 카나리 테스트 시나리오를 구축하고, 환경을 카나리 엔드포인트로 지정한 다음, 파이프라인에 CLI 단계를 하나 추가하세요. 다음 번의 잘못된 병합은 모든 요청이 아닌 소수의 요청에서만 문제가 발생할 것입니다.
FAQ (자주 묻는 질문)
- 카나리 테스트는 카나리 배포와 같은가요? 카나리 배포는 새로운 버전을 소량의 트래픽에 제공하는 릴리스 메커니즘입니다. 카나리 테스트는 그 기간 동안 대시보드만 지켜보는 대신 능동적으로 테스트를 실행하고 응답을 확인하는 작업입니다. 테스트를 수행하려면 배포가 필요하지만, 위험한 롤아웃을 제어된 롤아웃으로 바꾸는 것은 바로 테스트입니다.
- 카나리 테스트를 하려면 서비스 메시가 필요한가요? 아닙니다. Istio나 Linkerd와 같은 서비스 메시는 트래픽 분할을 더 쉽게 만들지만, 일반 로드 밸런서 가중치, 인그레스 컨트롤러의 카나리 주석, 심지어 DNS 가중치를 사용해서도 카나리를 실행할 수 있습니다. 이 가이드에서 중점적으로 다루는 워크플로우의 테스트 및 게이트 부분은 트래픽을 분할하는 방식과 관계없이 동일하게 작동합니다.
- 배포 후 스모크 테스트와는 어떻게 다른가요? 스모크 테스트는 일반적으로 완전히 배포된 릴리스에 대해 한 번 실행되어 작동 여부를 확인합니다. 카나리 테스트는 실제 트래픽의 일부만 처리하는 릴리스에 대해 실행되며, 증량 과정을 제어합니다. 확인 내용은 동일할 수 있지만, 차이점은 타이밍과 결과입니다. 실패한 스모크 테스트는 이미 모든 사용자에게 라이브 상태인 것을 롤백해야 함을 의미하고, 실패한 카나리 테스트는 5%에서 롤아웃을 중단함을 의미합니다. 스모크 테스트와 회귀 테스트 스위트의 차이점에 대해서는 저희 비교 가이드를 참조하십시오.
- 기존 API 테스트를 카나리 테스트로 재사용할 수 있나요? 대개는 그렇습니다. 실제 확인이 포함된 Apidog 테스트 시나리오가 이미 있다면, 기본 URL이 카나리인 환경을 지정하고 CLI로 실행하면 됩니다. 작업은 테스트가 상태 코드뿐만 아니라 응답 본문도 확인하도록 하고, 로드 밸런싱된 공개 URL 대신 카나리로 라우팅하는 데 있습니다.
- CI에서 카나리 테스트가 실패하면 어떻게 되나요? Apidog CLI는 확인 실패 시 0이 아닌 코드로 종료됩니다. 파이프라인은 이를 실패한 단계와 동일하게 처리합니다. 즉, 작업이 중지되고, 승격 단계가 건너뛰어지며,
if: failure()롤백 단계가 실행됩니다. 사람이 롤백이 일어나는 것을 지켜볼 필요가 없습니다.
