Message Queue 트러블슈팅: RabbitMQ 구축

RabbitMQ Operator를 GitOps(ArgoCD app-of-apps, sync-wave)로 배포하면서 발생한 문제들과 해결 과정을 기록합니다.
트러블슈팅 요약
| # | 문제 | 증상 | 원인 | 해결 시간 |
|---|---|---|---|---|
| 1 | Operator Path 오류 | ServiceAccount not found | 잘못된 kustomize path | 10분 |
| 2 | Control-Plane Toleration | Pod Pending | 누락된 taint toleration | 15분 |
| 3 | Namespace 충돌 | Sync 실패 | 두 Operator가 동일 NS 생성 | 20분 |
| 4 | 401 Unauthorized | Topology CR Ready=False | Network Policy + 인증 | 1시간 |
| 5 | Finalizer Stuck | CR Deleting 상태 고착 | 리소스 정리 실패 | 30분 |
| 6 | DNS 미등록 | NXDOMAIN | ExternalDNS 전파 대기 | 5분 |
| 7 | Management UI 401 | 브라우저 로그인 실패 | Istio retry + localStorage | 30분 |
1. Operator Path 오류
증상
$ kubectl get pods -n rabbitmq-system
Error from server: pods is forbidden: error looking up service account rabbitmq-system/rabbitmq-cluster-operator: serviceaccount "rabbitmq-cluster-operator" not foundArgoCD Application은 Synced 상태이지만, Operator Pod가 생성되지 않았다.
원인 분석
ArgoCD Application에서 잘못된 path를 참조.
# 잘못된 설정
source:
path: config/manager # Deployment만 포함config/manager는 Deployment 정의만 포함,
ServiceAccount, ClusterRole, ClusterRoleBinding, Namespace, CRD가 없음.
RabbitMQ Cluster Operator 디렉토리 구조
cluster-operator/config/
├── crd/ # CRD 정의만
├── manager/ # Deployment만 (❌ 불완전)
├── rbac/ # RBAC만
├── default/ # CRD + RBAC + Deployment (NS 제외)
└── installation/ # 완전한 설치 패키지 (✅ 권장)
├── namespace.yaml
├── serviceaccount.yaml
├── role.yaml
├── clusterrole.yaml
├── rolebinding.yaml
├── clusterrolebinding.yaml
├── deployment.yaml
└── kustomization.yaml해결
# 올바른 설정
source:
path: config/installation # 완전한 설치 패키지교훈: Operator 배포 시 공식 문서에서 권장하는 설치 경로를 확인해야 한다.
2. Control-Plane Toleration 누락
증상
$ kubectl get pods -n rabbitmq-system
NAME READY STATUS RESTARTS AGE
rabbitmq-cluster-operator-6c58b89fd5-xxxxx 0/1 Pending 0 5m
$ kubectl describe pod rabbitmq-cluster-operator-xxx -n rabbitmq-system
Events:
Warning FailedScheduling 0/16 nodes are available:
1 node(s) had untolerated taint {role=control-plane: NoSchedule}
1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }
...원인 분석
Operator를 control-plane 노드에 배치하려 했으나, node-role taint 지정 누락.
# 누락된 설정
tolerations:
- key: role
operator: Equal
value: control-plane
effect: NoSchedule
# node-role.kubernetes.io/control-plane taint 누락!k8s-master 노드의 실제 taint:
$ kubectl describe node k8s-master | grep Taints
Taints:
role=control-plane:NoSchedule
node-role.kubernetes.io/control-plane:NoSchedule # kubeadm 기본 taint해결
두 taint 모두에 대한 toleration 추가:
tolerations:
- key: role
operator: Equal
value: control-plane
effect: NoSchedule
- key: node-role.kubernetes.io/control-plane # 추가
operator: Exists
effect: NoSchedule교훈: 노드의 실제 taint를 kubectl describe node로 확인한 후 toleration을 설정해야 한다.
3. Namespace 충돌
증상
$ kubectl get applications -n argocd
dev-rabbitmq-operator Synced Healthy
dev-rabbitmq-topology-operator OutOfSync Degraded
# ArgoCD 에러 메시지
Namespace/rabbitmq-system is part of applications argocd/dev-rabbitmq-operator and argocd/dev-rabbitmq-topology-operator원인 분석
cluster-operator/config/installation/
└── namespace.yaml # rabbitmq-system
messaging-topology-operator/config/installation/cert-manager/
└── namespace.yaml # rabbitmq-system (동일)두 Operator의 config/installation 경로 모두 namespace.yaml을 포함.
ArgoCD는 동일 리소스가 여러 Application에 속하면 충돌로 판단.
해결
Topology Operator에서 Namespace 리소스를 제외한다:
# 30-rabbitmq-topology-operator.yaml
source:
kustomize:
patches:
- target:
kind: Namespace
name: rabbitmq-system
patch: |
$patch: delete
apiVersion: v1
kind: Namespace
metadata:
name: rabbitmq-system
destination:
namespace: rabbitmq-system
syncPolicy:
syncOptions:
- CreateNamespace=false # 중요: Cluster Operator가 이미 생성핵심 포인트:
$patch: delete로 kustomize 빌드에서 Namespace 제외CreateNamespace=false로 ArgoCD가 자동 생성하지 않도록 설정- Cluster Operator(sync-wave 29)가 먼저 Namespace를 생성
교훈: 여러 Operator를 같은 namespace에 배포할 때 리소스 소유권을 명확히 해야 한다.
4. Topology Operator 401 Unauthorized
증상
$ kubectl get vhost -n rabbitmq
NAME AGE READY
eco2-vhost 10m False
$ kubectl describe vhost eco2-vhost -n rabbitmq
Status:
Conditions:
Message: Error: API responded with a 401 Unauthorized
Reason: FailedCreateOrUpdate
Status: False
Type: ReadyTopology Operator가 RabbitMQ Management API에 인증 실패.
원인 분석 1: Network Policy
처음에는 Topology Operator가 RabbitMQ에 연결조차 못했다:
$ kubectl logs -n rabbitmq-system deployment/messaging-topology-operator
dial tcp 10.96.x.x:15672: connection refused문제: Network Policy에서 rabbitmq-system namespace가 15672 포트 접근을 허용 X
해결 1: Network Policy 수정
# allow-rabbitmq-access.yaml
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: rabbitmq-system # 추가
ports:
- protocol: TCP
port: 15672원인 분석 2: 인증 정보 불일치
Network Policy 수정 후에도 credentials 불일치로 401 오류가 지속.
RabbitmqCluster CR의 override 설정 (문제의 설정):
# cluster.yaml (잘못된 설정)
spec:
override:
statefulSet:
spec:
template:
spec:
containers:
- name: rabbitmq
env:
- name: RABBITMQ_DEFAULT_USER
valueFrom:
secretKeyRef:
name: rabbitmq-default-user # 수동 생성 Secret
key: username
- name: RABBITMQ_DEFAULT_PASS
valueFrom:
secretKeyRef:
name: rabbitmq-default-user
key: password문제점:
- Operator는 자동으로
eco2-rabbitmq-default-userSecret을 생성 - Topology Operator는 이 자동 생성된 Secret을 참조
- 하지만 RabbitMQ Pod는 수동 Secret의 credentials로 시작
- 결과: credentials 불일치 → 401 Unauthorized
┌──────────────────┐ "guest/guest" ┌──────────────────┐
│ Topology Operator│ ─────────────────────▶ │ RabbitMQ │
│ (auto-generated) │ │ (manual secret) │
└──────────────────┘ └──────────────────┘
│ │
│ eco2-rabbitmq-default-user │ rabbitmq-default-user
│ username: default_user_xxx │ username: admin
│ password: abc123... │ password: secret123
└───────────────────────────────────────────┘
불일치!해결 2: Override 제거
RabbitmqCluster CR에서 credentials override를 제거하고, Operator 자동 생성 Secret을 사용.
# cluster.yaml (올바른 설정)
spec:
# override 섹션 제거
# Credentials: Operator가 자동 생성하는 eco2-rabbitmq-default-user secret 사용
# Topology Operator가 이 secret을 참조하여 Management API 인증주의: 기존 PersistentVolume에 이전 credentials가 저장되어 있으면 Pod 재시작 후에도 이전 사용자 정보가 유지.
해결 2-1: 수동 사용자 추가 (PV 데이터 유지 시)
PV를 삭제하지 않고 해결하려면 auto-generated 사용자를 수동으로 추가:
# Auto-generated Secret 확인
$ kubectl get secret eco2-rabbitmq-default-user -n rabbitmq -o jsonpath='{.data.username}' | base64 -d
default_user_cv3Zf99xxx
$ kubectl get secret eco2-rabbitmq-default-user -n rabbitmq -o jsonpath='{.data.password}' | base64 -d
abc123xyz...
# RabbitMQ에 사용자 추가
$ kubectl exec -it eco2-rabbitmq-server-0 -n rabbitmq -- \
rabbitmqctl add_user default_user_cv3Zf99xxx abc123xyz...
$ kubectl exec -it eco2-rabbitmq-server-0 -n rabbitmq -- \
rabbitmqctl set_user_tags default_user_cv3Zf99xxx administrator
$ kubectl exec -it eco2-rabbitmq-server-0 -n rabbitmq -- \
rabbitmqctl set_permissions -p / default_user_cv3Zf99xxx ".*" ".*" ".*"
$ kubectl exec -it eco2-rabbitmq-server-0 -n rabbitmq -- \
rabbitmqctl set_permissions -p eco2 default_user_cv3Zf99xxx ".*" ".*" ".*"교훈:
- RabbitMQ Operator는 credentials를 자동 관리한다. 수동 override는 Topology Operator와 충돌.
- Network Policy 설정 시 Operator namespace도 고려.
5. Finalizer Stuck
증상
$ kubectl get vhost,exchange,queue,binding -n rabbitmq
NAME AGE READY
vhost.rabbitmq.com/eco2-vhost 1h False
$ kubectl delete vhost eco2-vhost -n rabbitmq
# 무한 대기...
$ kubectl get vhost eco2-vhost -n rabbitmq -o yaml
metadata:
deletionTimestamp: "2025-12-20T10:00:00Z"
finalizers:
- deletion.finalizers.vhosts.rabbitmq.comTopology CRs가 Deleting 상태에서 영구히 고착.
원인 분석
Kubernetes Custom Resource의 Finalizer 메커니즘:
┌────────────────────────────────────────────────────────────────────┐
│ Finalizer 동작 흐름 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 1. kubectl delete vhost eco2-vhost │
│ ↓ │
│ 2. API Server: deletionTimestamp 설정 │
│ ↓ │
│ 3. Topology Operator: RabbitMQ에서 vhost 삭제 시도 │
│ ↓ │
│ 4-a. 성공 → finalizer 제거 → CR 완전 삭제 │
│ 4-b. 실패 (401 등) → finalizer 유지 → 삭제 불가 (stuck!) │
│ │
└────────────────────────────────────────────────────────────────────┘Operator가 401 오류로 RabbitMQ와 통신할 수 없어서 finalizer를 제거 실패.
해결: 수동 Finalizer 제거
# 단일 리소스
$ kubectl patch vhost eco2-vhost -n rabbitmq \
--type=json \
-p='[{"op": "remove", "path": "/metadata/finalizers"}]'
# 전체 Topology CRs (스크립트)
for type in binding exchange queue vhost; do
for name in $(kubectl get $type -n rabbitmq -o name 2>/dev/null); do
kubectl patch $name -n rabbitmq \
--type=json \
-p='[{"op": "remove", "path": "/metadata/finalizers"}]' \
2>/dev/null || true
done
done주의: Finalizer를 수동 제거하면 RabbitMQ 내부의 실제 리소스는 삭제되지 않음. 필요 시 rabbitmqctl로 수동 정리.
ArgoCD Application 복구
Topology CRs 삭제 후 ArgoCD Application이 Missing 상태가 될 수 있음:
$ kubectl get application dev-rabbitmq-topology -n argocd
NAME SYNC STATUS HEALTH STATUS
dev-rabbitmq-topology Unknown Missing해결: Root Application 강제 동기화
$ kubectl patch application dev-root -n argocd \
--type=merge \
-p '{"operation":{"sync":{"prune":true}}}'교훈:
- Finalizer는 Operator의 정리 작업 완료를 보장하는 메커니즘.
- Operator와 대상 시스템 간 통신이 실패하면 finalizer가 stuck.
- 강제 삭제 시 실제 리소스 정리 여부를 확인.
6. DNS 미등록 (NXDOMAIN)
증상
브라우저에서 rabbitmq.dev.growbin.app 접속 시:
사이트에 연결할 수 없음
DNS_PROBE_FINISHED_NXDOMAIN원인 분석
ExternalDNS가 Ingress annotation을 감지하고 Route53에 레코드를 생성하는 데 시간이 걸린다.
# ExternalDNS 로그 확인
$ kubectl logs -n platform-system -l app.kubernetes.io/name=external-dns --tail=50 | grep rabbitmq
time="2025-12-22T02:10:47Z" level=info msg="Desired change: CREATE rabbitmq.dev.growbin.app CNAME"
time="2025-12-22T02:10:47Z" level=info msg="3 record(s) were successfully updated"
DNS 레코드는 생성되었지만, 로컬 DNS 캐시에 이전 NXDOMAIN 응답이 캐시되어 있었다.
해결
1. DNS 전파 확인:
$ nslookup rabbitmq.dev.growbin.app 8.8.8.8
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
rabbitmq.dev.growbin.app canonical name = k8s-ecoecomain-xxx.ap-northeast-2.elb.amazonaws.com
2. 로컬 DNS 캐시 플러시:
# macOS
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
# Chrome 브라우저
chrome://net-internals/#dns → Clear host cache
교훈: 새 도메인 추가 후 접속 실패 시, 먼저 공용 DNS(8.8.8.8)로 레코드 존재를 확인하고 로컬 캐시를 플러시해야 한다.
7. Management UI 브라우저 401 Unauthorized
증상
// 브라우저 콘솔
GET https://rabbitmq.dev.growbin.app/api/whoami 401 (Unauthorized)
main.js:1393 Failed to load resource: the server responded with a status of 401
그러나 curl로는 정상 응답:
$ curl -u 'admin:admin123' https://rabbitmq.dev.growbin.app/api/whoami
{"name":"admin","tags":["administrator"]}
원인 분석 1: Istio VirtualService retry 설정
VirtualService에 retriable-4xx가 포함되어 있었다:
retries:
attempts: 3
retryOn: connect-failure,reset,retriable-4xx,503 # 문제!
문제점: 401 응답 시 Istio가 재시도하면서 Authorization 헤더가 손실될 수 있다.
해결 1: retriable-4xx 제거
retries:
attempts: 3
retryOn: connect-failure,reset,503 # retriable-4xx 제거
원인 분석 2: 브라우저 localStorage 캐시
RabbitMQ Management UI는 로그인 정보를 localStorage에 저장한다. 이전 잘못된 인증 정보가 캐시되어 있으면 새 비밀번호로 로그인해도 실패한다.
┌─────────────────────────────────────────────────────────────────┐
│ 브라우저 인증 흐름 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 로그인 폼에서 username/password 입력 │
│ ↓ │
│ 2. JavaScript가 Base64 인코딩 후 localStorage에 저장 │
│ ↓ │
│ 3. API 호출 시 localStorage에서 credentials 읽어서 사용 │
│ ↓ │
│ 4. 이전 잘못된 credentials 캐시 → 401 Unauthorized │
│ │
└─────────────────────────────────────────────────────────────────┘해결 2: localStorage 삭제
방법 1: 시크릿/프라이빗 모드
Chrome: Cmd + Shift + N
Safari: Cmd + Shift + N방법 2: 개발자 도구에서 삭제
Cmd + Option + I(개발자 도구)- Application 탭 → Storage 섹션
- Local Storage →
https://rabbitmq.dev.growbin.app→ Clear - Cookies →
rabbitmq.dev.growbin.app→ Clear - 새로고침
방법 3: 사이트 데이터 삭제
Cmd + Shift + Delete(캐시 삭제 창)- 시간 범위: 전체 기간
- "쿠키 및 사이트 데이터" 체크
- 삭제
검증
# curl로 인증 테스트
$ curl -s -u 'admin:admin123' https://rabbitmq.dev.growbin.app/api/whoami
{"name":"admin","tags":["administrator"]}
교훈:
- Istio VirtualService의
retriable-4xx는 인증이 필요한 서비스에서 문제를 일으킬 수 있다. - 브라우저에서만 401 발생 시 localStorage/Cookie 캐시를 의심해야 한다.
- 시크릿 모드로 먼저 테스트하면 캐시 문제를 빠르게 확인할 수 있다.
체크리스트: RabbitMQ Operator 배포
배포 전 확인 사항:
- Cert-Manager 설치 완료 (Topology Operator Webhook용)
- 노드 taint 확인 (
kubectl describe node) - Operator path 확인 (
config/installation권장) - Network Policy에
rabbitmq-systemnamespace 포함 - RabbitmqCluster CR에서 credentials override 사용 안 함
- Sync-wave 순서 확인 (Operator → Cluster → Topology)