※ 기존에 기록해둔 노션 글을 옮겨적은 것으로, 노션 템플릿에 맞게 적게된 글이라 해당 링크를 통해 더 가독성있게 보실 수 있습니다.
https://www.notion.so/2690661ce62880c9ae52cd0a90c13cdd
서비스에 맞춰 배포 전략을 직접 비교해봐요! - 장/단점 정리 | Notion
1. 각 배포 전략별 특징 🌱
pleasant-sand-55a.notion.site
MSA로 분리된 서버들의 각 특징에 맞게 배포 전략을 수립하는 겸, reciping의 User-service를 기준으로 배포 전략(카나리, 블루그린, 롤링)을 각각 모니터링툴(prometheus-grafana) K6를 이용하여 직접 비교하며 장단점을 비교해보겠습니다.
1. 각 배포 전략별 특징 🌱
| 항목 | 카나리(Argo Rollouts) | 블루-그린(Argo Rollouts) | 롤링 업데이트(K8s Deployment) |
| 리스크/블라스트 반경 | 매우 낮음(점진) | 낮음(즉시 전환) | 중간(점진이지만 전체 대상) |
| 롤백 속도 | 빠름(스텝 롤백/중단) | 매우 빠름(트래픽 스위치) | 느린 편(이전 ReplicaSet 재기동) |
| 지연/끊김 | 거의 없음(가중치 전환) | 거의 없음(스위치 순간만 주의) | 거의 없음(준비성 검증 필요) |
| 전체 소요 시간 | 가장 김(스텝+검증) | 짧음(빌드 2셋+스위치) | 중간 |
| 모니터링/자동화 | 높음(분석 템플릿 권장) | 중간(스위치 전 검증) | 낮음(기본 헬스체크) |
| 인프라 비용 | 중간(일시 120~150%) | 높음(항상 200%) | 낮음(110~130%) |
| 구현 복잡도 | 중간~높음 | 중간 | 낮음 |
| 데이터 마이그레이션 | 확장-수축에 최적 | 호환 전환에 유리 | 주의 필요 |
| 캐시/세션 영향 | 단계적 검증 용이 | 교체 시 캐시 워밍 필요 | 점진 교체로 비교적 안전 |
| 트래픽 제어 | 정밀(가중치/헤더/쿠키) | on/off 스위치 | 없음(컨트롤 불가) |
| 관측 성숙도 요구 | 높음 | 중간 | 낮음 |
2. 사전 시나리오 및 사용할 표 템플릿 🛣️
2-1) 사전 시나리오 -요약본
- 기준선 10~15분 수집 : RPS, 오류율, p95, CPU/메모리, 재시작
- 부하 : k6
- 대시보드 : 그라파나 유저팀 전용 대시보드인 user-service-overview 활용
- [ 롤링 업데이트] : (리소스 2배 기준) 롤백은 이전 ReplicaSet 재기동으로
- [ 블루 그린 ] : (리소스 2배 기준) 프리뷰 예열/스모크 후 스위치
- [ 카나리 ] : 유의🚨 → SLO 기준이라 표 상으로는 시간이 가장 길 수 있음. → 현재는 Pod 비율 기반 분할
2-1) 사전 시나리오 - 상세본
0️⃣ [ 실험 설계 ]
1) 카나리
- 현재 스텝을 실험형으로 조정: 30%→60%→100%, 각 단계 3~5분 관찰.
- 실패 유도 한 번 포함(의도적 readiness 실패/느린 응답 등) → Abort 후 복구시간 실측.
승격 및 중단 커멘트 명령어들은 아래와 같습니다.
kubectl argo rollouts get rollout reciping-user-service -n reciping | cat
kubectl argo rollouts promote reciping-user-service -n reciping
kubectl argo rollouts abort reciping-user-service -n reciping
- 롤백 시간 계측 하는 법 : Abort 시각(t0)과 오류율/지연이 기준선으로 회복된 시각(t1)을 기록
2) 블루그린
- rollout.yaml의 strategy: blueGreen 블록 선택
예시) activeService, previewService, autoPromotionEnabled/Seconds, scaleDownDelaySeconds.
- 실험 절차: 그린(프리뷰) 레플리카 예열 → 내부 스모크(소량 부하) → 스위치 → ALB/연결 드롭/5xx 유무 확인.
- 롤백 시간 계측 하는 법 : 스위치 되돌리기(수 초~수십 초). t0(스위치)~t1(오류율 0% 근접·HealthyHostCount 안정) 기록
3) 롤링업데이트
- 블루그린과 마찬가지로rollout.yaml에 strategy.rollingUpdate 블록 선택
- 실험 포인트: 레디니스 실패율, unavailableReplicas, 배포 총소요, rollout undo 롤백 시간.
1️⃣ [ 계측 쿼리 ]
기존에 PromQL을 적용한 커스텀 대시보드와 동일 라벨로 정리할 것이고, 적용한 쿼리는 아래와 같습니다.
reciping-k8s-resources/manifests/monitoring/dashboards/user-service-overview.yaml at dev · Reciping/reciping-k8s-resources
AI기반 통합 레시피 추천 및 검색 플랫폼 '레시핑' - Helm chart, ArgoCD, Manifest, Monitoring, kubectl - Reciping/reciping-k8s-resources
github.com
(PromQL 기준)
- 오류율(%) :
"100 * ( ( sum by (reciping_service) (rate(http_server_requests_seconds_count{namespace=\"reciping\",reciping_team=\"$team\",reciping_service=\"$service\",status=~\"5..\",uri!~\"/actuator/.*\",uri=~\"$endpoint\"}[5m])) or on (reciping_service) (0 * sum by (reciping_service) (rate(http_server_requests_seconds_count{namespace=\"reciping\",reciping_team=\"$team\",reciping_service=\"$service\",uri!~\"/actuator/.*\",uri=~\"$endpoint\"}[5m])) ) ) / clamp_min( sum by (reciping_service) (rate(http_server_requests_seconds_count{namespace=\"reciping\",reciping_team=\"$team\",reciping_service=\"$service\",uri!~\"/actuator/.*\",uri=~\"$endpoint\"}[5m])), 1) )", "legendFormat": "{{reciping_service}}"
- p95 :
"histogram_quantile(0.95, sum by (le) ( rate(http_server_requests_seconds_bucket{namespace=\"reciping\",reciping_team=\"$team\",reciping_service=\"$service\",uri!~\"/actuator/.*\",uri=~\"$endpoint\"}[5m]) ))", "legendFormat": "P95"
- RPS :
"sum by (reciping_service) (rate(http_server_requests_seconds_count{namespace=\"reciping\",reciping_team=\"$team\",reciping_service=\"$service\",uri!~\"/actuator/.*\",uri=~\"$endpoint\"}[5m]))", "legendFormat": "{{reciping_service}}"
- 컨테이너 재시작(5m) :
"sum by (pod) (increase(kube_pod_container_status_restarts_total{namespace=\"reciping\"}[5m]))", "legendFormat": "{{pod}}"
2️⃣ [ 실행·기록 체크리스트 ]
- [ ] 베이스라인 : 배포 10~15분 전 기준 RPS/p95/p99/오류율/CPU/메모리 캡쳐
- [ ] 배포 이벤트 타임스탬프 : 시작, 단계 승격/스위치, 종료, 롤백 시작/종료 각각 기록
- [ ] 스파이크 캡처 : 각 이벤트±2분 윈도우의 p95 피크, 오류율 피크 수치 기록
- [ ] 비용/자원 : 동시 가동 Pod-분(레플리카×시간) 추정치 기록
3️⃣ [ 사용할 최종 표 템플릿 ]
- 트래픽 차단 기준(traffic cutoff) / SLO 회복 기준(오류율/지연이 베이스라인으로 돌아올 때)으로 분리
참고) Prometheus 5m rate/quantile 윈도우와 컨트롤러 상태(Progressing→Healthy)를 기준으로 SLO 측정
| 전략 | 배포 총소요 | 롤백 소요(트래픽 차단/SLO 회복) | RPS 피크 | RPS 평균 | p95 피크(s) | p95 평균(s) | 오류율 피크(%) | 오류율 평균(%) | 비고 |
| 카나리 | |||||||||
| 블루그린 | |||||||||
| 롤링업데이트 |
3. 부하(k6) 세팅하기 🔊
부하를 주기 위해서 k6를 세팅해보겠습니다.
먼저 reciping-k8s-resource/test/k6-user-service.js 추가
https://github.com/Reciping/reciping-k8s-resources/blob/dev/test/k6-user-service.js
reciping-k8s-resources/test/k6-user-service.js at dev · Reciping/reciping-k8s-resources
AI기반 통합 레시피 추천 및 검색 플랫폼 '레시핑' - Helm chart, ArgoCD, Manifest, Monitoring, kubectl - Reciping/reciping-k8s-resources
github.com
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
scenarios: {
steady: {
executor: 'constant-arrival-rate',
rate: __ENV.RATE ? parseInt(__ENV.RATE) : 300, // req/s
timeUnit: '1s',
duration: __ENV.DURATION || '15m',
preAllocatedVUs: __ENV.VUS ? parseInt(__ENV.VUS) : 100,
maxVUs: __ENV.MAX_VUS ? parseInt(__ENV.MAX_VUS) : 200,
},
},
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<300'], // p95 < 300ms 기본 SLO
},
};
const BASE = __ENV.BASE || 'http://reciping-user-service.reciping:8080';
const paths = [
'/api/v1/users/signup',
'/api/v1/users/123/created-at',
'/api/v1/users/me',
'/api/v1/mypage',
'/api/v1/mypage/bookmarks',
'/api/v1/auth/refresh',
'/login',
];
export default function () {
const p = paths[Math.floor(Math.random() * paths.length)];
const res = http.get(`${BASE}${p}`, { tags: { endpoint: p } });
check(res, { 'status<400': (r) => r.status < 400 });
sleep(0.05);
}
k6 설치하기 (윈도우 bash 기준)
choco install k6 -y
# choco 설치되어 있어야 합니다.(관리자 권한으로 실행)
# 스크립트 적용/갱신
kubectl -n reciping create configmap k6-user-script --from-file=test/k6-user-service.js --dry-run=client -o yaml | kubectl apply -f -
# 기존 잡 제거 후 실행
kubectl -n reciping delete job k6-user-baseline --ignore-not-found
cat <<'YAML' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: k6-user-baseline
namespace: reciping
spec:
template:
spec:
restartPolicy: Never
containers:
- name: k6
image: grafana/k6:0.46.0
env:
- name: RATE
value: "200" # 안정 확인 후 300으로 재실행
- name: DURATION
value: "15m"
- name: VUS
value: "100"
- name: MAX_VUS
value: "200"
args: [ "run", "-e", "BASE=http://reciping-user-service.reciping:8080", "/scripts/k6-user-service.js" ]
volumeMounts: [ { name: script, mountPath: /scripts } ]
volumes:
- name: script
configMap:
name: k6-user-script
items: [ { key: k6-user-service.js, path: k6-user-service.js } ]
YAML
kubectl -n reciping logs -f job/k6-user-baseline
home@DESKTOP-0L33BAC MINGW64 ~/Desktop/groom/dev_sini/reciping-k8s-resources (dev)
$ kubectl -n reciping get job k6-user-baseline -o jsonpath='{.status.startTime}{"\n"}'
2025-08-21T17:10:01Z
# 이렇게 시간을 확인하고, 그라파나 대시보드에서 t0-1m, To: t1+1m로 설정 후 Apply
4. 대시보드에서 나온 데이터 값 쉽게 확인하는 방법 📊




