-
Redis 3-Node 클러스터 프로비저닝 실측 로그이코에코(Eco²) 제작 문서 및 리포트/Logs 2025. 12. 26. 15:02

작업 일시: 2025-12-26
환경: AWS ap-northeast-2 (Development)
도구: Terraform + Ansible + kubeadm
이 문서는 단일 Redis 인스턴스를 용도별 3-Node 클러스터로 마이그레이션한 실제 작업 로그입니다.
클러스터 구축에 있어 재현 가능한 환경을 지향하며 트러블슈팅 과정을 포함하여 유사한 작업 시 참고할 수 있도록 작성했습니다.
Phase 1: 초기 상태 확인
$ aws ec2 describe-instances \ --filters "Name=instance-state-name,Values=running" \ --query 'Reservations[*].Instances[*].[Tags[?Key==`Name`].Value|[0],InstanceType,InstanceId]' \ --output table +----------------------+------------+-----------------------+ | k8s-postgresql | t3.large | i-xxxx...postgresql | | k8s-worker-ai | t3.medium | i-xxxx...worker-ai | | k8s-master | t3.xlarge | i-xxxx...master | | k8s-api-character | t3.small | i-xxxx...character | | k8s-api-chat | t3.medium | i-xxxx...chat | | k8s-api-auth | t3.small | i-xxxx...auth | | k8s-api-scan | t3.medium | i-xxxx...scan | | k8s-api-image | t3.small | i-xxxx...image | | k8s-worker-storage | t3.medium | i-xxxx...storage | | k8s-logging | t3.large | i-xxxx...logging | | k8s-api-my | t3.small | i-xxxx...my | | k8s-monitoring | t3.large | i-xxxx...monitoring | | k8s-rabbitmq | t3.medium | i-xxxx...rabbitmq | | k8s-api-location | t3.small | i-xxxx...location | | k8s-redis-cache | t3.small | i-xxxx...redis-cache | | k8s-redis-streams | t3.small | i-xxxx...redis-str | | k8s-ingress-gateway | t3.medium | i-xxxx...gateway | +----------------------+------------+-----------------------+vCPU 현황 (정리 전)
t3.xlarge 1 4 4 t3.large 3 2 6 t3.medium 6 2 12 t3.small 7 2 14 합계 17 - 36 vCPU - 한계: 43 vCPU (AWS On-Demand Quota, Default: 16)
- 여유: 7 vCPU
Phase 2: 고아 인스턴스 정리
이전 Terraform apply 실패로 인해 생성된 중복 인스턴스를 정리합니다.
⚠️ 고아 인스턴스란?: Terraform state에는 없지만 실제로 AWS에 존재하는 리소스.
apply 실패/중단 시 발생할 수 있으며, 비용과 quota를 소모합니다.# 중복 인스턴스 삭제 $ aws ec2 terminate-instances --instance-ids i-xxxx...cache i-xxxx...streams # 삭제 완료 대기 (약 30초) $ aws ec2 wait instance-terminated --instance-ids i-xxxx...cache i-xxxx...streams
Phase 3: Terraform State 정리
State에서 삭제된 리소스 참조를 제거합니다.
$ terraform state rm module.redis_cache.aws_instance.this Removed module.redis_cache.aws_instance.this Successfully removed 1 resource instance(s). $ terraform state rm module.redis_streams.aws_instance.this Removed module.redis_streams.aws_instance.this Successfully removed 1 resource instance(s).
Phase 4: Terraform Plan
-target옵션으로 Redis 모듈만 변경 범위를 제한합니다.$ terraform plan \ -var="dockerhub_password=$DOCKERHUB_TOKEN" \ -target=module.redis_auth \ -target=module.redis_streams \ -target=module.redis_cache \ -out=redis-3node.plan Plan: 3 to add, 0 to change, 0 to destroy. Changes to Outputs: + redis_auth_instance_id = (known after apply) + redis_auth_private_ip = (known after apply) + redis_auth_public_ip = (known after apply) ~ redis_cache_instance_id = "i-xxxx..." -> (known after apply) ~ redis_streams_instance_id = "i-xxxx..." -> (known after apply)
Phase 5: Terraform Apply
$ terraform apply redis-3node.plan module.redis_cache.aws_instance.this: Creating... module.redis_streams.aws_instance.this: Creating... module.redis_auth.aws_instance.this: Creating... module.redis_streams.aws_instance.this: Creation complete after 13s module.redis_auth.aws_instance.this: Creation complete after 13s module.redis_cache.aws_instance.this: Creation complete after 13s Apply complete! Resources: 3 added, 0 changed, 0 destroyed.소요 시간: 13초 (3개 인스턴스 병렬 생성)
Phase 6: 프로비저닝 결과
새로 생성된 Redis 노드
k8s-redis-auth t3.medium 2 4GB JWT Blacklist, OAuth State k8s-redis-streams t3.small 2 2GB SSE 이벤트 스트림 k8s-redis-cache t3.small 2 2GB Celery 결과, 이미지 캐시 t3.medium 선택 이유 (auth):
JWT Blacklist와 OAuth State는 시스템 전반에 영향을 미치는 보안 데이터입니다.
Sentinel HA 운영과 향후 확장을 고려하여 여유 있는 스펙을 선택했습니다.노드 스펙 확인
ubuntu@k8s-master:~$ kubectl describe nodes k8s-redis-auth k8s-redis-streams k8s-redis-cache \ | grep -E 'Name:|cpu:|memory:|Allocatable' | head -18 Name: k8s-redis-auth cpu: 2 memory: 3928852Ki # ~3.7GB Allocatable: cpu: 2 memory: 3826452Ki # ~3.6GB (시스템 예약 제외) Name: k8s-redis-streams cpu: 2 memory: 1956384Ki # ~1.9GB Allocatable: cpu: 2 memory: 1853984Ki # ~1.8GB Name: k8s-redis-cache cpu: 2 memory: 1956384Ki # ~1.9GB Allocatable: cpu: 2 memory: 1853984Ki # ~1.8GBvCPU 최종 현황
t3.xlarge 1 4 4 t3.large 3 2 6 t3.medium 7 2 14 t3.small 7 2 14 합계 18 - 38 vCPU - 여유: 5 vCPU ✅
Phase 7: Terraform Output 갱신 문제
문제 발생
-target옵션으로 apply 후terraform output이 기존 값을 반환:$ terraform output -raw ansible_inventory > hosts.ini $ grep -A3 "\[redis\]" hosts.ini [redis] k8s-redis ansible_host=<OLD_IP> # ❌ 삭제된 노드 IP원인 분석
# 개별 output은 정상 $ terraform output redis_auth_public_ip "<NEW_IP>" ✅ # State도 정상 $ terraform state list | grep redis module.redis_auth.aws_instance.this ✅ module.redis_cache.aws_instance.this ✅ module.redis_streams.aws_instance.this ✅원인:
-target옵션으로 apply하면 대상 외 output이 갱신되지 않음해결
# -refresh-only로 전체 state 갱신 (리소스 변경 없이) $ terraform apply -refresh-only -var="dockerhub_password=$TOKEN" -auto-approve # 이후 output 정상 출력 $ terraform output -raw ansible_inventory | grep -A3 "\[redis\]" [redis] k8s-redis-auth ansible_host=<NEW_IP> ✅ k8s-redis-streams ansible_host=<NEW_IP> ✅ k8s-redis-cache ansible_host=<NEW_IP> ✅💡 교훈:
-target사용 후에는 반드시-refresh-only로 output을 갱신해야 한다.
Phase 8: Ansible SSH 테스트
$ ansible -i inventory/hosts.ini redis -m ping k8s-redis-auth | SUCCESS => {"ping": "pong"} k8s-redis-streams | SUCCESS => {"ping": "pong"} k8s-redis-cache | SUCCESS => {"ping": "pong"}k8s-redis-auth ✅ SUCCESS pong k8s-redis-streams ✅ SUCCESS pong k8s-redis-cache ✅ SUCCESS pong
Phase 9: Kubernetes 런타임 설치
새 EC2 인스턴스에는 containerd, kubeadm이 없으므로 별도 플레이북 실행:
$ ansible-playbook -i inventory/hosts.ini playbooks/setup-new-nodes.yml -l redis설치 결과
k8s-redis-auth active ✅ v1.28.4 v1.28.4 k8s-redis-streams active ✅ v1.28.4 v1.28.4 k8s-redis-cache active ✅ v1.28.4 v1.28.4 PLAY RECAP k8s-redis-auth : ok=38 changed=18 failed=0 k8s-redis-streams : ok=37 changed=18 failed=0 k8s-redis-cache : ok=37 changed=18 failed=0
Phase 10: Worker Join
$ ansible-playbook -i inventory/hosts.ini playbooks/03-worker-join.yml \ -l redis -e kubectl_user=ubuntu결과
This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details.k8s-redis-auth ✅ aws:///ap-northeast-2a/... ok k8s-redis-streams ✅ aws:///ap-northeast-2b/... ok k8s-redis-cache ✅ aws:///ap-northeast-2c/... ok
Phase 11: 노드 라벨 적용
$ ansible-playbook -i inventory/hosts.ini playbooks/fix-node-labels.yml \ -l redis -e kubectl_user=ubuntu라벨 확인
k8s-redis-auth auth redis-auth Ready ✅ k8s-redis-streams streams redis-streams Ready ✅ k8s-redis-cache cache redis-cache Ready ✅ 기존 노드 정리
# 삭제된 EC2에 해당하는 NotReady 노드 제거 $ kubectl delete node k8s-redis
Phase 12: CI/CD 파이프라인 조정
1. Terraform fmt 실패
terraform fmt -check -recursive outputs.tf Error: Terraform exited with code 3.해결:
terraform fmt outputs.tf후 재push2. Redis 연결 에러
redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379원인:
domains/_shared/events/모듈이 테스트 시 Redis 연결 시도해결: GitHub Actions에 Redis 서비스 추가
services: redis: image: redis:7-alpine ports: - 6379:63793. shared_triggers 누락
해결: CI 매트릭스에 공유 모듈 트리거 추가
shared_triggers = { "domains/_shared/events": ["scan"], }
Phase 13: Redis Cluster 배포 완료
ArgoCD Application 상태
ubuntu@k8s-master:~$ kubectl get applications -n argocd | grep redis NAME SYNC STATUS HEALTH STATUS dev-redis-cluster Synced Healthy dev-redis-operator Synced HealthyRedisFailover CRs
ubuntu@k8s-master:~$ kubectl get redisfailover -n redis NAME REDIS SENTINELS AGE auth-redis 1 1 33m cache-redis 1 1 33m streams-redis 1 1 33mRedis Pod 배치 검증
ubuntu@k8s-master:~$ kubectl get pods -n redis -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES rfr-auth-redis-0 3/3 Running 0 37m 192.168.75.196 k8s-redis-auth <none> <none> rfr-cache-redis-0 3/3 Running 0 37m 192.168.46.132 k8s-redis-cache <none> <none> rfr-streams-redis-0 3/3 Running 0 37m 192.168.169.196 k8s-redis-streams <none> <none> rfs-auth-redis-66bf8f9657-dzp7v 2/2 Running 0 37m 192.168.75.195 k8s-redis-auth <none> <none> rfs-cache-redis-7845fbdd47-l27dr 2/2 Running 0 37m 192.168.46.131 k8s-redis-cache <none> <none> rfs-streams-redis-7d9c9986d9-twjdx 2/2 Running 0 37m 192.168.169.195 k8s-redis-streams <none> <none>배치 검증 결과:
rfr-auth-redis-0 k8s-redis-auth k8s-redis-auth ✅ rfr-cache-redis-0 k8s-redis-cache k8s-redis-cache ✅ rfr-streams-redis-0 k8s-redis-streams k8s-redis-streams ✅ rfs-auth-redis-* k8s-redis-auth k8s-redis-auth ✅ rfs-cache-redis-* k8s-redis-cache k8s-redis-cache ✅ rfs-streams-redis-* k8s-redis-streams k8s-redis-streams ✅ ✅ 모든 Pod가 의도한 노드에 정확히 배치됨 (NodeAffinity 정상 동작)
Node Affinity 설정 확인
ubuntu@k8s-master:~$ kubectl get redisfailover auth-redis -n redis \ -o jsonpath='{.spec.redis.affinity}' | python3 -m json.tool { "nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { "nodeSelectorTerms": [ { "matchExpressions": [ { "key": "redis-cluster", "operator": "In", "values": ["auth"] } ] } ] } } }Redis 설정 확인
ubuntu@k8s-master:~$ for pod in rfr-auth-redis-0 rfr-streams-redis-0 rfr-cache-redis-0; do echo -n "$pod: " kubectl exec -n redis $pod -c redis -- redis-cli CONFIG GET maxmemory-policy | tail -1 done rfr-auth-redis-0: noeviction rfr-streams-redis-0: noeviction rfr-cache-redis-0: allkeys-lruauth-redis 872.66K noeviction 보안 데이터 보호 streams-redis 893.45K noeviction 이벤트 유실 방지 cache-redis 926.23K allkeys-lru 오래된 캐시 자동 정리 (LRU) 앱 연결 상태
ubuntu@k8s-master:~$ kubectl exec -n scan deployment/scan-worker -c scan-worker -- python3 -c " import redis r = redis.from_url('redis://rfr-streams-redis.redis.svc.cluster.local:6379/0') print('Streams Redis:', r.ping()) " Streams Redis: Truescan-worker rfr-streams-redis ✅ Connected scan-worker rfr-cache-redis ✅ Connected ext-authz rfr-auth-redis ✅ Running (2 replicas)
체크리스트
Infrastructure Provisioning
- Terraform Apply (3-node Redis cluster)
- Ansible Inventory 갱신
- SSH 테스트 성공
- containerd, kubeadm v1.28.4 설치
- Worker Join 성공
- 노드 라벨 적용 (redis-cluster=auth/streams/cache)
- 기존 NotReady 노드 정리
Code & CI/CD
- Git 브랜치 분리 (
feat/redis-3node-cluster) - Pre-commit hooks 통과
- Terraform fmt 수정
- CI Redis 서비스 추가
- shared_triggers 추가
- CI 파이프라인 통과
- PR Merge → develop
Deployment
- ArgoCD Sync (Redis Operator)
- ArgoCD Sync (RedisFailover CRs)
- Redis Pod 정상 기동 (6 Pods)
- 환경변수 전파 확인
- k6 부하 테스트 (50 VU)
주요 교훈
-target사용 후-refresh-only필수- 일부 모듈만 apply하면 output이 갱신되지 않음
- 고아 인스턴스 주의
- apply 실패 시 AWS Console에서 직접 확인 필요
- vCPU quota 소모 원인이 될 수 있음
- 새 노드에는 런타임이 없음
- EC2 생성 후 containerd, kubeadm 설치 필요
- 별도 플레이북 준비 권장
- 공유 모듈 변경 시 CI 트리거 확인
_shared/변경이 의존 서비스 테스트를 트리거하는지 확인
참고 자료
'이코에코(Eco²) 제작 문서 및 리포트 > Logs' 카테고리의 다른 글
redis-pubsub, event-router 노드 프로비저닝 실측 로그 (0) 2025.12.27