Flagger + Istio A/B 라우팅: New Relic NRQL로 전환율을 배포 게이팅 기준으로 연동하기
배포는 더 이상 "올리고 지켜보는" 행위가 아니다. 현대적인 프로덕션 환경에서 릴리스는 가설 검증의 순간이며, 신버전이 실제로 비즈니스 지표를 개선하는지 데이터로 증명해야 한다. 그러나 에러율과 지연시간만으로 배포 성패를 판단하는 팀이 여전히 많다. 사용자가 더 많은 상품을 구매했는지, 세션을 더 오래 유지했는지는 Prometheus 대시보드에서는 보이지 않는다.
이 글을 읽고 나면, 전환율이 2.5% 아래로 떨어지는 순간 사람 개입 없이 자동 롤백되는 파이프라인을 오늘 프로덕션에 구성할 수 있다. Flagger의 MetricTemplate에 New Relic NRQL 쿼리를 연결하면, 전환율·세션 길이 같은 비즈니스 KPI를 배포 자동화의 게이팅 조건으로 직접 활용할 수 있다. 이 파이프라인이 가져오는 변화는 기술적인 것을 넘어선다. SRE는 Canary CR의 thresholdRange를 기준으로 PM과 대화하게 되고, PM이 합의한 "전환율 2.5% 이상"이라는 기준이 자동화 파이프라인에 직접 반영된다는 사실을 Git 커밋으로 확인할 수 있다. 인프라 엔지니어링과 프로덕트 분석이 같은 선언적 구성 파일 위에서 만나는 것이다.
이 글에서는 Flagger Canary CR 작성, Istio VirtualService 구조 이해, NRQL 사전 검증, New Relic MetricTemplate 구성까지 전체 파이프라인을 단계별로 다룬다. 시작 전 아래 선행 조건이 충족되어 있는지 확인하자.
선행 조건
- Kubernetes 클러스터에 Flagger 설치 완료
- Istio 서비스 메시 운영 중 (사이드카 주입 활성화)
- New Relic 계정 보유 및 Insights Query API 키 발급
- 애플리케이션에 New Relic APM 에이전트 및 Browser 에이전트 연동 완료
(PageAction,PageView이벤트가 New Relic에 수집되고 있어야 MetricTemplate이 동작한다)
핵심 개념
Flagger의 프로그레시브 딜리버리 파이프라인
Flagger는 Kubernetes 오퍼레이터로서 Canary CR(Custom Resource) 하나로 전체 A/B 테스트 파이프라인을 선언적으로 관리한다. 개발자가 Deployment 이미지를 변경하면 Flagger가 이를 감지하고 다음 흐름을 자동으로 실행한다.
Canary CR 변경 감지
→ Istio VirtualService 생성/수정 (헤더 매칭 라우팅)
→ 분석 인터벌마다 MetricTemplate으로 NRQL 쿼리 실행
→ 임계값 통과 시 배포 승인 / 초과 시 자동 롤백MetricTemplate: Flagger에서 외부 메트릭 공급자(New Relic, Datadog, Prometheus 등)에 쿼리를 보내기 위한 CRD. 쿼리 결과는 반드시 단일
float64값이어야 하며,Canary의metrics필드에서 임계값과 비교된다.
HTTP 헤더 기반 A/B 라우팅 vs 가중치 기반 카나리
두 전략은 목적이 다르다.
| 구분 | 가중치 기반 카나리 | HTTP 헤더 A/B 라우팅 |
|---|---|---|
| 라우팅 기준 | 트래픽 비율 (예: 10%) | 요청 헤더/쿠키 값 |
| 사용자 일관성 | 보장 어려움 | 동일 사용자 → 동일 버전 (세션 어피니티) |
| 적합한 서비스 | 백엔드 API | 프론트엔드, 결제 플로우 |
| 분석 목적 | 안정성 검증 | 비즈니스 KPI 비교 |
헤더 기반 방식은 x-user-group: beta 헤더를 가진 사용자만 신버전으로 라우팅한다. API Gateway나 BFF에서 특정 사용자 세그먼트에 이 헤더를 주입하면, 베타 유저 집단이 일관되게 신버전을 경험하며 정확한 비교 실험이 가능해진다.
NRQL과 Flagger MetricTemplate의 연동 구조
NRQL(New Relic Query Language)은 SQL과 유사한 문법으로 New Relic의 MELT 데이터(Metrics, Events, Logs, Traces)를 쿼리한다. Flagger는 분석 인터벌마다 MetricTemplate에 정의된 NRQL을 New Relic Insights Query API로 전송하고, 반환된 단일 숫자값을 thresholdRange와 비교한다.
MELT 데이터: New Relic의 4가지 핵심 데이터 유형.
PageAction(사용자 행동 이벤트),PageView(페이지 뷰),Transaction(서버 트랜잭션),Metric(수치 측정값). 비즈니스 메트릭 분석에는 주로PageAction과PageView를 활용한다.
NRQL 쿼리에서 {{ target }}과 {{ interval }} 같은 Flagger 템플릿 변수를 사용할 수 있다. {{ target }}은 Canary의 앱 이름으로 치환되고, {{ interval }}은 해당 메트릭의 인터벌 설정값(초 단위)으로 치환된다.
메트릭 레벨 interval과 분석 주기의 관계: Canary CR의 analysis.interval: 1m은 Flagger가 메트릭을 평가하는 주기다. metrics 배열 개별 메트릭의 interval: 5m은 Flagger가 NRQL의 {{ interval }} 변수를 치환할 때 사용하는 값(300초)이다. 즉, Flagger는 여전히 1분마다 쿼리를 실행하지만, 그 쿼리의 SINCE 범위가 5분(300초)으로 설정된다. 비즈니스 메트릭처럼 단기 샘플의 변동성이 클 때, 더 넓은 집계 윈도우로 안정적인 값을 얻는 데 활용하는 패턴이다.
사전 준비 체크리스트
실전 적용 전, 다음 항목을 순서대로 확인하자.
| 항목 | 확인 방법 |
|---|---|
| Flagger 설치 | kubectl get pods -n flagger-system |
| Istio 사이드카 주입 활성화 | kubectl get ns prod --show-labels → istio-injection=enabled 확인 |
| New Relic APM 연동 | New Relic UI → APM → 앱 이름 목록에서 확인 |
| New Relic Browser 에이전트 | New Relic UI → Browser → PageView / PageAction 이벤트 수집 확인 |
| Insights Query API 키 | New Relic UI → API keys → Ingest/Query 키 생성 |
| 카나리 식별 커스텀 속성 주입 | Browser 에이전트에서 newrelic.setCustomAttribute('userGroup', 'canary') 호출 여부 확인 |
중요: 브라우저 에이전트가
PageAction과PageView이벤트를 수집하지 않으면, 전환율·세션 길이 MetricTemplate의 NRQL 쿼리가 항상null을 반환해 분석이 실패한다. 체크리스트를 모두 통과한 상태에서 다음 단계로 진행하자.
실전 적용
예시 1: Flagger Canary CR — HTTP 헤더 A/B 라우팅 전체 구성
x-user-group: beta 헤더를 가진 요청은 카나리(신버전)로, 그 외 요청은 안정 버전(primary)으로 라우팅하는 전체 구성이다. 비즈니스 메트릭 3개(에러율, 전환율, 세션 길이)를 AND 조건으로 모두 통과해야 배포가 진행된다.
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: my-app
namespace: prod
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
# 카나리 파드 시작 시간 + 첫 분석 인터벌 합산보다 충분히 크게 설정
# 120초처럼 너무 짧으면 첫 분석이 완료되기 전에 타임아웃되어 즉시 롤백됨
progressDeadlineSeconds: 300
service:
port: 80
targetPort: 8080
gateways:
- istio-system/public-gateway
hosts:
- app.example.com
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
analysis:
interval: 1m
threshold: 5 # 연속 실패 허용 횟수 초과 시 롤백
iterations: 10 # 총 10회 분석 통과 시 배포 완료
match:
- headers:
x-user-group:
exact: "beta"
- headers:
cookie:
regex: ".*canary=true.*" # 헤더 조건과 OR로 동작 (둘 중 하나만 일치해도 카나리로 라우팅)
metrics:
- name: error-rate
templateRef:
name: newrelic-error-rate
namespace: prod
thresholdRange:
max: 5 # 에러율 5% 초과 시 롤백
interval: 1m
- name: conversion-rate
templateRef:
name: newrelic-conversion-rate
namespace: prod
thresholdRange:
min: 2.5 # 전환율 2.5% 미만 시 롤백
interval: 5m # NRQL {{ interval }} 변수를 300초로 설정 (넓은 집계 윈도우)
- name: session-duration
templateRef:
name: newrelic-session-duration
namespace: prod
thresholdRange:
min: 120 # 평균 세션 120초 미만 시 롤백
interval: 5m
webhooks:
# flagger-loadtester의 실제 Service 엔드포인트로 교체하세요
# 기본값: kubectl get svc -n <loadtester-namespace> | grep flagger-loadtester
- name: load-test
url: http://flagger-loadtester.prod/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://my-app.prod/"| 필드 | 설명 |
|---|---|
progressDeadlineSeconds |
카나리 파드가 Ready 상태가 되어야 하는 최대 시간. 파드 시작 시간보다 충분히 크게 설정해야 한다 |
analysis.match |
배열의 복수 항목은 OR 조건으로 동작. 하나라도 일치하면 카나리로 라우팅 |
analysis.interval |
Flagger가 메트릭을 평가하는 주기 |
메트릭 레벨 interval |
NRQL {{ interval }} 변수 치환값. 평가 주기와 별개로 쿼리의 SINCE 집계 범위만 제어 |
thresholdRange.min |
반환값이 이 값 미만이면 실패로 판정 |
thresholdRange.max |
반환값이 이 값 초과이면 실패로 판정 |
analysis.matchOR vs AND 주의:match배열의 복수 항목(헤더 조건 A, 쿠키 조건 B)은 OR로 동작해 둘 중 하나만 일치해도 카나리로 라우팅된다. 반면 Istio VirtualService의 단일match블록 안에 여러 헤더 조건을 나열하면 AND로 평가된다. 두 구조를 혼동하지 않도록 주의하자.
예시 2: Istio VirtualService — Flagger가 자동 생성하는 라우팅 구조
Flagger는 Canary CR을 기반으로 아래 VirtualService를 자동 생성하고 관리한다. 이 파일을 직접 작성할 필요는 없지만, 구조를 이해해야 트러블슈팅과 Kiali 시각화를 올바르게 해석할 수 있다.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-app
namespace: prod
spec:
gateways:
- istio-system/public-gateway
hosts:
- app.example.com
http:
# 1순위: 헤더 매칭 → 카나리(신버전) 서비스
- match:
- headers:
x-user-group:
exact: "beta"
route:
- destination:
host: my-app-canary
port:
number: 80
weight: 100
# 2순위: 기본 트래픽 → 안정(primary) 서비스
- route:
- destination:
host: my-app-primary
port:
number: 80
weight: 100my-app-canary vs my-app-primary: Flagger는 원본 Deployment(
my-app)를my-app-primary로 복제하고, 신버전 파드를my-app-canary로 노출한다. 사용자는 이 두 Service 중 하나로 라우팅된다.
예시 3: NRQL 쿼리 사전 검증 — MetricTemplate 적용 전 필수 단계
MetricTemplate에 NRQL을 적용하기 전, New Relic Query Builder에서 아래 쿼리를 실행해 데이터 형태와 값 범위를 반드시 확인하자. 데이터가 조회되지 않으면 다음 단계로 진행하지 말고 New Relic 에이전트 연동 상태를 먼저 점검해야 한다.
-- [검증용] 전환율: userGroup 커스텀 속성 기반 그룹별 비교
-- FACET/TIMESERIES 포함 — 대시보드 시각화용이며 MetricTemplate에는 사용 불가
SELECT
filter(count(*), WHERE action = 'purchase_complete') /
filter(count(*), WHERE action = 'product_view') * 100 AS 'ConversionRate'
FROM PageAction
WHERE appName = '<your-app-name-in-newrelic>'
FACET userAttributes.userGroup
TIMESERIES 5 minutes
SINCE 1 hour ago
-- [검증용] 세션 길이: 백분위수 포함 (A/B 그룹 비교)
SELECT average(session.duration) AS 'AvgSessionSec',
percentile(session.duration, 50, 90, 99) AS 'P50/P90/P99'
FROM PageView
WHERE appName = '<your-app-name-in-newrelic>'
FACET userAttributes.userGroup
SINCE 30 minutes ago
-- [검증용] 세션당 페이지뷰 수
SELECT count(*) / uniqueCount(session) AS 'PageviewsPerSession'
FROM PageView
WHERE appName = '<your-app-name-in-newrelic>'
FACET userAttributes.userGroup
SINCE 1 hour ago
-- [검증용] 장바구니 이탈율
SELECT
filter(uniqueCount(session), WHERE action = 'cart_abandon') /
uniqueCount(session) * 100 AS 'CartAbandonRate'
FROM PageAction
WHERE appName = '<your-app-name-in-newrelic>'
FACET userAttributes.userGroup
TIMESERIES 10 minutes
SINCE 2 hours agoMetricTemplate 전환 규칙: 대시보드 쿼리에서
FACET,TIMESERIES절을 제거하고 단일 숫자를 반환하도록 수정한 뒤 적용한다.SINCE {{ interval }} seconds ago로 Flagger 인터벌과 연동한다.userAttributes.userGroup에 데이터가 없다면, 브라우저 에이전트에서newrelic.setCustomAttribute('userGroup', 'canary')를 올바르게 호출하고 있는지 확인하자.
예시 4: New Relic MetricTemplate — 전환율·세션 길이·에러율
인증 Secret과 3개의 MetricTemplate을 모두 같은 네임스페이스에 배포해야 한다. secretRef는 동일 네임스페이스의 Secret만 참조할 수 있기 때문이다.
Secret 생성 (kubectl 방식 권장)
kubectl create secret generic newrelic-credentials \
-n prod \
--from-literal=newrelic_account_id=<your-account-id> \
--from-literal=newrelic_query_key=<your-insights-query-key>YAML로 관리할 경우 stringData를 사용하면 평문으로 작성해도 Kubernetes가 자동으로 base64 인코딩한다:
apiVersion: v1
kind: Secret
metadata:
name: newrelic-credentials
namespace: prod
type: Opaque
stringData:
newrelic_account_id: "your-account-id-here"
newrelic_query_key: "your-insights-query-key-here"MetricTemplate 배포
---
# 전환율 MetricTemplate
# 전제: Browser 에이전트에서 newrelic.setCustomAttribute('userGroup', 'canary')를
# 카나리로 라우팅된 사용자에게 주입해야 이 쿼리가 동작한다
apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
name: newrelic-conversion-rate
namespace: prod
spec:
provider:
type: newrelic
secretRef:
name: newrelic-credentials
query: |
SELECT
IF(
filter(count(*), WHERE action = 'product_view') > 0,
filter(count(*), WHERE action = 'purchase_complete') /
filter(count(*), WHERE action = 'product_view') * 100,
null
)
FROM PageAction
WHERE appName = '{{ target }}'
AND userAttributes.userGroup = 'canary'
SINCE {{ interval }} seconds ago
---
# 세션 길이 MetricTemplate
# 전제: Browser 에이전트에서 newrelic.setCustomAttribute('userGroup', 'canary')를
# 카나리로 라우팅된 사용자에게 주입해야 이 쿼리가 동작한다
apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
name: newrelic-session-duration
namespace: prod
spec:
provider:
type: newrelic
secretRef:
name: newrelic-credentials
query: |
SELECT average(session.duration)
FROM PageView
WHERE appName = '{{ target }}'
AND userAttributes.userGroup = 'canary'
SINCE {{ interval }} seconds ago
---
# 에러율 MetricTemplate
# httpResponseCode >= 500: 서버 5xx 에러 기준
# 4xx도 포함하려면 >= 400으로 변경하고 팀 기준에 맞게 조정
apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
name: newrelic-error-rate
namespace: prod
spec:
provider:
type: newrelic
secretRef:
name: newrelic-credentials
query: |
SELECT
filter(count(*), WHERE httpResponseCode >= 500) /
count(*) * 100
FROM Transaction
WHERE appName = '{{ target }}'
SINCE {{ interval }} seconds ago| 항목 | 설명 |
|---|---|
userAttributes.userGroup = 'canary' |
userAgent LIKE '%beta%' 대신 커스텀 속성을 사용. 오탐 위험이 없고 New Relic 모범 사례에 부합 |
IF(...) > 0, ..., null |
분모(ProductView)가 0일 때 나눗셈 오류 방지. null 반환 시 Flagger는 해당 인터벌 평가를 건너뜀 |
httpResponseCode >= 500 |
서버 에러(5xx)만 집계. 4xx 포함 여부는 서비스 특성에 따라 팀 기준으로 결정 |
secretRef |
MetricTemplate과 동일 네임스페이스의 Secret만 참조 가능 |
카나리 식별 커스텀 속성 주입 방법: New Relic Browser 에이전트 초기화 코드에서
newrelic.setCustomAttribute('userGroup', 'canary')를 호출한다. 서버 사이드 트랜잭션은 APM 에이전트의addCustomAttribute('deploymentGroup', 'canary')API나 환경 변수NEW_RELIC_METADATA_KUBERNETES_DEPLOYMENT_NAME을 활용해 배포 그룹을 구분한다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 자동화된 배포 게이팅 | 비즈니스 메트릭이 임계값을 벗어나면 사람 개입 없이 즉시 자동 롤백 |
| 선언적 GitOps 관리 | Canary CR YAML 하나로 전체 A/B 파이프라인을 Git에서 버전 관리 |
| 비즈니스 KPI 연동 | 에러율·지연시간을 넘어 전환율·세션 길이를 직접 배포 판단 기준으로 활용 |
| 세션 어피니티 보장 | 헤더/쿠키 라우팅으로 동일 사용자가 실험 기간 내내 같은 버전을 경험 |
| 다중 메트릭 AND 검증 | Prometheus·New Relic·Datadog 결과를 AND 조건으로 묶어 다층 검증 가능 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| NRQL 단일값 제약 | MetricTemplate 쿼리는 float64 하나만 반환해야 함. TIMESERIES·다중 컬럼 반환 시 파싱 오류 |
예시 3의 Query Builder 사전 검증 단계를 반드시 수행하고 단일값 형태로 변환 |
| 데이터 수집 지연 | New Relic 이벤트 수집에 수십 초~수 분 지연 존재 | interval 최소 1m, 비즈니스 메트릭은 5m 이상 설정 |
| 트래픽 대표성 편향 | 베타 헤더 사용자가 전체를 대표하지 않을 수 있음 | QA·내부 직원 대상으로 먼저 검증 후, 점진적으로 실제 베타 사용자 집단으로 확대 |
| Istio 사이드카 오버헤드 | Envoy 프록시의 레이턴시·메모리 부담 | 고트래픽 서비스는 Istio Ambient Mesh 마이그레이션 검토 |
| Secret 네임스페이스 제약 | secretRef는 동일 네임스페이스 Secret만 참조 가능 |
멀티 네임스페이스 운영 시 External Secrets Operator 또는 Vault Agent 연동으로 중앙 관리 |
Istio Ambient Mesh: 사이드카 없이 노드 레벨 L4 프록시(ztunnel)와 네임스페이스 레벨 L7 프록시(waypoint)로 서비스 메시를 구성하는 방식. 2025년 기준 GA 단계로 진입하며 사이드카 오버헤드를 대폭 줄일 수 있다.
실무에서 가장 흔한 실수
- MetricTemplate 쿼리에
TIMESERIES나FACET을 그대로 사용: New Relic이 배열 또는 다중 행을 반환하여 Flagger가 파싱에 실패하고 분석이 중단된다. 반드시 단일 숫자를 반환하는 집계 형태로 변환해야 한다. 진단:kubectl describe canary my-app -n prod에서"unexpected type"또는"no values found"오류를 확인. progressDeadlineSeconds를 너무 짧게 설정 (예: 120초): 파드 시작에 60초가 걸리면 첫 번째 분석 인터벌(1m)이 완료되기 전에 타임아웃이 발생해 즉시 롤백된다. 파드 시작 시간 + 첫 분석 인터벌 합산보다 충분히 크게(권장 300초 이상) 설정해야 한다. 진단:kubectl describe canary my-app -n prod→Progressing단계에서"deadline exceeded"메시지 확인.analysis.interval을 너무 짧게 설정 (30초 이하): New Relic 이벤트가 아직 수집되지 않아 쿼리 결과가 0이나 null을 반환하고, 이 값이 임계값을 벗어나 불필요한 롤백이 반복된다. 진단: Query Builder에서SINCE 30 seconds ago로 쿼리 실행 시 데이터 없음 확인.- 고정 임계값을 시간대·계절성 검토 없이 설정: 전환율은 평일 오전과 주말 저녁에 자연적으로 크게 달라진다. 좁은
thresholdRange는 정상 배포를 오탐 롤백시킨다. 최소 2~4주 과거 데이터로 정상 범위를 먼저 파악하고 하한값에 충분한 여유를 둬야 한다. 진단:SINCE 4 weeks ago TIMESERIES 1 day로 시간대별 분포 확인. - 카나리 식별 커스텀 속성 없이 전체 트래픽을 집계:
userAttributes.userGroup = 'canary'조건 없이appName만으로 필터링하면 primary + canary 트래픽이 합산되어 비교 실험 값이 희석된다. 진단: Query Builder에서FACET userAttributes.userGroup으로 그룹 분리 여부를 먼저 확인하자.
마치며
Flagger + Istio + New Relic NRQL 스택은 "배포"를 "제품 실험 자동화"로 격상시키는 가장 실용적인 경로다. 에러율이 정상이어도 전환율이 떨어지면 롤백되는 파이프라인은, SRE와 PM이 동일한 YAML의 thresholdRange를 기준으로 KPI 합의를 나누게 만든다. 인프라 엔지니어링과 프로덕트 분석이 같은 선언적 구성 파일 위에서 만나는 것이다.
지금 바로 시작할 수 있는 3단계:
- New Relic Query Builder에서 NRQL 검증: 앱의
PageAction,PageView이벤트 스키마를 확인하고, 전환율 쿼리가 단일 float64를 반환하는지 확인한다 (SELECT filter(count(*), WHERE action='purchase_complete') / filter(count(*), WHERE action='product_view') * 100 FROM PageAction WHERE appName='<your-app-name-in-newrelic>' SINCE 5 minutes ago) - MetricTemplate과 Secret 배포: 검증된 NRQL을
MetricTemplateCRD에 적용하고, New Relic Insights Query Key를kubectl create secret generic newrelic-credentials -n prod --from-literal=newrelic_account_id=<account-id> --from-literal=newrelic_query_key=<query-key>로 등록한다 - Canary CR에
analysis.match와metrics연결: 기존 Deployment에 Canary CR을 적용하고kubectl describe canary my-app -n prod로 분석 진행 상태를 실시간 모니터링한다
다음 글: Flagger
webhooks에 통계적 유의성 검증 서비스를 연결해 자연 변동폭에 강건한 A/B 테스트 게이팅 레이어를 구축하는 방법
참고 자료
- Istio A/B Testing | Flagger 공식 문서
- Metrics Analysis | Flagger 공식 문서
- Istio VirtualService 공식 레퍼런스
- Istio Request Routing 공식 태스크
- NRQL Reference | New Relic 공식 문서
- App data NRQL query examples | New Relic
- New Relic Data Types (MELT)
- fluxcd/flagger GitHub 레포지토리
- Flagger Error with New Relic metrics | GitHub Issue #820
- How to Configure A/B Testing with Flagger and Flux | OneUptime
- Mastering Progressive Delivery with Istio and Flagger | Medium
- Header-Based Routing in Istio without Header Propagation | Tetrate