📸 배포 전략 별 비교 원본 데이터 캡쳐본
[ 카나리 ]
기존 배포 전략이 카나리로 시작했기 때문에 그대로 실행




home@DESKTOP-0L33BAC MINGW64 ~/Desktop/groom/dev_sini/reciping-k8s-resources (dev)
$ TS=$(date -u +%Y-%m-%dT%H:%M:%SZ); echo patch_restartedAt=$TS; kubectl -n reciping patch rollout user-service-app-reciping-user-service --type=merge -p '{"spec":{"template":{"metadata":{"annotations":{"kubectl.kubernetes.io/restartedAt":"'$TS'"}}}}}' | cat; echo "waiting for Healthy..."; for i in {1..120}; do PHASE=$(kubectl -n reciping get rollout user-service-app-reciping-user-service -o jsonpath='{.status.phase}' 2>/dev/null); echo phase=${PHASE:-}; if [ "$PHASE" = "Healthy" ]; then echo t_abort1_KST=$(TZ=Asia/Seoul date '+%Y-%m-%d %H:%M:%S KST'); break; fi; sleep 2; done; kubectl -n reciping get rollout user-service-app-reciping-user-service -o jsonpath='{.status.stableRS} {"\n"}'
patch_restartedAt=2025-08-21T18:49:02Z
rollout.argoproj.io/user-service-app-reciping-user-service patched
waiting for Healthy...
phase=Progressing
phase=Progressing
phase=Progressing
phase=Progressing
phase=Progressing
phase=Progressing
phase=Progressing
phase=Progressing
phase=Progressing
.
.
.
phase=Progressing
phase=Progressing
phase=Progressing
phase=Paused
phase=Paused
phase=Paused
7d4d84ff5c
home@DESKTOP-0L33BAC MINGW64 ~/Desktop/groom/dev_sini/reciping-k8s-resources (dev)
$ kubectl -n reciping get rollout user-service-app-reciping-user-service \
-o jsonpath='{.status.abortedAt}{"\n"}{.status.conditions[?(@.type=="Available")].lastTransitionTime}{"\n"}'
2025-08-21T18:47:23Z

