Kubernetes Debugging Guide
Eco² 클러스터 개요
자체 관리형 K8s 클러스터 (EKS 아님)
| 항목 |
값 |
| 마스터 노드 |
k8s-master (13.209.44.249) |
| SSH 접속 |
ssh -i ~/.ssh/sesacthon.pem ubuntu@13.209.44.249 |
| GitOps 브랜치 |
develop (ArgoCD가 바라보는 브랜치) |
| 이미지 레지스트리 |
docker.io/mng990/eco2:<app>-<env>-latest |
배포 플로우
PR → develop 머지 → ArgoCD auto-sync → 클러스터 반영
주요 Namespace
| Namespace |
컴포넌트 |
chat |
chat-api, chat-worker |
auth |
auth-api, auth-worker, ext-authz |
scan |
scan-api, scan-worker |
character |
character-api, character-grpc, character-worker |
location |
location-api, location-grpc |
rabbitmq |
RabbitMQ Cluster (eco2-rabbitmq) |
redis |
Redis Sentinel (rfr-streams, rfr-pubsub) |
postgres |
PostgreSQL |
event-router |
event-router (Redis Streams → Pub/Sub) |
sse-consumer |
sse-gateway (SSE 스트리밍) |
Istio 라우팅 구조
API Gateway
| 항목 |
값 |
| API 도메인 |
api.dev.growbin.app |
| Gateway |
istio-system/eco2-gateway |
| 인증 방식 |
Cookie (s_access) → Header (Authorization: Bearer) 변환 |
VirtualService 라우팅
| 경로 |
서비스 |
Namespace |
/api/v1/chat |
chat-api |
chat |
/api/v1/auth |
auth-api |
auth |
/api/v1/scan |
scan-api |
scan |
/api/v1/character |
character-api |
character |
/api/v1/location |
location-api |
location |
/api/v1/users |
users-api |
users |
/api/v1/images |
images-api |
images |
/api/v1/info |
info-api |
info |
/api/v1/sse |
sse-gateway |
sse-consumer |
EnvoyFilter (인증 처리)
┌─────────────────────────────────────────────────────────────────────────┐
│ Cookie → Header 변환 (cookie-to-header) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Client Request │
│ ├─ Cookie: s_access=<JWT> │
│ └─ Cookie: s_refresh=<JWT> │
│ │
│ ↓ EnvoyFilter (Lua Script) │
│ │
│ Backend Request │
│ ├─ Authorization: Bearer <JWT> │
│ └─ X-Refresh-Token: Bearer <JWT> │
│ │
└─────────────────────────────────────────────────────────────────────────┘
API 테스트 (클러스터 외부)
# JWT 토큰으로 인증된 요청
TOKEN="<JWT_TOKEN>"
curl -X POST "https://api.dev.growbin.app/api/v1/chat" \
-H "Content-Type: application/json" \
-H "Cookie: s_access=$TOKEN" \
-d '{"message":"안녕하세요!"}'
# Canary 버전 테스트 (x-canary 헤더)
curl -X POST "https://api.dev.growbin.app/api/v1/chat" \
-H "Content-Type: application/json" \
-H "Cookie: s_access=$TOKEN" \
-H "x-canary: true" \
-d '{"message":"안녕하세요!"}'
Quick Reference
클러스터 접속 (SSH)
# 마스터 노드 접속
./scripts/utilities/connect-ssh.sh master
# 워커 노드 접속
./scripts/utilities/connect-ssh.sh worker-1
./scripts/utilities/connect-ssh.sh worker-2
# 스토리지 노드 접속
./scripts/utilities/connect-ssh.sh storage
# 원격 명령 실행 (비인터랙티브)
ssh -i ~/.ssh/sesacthon.pem -o StrictHostKeyChecking=no ubuntu@<IP> "kubectl get nodes"
원격 클러스터 점검 (Claude Code용)
로컬에서 VPN 없이 클러스터에 접속해야 할 때 사용하는 절차.
# 1. 마스터 노드 IP 조회
MASTER_IP=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=k8s-master" "Name=instance-state-name,Values=running" \
--query "Reservations[].Instances[].PublicIpAddress" \
--output text --region ap-northeast-2)
# 2. SSH 래퍼 함수 (편의용)
k8s() {
ssh -i ~/.ssh/sesacthon.pem -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
ubuntu@$MASTER_IP "$@"
}
# 3. 클러스터 상태 확인
k8s "kubectl get nodes"
k8s "kubectl get pods -n <namespace> -l app=<app-name> -o wide"
# 4. 컨테이너별 상태 확인
k8s "kubectl get pod -n <ns> -l app=<app> -o jsonpath='{range .items[*].status.containerStatuses[*]}{.name}: {.state}{\"\\n\"}{end}'"
# 5. 로그 확인
k8s "kubectl logs -n <ns> deploy/<deploy-name> -c <container> --tail=50"
k8s "kubectl logs -n <ns> deploy/<deploy-name> -c <container> --previous --tail=50"
# 6. 이벤트 확인
k8s "kubectl get events -n <ns> --sort-by='.lastTimestamp' | tail -20"
CI → 클러스터 반영 플로우
PR 머지 후 새 이미지를 클러스터에 반영하는 전체 절차.
┌─────────────────────────────────────────────────────────────────────────┐
│ Image Update Flow (kubectl 방식) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. PR → develop 머지 │
│ └─▶ GitHub Actions CI 트리거 │
│ │
│ 2. CI 로그 확인 및 업데이트 대상 파악 │
│ ├─▶ gh run list --limit 5 │
│ ├─▶ gh run view <run-id> --log │
│ └─▶ 빌드된 앱 목록 확인 (chat-api, chat-worker 등) │
│ │
│ 3. ArgoCD Application 라벨로 Hard Refresh 트리거 │
│ └─▶ kubectl label application -n argocd <app> \ │
│ argocd.argoproj.io/refresh=hard --overwrite │
│ │
│ 4. Deployment 삭제 (Self-Heal 트리거) │
│ └─▶ kubectl delete deploy <name> -n <namespace> │
│ │
│ 5. Pod 재생성 확인 │
│ └─▶ kubectl rollout status deploy/<name> -n <ns> --timeout=120s │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Step 1-2: CI 로그 확인 (로컬에서)
# 최근 CI 실행 목록 확인
gh run list --limit 5
# 특정 실행의 상세 정보 및 빌드된 앱 확인
gh run view <run-id>
# 로그에서 빌드된 이미지 확인
gh run view <run-id> --log | grep -E "Successfully built|Pushing|docker.io/mng990"
# jobs 확인 (어떤 앱이 빌드되었는지)
gh run view <run-id> --json jobs --jq '.jobs[] | select(.conclusion=="success") | .name'
Step 3-5: 클러스터 반영 (마스터 노드에서)
# SSH 접속
ssh -i ~/.ssh/sesacthon.pem ubuntu@13.209.44.249
# ArgoCD Application 라벨로 Hard Refresh (빌드된 앱만)
kubectl label application -n argocd dev-api-chat argocd.argoproj.io/refresh=hard --overwrite
kubectl label application -n argocd dev-chat-worker argocd.argoproj.io/refresh=hard --overwrite
# Deployment 삭제 (Self-Heal로 새 이미지 Pull)
kubectl delete deploy chat-api -n chat
kubectl delete deploy chat-worker -n chat
# Pod 재생성 대기
kubectl rollout status deploy/chat-api -n chat --timeout=120s
kubectl rollout status deploy/chat-worker -n chat --timeout=120s
# 새 이미지 digest 확인
kubectl get pods -n chat -o jsonpath='{range .items[*]}{.metadata.name}: {.status.containerStatuses[0].imageID}{"\n"}{end}'
ArgoCD Application 이름 규칙
| 앱 |
ArgoCD Application |
Namespace |
| chat-api |
dev-api-chat |
chat |
| chat-worker |
dev-chat-worker |
chat |
| scan-api |
dev-api-scan |
scan |
| scan-worker |
dev-scan-worker |
scan |
| auth-api |
dev-api-auth |
auth |
| character-api |
dev-api-character |
character |
| location-api |
dev-api-location |
location |
왜 Deployment를 삭제하는가?
- 이미지 태그가
latest이므로 digest가 바뀌어도 태그는 동일
- ArgoCD는 매니페스트 기준이라 이미지 태그만 보면 변경 없음
- Deployment 삭제 → Self-Heal 활성화 시 ArgoCD가 재생성 → 새 이미지 Pull
Pod 상태 확인
# Pod 목록 (상태 포함)
kubectl get pods -n eco2
# Pod 상세 정보 (이벤트 포함)
kubectl describe pod <pod-name> -n eco2
# Pod 로그
kubectl logs <pod-name> -n eco2 --tail=100 -f
# 이전 컨테이너 로그 (재시작된 경우)
kubectl logs <pod-name> -n eco2 --previous
일반적인 문제 진단
# CrashLoopBackOff
kubectl logs <pod-name> -n eco2 --previous
kubectl describe pod <pod-name> -n eco2 | grep -A 10 "Events:"
# ImagePullBackOff
kubectl describe pod <pod-name> -n eco2 | grep -A 5 "Image:"
# Pending 상태
kubectl describe pod <pod-name> -n eco2 | grep -A 10 "Events:"
kubectl get events -n eco2 --sort-by='.lastTimestamp'
실시간 디버깅
# Pod 내부 쉘 접속
kubectl exec -it <pod-name> -n eco2 -- /bin/sh
# 특정 컨테이너 접속 (multi-container)
kubectl exec -it <pod-name> -c <container-name> -n eco2 -- /bin/sh
# 임시 디버그 컨테이너
kubectl debug -it <pod-name> -n eco2 --image=busybox
Eco² 라벨 정책
핵심 라벨
| 라벨 |
값 |
용도 |
app |
서비스명 |
Pod selector |
domain |
auth, chat, scan, character, location |
서비스 도메인 |
tier |
business-logic, worker, integration, data |
아키텍처 계층 |
version |
v1, v2 |
Canary/Istio 라우팅 |
environment |
dev, prod |
환경 구분 |
라벨 기반 조회 (자주 사용)
# 계층별 조회
kubectl get pods -l tier=business-logic -A # API 서비스
kubectl get pods -l tier=worker -A # 워커
kubectl get pods -l tier=integration -A # Event Router, SSE
kubectl get pods -l tier=data -A # Redis, PostgreSQL
# 도메인별 조회
kubectl get pods -l domain=chat -A # Chat 관련 전체
kubectl get pods -l domain=scan -A # Scan 관련 전체
kubectl get pods -l domain=character -A # Character 서비스
# Canary 배포 확인
kubectl get pods -l version=v2 -A # Canary 버전만
# 환경별 조회
kubectl get pods -l environment=dev -A
kubectl get pods -l environment=prod -A
Eco² 컴포넌트
| 컴포넌트 |
Namespace |
tier |
domain |
| chat-api |
chat |
business-logic |
chat |
| chat-worker |
chat |
worker |
chat |
| event-router |
event-router |
integration |
event-router |
| sse-gateway |
sse-consumer |
integration |
sse |
| character-api |
character |
business-logic |
character |
| location-api |
location |
business-logic |
location |
| scan-api |
scan |
business-logic |
scan |
| scan-worker |
scan |
worker |
scan |
| auth-api |
auth |
business-logic |
auth |
Reference Files
디버깅
인프라/배포
자주 사용하는 명령어
# 전체 리소스 상태
kubectl get all -n eco2
# 최근 이벤트
kubectl get events -n eco2 --sort-by='.lastTimestamp' | tail -20
# 리소스 사용량
kubectl top pods -n eco2
# ConfigMap/Secret 확인
kubectl get configmap -n eco2
kubectl get secret -n eco2