ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 이코에코(Eco²) Streams & Scaling for SSE #1: 3-Node Redis Cluster 리소스 프로비저닝
    이코에코(Eco²)/Event Streams & Scaling 2025. 12. 26. 13:23

     

    Kubernetes 클러스터에 Redis 3노드를 추가하는 인프라 프로비저닝 과정을 다룹니다.

    목표

    항목 Before After
    노드 수 1 (k8s-redis) 3 (auth, streams, cache)
    인스턴스 t3.medium ×1 t3.medium ×1 + t3.small ×2
    vCPU 2 6
    메모리 4GB 8GB

    Phase 1: Terraform 변경

    기존 모듈

    # terraform/main.tf (Before)
    module "redis" {
      source        = "./modules/ec2"
      name          = "k8s-redis"
      instance_type = "t3.medium"
      # ...
    }

    3개 모듈로 분리

    # terraform/main.tf (After)
    module "redis_auth" {
      source        = "./modules/ec2"
      name          = "k8s-redis-auth"
      instance_type = "t3.medium"  # JWT Blacklist 중요도 높음
      node_labels   = { "redis-cluster" = "auth" }
      # ...
    }
    
    module "redis_streams" {
      source        = "./modules/ec2"
      name          = "k8s-redis-streams"
      instance_type = "t3.small"
      node_labels   = { "redis-cluster" = "streams" }
      # ...
    }
    
    module "redis_cache" {
      source        = "./modules/ec2"
      name          = "k8s-redis-cache"
      instance_type = "t3.small"
      node_labels   = { "redis-cluster" = "cache" }
      # ...
    }

    Ansible Inventory 템플릿

    # terraform/templates/hosts.tpl
    [redis]
    k8s-redis-auth ansible_host=${redis_auth_public_ip} private_ip=${redis_auth_private_ip} redis_cluster=auth
    k8s-redis-streams ansible_host=${redis_streams_public_ip} private_ip=${redis_streams_private_ip} redis_cluster=streams
    k8s-redis-cache ansible_host=${redis_cache_public_ip} private_ip=${redis_cache_private_ip} redis_cluster=cache

    Phase 2: vCPU 한계 이슈

    문제 발생

    terraform apply redis-only.plan
    
    Error: creating EC2 Instance: operation error EC2: RunInstances, 
    api error VcpuLimitExceeded: You have requested more vCPU capacity 
    than your current vCPU limit of 43 allows

    vCPU 현황 분석

    aws ec2 describe-instances \
      --filters "Name=instance-state-name,Values=running" \
      --query 'Reservations[*].Instances[*].InstanceType' \
      --output text | tr '\t' '\n' | sort | uniq -c
    타입 개수 vCPU 소계
    t3.xlarge 1 4 4
    t3.large 3 2 6
    t3.medium 7 2 14
    t3.small 9 2 18
    합계 20 - 42 vCPU

    계산: 42 + 6(새 Redis) = 48 > 43 (한계 초과)

    고아 인스턴스 발견

    이전 Terraform apply 실패로 생성된 중복 인스턴스:

    aws ec2 describe-instances \
      --filters "Name=tag:Name,Values=k8s-redis-*" \
      --query 'Reservations[*].Instances[*].[InstanceId,Tags[?Key==`Name`].Value|[0]]' \
      --output table
    Instance ID Name State Terraform State
    i-0e9a1d55b934e6990 k8s-redis-cache running 없음 (고아)
    i-0d2620f00548d6e87 k8s-redis-streams running 없음 (고아)

    해결: 고아 인스턴스 정리

    # 1. 고아 인스턴스 종료
    aws ec2 terminate-instances --instance-ids \
      i-0e9a1d55b934e6990 \
      i-0d2620f00548d6e87
    
    # 2. 종료 완료 대기
    aws ec2 wait instance-terminated --instance-ids \
      i-0e9a1d55b934e6990 \
      i-0d2620f00548d6e87
    
    # 3. Terraform State 정리 (필요시)
    terraform state rm module.redis_cache.aws_instance.this
    terraform state rm module.redis_streams.aws_instance.this

    정리 후 vCPU

    타입 개수 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 - 36 = 7 vCPU > 6 vCPU (필요) ✅


    Phase 3: Terraform Apply

    Target 옵션으로 범위 제한

    전체 plan 시 AMI 버전 변경으로 모든 노드가 replace 대상이 됨.
    -target 옵션으로 Redis 모듈만 적용:

    # Plan
    terraform plan \
      -var="dockerhub_password=xxx" \
      -target=module.redis_auth \
      -target=module.redis_streams \
      -target=module.redis_cache \
      -out=redis-fresh.plan
    
    # Apply
    terraform apply redis-fresh.plan

    결과

    module.redis_auth.aws_instance.this: Creating...
    module.redis_streams.aws_instance.this: Creating...
    module.redis_cache.aws_instance.this: Creating...
    
    module.redis_auth.aws_instance.this: Creation complete after 35s [id=i-xxx]
    module.redis_streams.aws_instance.this: Creation complete after 36s [id=i-xxx]
    module.redis_cache.aws_instance.this: Creation complete after 37s [id=i-xxx]
    
    Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

    Inventory 생성

    # Terraform Output → Ansible Inventory
    terraform output -raw ansible_inventory > ../ansible/inventory/hosts.ini
    
    # 확인
    grep -A5 "[redis]" ../ansible/inventory/hosts.ini
    [redis]
    k8s-redis-auth ansible_host=3.34.128.200 private_ip=10.0.2.43 redis_cluster=auth
    k8s-redis-streams ansible_host=3.38.200.174 private_ip=10.0.2.215 redis_cluster=streams
    k8s-redis-cache ansible_host=15.165.200.60 private_ip=10.0.2.202 redis_cluster=cache

    Phase 4: Ansible 설정

    SSH 연결 확인

    cd ../ansible
    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" }

    새 노드 기본 설정

    새로 생성된 EC2 인스턴스에는 Docker, containerd, kubeadm이 설치되어 있지 않음.
    전용 playbook으로 초기 설정:

    ansible-playbook -i inventory/hosts.ini playbooks/setup-new-nodes.yml -l redis
    # ansible/playbooks/setup-new-nodes.yml
    - name: Prerequisites - OS 설정
      hosts: all
      roles:
        - common  # 커널 모듈, sysctl, swap 비활성화
    
    - name: Docker/Containerd 설치
      hosts: all
      roles:
        - docker  # containerd CRI 설정
    
    - name: Kubernetes 패키지 설치
      hosts: all
      roles:
        - kubernetes  # kubeadm, kubelet, kubectl

    결과

    TASK [설치 상태 출력] ****
    ok: [k8s-redis-auth] => 
      msg:
      - 'containerd: active'
      - 'kubeadm: v1.28.4'
      - 'kubelet: Kubernetes v1.28.4'

    Phase 5: 클러스터 조인

    Join Token 생성

    ssh -i ~/.ssh/sesacthon.pem ubuntu@<master-ip> \
      "kubeadm token create --print-join-command" > /tmp/kubeadm_join_command.sh

    Worker Join

    ansible-playbook -i inventory/hosts.ini playbooks/03-worker-join.yml \
      -l redis \
      -e kubectl_user=ubuntu

    노드 라벨 적용

    ansible-playbook -i inventory/hosts.ini playbooks/fix-node-labels.yml \
      -l redis \
      -e kubectl_user=ubuntu

    최종 확인

    kubectl get nodes -l 'redis-cluster' -o wide
    NAME                STATUS   ROLES    AGE   VERSION   INTERNAL-IP
    k8s-redis-auth      Ready    <none>   45m   v1.28.4   10.0.2.43
    k8s-redis-streams   Ready    <none>   45m   v1.28.4   10.0.2.215
    k8s-redis-cache     Ready    <none>   45m   v1.28.4   10.0.2.202

    교훈 및 Best Practices

    1. AMI Lifecycle 관리

    resource "aws_instance" "this" {
      lifecycle {
        ignore_changes = [ami]  # AMI 변경 무시
      }
    }

    2. vCPU 한계 사전 확인

    # 프로비저닝 전 확인 스크립트
    CURRENT=$(aws ec2 describe-instances ... | wc -l)
    LIMIT=$(aws service-quotas get-service-quota ... | jq '.Quota.Value')
    NEEDED=6
    
    if [ $((CURRENT + NEEDED)) -gt $LIMIT ]; then
      echo "⚠️ vCPU 한계 초과 예상"
    fi

    3. Target 옵션 활용

    # 영향 범위 제한
    terraform plan -target=module.specific_module

    4. State 불일치 복구

    # 고아 리소스 정리
    terraform state rm <resource>
    
    # 또는 import
    terraform import module.xxx.aws_instance.this <instance-id>

    참고 자료

    댓글

ABOUT ME

🎓 부산대학교 정보컴퓨터공학과 학사: 2017.03 - 2023.08
☁️ Rakuten Symphony Jr. Cloud Engineer: 2024.12.09 - 2025.08.31
🏆 2025 AI 새싹톤 우수상 수상: 2025.10.30 - 2025.12.02
🌏 이코에코(Eco²) 백엔드/인프라 고도화 중: 2025.12 - Present

Designed by Mango