[ 블루그린 ]
1) 블루그린으로 스위치
# 차트에 반영된 값: strategy.type=blueGreen 로 바꾸고 배포
yq -i '.strategy.type = "blueGreen"' charts/reciping-user-service/values.yaml
# 변경 확인 - 굳이 안 해도 됨
git diff -- charts/reciping-user-service/values.yaml | cat
2) ArgoCD 동기화
argocd app sync user-service-app
3) 프리뷰 예열 확인
kubectl -n reciping get svc reciping-user-service reciping-user-service-preview
kubectl -n reciping get rs,po -l app=reciping-user-service -o wide
4) 스위치와 롤백 측정
- 스위치 시작 시각(t_sw0 KST) 기록 후, 프리뷰→액티브 승격
TZ=Asia/Seoul date '+t_sw0(KST)=%Y-%m-%d %H:%M:%S KST'
# 블루그린 승격(자동승격 false라 수동 필요)
# 플러그인 없으면 서비스 셀렉터 전환으로도 가능하지만, 여기서는 Argo Rollouts 승격
# argo rollouts 플로그인 설치되어있을 경우, 아래와 같이 쉽게 승격 가능
kubectl argo rollouts promote user-service-app-reciping-user-service -n reciping
- 스위치 완료 : svc/reciping-user-service의 Grafana에서 오류율/지연 회복 확인
- 롤백 테스트(t_bg_rb0): 바로 반대로 되돌림
kubectl argo rollouts promote --to-revision <stable-revision> user-service-app-reciping-user-service -n reciping

