-
Notifications
You must be signed in to change notification settings - Fork 0
Rollback Runbook
이 문서는 Web Deployment의 이미지 태그 롤백 절차를 정의한다.
GitOps 원칙(CI_CD_GitOps)에 따라 kubectl rollout undo가 아닌 Git 기반 롤백을 표준으로 한다.
- 실행 상태 머신 및 Job 규격 → Execution Lifecycle
- 증상 기반 장애 대응 → Runbook-Troubleshooting
- 보안 정책 (Secret, RBAC) → Security Model
환경: 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
긴급 상황 시 아래만 순서대로 실행한다. 상세 설명은 각 섹션 참조.
# 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| 상황 | 예시 | 조치 |
|---|---|---|
| 새 배포 후 /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 참조.
| 도구 | 확인 명령 | 비고 |
|---|---|---|
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"}'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를 로컬 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임시 조치. Git 이력 없이 즉시 클러스터 상태를 변경한다.
- Argo CD가 이전 Git revision으로 sync하여 즉시 복구.
- self-heal이 활성화되어 있으므로, Git을 수정하지 않으면 다음 sync 시 되돌아간다.
- 반드시 방법 B를 후속으로 수행하여 Git 상태를 확정할 것.
Git 이력에 롤백 기록이 남으며, GitOps 원칙을 준수한다.
- rollback 브랜치에서 이미지 태그 변경 → PR 생성 → merge → Argo CD auto-sync.
- 감사 추적(audit trail)이 Git에 남는다.
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-backtestargocd CLI 없이 (kubectl만으로):
argocd CLI가 설치되어 있지 않으면 Argo CD UI를 사용한다.
kubectl port-forward svc/argocd-server -n argocd 9090:443 &- 브라우저에서
https://localhost:9090접속 -
stock-backtestApplication 선택 - "SYNC" → "REVISION" 필드에 이전 커밋 SHA 입력 → "SYNCHRONIZE"
후속 조치: 반드시 아래 4-B를 수행하여 Git 상태를 확정한다.
CURRENT_IMAGE=$(kubectl get deploy web -n stock-backtest \
-o jsonpath='{.spec.template.spec.containers[0].image}')
CURRENT_SHA=${CURRENT_IMAGE##*:}
echo "현재 배포: ${CURRENT_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-backtestrollout history는 클러스터에 남아 있는 ReplicaSet 기반이므로 Git 이력보다 짧을 수 있다. Git 히스토리를 우선 참조하되, 클러스터 현황 확인용으로 보조 사용한다.
방법 3 — GHCR 태그 확인
gh api /orgs/msp-architect-2026/packages/container/stock-backtest/versions \
--jq '.[].metadata.container.tags[]' | head -10PREV_SHA 결정 후:
PREV_SHA=<선정한_7자리_SHA>
echo "롤백 대상: ${PREV_SHA}"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 등)만 정확히 수정되었는지 눈으로 확인하세요.
# 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}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# 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"}'섹션 4-C 참조. merge 후 자동 생성된 promote PR을 반드시 확인한다.
롤백 PR merge 후 CI가 자동으로 promote PR을 생성한다.
CI 파이프라인 (ci.yml)은 main에 push가 발생하면 자동으로:
- 테스트 실행
- Docker 이미지 빌드 + GHCR push
- 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"롤백 완료 후 아래 AC (Acceptance Criteria)를 모두 통과해야 한다.
기대: 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"}'기대: 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기대: 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기대: HTTP 200 + run_id 포함 + status가 PENDING, 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 필드 존재 | [ ] |
증상: 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}]을 추가해야 한다.
증상: 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')
"해결 순서:
-
kubectl get secret web-secret -n stock-backtest→ 없으면 6-3 참조 -
kubectl get pods -l app=mysql -n stock-backtest→ Ready 확인 - Secret 값이 placeholder가 아닌지 확인 → placeholder면 6-3 참조
| 항목 | 내용 |
|---|---|
| 발생일 | 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.yaml에 secret-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.yaml의 directory.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 설정이 빠지면 삭제될 수 있다. |
# 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# 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롤백 수행 시 아래 표를 복사하여 기록한다.
| 항목 | 값 |
|---|---|
| 날짜/시간 | |
| 수행자 | |
| 롤백 사유 | |
| 롤백 방법 | [ ] 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 번호 | # |
| 처리 | [ ] 닫음 / [ ] 무시 / [ ] N/A |
(자유 기재)
| 날짜 | PR | CURRENT → PREV | 소요 시간 | 결과 | 비고 |
|---|---|---|---|---|---|
| 2026-03-03 | (최초 작성) | 78fbd6c → 135b55b | - | - | Runbook 작성 |
이 문서에서 민감정보는 <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 참조 |
- CI_CD_GitOps — 전체 배포 흐름, Image Tag Strategy, GitOps 전략
- Runbook-Troubleshooting — 증상 기반 장애 대응 (run_id triage)
- Execution Lifecycle — 상태 머신, Job Lifecycle Policy
- Security Model — Secret 관리, RBAC, 이미지 공급망 보안
- Reproducibility — 재현성 식별자, 이미지 태그와 재현성의 관계
- Rule 7 (Secret), Rule 10 (Immutable Image Tags)