문제 상황#

  • API 요청 시 503 Service Unavailable 에러
  • Istio 로그: no_healthy_upstream
  • 파드는 Running 상태인데 서비스 연결 안 됨

원인 분석#

Service selector 라벨과 Pod 라벨 불일치

# 엔드포인트 확인 - <none>이면 문제!
kubectl -n staging-webs get endpoints api-gateway
NAME          ENDPOINTS   AGE
api-gateway   <none>      34h   # ← 파드 IP가 없음!

동작 원리#

┌─────────────────────────────────────────────────────────┐
│  Service (api-gateway)                                  │
│  selector:                                              │
│    app: staging-api-gateway                             │
│    app.kubernetes.io/part-of: staging-webs  ← 필수!     │
└─────────────────────────────────────────────────────────┘
                         │
                         ▼ selector 라벨 가진 파드 검색
┌─────────────────────────────────────────────────────────┐
│  Endpoints (자동 생성)                                   │
│  addresses:                                             │
│    - 10.0.20.100 (파드1 IP)                             │
│    - 10.0.20.101 (파드2 IP)                             │
└─────────────────────────────────────────────────────────┘
                         │
                         ▼ 트래픽 로드밸런싱
┌─────────────────────────────────────────────────────────┐
│  Pod (라벨이 selector와 매칭되어야 함)                    │
│  labels:                                                │
│    app: staging-api-gateway           ✓                 │
│    app.kubernetes.io/part-of: staging-webs  ✓           │
└─────────────────────────────────────────────────────────┘

핵심: Service selector의 모든 라벨이 Pod에 있어야 Endpoints에 등록됨

진단 방법#

1. 엔드포인트 확인#

kubectl -n <namespace> get endpoints <service-name>

# 정상: IP 있음
NAME          ENDPOINTS         AGE
api-gateway   10.0.20.100:8085  34h

# 문제: <none>
NAME          ENDPOINTS   AGE
api-gateway   <none>      34h

2. Service selector 확인#

kubectl -n <namespace> get svc <service-name> -o yaml | grep -A 10 "selector:"

3. Pod 라벨 확인#

kubectl -n <namespace> get pod <pod-name> --show-labels

4. 라벨 비교#

# Service selector
selector:
  app: staging-api-gateway
  app.kubernetes.io/component: backend
  app.kubernetes.io/instance: staging-api-gateway
  app.kubernetes.io/name: java-service
  app.kubernetes.io/part-of: staging-webs   # ← 이거 있는지 확인!

# Pod labels
app=staging-api-gateway                      ✓
app.kubernetes.io/component=backend          ✓
app.kubernetes.io/instance=staging-api-gateway ✓
app.kubernetes.io/name=java-service          ✓
app.kubernetes.io/part-of=???                ✗ ← 없으면 매칭 실패!

해결#

방법 1: 앱 재배포 (ArgoCD sync)#

# ArgoCD CLI
argocd app sync <app-name>

# kubectl로 sync 트리거
kubectl -n argocd patch application <app-name> \
  --type merge \
  -p '{"operation":{"initiatedBy":{"username":"admin"},"sync":{}}}'

방법 2: 수동 라벨 추가 (임시 조치)#

kubectl -n <namespace> label pod <pod-name> app.kubernetes.io/part-of=staging-webs

방법 3: Deployment 라벨 수정#

kubectl -n <namespace> patch deployment <deployment-name> \
  --type merge \
  -p '{"spec":{"template":{"metadata":{"labels":{"app.kubernetes.io/part-of":"staging-webs"}}}}}'

예방 방법#

  1. Helm 차트에서 selectorLabels 일관성 유지

    • Service selector와 Deployment pod template labels가 동일해야 함
  2. selectorLabels는 immutable

    • 한번 정하면 변경하지 않는 것이 좋음
    • 변경 시 Service와 Deployment 모두 업데이트 필요
  3. ArgoCD 사용 시 주의

    • 차트 수정 후 반드시 모든 관련 앱 sync
    • OutOfSync 상태 방치하지 않기

관련 명령어 모음#

# 전체 진단 한번에
NS=staging-webs
SVC=api-gateway

echo "=== Endpoints ==="
kubectl -n $NS get endpoints $SVC

echo "=== Service Selector ==="
kubectl -n $NS get svc $SVC -o jsonpath='{.spec.selector}' | jq .

echo "=== Pod Labels ==="
kubectl -n $NS get pods -l app=$SVC --show-labels

echo "=== Istio 로그 (최근 에러) ==="
kubectl -n istio-system logs -l app=istio-ingressgateway --tail=10 | grep "no_healthy"

참고#