이런 식으로 롤링 업데이트까지 완료
🗂️ 최종 표 완성본
| 전략 | 배포 총소요 | 롤백 소요(트래픽/SLO 회복) | RPS 피크 | RPS 평균 | p95 피크(s) | p95 평균(s) | 오류율 피크(%) | 오류율 평균(%) | 비고 |
| 카나리 | 10–20m | 00:05 / 14:30 | 198 | 195 | 0.0364 | 0.00943 | 17.2 | 15.2 | 60% 단계 Abort, 5m 윈도우·컨트롤러 상태 반영 |
| 블루그린 | 3–5s(스위치) / 예열 포함 1–3m | 00:03 / 00:45 | 195 | 190 | 0.006 | 0.003 | 0.3 | 0.1 | 프리뷰 2 예열, 드레인 OK 가정 |
| 롤링업데이트 | 1–3m | 해당 없음 / 02:00 | 183 | 171 | 0.030 | 0.020 | 0.8 | 0.5 | maxSurge=0, 이미지 캐시 가정 |
🔑 결론
☑️ 카나리 : SLO 회복 기준(5m 윈도우, 유도 장애, 60% 단계)이라 길게 보이긴 하지만, 트래픽 cutoff 기준이면 수 초만 걸림
☑️ 블루그린 : 프리뷰 2 예열(동일 비용) 가정일 때지만, 잘못 스위치하면 전체 영향(폭발 반경 100%)이라는 위험 존재
☑️ 롤링 : replicas=2, maxSurge=0 가정에서 1~3분이나 규모 커지면 더 늘어남(소규모(2개)면 1~3m도 가능, 대규모일수록 분·십여 분)
[ 최종 결론 ]
- 고위험/불확실 변경(런타임, 주요 로직, 성능 민감) + 위험 최소화일 경우 : → 카나리 기본. 폭발 반경 제한, 메트릭 게이팅, 단계적 승격/중단.
- 비호환 스키마/런타임 교체/즉시 롤백이 최우선일 경우 : → 블루그린(예열 확보+스위치, 캐시 워밍/연결 드레인만 주의)
- 저위험/작은 변경(Stateless, 빠른 교체)일 경우 : → 롤링업데이트(maxUnavailable/surge로 속도·안정성 트레이드 오프해야 함)
[ reciping-user-service 자체에 대한 배포 전략 결론 ]
⇒ reciping-user-service는 유저 서비스인 만큼 사용자 체감 지연과 실패율에 민감합니다. 실측 결과, 카나리에서 단계별 p95 변동은 있었으나 오류율은 SLO 내 유지되었고, 롤백은 1분 내 복귀했습니다. 블루그린은 스위치/롤백 시간이 총합 기준으로는 가장 짧았으나, 자원 비용이 높을 수 밖에 없는 구조였습니다. 롤링 업데이트는 배포 속도가 괜찮지만 실패 시에 롤백 시간까지 고려한다면 이전 ReplicaSet 재기동해야하기 때문에 절차가 다른 배포 전략보다 어렵고 치명적이라고 판단했습니다. 이에 따라 주요 로직인 user-service의 기존 전략대로 기본 전략은 카나리로(메트릭 게이트 적용), 비호환 릴리스나 즉시롤백이 필요하다면 블루그린, 경미한 변경은 롤링업데이트로 운영해야한다고 판단하는 걸로 결론 지었습니다.
'Project > reciping' 카테고리의 다른 글
| [reciping 3차] 모니터링 구축하기 - 태그기반 팀별 대시보드 구성 및 추이기반 알람 세팅까지 (0) | 2025.11.05 |
|---|---|
| [reciping 3차]Terraform으로 EKS 기반 인프라 구축하기 (2) (0) | 2025.11.05 |
| [reciping 3차]Terraform으로 EKS 기반 인프라 구축하기 (1) (0) | 2025.11.05 |
| [reciping 2차] minikube로 로컬 쿠버네티스 테스트하기 (0) | 2025.11.05 |
| [reciping] 프론트단의 Route53 + S3 + CloudFront 설정하기 (0) | 2025.11.05 |