Skip to content

Rollback Runbook

JJong-03 edited this page Mar 12, 2026 · 4 revisions

롤백 런북 (Rollback Runbook)

이 문서는 Web Deployment의 이미지 태그 롤백 절차를 정의한다. GitOps 원칙(CI_CD_GitOps)에 따라 kubectl rollout undo가 아닌 Git 기반 롤백을 표준으로 한다.

환경: kind + ingress-nginx (WSL2), Argo CD auto-sync + self-heal + prune GitOps 원칙: main 브랜치의 k8s/ 디렉터리가 Source of Truth (CI_CD_GitOps) sed 호환: 이 문서의 sed 명령은 GNU sed (WSL/Linux 기본) 기준. macOS는 gsed 사용. 최종 수정: 2026-03-03


10분 내 복구 체크리스트 (Quick Reference)

긴급 상황 시 아래만 순서대로 실행한다. 상세 설명은 각 섹션 참조.

# 1. 현재 배포 SHA 확인
CURRENT_IMAGE=$(kubectl get deploy web -n stock-backtest \
  -o jsonpath='{.spec.template.spec.containers[0].image}')
CURRENT_SHA=${CURRENT_IMAGE##*:}
echo "Current: ${CURRENT_SHA}"

# 2. 롤백 대상 SHA 선정
# 방법 A: Git promote 이력에서 선정 (권장)
git log --oneline --grep='chore(promote)' main | head -5

# 방법 B: Kubernetes rollout 이력에서 선정 (보조)
kubectl rollout history deploy/web -n stock-backtest

PREV_SHA=<선정한_7자리_SHA>

# 3. 롤백 브랜치 생성 + 이미지 태그 변경 (GNU sed 기준)
git checkout main && git pull origin main
git checkout -b rollback/${PREV_SHA}
sed -i -E "s|image: ghcr\.io/msp-architect-2026/stock-backtest:[a-f0-9]{7}|image: ghcr.io/msp-architect-2026/stock-backtest:${PREV_SHA}|g" k8s/web-deployment.yaml
sed -i "/name: WORKER_IMAGE/{n; s|value: \".*\"|value: \"ghcr.io/msp-architect-2026/stock-backtest:${PREV_SHA}\"|}" k8s/web-deployment.yaml
git add k8s/web-deployment.yaml
git commit -m "chore(rollback): revert image to ${PREV_SHA}"
git push origin rollback/${PREV_SHA}

# 4. PR 생성 + merge
gh pr create --title "chore(rollback): revert to ${PREV_SHA}" \
  --body "Rollback from ${CURRENT_SHA} to ${PREV_SHA}" \
  --base main --head "rollback/${PREV_SHA}"
# GitHub에서 PR merge (또는 gh pr merge <PR번호> --merge)

# ⚠️  merge 후 CI가 promote PR을 자동 생성한다 — 무시하거나 닫을 것 (섹션 4-C 참조)

# 5. Argo CD sync 확인 (kubectl만으로 가능, argocd CLI 불필요)
kubectl get application stock-backtest -n argocd \
  -o jsonpath='Sync={.status.sync.status} Health={.status.health.status}{"\n"}'
kubectl rollout status deploy/web -n stock-backtest --timeout=120s

# 6. 검증
kubectl get deploy web -n stock-backtest -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
kubectl port-forward svc/ingress-nginx-controller -n ingress-nginx 8080:80 &
curl -s -w "\nHTTP=%{http_code}\n" -H "Host: stock-backtest.local" http://localhost:8080/health

1. 범위

언제 롤백을 수행하는가

상황 예시 조치
새 배포 후 /health 실패 Pod CrashLoopBackOff, readinessProbe 실패 즉시 롤백
새 배포 후 기능 장애 백테스트 요청 실패, Worker Job 생성 불가 원인 분석 후 롤백 판단
이미지 문제 ImagePullBackOff, 잘못된 태그 push 즉시 롤백
DB 스키마 비호환 새 코드가 없는 컬럼 참조 롤백 + DB 수정 후 재배포

범위

  • MySQL StatefulSet, Argo CD Application 설정의 롤백은 별도 절차.
  • Worker Job 이미지는 WORKER_IMAGE 환경변수로 Web Deployment와 함께 롤백된다.
  • 이미지 태그 전략은 Rule 10, 상세는 CI_CD_GitOps 참조.

2. 전제조건 / 준비물

필수 도구

도구 확인 명령 비고
kubectl kubectl version --client 클러스터 접근 가능해야 함
git git --version main 브랜치 push 권한 필요
gh (GitHub CLI) gh --version PR 생성용 (선택: GitHub UI 대체 가능)
argocd CLI argocd version --client 선택: 긴급 롤백(4-A)에서만 사용

클러스터 상태 확인

# kind 클러스터 동작 확인
kubectl cluster-info

# stock-backtest Pod 상태
kubectl get pods -n stock-backtest

# Argo CD Application 상태 (kubectl만으로 확인)
kubectl get application stock-backtest -n argocd \
  -o jsonpath='Sync={.status.sync.status} Health={.status.health.status}{"\n"}'

Ingress 검증용 port-forward

kind 환경에서는 Ingress가 외부에 직접 노출되지 않는다. port-forward로 검증한다.

# 기존 점유 프로세스 정리 (lsof 없으면: fuser -k 8080/tcp 2>/dev/null)
lsof -ti :8080 | xargs kill 2>/dev/null

# Ingress controller를 로컬 8080으로 포워딩
kubectl port-forward svc/ingress-nginx-controller -n ingress-nginx 8080:80 &

# 이후 모든 HTTP 검증:
curl -s -H "Host: stock-backtest.local" http://localhost:8080/<path>

Argo CD UI 접근 (선택)

# Argo CD UI를 로컬 9090으로 포워딩
kubectl port-forward svc/argocd-server -n argocd 9090:443 &

# 브라우저에서 https://localhost:9090 접속 (self-signed 인증서 경고 → 무시하고 진행)
# 초기 admin 비밀번호 확인:
kubectl get secret argocd-initial-admin-secret -n argocd \
  -o jsonpath='{.data.password}' | base64 -d && echo

3. 롤백 전략

방법 A — 긴급 롤백 (Argo CD CLI/UI)

임시 조치. Git 이력 없이 즉시 클러스터 상태를 변경한다.

  • Argo CD가 이전 Git revision으로 sync하여 즉시 복구.
  • self-heal이 활성화되어 있으므로, Git을 수정하지 않으면 다음 sync 시 되돌아간다.
  • 반드시 방법 B를 후속으로 수행하여 Git 상태를 확정할 것.

방법 B — Git 기반 롤백 (권장, 표준 절차)

Git 이력에 롤백 기록이 남으며, GitOps 원칙을 준수한다.

  • rollback 브랜치에서 이미지 태그 변경 → PR 생성 → merge → Argo CD auto-sync.
  • 감사 추적(audit trail)이 Git에 남는다.

4. 롤백 절차 (Step-by-Step)

4-A. 긴급 롤백 (Argo CD — 임시 조치)

self-heal 활성 상태이므로 이 조치만으로는 영구적이지 않다. 즉시 복구가 필요할 때만 사용하고, 반드시 4-B를 후속 수행한다.

argocd CLI 사용:

# Argo CD 로그인 (port-forward 9090 사전 필요)
argocd login localhost:9090 --insecure

# 이전 정상 commit의 full SHA 확인
git log --oneline main | head -10
PREV_COMMIT_FULL=<40자리_full_SHA>

# 해당 revision으로 sync
argocd app sync stock-backtest --revision ${PREV_COMMIT_FULL}

# 결과 확인
argocd app get stock-backtest

argocd CLI 없이 (kubectl만으로):

argocd CLI가 설치되어 있지 않으면 Argo CD UI를 사용한다.

  1. kubectl port-forward svc/argocd-server -n argocd 9090:443 &
  2. 브라우저에서 https://localhost:9090 접속
  3. stock-backtest Application 선택
  4. "SYNC" → "REVISION" 필드에 이전 커밋 SHA 입력 → "SYNCHRONIZE"

후속 조치: 반드시 아래 4-B를 수행하여 Git 상태를 확정한다.


4-B. Git 기반 롤백 (권장 — 표준 절차)

Step 1: 현재 배포 SHA 확인

CURRENT_IMAGE=$(kubectl get deploy web -n stock-backtest \
  -o jsonpath='{.spec.template.spec.containers[0].image}')
CURRENT_SHA=${CURRENT_IMAGE##*:}
echo "현재 배포: ${CURRENT_SHA}"

Step 2: 롤백 대상 SHA (PREV_SHA) 선정

방법 1 — Git promote 이력 (권장)

git log --oneline --grep='chore(promote)' main | head -5

출력 예시:

6617aec chore(promote): update image to ghcr.io/msp-architect-2026/stock-backtest:78fbd6c
f857c82 chore(promote): update image to ghcr.io/msp-architect-2026/stock-backtest:135b55b
2f2a1df chore(promote): update image to ghcr.io/msp-architect-2026/stock-backtest:5ef903e

→ 현재가 78fbd6c이면 직전 정상 태그 135b55b를 선정.

방법 2 — Kubernetes rollout 이력 (보조)

kubectl rollout history deploy/web -n stock-backtest

rollout history는 클러스터에 남아 있는 ReplicaSet 기반이므로 Git 이력보다 짧을 수 있다. Git 히스토리를 우선 참조하되, 클러스터 현황 확인용으로 보조 사용한다.

방법 3 — GHCR 태그 확인

gh api /orgs/msp-architect-2026/packages/container/stock-backtest/versions \
  --jq '.[].metadata.container.tags[]' | head -10

PREV_SHA 결정 후:

PREV_SHA=<선정한_7자리_SHA>
echo "롤백 대상: ${PREV_SHA}"

Step 3: 롤백 브랜치 생성 + 이미지 태그 변경

git checkout main && git pull origin main
git checkout -b rollback/${PREV_SHA}

IMAGE="ghcr.io/msp-architect-2026/stock-backtest:${PREV_SHA}"

# container image 변경 (GNU sed)
sed -i -E "s|image: ghcr\.io/msp-architect-2026/stock-backtest:[a-f0-9]{7}|image: ${IMAGE}|g" \
  k8s/web-deployment.yaml

# WORKER_IMAGE env value 변경
sed -i "/name: WORKER_IMAGE/{n; s|value: \".*\"|value: \"${IMAGE}\"|}" \
  k8s/web-deployment.yaml

⚠️ MUST DO: sed 실행 후 반드시 git diff로 의도한 라인(WORKER_IMAGE 등)만 정확히 수정되었는지 눈으로 확인하세요.

Step 4: 변경 확인 + 커밋

# image와 WORKER_IMAGE 모두 PREV_SHA인지 확인
git diff k8s/web-deployment.yaml

git add k8s/web-deployment.yaml
git commit -m "chore(rollback): revert image to ${PREV_SHA}"
git push origin rollback/${PREV_SHA}

Step 5: PR 생성 + merge

gh pr create \
  --title "chore(rollback): revert to ${PREV_SHA}" \
  --body "$(cat <<EOF
## Rollback

- **From:** \`${CURRENT_SHA}\`
- **To:** \`${PREV_SHA}\`
- **Reason:** <롤백 사유 기재>

### Changes
- \`k8s/web-deployment.yaml\`: container image + WORKER_IMAGE reverted
EOF
)" \
  --base main \
  --head "rollback/${PREV_SHA}"

# merge
gh pr list --head "rollback/${PREV_SHA}"
gh pr merge <PR번호> --merge

Step 6: Argo CD sync 확인

# auto-sync 대기 (보통 3분 이내)
kubectl get application stock-backtest -n argocd \
  -o jsonpath='Sync={.status.sync.status} Health={.status.health.status}{"\n"}'

# Pod rollout 완료 대기
kubectl rollout status deploy/web -n stock-backtest --timeout=120s

# 배포된 이미지 확인
kubectl get deploy web -n stock-backtest \
  -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'

Step 7: Promote PR 처리

섹션 4-C 참조. merge 후 자동 생성된 promote PR을 반드시 확인한다.


4-C. Promote PR 자동 생성 주의사항

롤백 PR merge 후 CI가 자동으로 promote PR을 생성한다.

CI 파이프라인 (ci.yml)은 main에 push가 발생하면 자동으로:

  1. 테스트 실행
  2. Docker 이미지 빌드 + GHCR push
  3. promote PR 생성 (이미지 태그를 새 commit SHA로 갱신)

롤백 PR merge도 main push이므로 이 파이프라인이 동작한다. 그러나 롤백 커밋의 promote PR은 롤백한 이미지를 다시 최신으로 되돌리는 결과를 낳는다.

운영 규칙:

상황 조치
롤백 직후 생성된 promote PR merge하지 말 것. 닫거나 무시한다.
이후 새 코드 push로 생성된 promote PR 검증 후 정상 merge한다.
promote PR이 여러 개 쌓인 경우 최신 promote PR만 merge. 이전 것은 모두 닫는다.
# 열린 promote PR 확인 (방법 1: 브랜치명 기반)
gh pr list --state open --head "promote/"

# 열린 promote PR 확인 (방법 2: 제목 검색)
gh pr list --state open --search "chore(promote):"

# 불필요한 promote PR 닫기
gh pr close <PR번호> --comment "Superseded by rollback"

5. 검증 체크리스트

롤백 완료 후 아래 AC (Acceptance Criteria)를 모두 통과해야 한다.

AC1: 이미지 태그 일치

기대: Deployment, Pod, WORKER_IMAGE 모두 ...:PREV_SHA

# Deployment spec
kubectl get deploy web -n stock-backtest \
  -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'

# 실행 중인 Pod
kubectl get pods -l app=web -n stock-backtest \
  -o jsonpath='{.items[*].spec.containers[0].image}{"\n"}'

# WORKER_IMAGE 환경변수
kubectl get deploy web -n stock-backtest \
  -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="WORKER_IMAGE")].value}{"\n"}'

AC2: /health 200 OK

기대: HTTP 200 + JSON 응답

kubectl port-forward svc/ingress-nginx-controller -n ingress-nginx 8080:80 &
curl -s -w "\nHTTP=%{http_code}\n" \
  -H "Host: stock-backtest.local" http://localhost:8080/health

AC3: 백테스트 요청 수락

기대: HTTP 202 + run_id (UUID) + status="PENDING"

curl -s -X POST \
  -H "Host: stock-backtest.local" \
  -H "Content-Type: application/json" \
  -d '{
    "ticker": "AAPL.csv",
    "rule_type": "RSI",
    "params": {"period": 14, "oversold": 30, "overbought": 70},
    "start_date": "2020-01-01",
    "end_date": "2024-01-01"
  }' \
  http://localhost:8080/run_backtest | python3 -m json.tool

AC4: Status API 응답 정상

기대: HTTP 200 + run_id 포함 + statusPENDING, RUNNING, SUCCEEDED, FAILED 중 하나

비동기 K8s Job 특성상 조회 시점에 따라 PENDING이 수십 초간 유지될 수 있다. 롤백 검증의 핵심은 status API가 정상 응답하는지이며, 최종 상태 도달 여부는 부차적이다.

RUN_ID=<AC3에서_받은_run_id>

# status API 정상 응답 확인 (즉시 조회 가능)
curl -s -w "\nHTTP=%{http_code}\n" \
  -H "Host: stock-backtest.local" \
  http://localhost:8080/status/${RUN_ID} | python3 -m json.tool
# → HTTP 200 + JSON에 run_id, status 필드 존재 확인

검증 결과 표

AC 항목 기대값 Pass
AC1-a Deployment image ...:PREV_SHA [ ]
AC1-b Pod image ...:PREV_SHA [ ]
AC1-c WORKER_IMAGE env ...:PREV_SHA [ ]
AC2 GET /health HTTP 200, {"status":"healthy"} [ ]
AC3 POST /run_backtest HTTP 202, run_id 반환 [ ]
AC4 GET /status/<run_id> HTTP 200, status 필드 존재 [ ]

6. 장애/실패 시 트러블슈팅

6-1. ImagePullBackOff

증상: Pod이 ImagePullBackOff 또는 ErrImagePull 상태.

kubectl describe pod -l app=web -n stock-backtest | grep -A5 "Events:"

원인별 해결:

원인 해결
GHCR에 해당 태그 없음 태그 존재 확인: gh api /orgs/msp-architect-2026/packages/container/stock-backtest/versions --jq '.[].metadata.container.tags[]'
GHCR private + imagePullSecret 없음 아래 명령으로 생성 (섹션 8 보안 참조)
kubectl create secret docker-registry ghcr-secret \
  -n stock-backtest \
  --docker-server=ghcr.io \
  --docker-username=<GITHUB_USERNAME> \
  --docker-password=<redacted> \
  --docker-email=<redacted>

현재 public repo라면 imagePullSecret 불필요. private 전환 시 web-deployment.yaml에 imagePullSecrets: [{name: ghcr-secret}]을 추가해야 한다.


6-2. DB 접속 실패 (1045 Access denied)

증상: Web Pod 로그에 Access denied for user 또는 Can't connect to MySQL server.

# Web Pod 로그 확인
kubectl logs -l app=web -n stock-backtest --tail=30

# MySQL Pod 상태
kubectl get pods -l app=mysql -n stock-backtest

# DB 접속 테스트 (SQLAlchemy 2.0 호환)
kubectl exec -it deploy/web -n stock-backtest -- \
  python3 -c "
from sqlalchemy import text
from extensions import db
from app import create_app
app = create_app()
with app.app_context():
    with db.engine.connect() as conn:
        conn.execute(text('SELECT 1'))
    print('DB OK')
"

해결 순서:

  1. kubectl get secret web-secret -n stock-backtest → 없으면 6-3 참조
  2. kubectl get pods -l app=mysql -n stock-backtest → Ready 확인
  3. Secret 값이 placeholder가 아닌지 확인 → placeholder면 6-3 참조

6-3. Argo CD Secret 덮어쓰기 — 실제 장애 사례 (Real Incident)

발생 이력

항목 내용
발생일 2026-03-03 (Phase 4 초기 배포 중)
증상 Promote PR merge 후 DB 접속 실패 (1045 Access denied)
근본 원인 k8s/secret-template.yaml이 Argo CD sync 대상에 포함. auto-sync가 placeholder 값(changeme)으로 실제 Secret을 덮어씀.
영향 Web Pod가 MySQL에 접속 불가 → /health 실패 → 서비스 중단
해결 argocd-app.yamlsecret-template.yaml exclude 추가 → secret.yaml 수동 apply → rollout restart

발생 메커니즘

Git: k8s/secret-template.yaml (placeholder 값: changeme)
  ↓ Argo CD auto-sync
Cluster: Secret web-secret (실제 값 → placeholder로 덮어씌워짐)
  ↓
Web Pod: DB_PASSWORD=changeme → MySQL 접속 실패

복구 절차

# 1. exclude 설정 확인
grep 'exclude' k8s/argocd-app.yaml
# 기대: exclude: "{argocd-app.yaml,worker-job-template.yaml,secret-template.yaml}"

# 2. exclude 설정이 빠져 있으면 추가 후 apply
kubectl apply -f k8s/argocd-app.yaml

# 3. 실제 Secret 수동 apply (로컬 k8s/secret.yaml — Git에 없음)
kubectl apply -f k8s/secret.yaml

# 4. Web Pod 재시작
kubectl rollout restart deploy/web -n stock-backtest
kubectl rollout status deploy/web -n stock-backtest --timeout=120s

# 5. 검증
curl -s -H "Host: stock-backtest.local" http://localhost:8080/health

예방 규칙

규칙 설명
exclude 필수 secret-template.yaml은 반드시 argocd-app.yamldirectory.exclude에 포함
Git 커밋 금지 실제 Secret(k8s/secret.yaml)은 .gitignore에 등록. 절대 커밋하지 않음
auto-sync 후 확인 Argo CD sync 발생 시 kubectl get secret web-secret -n stock-backtest로 값이 정상인지 확인
수동 apply 주의 auto-sync 환경에서 kubectl apply한 리소스는 Git에 없으면 prune 대상. Secret은 exclude되어 있으므로 prune되지 않지만, exclude 설정이 빠지면 삭제될 수 있다.

6-4. Argo CD sync 실패

# Application 상태 확인 (kubectl만으로)
kubectl get application stock-backtest -n argocd \
  -o jsonpath='Sync={.status.sync.status} Health={.status.health.status}{"\n"}'

# 상세 조건 확인
kubectl get application stock-backtest -n argocd \
  -o jsonpath='{.status.conditions[*].message}{"\n"}'

# Argo CD controller 로그
kubectl logs -l app.kubernetes.io/name=argocd-application-controller -n argocd --tail=30

# 수동 sync (argocd CLI가 있는 경우)
argocd app sync stock-backtest --force

6-5. port-forward 실패

# ingress-nginx 서비스 확인
kubectl get svc -n ingress-nginx

# 기존 프로세스 정리 후 재시도 (lsof 없으면: fuser -k 8080/tcp 2>/dev/null)
lsof -ti :8080 | xargs kill 2>/dev/null
kubectl port-forward svc/ingress-nginx-controller -n ingress-nginx 8080:80 &

# 대안: Ingress 우회, web service 직접 포워딩
kubectl port-forward svc/web -n stock-backtest 8080:80 &
curl -s http://localhost:8080/health

7. 리허설 기록 템플릿

롤백 수행 시 아래 표를 복사하여 기록한다.

기본 정보

항목
날짜/시간
수행자
롤백 사유
롤백 방법 [ ] A (긴급) / [ ] B (Git 기반)
롤백 PR 번호 #
CURRENT_SHA
PREV_SHA
소요 시간 (rollout 시작~완료)

검증 결과

AC 항목 Pass 비고
AC1-a Deployment image = PREV_SHA [ ]
AC1-b Pod image = PREV_SHA [ ]
AC1-c WORKER_IMAGE = PREV_SHA [ ]
AC2 GET /health → 200 [ ]
AC3 POST /run_backtest → 202 [ ] run_id:
AC4 GET /status → 200 + status 필드 [ ]

Promote PR 처리

항목
자동 생성된 promote PR 번호 #
처리 [ ] 닫음 / [ ] 무시 / [ ] N/A

특이사항

(자유 기재)

리허설 이력

날짜 PR CURRENT → PREV 소요 시간 결과 비고
2026-03-03 (최초 작성) 78fbd6c → 135b55b - - Runbook 작성

8. 보안 원칙

이 문서에서 민감정보는 <redacted>로 표기한다. Secret 관리 정책, RBAC, 이미지 공급망 보안의 전체 사양은 Security Model 참조.

롤백 수행 시 보안 체크리스트:

항목 확인
PR/커밋에 실제 Secret 값이 포함되지 않았는가 [[Rule 7
GHCR에서 대상 이미지 태그가 존재하는가 6-1 ImagePullBackOff 참조
argocd-app.yaml에서 secret-template.yaml이 exclude되어 있는가 6-3 실제 장애 사례 참조
Private repo 전환 시 imagePullSecrets 설정이 완료되었는가 Security Model 참조

See Also

Clone this wiki locally