13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] 클라우드 인프라

  • [K8s] 쿠버네티스 비용 최적화 5가지 핵심 전략 – 클라우드 비용 폭탄 방지

    쿠버네티스 비용 최적화 5가지 핵심 전략: 클라우드 비용 폭탄 방지 완벽 가이드

    안녕하세요, 13년차 인프라 엔지니어 ’13년차의 서버실’입니다. 클라우드 인프라를 운영하다 보면 정말 다양한 경험을 하게 되죠. 그중에서도 클라우드 비용만큼 심장을 철렁하게 만드는 것도 없을 겁니다. 특히 쿠버네티스(Kubernetes, K8s)를 도입하고 나서 ‘비용 폭탄’을 맞았다는 이야기를 참 많이 듣더라고요. 제가 직접 홈랩에서 다양한 기술을 실험하고 실제 프로덕션 환경에서 쿠버네티스를 운영하면서 겪었던 경험과 노하우를 바탕으로, 클라우드 비용을 효과적으로 최적화하는 5가지 핵심 전략을 오늘 아낌없이 풀어보려고 합니다.

    혹시 지금 여러분의 클라우드 청구서가 예상보다 많이 나왔거나, 쿠버네티스 비용 최적화에 어려움을 겪고 있다면 이 글이 분명 큰 도움이 될 거예요. 저도 처음엔 이게 뭔가 싶었는데, 하나하나 적용해보니 정말 확실한 차이가 나더라고요. 자, 그럼 함께 클라우드 비용 폭탄을 막으러 떠나볼까요?

    쿠버네티스 클라우드 비용 구조 개요 다이어그램

    클라우드 비용, 정말 폭탄 맞기 쉬워요

    사실 클라우드 환경에서는 리소스를 유연하게 쓸 수 있다는 게 큰 장점이에요. 근데 그만큼 자원 사용량을 예측하고 관리하기가 쉽지 않거든요. 특히 쿠버네티스는 컨테이너 오케스트레이션이라는 강력한 기능을 제공하지만, 그 복잡성 때문에 비용 최적화가 더 어렵게 느껴지기도 합니다. 파드(Pod)들이 알아서 스케일 업/다운되고, 워커 노드(Worker Node)들도 자동으로 늘었다 줄었다 하니, 대체 어디서 돈이 나가는지 파악하기가 여간 어려운 게 아니거든요. 저도 처음엔 그냥 잘 돌아가면 됐지 싶었는데, 월말에 청구서 보고 깜짝 놀란 적이 한두 번이 아니었습니다. 😅

    왜 쿠버네티스에서 비용 관리가 어려울까요?

    쿠버네티스는 다양한 추상화 계층(Abstraction Layer)을 가지고 있어서, 실제 물리적인 자원 사용량과 애플리케이션이 요구하는 자원량 사이의 괴리가 쉽게 발생합니다. 예를 들어, 파드에 필요한 CPU와 메모리를 정확히 예측하기 어렵고, 노드에 남는 자원이 있어도 파드가 스케줄링되지 않는 경우가 생기기도 하죠. 또한, 멀티 테넌시(Multi-tenancy) 환경에서는 여러 팀이 하나의 클러스터를 공유하기 때문에, 어떤 팀이 얼마나 비용을 소모하고 있는지 추적하기도 힘들어집니다. 이런 점들이 K8s 비용 절감을 어렵게 만드는 주된 이유입니다.

    쿠버네티스 비용 최적화 5가지 핵심 전략

    그럼 이제 제가 직접 해보고 효과를 본 5가지 핵심 전략을 소개해 드릴게요. 이 방법들을 잘 활용하면 클라우드 비용 관리의 새로운 지평을 열 수 있을 겁니다.

    1. 리소스 요청(Requests)과 제한(Limits) 정확히 설정하기

    이건 정말 기본 중의 기본이지만, 가장 많이 간과되기도 하는 부분입니다. 쿠버네티스에서 파드를 배포할 때, 각 컨테이너가 사용할 CPU와 메모리 양을 resources.requests와 resources.limits로 명시해줘야 합니다. Requests(요청)는 파드가 스케줄링될 때 필요한 최소 자원이고, Limits(제한)는 파드가 최대로 사용할 수 있는 자원량이에요.

    이 값을 너무 높게 설정하면 노드에 자원이 남아돌아도 다른 파드가 스케줄링되지 않아 불필요하게 노드를 증설하게 되고, 너무 낮게 설정하면 파드가 갑자기 죽거나 성능 저하가 발생해요. 제가 처음엔 그냥 대충 설정했었는데, 결국 자원이 부족해서 파드가 툭툭 죽는 바람에 며칠 밤낮을 고생했었죠. 😭

    애플리케이션의 실제 사용량을 모니터링해서 적절한 값을 찾아야 합니다. Prometheus(프로메테우스)나 Grafana(그라파나) 같은 툴로 사용량을 꾸준히 확인하면서 최적의 값을 찾아가는 과정이 정말 중요해요.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-app
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: my-app
      template:
        metadata:
          labels:
            app: my-app
        spec:
          containers:
          - name: my-container
            image: my-image:latest
            resources:
              requests:
                cpu: "200m"     # 0.2 CPU 코어 요청
                memory: "256Mi"  # 256 MiB 메모리 요청
              limits:
                cpu: "500m"     # 0.5 CPU 코어 제한
                memory: "512Mi"  # 512 MiB 메모리 제한
    

    여기서 200m은 0.2 CPU 코어, 256Mi는 256 메가바이트를 의미합니다. 애플리케이션의 특성을 잘 파악해서 이 값을 정교하게 설정하는 것이 쿠버네티스 비용 최적화의 첫걸음입니다.

    2. 효율적인 워커 노드(Worker Node) 관리하기 (클러스터 오토스케일링)

    쿠버네티스 클러스터에서 가장 큰 비용을 차지하는 부분은 바로 워커 노드입니다. 사용량이 적을 때는 노드를 줄이고, 많을 때는 늘려주는 클러스터 오토스케일러(Cluster Autoscaler)를 활용해야 합니다. 클라우드 제공업체(AWS, GCP, Azure 등)마다 자체적인 오토스케일링 기능을 제공하며, 쿠버네티스에서는 Cluster Autoscaler가 이를 연동하여 노드 수를 자동으로 조절해줍니다.

    이걸 적용하면 피크 타임에는 충분한 자원을 제공하고, 유휴 시간에는 불필요한 노드를 종료시켜 비용을 확실히 절감할 수 있습니다. 제가 처음엔 수동으로 노드를 늘렸다 줄였다 했는데, 이게 정말 비효율적이더라고요. Cluster Autoscaler를 도입하고 나서는 야간이나 주말에 노드 수가 자동으로 줄어드는 걸 보고 속으로 쾌재를 불렀습니다. 🎉

    3. 스팟 인스턴스(Spot Instances) 적극 활용하기

    클라우드 환경에서는 일반 온디맨드(On-demand) 인스턴스보다 훨씬 저렴하게 쓸 수 있는 스팟 인스턴스(Spot Instances)라는 게 있습니다. AWS의 Spot Instances, GCP의 Preemptible VMs, Azure의 Spot VMs 등이 여기에 해당하죠. 이 인스턴스들은 클라우드 제공업체의 여유 자원을 활용하는 방식이라 가격이 정말 저렴해요.

    물론, 클라우드 제공업체에 자원이 부족해지면 언제든지 회수될 수 있다는 단점이 있지만, Stateless(무상태)하고 Fault-tolerant(장애 허용)한 워크로드(예: 배치 처리, 개발/테스트 환경, 일부 백엔드 서비스)에는 정말 안성맞춤입니다. 저도 개발 환경이나 CI/CD 파이프라인에는 스팟 인스턴스를 적극적으로 활용해서 비용을 많이 아끼고 있어요. 중요한 건, 애플리케이션이 스팟 인스턴스가 회수되더라도 문제없이 동작하도록 잘 설계해야 한다는 점이에요.

    4. 비용 모니터링 및 분석 도구 활용하기 (Kubecost)

    눈에 보이지 않는 건 관리할 수 없습니다. 쿠버네티스 환경에서 비용을 정확하게 추적하고 분석하려면 전용 도구가 필수적이에요. 제가 여러 도구를 써봤지만, 그중에서도 Kubecost(쿠베코스트)는 정말 강력한 툴이더라고요. Kubecost는 클러스터 내의 리소스 사용량과 클라우드 제공업체의 비용 데이터를 연동하여, 파드, 디플로이먼트(Deployment), 네임스페이스(Namespace) 등 쿠버네티스 오브젝트(Object)별로 상세한 비용 분석 정보를 제공해줍니다.

    설치도 Helm(헬름) 차트(Chart)로 정말 간단하게 할 수 있어요.

    helm repo add kubecost https://kubecost.github.io/cost-analyzer/
    helm install kubecost kubecost/cost-analyzer --namespace kubecost --create-namespace
    

    이렇게 설치하고 나면 웹 UI에 접속해서 우리 클러스터에서 어떤 리소스가 얼마나 비용을 잡아먹고 있는지 한눈에 확인할 수 있습니다. 저도 Kubecost 덕분에 특정 네임스페이스에서 불필요하게 많은 리소스를 쓰고 있다는 걸 파악하고 바로 조치할 수 있었어요. FinOps(파인옵스) Kubernetes를 실현하는 데 핵심적인 도구라고 할 수 있습니다. 💡

    Kubecost 대시보드 예시: 쿠버네티스 비용 분석 화면

    5. 네임스페이스(Namespace) 및 라벨(Label) 기반 비용 할당

    클라우드 비용을 효과적으로 관리하려면, 누가 얼마나 쓰고 있는지 명확히 아는 것이 정말 중요합니다. 쿠버네티스에서는 네임스페이스(Namespace)와 라벨(Label)을 활용하여 비용을 할당하고 추적할 수 있어요. 예를 들어, 각 팀이나 프로젝트별로 별도의 네임스페이스를 사용하고, 모든 리소스에 team: frontend, project: my-service와 같은 라벨을 붙이는 거죠.

    이렇게 하면 Kubecost 같은 도구를 활용했을 때, 특정 팀이나 프로젝트가 사용하는 자원과 그에 따른 비용을 정확히 파악할 수 있습니다. 이는 FinOps(Financial Operations)의 핵심 원칙 중 하나인데, 개발팀이 자신의 서비스가 사용하는 클라우드 비용을 인지하고 책임감을 가지도록 유도할 수 있어요. 저도 처음엔 라벨링이 귀찮았는데, 나중에 비용 분석할 때 라벨이 잘 되어 있으면 정말 편하더라고요.

    ⚠️ 삽질 경험: 과도한 리소스 할당과 스팟 인스턴스 주의사항

    제가 겪었던 삽질 중 하나는 파드에 limits를 너무 낮게 설정해서 OOMKilled(Out Of Memory Killed)로 파드가 계속 죽어나갔던 경험입니다. 처음엔 왜 죽는지 몰라서 밤늦게까지 삽질하다가 겨우 limits 부족인 걸 알아냈죠. 너무 아끼려다가 오히려 서비스 장애를 유발한 셈이에요. 🤦‍♂️

    또 다른 삽질은 너무 많은 핵심 서비스를 스팟 인스턴스에 올렸다가 클라우드 제공업체의 자원 부족으로 인스턴스가 회수되면서 서비스가 일시적으로 마비되었던 경험입니다. 스팟 인스턴스는 분명 비용 절감에 효과적이지만, 안정성이 중요한 서비스에는 온디맨드 인스턴스를 쓰는 것이 현명합니다. 항상 워크로드의 특성을 고려해서 적절한 인스턴스 유형을 선택해야 해요. 이 경험을 통해 내결함성(Fault Tolerance) 설계의 중요성을 다시 한번 깨달았습니다.

    최적화 효과 확인하기

    이런 전략들을 적용하고 나면, 반드시 그 효과를 측정해야겠죠? 클라우드 제공업체의 비용 청구서를 정기적으로 확인하고, Kubecost 같은 도구에서 제공하는 월별 비용 보고서를 분석해보세요. 시간이 지남에 따라 비용이 어떻게 변화하는지 추이를 보는 것이 정말 중요합니다. 저희 팀도 최적화 전략 적용 후 약 3개월 만에 클라우드 비용을 20% 이상 절감하는 성과를 거두었습니다. ✅

    쿠버네티스 비용 최적화 후 월별 비용 절감 효과 그래프

    마무리하며: FinOps는 선택이 아닌 필수

    오늘 쿠버네티스 비용 최적화를 위한 5가지 핵심 전략에 대해 이야기해 봤습니다. 정리하자면, 리소스 요청/제한 정확히 설정하기, 클러스터 오토스케일링 활용하기, 스팟 인스턴스 적극 활용하기, Kubecost 같은 비용 모니터링 도구 사용하기, 그리고 네임스페이스/라벨 기반 비용 할당입니다.

    클라우드 환경, 특히 쿠버네티스 환경에서의 비용 관리는 더 이상 개발이나 운영팀만의 문제가 아닙니다. FinOps(파인옵스)는 재무(Finance)와 개발/운영(DevOps)의 결합으로, 클라우드 비용을 효율적으로 관리하고 최적화하기 위한 문화와 실천을 의미합니다. 클라우드 비용 관리는 이제 선택이 아니라 필수가 된 거죠.

    제가 알려드린 전략들을 적용해보면서 여러분만의 최적화 방법을 찾아나가시길 바랍니다. 처음엔 어렵고 복잡하게 느껴질 수 있지만, 꾸준히 노력하면 분명 좋은 결과를 얻을 수 있을 거예요. 다음번에는 Kubecost를 활용한 좀 더 심화된 분석 방법에 대해 다뤄볼까 합니다. 기대해주세요! 다음 글에서 만나요! 👋

    FinOps 쿠버네티스 비용 관리 워크플로우 인포그래픽

  • [Kubernetes] Helm 차트 비용 최적화 전략: 클라우드 리소스 낭비 줄이기

    [Kubernetes] Helm 차트 비용 최적화 전략: 클라우드 리소스 낭비 줄이기

    [Kubernetes] Helm 차트 비용 최적화 전략: 클라우드 리소스 낭비 줄이기

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 클라우드 비용 때문에 밤잠 설치셨던 분들을 위한 이야기를 해볼까 합니다. <code>Kubernetes 환경에서 Helm을 쓰다 보면 편리함 뒤에 숨겨진 클라우드 리소스 낭비라는 복병을 만나게 될 때가 많거든요. 저도 처음엔 이게 뭔가 싶었는데, 월말 청구서를 받아보고 깜짝 놀라 아찔했던 경험이 한두 번이 아닙니다. 😮

    특히 Helm 차트는 재사용성과 배포 편의성을 높여주지만, 기본값이 너무 후하거나, 최적화 없이 무심코 배포하다 보면 불필요하게 많은 리소스를 할당하게 됩니다. 결국 이 모든 게 불어나는 클라우드 비용으로 이어지죠. 그래서 오늘은 제가 직접 삽질하며 터득한 Helm 비용 최적화 전략에 대해 이야기해볼까 합니다. 쿠버네티스 비용 절감, 함께 고민하고 해결해나가 봐요!

    클라우드 환경에서 Helm을 사용하여 Kubernetes 리소스를 배포하고, 이 과정에서 리소스 낭비가 발생하는 상황을 개념적으로 보여주는 다이어그램입니다.

    Helm과 클라우드 비용 낭비 이해하기

    Helm(헬름)은 Kubernetes(쿠버네티스) 애플리케이션을 관리하는 패키지 매니저입니다. 복잡한 Kubernetes 배포를 Chart(차트)라는 형태로 묶어서 쉽게 설치, 업데이트, 삭제할 수 있게 해주죠. 마치 apt나 yum처럼요. 정말 편리한 도구인데, 이 편리함이 때로는 방심을 부르기도 합니다.

    많은 Helm 차트들은 ‘일단 잘 돌아가게’ 만드는 데 초점이 맞춰져 있습니다. 그래서 기본 values.yaml 파일에 명시된 리소스 요청(requests)이나 제한(limits)이 실제 필요한 것보다 훨씬 크게 잡혀있는 경우가 많아요. 예를 들어, 작은 개발 환경인데도 CPU 1코어, 메모리 2GB를 기본으로 할당한다거나 하는 식이죠. 이게 여러 개의 Pod(파드)로 복제되고, 여러 서비스가 배포되면 눈덩이처럼 불어나는 건 순식간입니다. 😱

    • 리소스 요청 (requests): Kubernetes 스케줄러가 Pod를 노드에 할당할 때 필요한 최소 리소스입니다. 이만큼은 보장해달라는 의미죠.
    • 리소스 제한 (limits): Pod가 사용할 수 있는 최대 리소스입니다. 이 이상은 사용하지 못하게 막는 거죠.

    이 두 가지 설정이 너무 높게 잡히면, 사용하지도 않는 리소스를 미리 예약하고 돈을 내는 꼴이 됩니다. 클라우드 비용 분석을 해보면 예상치 못한 곳에서 돈이 새고 있다는 걸 알 수 있어요. K8s 배포 최적화의 첫걸음은 바로 여기서 시작됩니다.

    실전 Helm 차트 리소스 최적화 단계

    1. 정확한 리소스 요청/Limit 설정

    가장 기본적이면서도 중요한 단계입니다. values.yaml 파일에서 각 컨테이너의 resources 섹션을 꼼꼼히 검토해야 합니다. 제가 직접 운영하던 서비스에서 컨테이너들이 실제 얼마나 리소스를 쓰는지 모니터링 툴로 쭉 살펴봤거든요. 처음엔 그냥 대충 넣어놨었는데, 실제로 써보니 CPU는 0.1코어, 메모리는 256MB면 충분하더라고요. 이걸 1코어 1GB로 해놓고 있었다니… 🤦‍♂️

    예시 values.yaml:

    # myapp/values.yaml
    replicaCount: 2
    
    image:
      repository: myapp/web
      pullPolicy: IfNotPresent
      tag: "1.0.0"
    
    resources:
      requests:
        cpu: 100m  # 0.1 core
        memory: 256Mi
      limits:
        cpu: 200m  # 0.2 core
        memory: 512Mi
    
    service:
      type: ClusterIP
      port: 80
    

    여기서 cpu: 100m은 0.1 코어를 의미하고, memory: 256Mi는 256 메비바이트를 의미합니다. 처음에는 조금 타이트하게 설정하고, 서비스 운영 중 모니터링하면서 점진적으로 늘려나가는 것을 추천해요. ⚠️ 너무 타이트하게 잡으면 OOMKilled(메모리 부족으로 인한 강제 종료)나 CPU Throttling(CPU 사용 제한으로 인한 성능 저하)이 발생할 수 있으니 주의해야 합니다.

    2. HPA, VPA를 활용한 자동 스케일링

    수동으로 리소스를 조정하는 건 한계가 있습니다. 사용량에 따라 자동으로 리소스를 조절해주는 Horizontal Pod Autoscaler (HPA, 수평 Pod 자동 확장)와 Vertical Pod Autoscaler (VPA, 수직 Pod 자동 확장)를 적극 활용해야 합니다. 제가 홈랩에서 여러 서비스에 적용해봤는데, 확실히 피크 타임에는 리소스를 늘려주고, 한가할 때는 줄여주니 Helm 리소스 관리에 엄청난 도움이 되더라고요!

    • HPA: Pod의 개수를 자동으로 늘리거나 줄입니다. CPU 사용률, 메모리 사용률, 또는 커스텀 메트릭을 기준으로 합니다.
    • VPA: Pod의 requests와 limits를 자동으로 조절합니다. Pod를 재시작해야 적용되는 경우가 많으니 주의해야 합니다.

    values.yaml에 HPA를 추가하는 예시:

    # myapp/values.yaml
    ...
    hpa:
      enabled: true
      minReplicas: 1
      maxReplicas: 5
      targetCPUUtilizationPercentage: 70 # CPU 사용률이 70%를 넘으면 Pod 개수 증가
      targetMemoryUtilizationPercentage: 80 # 메모리 사용률이 80%를 넘으면 Pod 개수 증가
    ...
    

    VPA는 조금 더 복잡한 설정이 필요하지만, Kubernetes 클러스터에 VPA 컨트롤러가 설치되어 있다면 values.yaml에서 간단히 활성화할 수 있습니다. K8s 배포 최적화를 위한 필수 요소라고 생각합니다.

    Helm 차트 values.yaml 리소스, HPA, VPA 설정 구성 다이어그램

    Helm 차트의 values.yaml 파일에서 리소스 요청(requests), 제한(limits), HPA, VPA 설정을 보여주는 YAML 코드 스니펫과 설명이 포함된 구성 다이어그램입니다.

    3. 불필요한 리소스 제거 및 효율적인 차트 설계

    가끔 Helm 차트를 보면 기본적으로 따라오는 사이드카 컨테이너나 불필요한 리소스들이 있어요. 예를 들어, 로깅 에이전트나 모니터링 에이전트가 모든 Pod에 기본적으로 포함되어 있는데, 이미 클러스터 레벨에서 DaemonSet(데몬셋)으로 관리하고 있다면 중복일 수 있습니다. 이런 것들은 과감히 values.yaml에서 enabled: false로 꺼버려야 합니다.

    • 불필요한 컨테이너/Sidecar(사이드카) 비활성화: values.yaml을 확인하여 사용하지 않는 기능을 끄세요.
    • 효율적인 _helpers.tpl 활용: 공통적으로 사용되는 라벨, 어노테이션 등을 _helpers.tpl 파일에 정의하여 중복을 줄이고 일관성을 유지합니다.
    • Node Affinity(노드 어피니티) / Tolerations(톨러레이션): 특정 워크로드를 특정 노드에 배치하여 리소스 활용도를 높일 수 있습니다. 예를 들어, GPU 노드에만 특정 머신러닝 워크로드를 배치하는 거죠.

    ⚠️ 주의사항과 삽질 경험

    Helm 비용 최적화는 한 번에 끝나지 않는 여정입니다. 저도 여러 번 뼈아픈 경험을 했는데요.

    • 과도한 리소스 절감은 독이 됩니다: 너무 아끼려다가 서비스가 느려지거나 뻗어버리는 경험, 저도 해봤습니다. 😭 결국 비싼 야간 작업비와 고객 불만으로 이어지더라고요. 항상 모니터링과 테스트가 병행되어야 합니다.
    • Helm Release(헬름 릴리즈) 정리: helm uninstall만으로는 완전히 사라지지 않는 경우가 있습니다. --purge 옵션을 사용하거나, Kubernetes 리소스를 직접 확인해서 삭제하는 습관을 들이세요. Secret(시크릿)이나 PersistentVolumeClaim(영구 볼륨 클레임) 같은 것들이 남아있어 불필요한 비용을 발생시키기도 합니다.
    • VPA와 HPA의 충돌: VPA와 HPA를 동시에 사용할 때 주의해야 합니다. 서로 다른 방식으로 스케일링을 시도하기 때문에 충돌이 발생할 수 있습니다. 일반적으로 VPA는 requests/limits를 조절하고, HPA는 Pod 개수를 조절하므로, VPA가 requests/limits를 관리하고 HPA는 replicaCount만 조절하도록 설정하는 것이 좋습니다.

    검증 및 지속적인 모니터링

    최적화 작업을 마쳤다면, 반드시 그 효과를 검증해야 합니다. Prometheus(프로메테우스)와 Grafana(그라파나) 같은 모니터링 툴을 활용해서 Pod별 CPU, 메모리 사용량을 꾸준히 지켜봐야 합니다. 클라우드 제공업체의 대시보드에서 실제 청구되는 비용도 확인해야겠죠. 저는 최적화 작업 후 한 달 정도 지켜보니, 이전보다 CPU 사용률은 높아졌지만, 전체 Pod 개수는 줄고 클라우드 비용도 눈에 띄게 줄어드는 걸 보면서 얼마나 뿌듯했는지 모릅니다. 🎉

    Helm 비용 최적화 전후 클라우드 리소스 사용량 및 비용 변화 대시보드 그래프

    최적화 전후의 클라우드 리소스 사용량 및 비용 변화를 보여주는 대시보드 그래프입니다.

    마무리하며: Helm 비용 최적화는 끝없는 여정

    Helm 차트 비용 최적화는 한 번 설정하고 끝나는 작업이 아닙니다. 서비스의 트래픽 패턴이 바뀌거나, 새로운 기능을 추가할 때마다 리소스 요구사항도 달라지거든요. 그래서 주기적인 검토와 튜닝이 필수입니다. 마치 자동차 정비하듯이요.

    오늘 제가 공유한 Helm 리소스 관리 팁들이 여러분의 클라우드 비용 절감에 조금이나마 도움이 되었으면 좋겠습니다. 처음엔 복잡하고 어렵게 느껴질 수 있지만, 한번 손에 익으면 클라우드 자원을 훨씬 효율적으로 사용할 수 있게 될 거예요. FinOps(파인옵스)의 관점에서 봐도, 엔지니어가 직접 비용 최적화에 참여하는 것은 매우 중요합니다.

    혹시 이런 경험 있으신가요? 아니면 더 좋은 팁이 있다면 댓글로 공유해주세요! 저도 계속 배우고 있습니다. 다음 글에서는 더 재미있는 Kubernetes 이야기로 찾아오겠습니다. 그때까지 모두 즐거운 삽질(?) 되시길 바랍니다! 😊

    Helm 비용 최적화 핵심 전략 요약 및 비용 절감 효과 비교 인포그래픽

    Helm 비용 최적화의 핵심 전략들을 요약하고, 최적화 전후의 비용 절감 효과를 인포그래픽 형태로 비교하는 이미지입니다.

  • [Cloud] AWS IAM 권한 설계 베스트 프랙티스 – CISA GovCloud 키 유출 사건으로 배우는 클라우드 보안

    [Cloud] AWS IAM 권한 설계 베스트 프랙티스 – CISA GovCloud 키 유출 사건으로 배우는 클라우드 보안

    AWS IAM 권한 설계 베스트 프랙티스 – CISA GovCloud 키 유출 사건으로 배우는 클라우드 보안

    안녕하세요, 13년차 서버실 지킴이 ’13년차의 서버실’입니다. 오늘은 최근 발생했던 흥미롭지만 무서운 사건을 통해 AWS IAM 권한 설계의 중요성을 다시 한번 되짚어보려고 합니다. 바로 CISA (Cybersecurity and Infrastructure Security Agency)의 AWS GovCloud 키 유출 사건인데요. 미국 연방 기관에서 이런 보안 사고가 터졌다는 소식을 듣고 저도 처음엔 ‘설마 저런 일이…’ 싶었는데, 내용을 살펴보니 우리가 평소에 간과하기 쉬운 부분에서 발생한 문제더라고요.

    솔직히 인프라 엔지니어라면 누구나 한 번쯤은 ‘빨리 배포해야 하는데’, ‘이거 권한 없어서 안 되네, 그냥 다 열어버릴까?’ 하는 유혹에 빠져본 적 있을 겁니다. 저도 처음엔 이게 뭔가 싶어서 그냥 `*` (와일드카드)로 대충 권한 설정했다가 나중에 등골이 오싹했던 경험이 있거든요. 실제로 이런 클라우드 보안 사고가 터지면 수습하기 정말 힘들고, 기업 이미지에도 치명타를 입을 수 있습니다. 그래서 오늘은 CISA 사건을 교훈 삼아, AWS IAM(Identity and Access Management) 권한 설계를 어떻게 해야 안전하고 효율적으로 할 수 있을지, 제 삽질 경험을 녹여서 이야기해볼까 합니다.

    AWS IAM이 중심이 되는 클라우드 보안 아키텍처 다이어그램

    클라우드 환경에서 IAM은 단순한 사용자 관리를 넘어 전체 보안의 핵심입니다.

    AWS IAM, 최소 권한 원칙(Principle of Least Privilege)이란?

    본격적인 이야기에 앞서, 핵심 개념인 AWS IAM과 최소 권한 원칙(Principle of Least Privilege)에 대해 잠깐 짚고 넘어갈게요.

    • AWS IAM (Identity and Access Management, 아이덴티티 및 접근 관리): AWS 클라우드 리소스에 대한 접근을 안전하게 관리하는 웹 서비스입니다. 누가 어떤 리소스에 어떤 작업을 할 수 있는지 정의하는 역할을 하죠. 쉽게 말해, ‘회사 문지기’라고 생각하시면 편합니다. 누가 회사에 들어올 수 있고(인증), 어디까지 갈 수 있는지(권한 부여)를 결정하는 거죠.
    • 최소 권한 원칙 (Principle of Least Privilege): 이 원칙은 ‘필요한 최소한의 권한만 부여하라’는 강력한 보안 철학입니다. 예를 들어, 웹 서버가 S3 버킷에 파일을 업로드해야 한다면, S3에 파일을 읽고 쓰는 권한만 주면 됩니다. S3의 모든 설정 변경 권한이나 다른 서비스의 권한까지 줄 필요는 없다는 거죠. CISA 사건도 이 최소 권한 원칙이 제대로 지켜지지 않아 발생한 부분이 크다고 볼 수 있습니다.

    AWS IAM은 크게 IAM 사용자(User), IAM 그룹(Group), IAM 역할(Role), 그리고 이들의 권한을 정의하는 정책(Policy)으로 구성됩니다. 이 네 가지 요소를 잘 조합해서 안전한 환경을 만드는 게 핵심이에요.

    실전 구현: AWS IAM 권한 설계 베스트 프랙티스

    자, 이제 저의 홈랩에서 직접 적용하고 있는 AWS IAM 권한 설계 베스트 프랙티스 몇 가지를 소개해드릴게요. 이건 제가 직접 여러 번의 삽질 끝에 깨달은 소중한 경험들이거든요.

    1. 루트 사용자(Root User)는 금고에! MFA(Multi-Factor Authentication)는 필수!

      AWS 계정을 처음 만들면 ‘루트 사용자’가 생성되죠? 이 루트 사용자는 계정의 모든 권한을 가지고 있는 슈퍼 계정입니다. 절대! 일상적인 작업에 사용하면 안 됩니다. 저도 처음엔 그냥 루트 계정으로 다 했었는데, 나중에 보안 교육을 받고 식겁했어요. 루트 사용자는 계정 생성 후 한 번만 사용해서 IAM 사용자를 만들고, 강력한 암호와 함께 MFA (Multi-Factor Authentication, 다단계 인증)를 설정한 뒤 금고에 넣어두세요. 그리고 다시는 사용하지 않는 것이 좋습니다. 혹시 아직 MFA 설정 안 하셨다면 지금 당장 설정하세요! ⚠️

    2. IAM 사용자(User) 대신 IAM 역할(Role)을 적극 활용하세요!

      많은 분들이 EC2 인스턴스나 람다(Lambda) 함수에 권한을 줄 때, IAM 사용자 자격 증명(Access Key, Secret Key)을 직접 코드에 하드코딩하는 경우가 있습니다. 절대 금물입니다! 이 키가 유출되면 CISA 사건처럼 큰일 날 수 있어요. 대신 IAM 역할(Role)을 사용해야 합니다. IAM 역할은 특정 AWS 서비스나 사용자가 임시 자격증명을 자동으로 받아서 다른 리소스에 접근할 수 있게 해주는 기능이거든요. 키를 직접 관리할 필요가 없어서 훨씬 안전하고 편리하죠.

      예를 들어, EC2 인스턴스가 S3 버킷에 파일을 업로드해야 한다면, 다음과 같은 정책을 가진 IAM 역할을 만들어서 EC2 인스턴스에 할당하면 됩니다.

      {
      "Version": "2012-10-17",
      "Statement": [
      {
      "Effect": "Allow",
      "Action": [
      "s3:PutObject",
      "s3:GetObject",
      "s3:ListBucket"
      ],
      "Resource": [
      "arn:aws:s3:::my-secure-bucket",
      "arn:aws:s3:::my-secure-bucket/*"
      ]
      }
      ]
      }

      이 역할은 <code>my-secure-bucket이라는 S3 버킷에 대해서만 객체를 넣고, 가져오고, 버킷 내용을 나열하는 최소한의 권한만 부여합니다. 🎉

    3. 최소 권한 원칙에 기반한 정책(Policy) 설계

      위 예시처럼, 필요한 최소한의 작업(Action)과 리소스(Resource)만 명시하는 커스텀 정책(Custom Policy)을 만드는 습관을 들여야 합니다. AWS에서 제공하는 관리형 정책(Managed Policy)도 편리하지만, 너무 포괄적인 권한을 포함하는 경우가 많아요. 예를 들어, AmazonS3FullAccess 정책은 S3의 모든 작업을 허용하는데, 이건 대부분의 경우 과도한 권한입니다. 제가 직접 해보니 커스텀 정책이 손은 더 가지만, 훨씬 안전하더라고요. 처음엔 좀 어렵게 느껴질 수 있지만, 몇 번 만들어보면 익숙해집니다.

    4. 조건 키(Condition Keys)를 활용한 정교한 제어

      IAM 정책은 단순히 ‘누가 무엇을 할 수 있는가’를 넘어, ‘언제, 어디서, 어떻게’ 할 수 있는가까지 제어할 수 있습니다. 바로 조건 키(Condition Keys)를 활용하는 건데요. 특정 IP 주소 대역에서만 접근을 허용하거나, MFA 인증이 된 사용자만 특정 작업을 수행할 수 있도록 강제하는 등의 설정이 가능합니다. CISA 사건처럼 키가 유출되더라도, 추가적인 조건이 없다면 접근을 막을 수 있는 강력한 방어선이 됩니다.

      {
      "Version": "2012-10-17",
      "Statement": [
      {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-secure-bucket/*",
      "Condition": {
      "IpAddress": {
      "aws:SourceIp": "203.0.113.0/24"
      },
      "Bool": {
      "aws:MultiFactorAuthPresent": "true"
      }
      }
      }
      ]
      }

      위 정책은 203.0.113.0/24 IP 대역에서 MFA 인증을 거친 사용자만 S3 객체를 가져올 수 있도록 허용합니다. 💡

    5. AWS Access Analyzer로 권한 검토하기

      아무리 열심히 정책을 설계해도, 사람인지라 실수는 할 수 있습니다. 이때 AWS Access Analyzer가 정말 큰 도움이 됩니다. 이 서비스는 외부 엔티티(다른 AWS 계정, IAM 사용자, 역할 등)가 내 계정의 리소스(S3 버킷, SQS 큐 등)에 접근할 수 있는 경로를 분석하고 경고해줍니다. 제가 직접 써보니 예상치 못한 경로로 외부에 리소스가 공유된 걸 찾아내서 바로 조치할 수 있었어요. 정기적으로 Access Analyzer를 돌려 외부 접근 권한을 점검하는 습관을 들이는 게 좋습니다.

    AWS Access Analyzer 대시보드 화면

    Access Analyzer는 예상치 못한 권한 설정을 찾아내고 시각적으로 경고해줍니다.

    ⚠️ 주의사항 및 트러블슈팅: 삽질은 나의 힘!

    저도 AWS IAM 권한 설계를 하면서 수많은 삽질을 했습니다. 여러분은 저 같은 실수 하지 마시라고 몇 가지 팁을 공유해드려요.

    • 와일드카드(`*`) 사용은 정말 신중하게!

      특히 Action: "*"이나 Resource: "*"는 치명적일 수 있습니다. 처음엔 귀찮아서 이렇게 줬다가 나중에 ‘아차!’ 싶었던 적이 한두 번이 아니거든요. 항상 필요한 최소한의 범위만 지정하는 연습을 해야 합니다.

    • IAM Policy Simulator로 미리 테스트해보세요

      정책을 만들었는데 제대로 작동하는지 확신이 없을 때, IAM Policy Simulator를 사용하면 아주 유용합니다. 특정 사용자/역할이 특정 리소스에 대해 특정 작업을 수행할 수 있는지 시뮬레이션해볼 수 있거든요. 이거 써보니 삽질 많이 줄었습니다. ‘아, 이렇게 하면 되는구나!’ 하고 바로 알 수 있어서 진짜 편하더라고요.

    • 너무 빡빡한 권한은 오히려 문제!

      최소 권한 원칙도 좋지만, 너무 권한을 빡빡하게 설정해서 정작 필요한 작업이 안 되는 경우도 있습니다. 예를 들어, S3 버킷에 객체를 업로드하는 권한만 줬는데, 버킷 리스트를 볼 수 없어서 디버깅이 어려운 경우가 있었어요. 이때는 임시로 디버깅용 권한을 추가했다가, 문제 해결 후 다시 최소 권한으로 돌리는 유연함도 필요하더라고요. 역시 디버깅이 중요하더라고요.

    AWS IAM Policy Simulator 사용 화면

    정책 적용 전 IAM Policy Simulator로 미리 검증하면 불필요한 오류를 줄일 수 있습니다.

    AWS Access Analyzer를 통한 최종 검증

    권한 설정을 모두 마쳤다면, 다시 한번 AWS Access Analyzer를 통해 점검하는 과정을 거쳐야 합니다. 저는 이 과정을 습관처럼 하고 있는데요, 실제로 개발팀에서 실수로 특정 S3 버킷을 ‘Public’으로 열어둔 것을 Access Analyzer가 잡아내서 바로 수정했던 경험이 있습니다. 이렇게 검증하는 과정이 진짜 중요합니다. 단순한 실수가 큰 보안 사고로 이어질 수 있거든요.

    Access Analyzer는 지속적으로 계정을 모니터링하여 예상치 못한 외부 접근을 감지하고, 이에 대한 상세한 보고서를 제공합니다. 이 보고서를 바탕으로 정책을 조정하거나, 문제가 되는 설정을 수정하는 것이 중요합니다. ✅

    마무리하며: 보안은 끝이 없는 마라톤!

    오늘은 CISA AWS GovCloud 키 유출 사건을 통해 AWS IAM 권한 설계의 중요성과 베스트 프랙티스에 대해 이야기해봤습니다. 13년차 인프라 엔지니어로 살아오면서 느낀 건데, 보안은 정말 끝이 없는 마라톤과 같아요. 한 번 설정했다고 끝이 아니라, 지속적으로 검토하고 개선해야 합니다.

    오늘 제가 소개해드린 내용은 AWS IAM 권한 설계의 기본적인 베스트 프랙티스이지만, 이것만 잘 지켜도 대부분의 보안 위협으로부터 계정을 안전하게 보호할 수 있을 겁니다. 특히 최소 권한 원칙과 IAM 역할 활용은 꼭 기억해주세요. 혹시 이 글을 읽고 여러분의 AWS 환경을 다시 한번 점검해보는 계기가 된다면, 정말 기쁠 것 같네요.

    다음 글에서는 AWS GuardDuty나 Security Hub 같은 다른 보안 서비스들을 활용한 클라우드 보안 강화 방안에 대해 다뤄볼까 합니다. 기대해주세요! 👋

    AWS IAM 권한 설계 베스트 프랙티스 요약 인포그래픽

    안전한 AWS 환경을 위한 IAM 베스트 프랙티스 요약.

  • [Cloud] Pulumi vs Terraform: 멀티 클라우드 IaC 선택 가이드 및 실전 활용

    [Cloud] Pulumi vs Terraform: 멀티 클라우드 IaC 선택 가이드 및 실전 활용

    IaC(Infrastructure as Code)는 왜 필요할까요?

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘도 여전히 복잡한 인프라 구성에 머리 싸매고 계신가요? 요즘 같은 멀티 클라우드(Multi-Cloud) 시대에는 온프레미스(On-Premise)와 여러 클라우드 벤더(Cloud Vendor)를 넘나들며 인프라를 관리해야 하는 경우가 흔하죠. 저도 처음엔 수동으로 서버 올리고 네트워크 설정하고… 휴, 생각만 해도 아찔하네요. 사람이 일일이 하다 보니 휴먼 에러(Human Error)는 기본이고, 속도는 느려터지고, 무엇보다 재현성이 없어서 문제였습니다.

    그래서 등장한 개념이 바로 IaC(Infrastructure as Code, 코드형 인프라)예요. 쉽게 말해, 인프라를 코드로 정의하고 관리하는 방식이죠. Git 같은 버전 관리 시스템으로 관리할 수 있고, 자동화는 물론 팀원들과의 협업도 훨씬 수월해집니다. 제가 홈랩에서 이것저것 테스트해볼 때도, IaC 덕분에 환경을 순식간에 구축하고 파괴하면서 실험할 수 있었거든요. 시간 절약이 어마어마해요!

    멀티 클라우드 환경에서 IaC를 사용하는 인프라 프로비저닝 개념 다이어그램

    Terraform vs Pulumi: 핵심 개념 파헤치기

    IaC 도구는 정말 다양합니다만, 현재 시장에서 가장 강력한 양대 산맥을 꼽으라면 단연 Terraform(테라폼)과 Pulumi(풀루미)일 겁니다. 두 도구 모두 클라우드 인프라를 코드로 정의하고 배포할 수 있게 해주지만, 접근 방식에 큰 차이가 있어요. 저도 처음엔 뭐가 뭔지 헷갈려서 한참을 삽질했었죠.

    Terraform (테라폼)

    • 선언적 언어(Declarative Language): Terraform은 HCL(HashiCorp Configuration Language)이라는 자체 언어를 사용해요. "최종 상태가 이렇게 되어야 한다"라고 선언하면, Terraform이 현재 상태와 비교해서 필요한 변경 사항을 알아서 적용해줍니다. 예를 들어, aws_instance 리소스 블록을 정의하면, Terraform이 AWS에 해당 인스턴스를 생성하거나 업데이트하죠.
    • 상태 파일(State File) 관리: Terraform은 배포된 인프라의 현재 상태를 .tfstate 파일에 기록해요. 이 상태 파일을 통해 다음에 어떤 변경 사항을 적용할지 판단하는데, 이 파일 관리가 꽤 중요하고 때론 골치 아울 때도 있어요. S3 같은 원격 스토리지(Remote Storage)에 저장하고 잠금(Locking) 기능을 사용하는 게 일반적이에요.
    • 모듈(Modules): 재사용 가능한 인프라 구성 단위를 모듈로 만들 수 있어서, 복잡한 인프라도 효율적으로 관리할 수 있어요.

    Pulumi (풀루미)

    • 범용 프로그래밍 언어(General-Purpose Programming Language): Pulumi의 가장 큰 특징은 Python, TypeScript, Go, C# 등 여러분이 익숙한 프로그래밍 언어를 그대로 사용한다는 점이에요. 이게 진짜 매력적입니다! 일반 애플리케이션 개발하듯이 인프라 코드를 작성할 수 있어요. 조건문, 반복문, 함수 등 프로그래밍 언어의 모든 기능을 활용할 수 있다는 것이 큰 장점이죠.
    • 상태 관리(State Management): Pulumi도 인프라 상태를 관리하지만, 기본적으로 Pulumi 서비스에서 관리해주거나 S3, Azure Blob Storage 등 다양한 백엔드(Backend)를 지원해요. Terraform처럼 로컬 .tfstate 파일에 얽매이지 않아서 좀 더 유연하더라고요.
    • 테스트 용이성(Testability): 프로그래밍 언어의 장점을 살려 단위 테스트(Unit Test), 통합 테스트(Integration Test) 등을 적용하기가 훨씬 쉬워요. 저도 TypeScript로 Pulumi 코드를 작성하면서 Jest 같은 프레임워크로 테스트를 붙여보니, 안정성이 확 올라가더라고요.

    멀티 클라우드 환경에서 Pulumi와 Terraform 비교 분석

    자, 그럼 두 도구를 멀티 클라우드 관점에서 비교해볼까요? 제가 여러 프로젝트에 적용해보면서 느낀 점들을 솔직하게 말씀드릴게요.

    특징 Terraform Pulumi
    언어 HCL (HashiCorp Configuration Language) Python, TypeScript, Go, C#, Java 등 범용 프로그래밍 언어
    학습 곡선 HCL 학습 필요 (비교적 쉬움) 선택 언어에 익숙하다면 낮음
    추상화 수준 선언적, 모듈 기반 프로그래밍 언어의 강력한 추상화 및 로직 구현 가능
    상태 관리 .tfstate 파일 (로컬/원격), 잠금 기능 필수 Pulumi 서비스 또는 다양한 백엔드 지원, 더 유연함
    멀티 클라우드 지원 각 클라우드 프로바이더(Provider) 플러그인 각 클라우드 SDK 기반 라이브러리 (더 깊은 통합 가능)
    테스트 terraform plan, 외부 도구 활용 프로그래밍 언어의 테스트 프레임워크 활용 용이
    확장성 프로바이더 개발, 프로비저너(Provisioner) 프로그래밍 언어의 모든 기능 활용, SDK 개발
    커뮤니티/생태계 오래되고 매우 활발함, 방대한 자료 빠르게 성장 중, 비교적 최신

    Terraform은 꽤 오랫동안 IaC 시장을 지배해왔기 때문에 커뮤니티 자료나 레퍼런스가 정말 많아요. 저도 처음엔 Terraform으로 시작했고요. 반면 Pulumi는 비교적 최근에 떠오른 도구지만, 프로그래밍 언어의 유연성 때문에 개발자 친화적이라는 평이 많습니다. 특히 복잡한 로직이 필요한 인프라 구성이나 기존 개발 워크플로우(Workflow)와 통합하고 싶을 때 Pulumi가 빛을 발하더라고요.

    실전 활용: 간단한 웹 서버 배포 (Terraform vs Pulumi)

    백문이 불여일견이죠! AWS에 간단한 EC2 인스턴스(Instance)를 배포하는 예제를 통해 두 도구의 차이를 직접 느껴보시죠. 제 홈랩에서도 늘 이렇게 시작하곤 합니다.

    Terraform 예제

    main.tf 파일에 다음과 같이 작성해요.

    # main.tf
    
    provider "aws" {
      region = "ap-northeast-2" # 서울 리전
    }
    
    resource "aws_instance" "web_server" {
      ami           = "ami-0a0c4fdd9d45e4895" # Ubuntu 20.04 LTS (서울 리전 기준)
      instance_type = "t2.micro"
      tags = {
        Name = "MyWebServer-Terraform"
      }
    }
    

    명령어는 간단해요.

    terraform init
    terraform plan
    terraform apply --auto-approve
    

    terraform init으로 프로바이더를 초기화하고, terraform plan으로 어떤 변경 사항이 생길지 미리 확인합니다. 마지막으로 terraform apply로 인프라를 배포하죠. 정말 직관적이에요!

    Pulumi 예제 (TypeScript)

    index.ts 파일에 다음과 같이 작성해요.

    // index.ts
    
    import * as aws from "@pulumi/aws";
    
    const webServer = new aws.ec2.Instance("web-server-pulumi", {
        ami: "ami-0a0c4fdd9d45e4895", // Ubuntu 20.04 LTS (서울 리전 기준)
        instanceType: "t2.micro",
        tags: {
            Name: "MyWebServer-Pulumi",
        },
    });
    
    export const instanceId = webServer.id;
    export const publicIp = webServer.publicIp;
    

    명령어는 다음과 같아요.

    pulumi stack init dev # 스택 초기화 (환경 분리)
    pulumi up --yes # 인프라 배포
    

    Pulumi는 스택(Stack) 개념이 있어서 개발, 스테이징, 프로덕션 환경을 깔끔하게 분리할 수 있어요. pulumi up 명령어로 배포하고, --yes 옵션으로 확인 과정을 생략할 수 있습니다. 프로그래밍 언어의 문법을 그대로 따르기 때문에, 개발자라면 훨씬 친숙하게 느껴지실 거예요.

    Terraform과 Pulumi로 생성된 AWS EC2 인스턴스 목록

    ⚠️ 제가 겪었던 삽질과 트러블슈팅 경험

    13년차 엔지니어도 삽질은 피할 수 없죠! 저도 이 두 도구를 쓰면서 여러 번 멘붕에 빠졌어요. 몇 가지 기억에 남는 삽질 경험과 해결법을 공유할게요.

    Terraform: 상태 파일 관리의 늪

    제일 많이 겪었던 건 상태 파일(State File) 충돌이에요. 팀원 여럿이 동시에 같은 인프라를 건드리다 보면 .tfstate 파일이 꼬이는 경우가 생기더라고요. 특히 협업 툴(Collaboration Tool)이나 CI/CD 파이프라인(Pipeline) 없이 수동으로 작업할 때 심했습니다. 상태 파일이 꼬이면 terraform plan 결과가 이상하게 나오거나, 심지어 실제 인프라와 상태 파일 간의 불일치(Drift)가 발생해서 엉뚱한 리소스가 삭제될 뻔한 적도 있었죠. 😱

    • 해결법: 항상 원격 백엔드(Remote Backend) (예: AWS S3 + DynamoDB Lock)를 사용하고, 상태 잠금(State Locking)을 활성화해야 해요. 그리고 CI/CD 파이프라인을 구축해서 모든 변경 사항은 파이프라인을 통해서만 적용되도록 강제하는 것이 가장 안전합니다.

    Pulumi: 언어 버전 의존성

    Pulumi는 범용 프로그래밍 언어를 사용하다 보니, 언어 버전이나 패키지 의존성(Package Dependency) 문제로 삽질한 적도 있어요. 예를 들어, 특정 Pulumi AWS 라이브러리가 특정 Node.js 버전에서만 제대로 동작한다든지, npm install 과정에서 에러가 나는 경우요. 이게 진짜 별것 아닌 것 같아도, 디버깅(Debugging)하다 보면 시간을 꽤 잡아먹어요. 😅

    • 해결법: package.json(TypeScript/Node.js)이나 requirements.txt(Python)에 정확한 버전 정보를 명시하고, nvm(Node Version Manager)이나 pyenv(Python Version Manager) 같은 도구로 개발 환경을 일관되게 유지하는 것이 중요해요. 그리고 Pulumi 관련 라이브러리들도 최신 버전으로 꾸준히 업데이트해주는 게 좋아요.

    공통: 리프레시 명령어의 함정

    가끔 실제 인프라가 변경되었는데 상태 파일에 반영이 안 되는 불일치가 생기면 terraform refresh나 pulumi refresh 명령어를 사용하게 되죠. 그런데 이 명령어를 너무 맹신하면 안 돼요. 특히 중요한 리소스에 대해서는 리프레시 전에 항상 백업(Backup)을 해두거나, plan 명령어로 예상 변경 사항을 꼼꼼히 확인하는 습관을 들이세요. 예전에 리프레시 한 번 잘못했다가 스테이징 환경이 날아갈 뻔한 아찔한 경험도 있습니다. ⚠️

    그래서, 어떤 IaC 도구를 선택해야 할까요?

    결론부터 말씀드리자면, "정답은 없다"예요! (싱거운가요? ㅎㅎ) 하지만 여러분의 상황에 맞는 최적의 선택은 분명히 있습니다.

    Terraform을 선택해야 하는 경우

    • 인프라 전문 팀/운영 팀이 주도적으로 IaC를 도입할 때: HCL은 인프라 관리에 특화되어 있어 직관적이에요.
    • 방대한 기존 레퍼런스와 안정적인 커뮤니티 지원이 중요할 때: 오래된 만큼 자료가 정말 많습니다.
    • 비교적 단순하고 선언적인 인프라 구성이 많을 때: 복잡한 로직 없이 리소스 정의만으로 충분한 경우요.
    • GitOps(깃옵스) 워크플로우를 강력하게 따르고자 할 때: Terraform Cloud/Enterprise 같은 솔루션과 통합이 잘 되어 있어요.

    Pulumi를 선택해야 하는 경우

    • 개발 팀이 직접 인프라를 관리하거나, 개발 워크플로우에 IaC를 통합하고 싶을 때: 익숙한 프로그래밍 언어로 개발 생산성을 높일 수 있어요.
    • 복잡한 로직이나 조건부 인프라 구성이 필요할 때: 프로그래밍 언어의 모든 기능을 활용하여 동적인 인프라를 만들 수 있습니다.
    • 인프라 코드에 단위 테스트/통합 테스트를 적극적으로 적용하여 안정성을 높이고 싶을 때요.
    • 서버리스(Serverless) 아키텍처나 컨테이너 기반 환경(Containerized Environment)에 더 깊이 통합하고 싶을 때: Lambda 함수나 Kubernetes 리소스를 직접 코드로 제어하기가 편해요.

    Terraform과 Pulumi 선택 가이드 요약 인포그래픽

    마무리하며: 13년차 인프라 엔지니어의 조언

    오늘은 Pulumi(풀루미)와 Terraform(테라폼), 두 가지 강력한 IaC 도구에 대해 깊이 있게 다뤄봤어요. 멀티 클라우드 환경에서 인프라를 효율적으로 관리하고 자동화하는 것은 이제 선택이 아닌 필수가 되었죠. 저도 처음엔 "이걸 언제 다 배우지?" 했었는데, 막상 익숙해지니 정말 편하더라고요. 삽질 끝에 드디어 원하는 대로 인프라가 배포될 때의 그 희열은 경험해본 사람만 알 겁니다! 🎉

    두 도구 모두 강력한 장점을 가지고 있으니, 여러분의 팀 구성원들이 어떤 언어에 더 익숙한지, 어떤 유형의 인프라를 주로 다루는지를 고려해서 현명하게 선택하시길 바랍니다. 추가로 어떤 수준의 추상화와 유연성이 필요한지도 함께 생각하면서요. 가능하다면 작은 프로젝트에 두 가지를 모두 적용해보면서 장단점을 직접 느껴보는 것도 좋은 방법입니다.

    인프라 코드를 작성하는 것은 마치 소프트웨어 개발과 같아요. 지속적으로 리팩토링(Refactoring)하고, 버전 관리를 철저히 하며, 테스트를 통해 안정성을 확보하는 것이 중요해요. 이 글이 여러분의 IaC 여정에 조금이나마 도움이 되었기를 바랍니다. 다음번에는 더 유익한 주제로 찾아올게요. 감사합니다!

    홈랩에서 IaC 코드를 작성하는 13년차 인프라 엔지니어

  • [Cloud] Terraform State 관리 완벽 가이드: S3, DynamoDB 활용 모범 사례

    [Cloud] Terraform State 관리 완벽 가이드: S3, DynamoDB 활용 모범 사례

    안녕하세요, 13년차의 서버실 주인장입니다. 오늘은 인프라 엔지니어라면 한 번쯤은 머리를 싸매고 고민했을 주제, 바로 Terraform State 관리에 대해 이야기해보려고 합니다.

    제가 처음 Terraform을 접했을 때, 로컬에 생성되는 <code>terraform.tfstate 파일이 그렇게 귀찮을 수가 없더라고요. 혼자 작업할 때는 괜찮았는데, 팀원들과 함께 프로젝트를 진행하면서부터는 ‘이거 큰일 나겠다!’ 싶었습니다. 서로 다른 사람이 동시에 terraform apply를 실행하면 어떻게 될까요? 네, 상상만 해도 끔찍하죠. State 파일이 꼬여서 인프라가 엉망진창이 되는 대참사를 막기 위해, 오늘은 AWS S3와 DynamoDB를 활용한 Terraform State 관리 모범 사례를 완벽하게 파헤쳐 보려고 합니다. 💡

    Terraform State 관리의 전체 아키텍처 다이어그램입니다. S3와 DynamoDB가 어떻게 상호작용하는지 한눈에 볼 수 있습니다.

    Terraform State, 왜 중요한가요?

    Terraform State(테라폼 상태 파일)는 Terraform이 관리하는 실제 인프라 자원들의 현황을 기록한 파일입니다. 쉽게 말해, “지금 내가 관리하고 있는 인프라가 어떤 모습으로 생겼는지”를 알려주는 지도 같은 거죠. 이 파일이 있어야 Terraform은 다음에 apply를 실행했을 때, 현재 인프라 상태와 새로운 설정 파일(.tf) 간의 차이점을 파악하고, 필요한 변경 사항만 적용할 수 있습니다.

    만약 State 파일이 없거나, 여러 사람이 각자 다른 버전을 가지고 있다면 어떻게 될까요? Terraform은 인프라의 실제 상태를 알 수 없어서 엉뚱한 자원을 생성하거나, 심지어는 멀쩡한 자원을 삭제해버리는 불상사가 발생할 수 있습니다. 그래서 안정적인 IaC(Infrastructure as Code, 코드형 인프라) 운영을 위해서는 Terraform State 관리가 정말 중요합니다. 특히 팀 협업 환경에서는 이 State 파일을 안전하고 일관되게 관리하는 것이 핵심 중의 핵심입니다.

    S3 백엔드로 원격 State 관리하기

    로컬에 State 파일을 두는 것은 개인적인 실험 환경에서는 괜찮지만, 실제 프로덕션 환경이나 팀 프로젝트에서는 절대 금물입니다. 제가 예전에 홈랩에서 이것저것 테스트하다가 USB를 날려먹으면서 State 파일도 같이 날려버린 적이 있었거든요. 그때 복구하느라 며칠 밤낮을 새웠던 걸 생각하면 아직도 아찔합니다. 😨

    그래서 우리는 S3 백엔드(S3 backend)를 사용해서 Terraform State 파일을 원격으로 저장할 겁니다. AWS S3는 객체 스토리지 서비스로, 높은 내구성(durability), 가용성(availability), 그리고 버전 관리(versioning) 기능을 제공해서 Terraform State를 저장하기에 아주 적합합니다. S3에 State 파일을 저장하면 팀원 누구나 안전하게 접근할 수 있거든요.

    1. S3 버킷 생성하기

    먼저 Terraform State 파일을 저장할 S3 버킷을 생성해야 합니다. 버킷 이름은 전역적으로 고유해야 하며, 버전 관리(Versioning)를 활성화하는 것을 강력히 권장합니다. 혹시 모를 실수로 State 파일이 변경되거나 삭제되더라도 이전 버전으로 복구할 수 있거든요. 저도 예전에 실수로 terraform destroy를 잘못 날렸다가 S3 버전 관리 덕분에 한숨 돌린 적이 있습니다.

    aws s3api create-bucket \
      --bucket my-terraform-state-bucket-13years-infra \
      --region ap-northeast-2 \
      --create-bucket-configuration LocationConstraint=ap-northeast-2
    
    aws s3api put-bucket-versioning \
      --bucket my-terraform-state-bucket-13years-infra \
      --versioning-configuration Status=Enabled
    

    콘솔에서 직접 생성하셔도 됩니다. 이때 중요한 건, “버전 관리”를 꼭 켜주세요! 그리고 필요하다면 서버 측 암호화(Server-side encryption)도 적용하는 게 좋습니다. 💡

    Terraform State 저장을 위한 AWS S3 백엔드 버킷 설정 화면입니다. 버전 관리와 암호화 설정이 중요합니다.

    2. Terraform 구성 파일에 S3 백엔드 설정 추가하기

    이제 Terraform 프로젝트의 main.tf (또는 별도의 backend.tf) 파일에 S3 백엔드 설정을 추가합니다.

    terraform {
      backend "s3" {
        bucket         = "my-terraform-state-bucket-13years-infra" # 위에서 생성한 S3 버킷 이름
        key            = "global/terraform.tfstate"                 # S3 버킷 내 State 파일 경로
        region         = "ap-northeast-2"                           # S3 버킷 리전
        encrypt        = true                                       # State 파일 암호화 활성화
        # dynamodb_table = "my-terraform-locks"                     # State Locking을 위해 나중에 추가 (지금은 주석 처리)
      }
    }
    
    # 예시: VPC를 생성하는 리소스
    resource "aws_vpc" "main" {
      cidr_block = "10.0.0.0/16"
      tags = {
        Name = "MyVPC-ManagedByTerraform"
      }
    }
    

    여기서 key는 S3 버킷 내에서 State 파일이 저장될 경로와 파일명을 지정합니다. 저는 보통 프로젝트별로 또는 환경별로 구분해서 project-name/env/terraform.tfstate 이런 식으로 관리하곤 합니다.

    3. `terraform init` 실행하기

    설정을 추가했다면, 이제 terraform init 명령어를 실행해야 합니다. 이 명령어가 백엔드 설정을 초기화하고, S3 버킷에 State 파일을 생성하거나 기존 파일을 연결합니다.

    terraform init
    

    성공적으로 실행되면 다음과 같은 메시지를 볼 수 있습니다.

    Initializing the backend...
    Successfully configured the backend "s3"! Terraform will now
    persist and retrieve its state from this configuration.
    
    ... (다른 초기화 메시지) ...
    
    Terraform has been successfully initialized!
    

    이제 여러분의 State 파일은 안전하게 S3에 저장됩니다. ✅

    DynamoDB로 State Locking 구현하기

    S3 백엔드만으로는 협업 시 발생할 수 있는 문제를 완전히 해결할 수 없습니다. 바로 동시성 문제(concurrency issue)죠. 여러 사람이 동시에 terraform apply를 실행하면 어떻게 될까요? State 파일이 꼬여버리거나, 한 사람이 작업하는 동안 다른 사람이 같은 자원을 변경해서 예상치 못한 결과가 나올 수 있습니다. 제가 처음 이 문제에 부딪혔을 때, “이거 진짜 난감하네…” 싶었습니다. 😅

    그래서 필요한 게 바로 State Locking(상태 잠금)입니다. Terraform은 AWS DynamoDB를 활용하여 이 State Locking을 구현할 수 있습니다. DynamoDB는 NoSQL 데이터베이스 서비스로, 높은 성능과 가용성을 제공하며, 특히 잠금 메커니즘을 구현하기에 아주 적합합니다. 한 번에 한 사람만 State 파일을 변경할 수 있도록 잠금을 걸어주는 거죠.

    1. DynamoDB 테이블 생성하기

    DynamoDB 테이블을 생성합니다. 이때 테이블 이름은 자유롭게 지정할 수 있지만, 파티션 키(Partition Key)는 반드시 LockID로 설정해야 합니다. 이 LockID를 기준으로 잠금 레코드가 저장되기 때문입니다.

    AWS CLI를 사용한 생성 예시입니다.

    aws dynamodb create-table \
      --table-name my-terraform-locks \
      --attribute-definitions AttributeName=LockID,AttributeType=S \
      --key-schema AttributeName=LockID,KeyType=HASH \
      --billing-mode PAY_PER_REQUEST
    

    PAY_PER_REQUEST 모드를 사용하면 사용한 만큼만 비용을 지불하므로, 트래픽이 많지 않은 State Locking 용도로는 비용 효율적입니다.

    2. Terraform 구성 파일에 DynamoDB 설정 추가하기

    이제 이전에 설정했던 S3 백엔드 블록에 dynamodb_table 인자를 추가합니다.

    terraform {
      backend "s3" {
        bucket         = "my-terraform-state-bucket-13years-infra"
        key            = "global/terraform.tfstate"
        region         = "ap-northeast-2"
        encrypt        = true
        dynamodb_table = "my-terraform-locks" # 새로 생성한 DynamoDB 테이블 이름
      }
    }
    
    # ... (나머지 리소스 정의) ...
    

    3. `terraform init` 다시 실행하기

    백엔드 설정을 변경했으니, terraform init 명령어를 다시 실행해야 합니다. Terraform이 변경된 백엔드 설정을 인식하고 DynamoDB 테이블을 활용하기 시작합니다.

    terraform init
    

    이제부터 terraform apply나 terraform destroy 같은 State를 변경하는 명령어를 실행할 때, Terraform은 DynamoDB 테이블에 잠금 레코드를 생성하여 다른 사용자가 동시에 작업을 하지 못하도록 막아줍니다. 협업 환경에서 안정성을 크게 높여주는 핵심 기능이죠! 🎉

    ⚠️ 삽질 경험: 흔한 실수와 해결책

    제가 이 설정을 하면서 겪었던 몇 가지 삽질 경험과 그 해결책을 공유해 드릴게요. 여러분은 저처럼 고생하지 마시라고요! 😅

    • S3 버킷 권한 문제: terraform init이나 apply 시 “Access Denied” 오류가 발생하면, Terraform을 실행하는 IAM 사용자 또는 역할에 S3 버킷에 대한 s3:GetObject, s3:PutObject, s3:ListBucket 등의 권한이 제대로 부여되었는지 확인해야 합니다. 특히 S3 버킷 정책(Bucket Policy)이 설정되어 있다면 더욱 꼼꼼히 봐야 합니다.
    • DynamoDB 테이블 이름 오타 또는 권한 부족: DynamoDB 테이블 이름을 backend "s3" 블록에 잘못 적거나, 테이블에 대한 dynamodb:GetItem, dynamodb:PutItem, dynamodb:DeleteItem 권한이 없으면 잠금 오류가 발생합니다. 반드시 LockID 파티션 키로 테이블이 생성되었는지, 그리고 IAM 권한이 충분한지 확인하세요.
    • `terraform init` 재실행 누락: 백엔드 설정을 변경하고 terraform init을 다시 실행하지 않아서 변경사항이 적용되지 않는 경우가 많습니다. 저도 자주 까먹어서 “분명히 설정했는데 왜 안 되지?” 하고 한참 헤맸던 적이 있습니다. 🤦‍♂️ 백엔드 설정이 바뀌면 무조건 terraform init! 기억하세요.
    • S3 버전 관리 미활성화: 실수로 terraform state rm 같은 명령어를 잘못 실행해서 State 파일이 삭제되었을 때, S3 버전 관리가 활성화되어 있다면 이전 버전으로 복구할 수 있습니다. 이거 정말 생명줄 같은 기능이니 꼭 활성화하세요!

    이런 작은 실수가 큰 문제로 이어질 수 있으니, 항상 주의 깊게 확인하는 습관을 들이는 것이 중요합니다.

    Terraform State 관리 모범 사례를 요약한 인포그래픽입니다. 핵심 원칙들을 한눈에 파악할 수 있습니다.

    검증 및 결과: 이제 안심하고 협업하세요!

    모든 설정이 완료되었다면, 이제 팀원들과 함께 걱정 없이 Terraform을 사용할 수 있습니다. State Locking이 제대로 작동하는지 확인하는 가장 좋은 방법은, 두 명의 사용자가 동시에 terraform apply를 실행해보는 것입니다. 한 명의 작업이 진행되는 동안 다른 한 명은 잠금 오류 메시지를 받게 될 거예요. 이렇게 되면 성공입니다!

    또한, terraform state list 명령어를 통해 S3에 저장된 State 파일의 내용을 확인하거나, AWS 콘솔에서 S3 버킷과 DynamoDB 테이블의 항목들을 직접 확인해볼 수도 있습니다. DynamoDB 테이블에는 LockID를 가진 항목이 잠금 상태를 나타냅니다.

    terraform state list
    

    이제 여러분의 IaC 상태 관리(IaC state management)는 훨씬 더 안정적이고 견고해졌을 겁니다. 팀원들과의 Terraform 협업(Terraform collaboration)도 한층 부드러워질 거고요. 드디어 됐다! 이 안정감, 진짜 편하더라고요. 👍

    Terraform 명령어를 실행하여 State가 성공적으로 관리되고 있음을 보여주는 스크린샷입니다. DynamoDB 락도 확인해볼 수 있습니다.

    마무리하며: 더 나은 IaC 여정을 위해

    오늘은 Terraform State를 S3와 DynamoDB를 활용하여 안전하게 관리하는 방법에 대해 자세히 알아봤습니다. 13년차 엔지니어로서 직접 겪었던 경험과 삽질까지 솔직하게 공유해 드렸는데, 도움이 되셨으면 좋겠습니다.

    Terraform State 관리는 단순한 파일 저장을 넘어, 안정적인 인프라 운영과 효율적인 팀 협업을 위한 필수적인 요소입니다. S3의 내구성과 버전 관리, 그리고 DynamoDB의 강력한 잠금 기능을 활용하면, 여러분의 IaC 여정은 훨씬 더 순탄해질 거예요.

    다음 글에서는 Terraform State 파일을 더욱 안전하게 보호하기 위한 추가적인 보안 설정(예: IAM 정책 강화, S3 버킷 정책, VPC Endpoint 활용 등)에 대해 더 깊게 다뤄볼 예정입니다. 기대해주세요! 😊

  • [Cloud] Terraform AWS EKS 모듈 활용: 프로덕션 클러스터 배포 및 관리 가이드

    [Cloud] Terraform AWS EKS 모듈 활용: 프로덕션 클러스터 배포 및 관리 가이드

    안녕하세요, 13년차 서버 인프라 엔지니어입니다. 오늘은 Terraform AWS EKS 모듈을 활용해서 프로덕션 환경에 바로 적용할 수 있는 쿠버네티스(Kubernetes) 클러스터를 배포하고 관리하는 방법을 나눠볼게요. 사실 저도 쿠버네티스를 처음 접했을 땐 ‘이걸 어떻게 프로덕션에 안정적으로 올리지?’ 고민이 정말 많았거든요. 수많은 설정과 의존성 때문에 삽질을 꽤 했었습니다. ㅎㅎ

    그런데 Terraform(테라폼)과 AWS EKS 모듈을 써보니까, 정말 복잡했던 배포 과정이 깔끔하게 정리되더라고요. 마치 복잡한 미로에서 벗어나는 기분이었죠. 오늘은 제가 직접 경험했던 노하우를 바탕으로, Terraform EKS 모듈이 왜 프로덕션 환경에 필수적인지, 그리고 어떻게 활용하는지 자세히 알려드릴게요.

    Terraform으로 관리되는 AWS EKS 클러스터의 일반적인 아키텍처입니다.

    개념 정리: 왜 Terraform과 EKS 모듈이 필요할까요?

    본격적인 실전으로 들어가기 전에, 핵심 개념들을 간단히 짚고 넘어갈게요. 이미 잘 알고 계신 분들도 있겠지만, 혹시 모르니까 멘토처럼 쉽게 설명해드릴게요.

    • IaC (Infrastructure as Code, 코드형 인프라스트럭처): 인프라를 코드로 관리한다는 뜻이에요. 예전에는 서버 한 대 놓으려면 직접 전산실 가서 설치하고 케이블 연결하고 그랬잖아요? 요즘은 그런 모든 과정을 코드로 정의하고 자동화합니다. Terraform이 바로 이 IaC를 구현하는 대표적인 도구거든요. 코드로 인프라를 관리하면 반복 가능하고, 버전 관리도 되고, 무엇보다 휴먼 에러가 확 줄어든다는 게 최고의 장점입니다. 제가 직접 써보니 정말 그렇더라고요!

    • AWS EKS (Amazon Elastic Kubernetes Service): AWS에서 제공하는 관리형 쿠버네티스 서비스예요. 쿠버네티스는 컨테이너화된 워크로드를 배포하고 관리하는 오픈소스 시스템인데, EKS는 이 쿠버네티스 컨트롤 플레인(Control Plane)을 AWS가 대신 관리해준다는 뜻입니다. 덕분에 우리는 마스터 노드 관리 부담 없이 워커 노드(Worker Node)에만 집중할 수 있죠. 프로덕션 환경에선 안정성이 생명인데, AWS가 관리해주니 정말 마음이 든든합니다.

    • Terraform AWS EKS 모듈: Terraform 오픈소스 커뮤니티가 만들어 놓은 ‘모듈(Module)’이 있습니다. 이 EKS 모듈은 AWS EKS 클러스터를 배포하는 데 필요한 모든 리소스(VPC, 서브넷, IAM 역할, EKS 클러스터 자체, 노드 그룹 등)를 미리 정의해둔 템플릿 모음이에요. 이걸 사용하면 수백 줄이 넘을 수 있는 Terraform 코드를 몇 줄로 줄일 수 있거든요. 처음엔 직접 다 만들었었는데, 이 모듈을 발견하고 나서는 ‘아, 이래서 다들 모듈을 쓰는구나!’ 하고 무릎을 탁 쳤습니다. 생산성 향상에 정말 최고예요. 🚀

    실전 구현: Terraform으로 EKS 클러스터 배포하기

    이제 실제로 Terraform AWS EKS 모듈을 사용해서 프로덕션용 EKS 클러스터를 배포해볼게요. 단계별로 차근차근 따라오면 됩니다. 제가 홈랩에서 여러 번 테스트해보고 가장 안정적인 방법을 알려드릴게요.

    1. 프로젝트 구조 및 초기 설정

    먼저 다음과 같은 디렉토리 구조를 만들어주세요.

    mydir/
    ├── main.tf
    ├── variables.tf
    └── outputs.tf
    

    main.tf 파일에 AWS Provider(프로바이더)를 설정하고, Terraform EKS 모듈을 정의합니다.

    # main.tf
    
    terraform {
      required_version = ">= 1.0.0"
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.0"
        }
      }
    }
    
    provider "aws" {
      region = var.aws_region
    }
    
    module "vpc" {
      source  = "terraform-aws-modules/vpc/aws"
      version = "~> 5.0"
    
      name = "${var.cluster_name}-vpc"
      cidr = "10.0.0.0/16"
    
      azs             = data.aws_availability_zones.available.names
      private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
      public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
    
      enable_nat_gateway = true
      single_nat_gateway = true
    
      tags = {
        "kubernetes.io/cluster/${var.cluster_name}" = "owned"
        "kubernetes.io/role/internal-elb"           = "1"
        "kubernetes.io/role/elb"                    = "1"
      }
    }
    
    module "eks" {
      source  = "terraform-aws-modules/eks/aws"
      version = "~> 20.0" # Terraform Registry에서 최신 안정 버전을 확인하세요!
    
      cluster_name    = var.cluster_name
      cluster_version = var.cluster_version
    
      vpc_id     = module.vpc.vpc_id
      subnet_ids = module.vpc.private_subnets
    
      # EKS 클러스터 로깅 활성화 (프로덕션 필수!)
      cluster_enabled_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"]
    
      # 관리형 노드 그룹 (Managed Node Groups)
      managed_node_groups = {
        general = {
          name            = "general-nodes"
          instance_types  = ["t3.medium"]
          min_size        = 2
          max_size        = 5
          desired_size    = 3
          disk_size       = 50
          labels          = { env = "production", role = "general" }
          capacity_type   = "ON_DEMAND"
          # EKS 노드에 SSH 접속이 필요하다면 아래 주석 해제 후 키 페어 이름 설정
          # key_name = "your-ssh-key-name"
        }
        # 추가 노드 그룹이 필요하면 여기에 정의
        # spot = {
        #   name          = "spot-nodes"
        #   instance_types  = ["t3.small", "t3.medium"]
        #   min_size        = 0
        #   max_size        = 10
        #   desired_size    = 1
        #   capacity_type   = "SPOT"
        #   disk_size       = 20
        # }
      }
    
      tags = {
        Project     = "EKS-Production"
        Environment = "Prod"
      }
    }
    
    data "aws_availability_zones" "available" {}
    

    ⚠️ 주의사항: t3.medium은 테스트용으로 괜찮지만, 실제 프로덕션 워크로드에는 더 큰 인스턴스 타입(예: m5.large, c5.large)을 고려하세요. 그리고 version = "~> 20.0" 부분은 항상 Terraform Registry에서 최신 안정 버전을 확인해서 사용하세요. Terraform AWS EKS 모듈이 워낙 빠르게 업데이트돼서 제가 작성한 시점과 다를 수 있거든요.

    variables.tf 파일에는 클러스터 이름, AWS 리전, EKS 버전 등 변경될 수 있는 값들을 정의합니다.

    # variables.tf
    
    variable "aws_region" {
      description = "AWS region."
      type        = string
      default     = "ap-northeast-2" # 서울 리전
    }
    
    variable "cluster_name" {
      description = "Name of the EKS cluster."
      type        = string
      default     = "my-prod-eks-cluster"
    }
    
    variable "cluster_version" {
      description = "Kubernetes version."
      type        = string
      default     = "1.28" # 사용 가능한 EKS 버전 확인 후 지정
    }
    

    outputs.tf 파일에는 배포 후 필요한 정보를 출력하도록 설정합니다. 클러스터 엔드포인트나 kubeconfig 명령어 같은 것들이죠.

    # outputs.tf
    
    output "cluster_endpoint" {
      description = "Endpoint for EKS Control Plane."
      value       = module.eks.cluster_endpoint
    }
    
    output "kubeconfig_command" {
      description = "Command to configure kubectl."
      value       = "aws eks update-kubeconfig --region ${var.aws_region} --name ${var.cluster_name}"
    }
    
    output "cluster_security_group_id" {
      description = "Security group ID of the EKS cluster."
      value       = module.eks.cluster_security_group_id
    }
    

    Terraform EKS 모듈을 위한 주요 구성 파일들의 역할과 관계를 보여줍니다.

    2. Terraform 명령 실행

    파일들을 모두 작성했다면, 터미널을 열고 해당 디렉토리로 이동해서 다음 명령어를 실행합니다.

    1. Terraform 초기화 (Initialize): 필요한 프로바이더와 모듈을 다운로드합니다.

      terraform init
      
    2. Terraform 실행 계획 (Plan): 실제로 어떤 리소스들이 생성/변경/삭제될지 미리 보여줍니다. 이 단계에서 항상 꼼꼼히 확인하는 습관을 들이셔야 해요. 저는 여기서 실수 몇 번 해보고 크게 깨달았습니다. 😅

      terraform plan
      
    3. Terraform 적용 (Apply): 계획대로 리소스를 AWS에 배포합니다. 시간이 좀 걸릴 수 있어요. 커피 한 잔 마시면서 기다려보세요. 😊

      terraform apply --auto-approve
      

      --auto-approve 옵션은 실제 프로덕션 환경에서는 신중하게 사용해야 합니다. 보통은 terraform apply만 실행해서 직접 승인하는 과정을 거치는 게 안전하거든요.

    ⚠️ 주의사항: 삽질 경험과 해결책

    제가 13년간 인프라 엔지니어로 일하면서 느낀 건, 아무리 잘 만들어진 도구라도 ‘삽질’은 피할 수 없다는 거예요. Terraform EKS 모듈도 마찬가지입니다. 몇 가지 흔한 문제와 제가 겪었던 해결책을 공유해드릴게요.

    • VPC Subnet Tag 누락: EKS 클러스터가 서브넷을 제대로 인식하지 못해서 배포가 실패하는 경우가 종종 있어요. 특히 다른 모듈로 VPC를 만들었거나 수동으로 서브넷을 구성했을 때 그렇더라고요. EKS는 워커 노드를 프로비저닝할 때 특정 태그(kubernetes.io/cluster/YOUR_CLUSTER_NAME과 kubernetes.io/role/internal-elb 또는 kubernetes.io/role/elb)가 있는 서브넷을 찾습니다. main.tf의 VPC 모듈 설정에서 태그를 꼭 넣어주세요.

      tags = {
        "kubernetes.io/cluster/${var.cluster_name}" = "owned"
        "kubernetes.io/role/internal-elb"           = "1"
        "kubernetes.io/role/elb"                    = "1"
      }
      
    • IAM 권한 부족: Terraform을 실행하는 IAM 사용자 또는 역할에 EKS, EC2, IAM, VPC 관련 권한이 충분히 부여되지 않으면 문제가 생길 수 있습니다. 특히 EKS Administrator와 유사한 관리자 권한을 가진 정책을 사용하거나, 필요한 최소 권한을 직접 설정해야 하죠. 저는 처음에 너무 최소 권한만 주려다가 여러 번 권한 에러를 만났습니다. 그럴 땐 일단 잠시 넓은 권한을 줘서 문제가 권한 때문인지 확인하고, 잘 되면 다시 최소 권한으로 조이는 방법을 썼어요.

    • 모듈 버전 충돌 또는 비호환성: Terraform AWS EKS 모듈은 빠르게 업데이트됩니다. 특정 Terraform 버전, AWS Provider 버전, EKS 클러스터 버전과의 호환성을 항상 확인해야 해요. Terraform Registry에서 해당 모듈의 Required Providers와 Requirements 섹션을 꼭 확인하세요. 버전이 맞지 않으면 예상치 못한 에러가 발생할 수 있거든요.

    • 노드 그룹의 인스턴스 타입/AMI 문제: EKS 워커 노드가 정상적으로 클러스터에 조인되지 않는 경우가 있습니다. 주로 EKS 버전과 호환되지 않는 AMI(Amazon Machine Image)를 사용했거나, 인스턴스 타입이 해당 리전에서 지원되지 않을 때 발생합니다. Terraform EKS 모듈은 기본적으로 EKS 최적화 AMI를 사용하지만, 사용자 지정 AMI를 사용할 경우 주의해야 합니다.

    검증 및 결과: 클러스터 확인하기

    Terraform apply가 성공적으로 완료되었다면, 이제 EKS 클러스터가 잘 배포되었는지 확인해볼 차례예요. 🎉

    1. Kubeconfig 설정

    먼저 outputs.tf에서 출력된 kubeconfig_command를 실행해서 kubectl이 EKS 클러스터에 접속할 수 있도록 설정합니다. 이 명령어를 실행하면 ~/.kube/config 파일이 업데이트될 겁니다.

    aws eks update-kubeconfig --region ap-northeast-2 --name my-prod-eks-cluster
    

    2. 노드 확인

    이제 kubectl 명령어로 클러스터 노드들을 확인해봅시다. 워커 노드들이 Ready 상태로 잘 올라와 있어야 합니다.

    kubectl get nodes
    
    NAME                                           STATUS   ROLES    AGE     VERSION
    ip-10-0-1-123.ap-northeast-2.compute.internal   Ready    <none>   5m20s   v1.28.x
    ip-10-0-2-234.ap-northeast-2.compute.internal   Ready    <none>   5m15s   v1.28.x
    ip-10-0-3-345.ap-northeast-2.compute.internal   Ready    <none>   5m10s   v1.28.x
    

    3. AWS Console에서 확인

    AWS Management Console(관리 콘솔)에 로그인해서 EKS 서비스로 이동하면, 방금 배포한 클러스터가 목록에 보일 거예요. 클러스터 이름을 클릭해서 상세 정보를 확인하고, 노드 그룹 탭에서 워커 노드들이 정상적으로 실행 중인지도 확인해볼 수 있습니다.

    AWS EKS 콘솔에서 배포된 클러스터와 노드 그룹의 상태를 확인하는 모습입니다.

    마무리, 그리고 다음 단계

    오늘은 Terraform AWS EKS 모듈을 활용해서 프로덕션 레디(Production-Ready) 쿠버네티스 클러스터를 배포하고 관리하는 방법을 자세히 알아봤습니다. 제가 직접 겪은 삽질 경험과 해결책도 함께 공유해드렸는데, 도움이 되셨으면 좋겠네요.

    Terraform EKS 모듈 덕분에 우리는 복잡한 EKS 인프라를 빠르고 안정적으로 구축할 수 있게 됐어요. IaC의 강력함을 다시 한번 느낄 수 있었던 경험이었죠. 처음엔 진입 장벽이 좀 있다고 느낄 수 있지만, 한번 익숙해지면 이만큼 편리한 게 없습니다. 정말 강력한 도구거든요.

    Terraform AWS EKS 모듈을 사용했을 때 얻을 수 있는 주요 이점들을 시각적으로 요약했습니다.

    다음 단계로는 이렇게 배포된 EKS 클러스터에 Ingress Controller(인그레스 컨트롤러, 외부 트래픽을 클러스터 내부 서비스로 라우팅), Cert-Manager(인증서 관리), Prometheus(프로메테우스, 모니터링 시스템) 같은 필수 애드온(Add-on)들을 Terraform으로 함께 배포하는 방법을 다뤄볼 예정이에요. 그리고 CI/CD 파이프라인(Continuous Integration/Continuous Deployment Pipeline, 지속적 통합/배포 파이프라인)과 연동해서 인프라 변경을 자동화하는 방법도 흥미로운 주제가 될 것 같습니다.

    궁금한 점이나 추가적인 삽질 경험이 있으시다면 댓글로 자유롭게 남겨주세요! 함께 고민하고 해결해나가는 게 인프라 엔지니어의 묘미 아니겠습니까? 😊

  • [Cloud] Terraform으로 AWS/GCP/Azure 멀티 클라우드 환경 구축 가이드

    멀티 클라우드, 왜 지금 이 시점에 Terraform인가요?

    솔직히 말씀드리면, 저도 처음엔 멀티 클라우드가 ‘대기업 얘기’인 줄 알았거든요. AWS 하나만 잘 써도 충분하지 않냐고 생각했는데… 현실은 달랐습니다. 어느 날 갑자기 고객사가 “GCP도 써야 한다”고 했을 때, 그 막막함이란 😅

    요즘은 규모에 상관없이 AWS, GCP, Azure를 동시에 다뤄야 하는 상황이 생각보다 자주 옵니다. 벤더 종속(Vendor Lock-in)을 피하려는 전략적 이유도 있고, 각 클라우드의 강점을 조합하려는 경우도 많죠. ML/AI 워크로드는 GCP, 기업 솔루션은 Azure, 메인 인프라는 AWS처럼요.

    여기서 Terraform 멀티 클라우드 구성이 빛을 발합니다. 하나의 도구로 세 개의 클라우드를 동시에 관리할 수 있다는 건, 13년 동안 인프라 일을 해온 저한테도 꽤 충격적인 경험이었어요. 이 글에서는 제가 직접 홈랩에서 구성해보고 실무에 적용한 경험을 바탕으로, Terraform으로 AWS/GCP/Azure 멀티 클라우드 환경을 구축하는 방법을 단계별로 알려드릴게요.

    ▲ Terraform 하나로 AWS, GCP, Azure를 동시에 관리하는 멀티 클라우드 아키텍처 개요도

    Terraform 멀티 클라우드가 뭔지 쉽게 풀어볼게요

    Terraform은 HashiCorp에서 만든 IaC(Infrastructure as Code) 도구예요. 쉽게 말해, 클라우드 콘솔에서 마우스로 클릭클릭 하던 걸 코드로 대체하는 거죠. 인프라 구성을 코드로 정의하고 관리하는 방식이라고 보면 됩니다.

    핵심 개념 몇 가지만 짚고 넘어갈게요.

    • Provider(프로바이더): 각 클라우드 벤더와 통신하는 플러그인. AWS, GCP, Azure 각각 별도 프로바이더가 있어요.
    • Resource(리소스): 실제로 생성할 인프라 구성 요소. EC2 인스턴스, GCS 버킷, Azure VM 같은 것들이죠.
    • State(스테이트): Terraform이 현재 인프라 상태를 추적하는 파일. 멀티 클라우드에서 이게 특히 중요합니다.
    • Module(모듈): 재사용 가능한 Terraform 코드 묶음. 멀티 클라우드 구성에서 필수예요.
    • Workspace(워크스페이스): 동일한 코드베이스로 여러 환경(dev/staging/prod)을 관리하는 방법.

    멀티 클라우드 구성에서 핵심은 하나의 Terraform 프로젝트에 여러 프로바이더를 동시에 선언하는 거예요. 처음엔 이게 되나 싶었는데, 실제로 해보니까 생각보다 깔끔하게 동작하더라고요.

    단일 클라우드 vs 멀티 클라우드 Terraform 비교

    구분 단일 클라우드 멀티 클라우드
    Provider 수 1개 2개 이상
    State 관리 단일 backend 분리 또는 통합 backend
    인증 관리 단순 각 클라우드별 별도 인증 필요
    코드 복잡도 낮음 중~높음 (모듈화 필수)
    장점 단순, 빠른 구성 벤더 독립성, 최적 서비스 조합

    사전 준비: 환경 세팅부터 제대로

    본격적인 구성 전에 준비해야 할 것들이 있어요. 저도 이 부분에서 삽질을 좀 했습니다 ㅎㅎ. 특히 인증 부분에서요.

    1. Terraform 설치

    Terraform은 1.0 버전 이후로 꽤 안정화됐어요. 멀티 클라우드 구성을 처음 하신다면 최신 stable 버전을 사용하시길 권장합니다.

    # macOS (Homebrew)
    brew tap hashicorp/tap
    brew install hashicorp/tap/terraform
    
    # Ubuntu/Debian
    wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
    echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
    sudo apt update && sudo apt install terraform
    
    # 버전 확인
    terraform version

    2. 각 클라우드 CLI 설치 및 인증

    Terraform이 각 클라우드와 통신하려면 인증 정보가 필요해요. CLI를 통한 인증이 가장 깔끔합니다.

    # AWS CLI 인증
    aws configure
    # AWS Access Key ID, Secret Access Key, Region 입력
    
    # GCP 인증
    gcloud auth application-default login
    # 브라우저에서 Google 계정 인증
    
    # Azure CLI 인증
    az login
    # 브라우저에서 Microsoft 계정 인증

    💡 팁: 실무에서는 각 클라우드의 서비스 계정(Service Account) 또는 IAM Role을 사용하는 게 보안상 훨씬 좋아요. 개인 계정 인증은 개발/테스트 환경에서만 쓰세요.

    실전 구현: 멀티 클라우드 Terraform 프로젝트 구성

    자, 이제 본론입니다. 제가 실제로 구성한 디렉터리 구조부터 보여드릴게요. 처음에 플랫 구조로 했다가 나중에 너무 복잡해져서 모듈 구조로 전면 개편했던 경험이 있어서, 처음부터 제대로 된 구조로 시작하시길 권해드립니다.

    프로젝트 디렉터리 구조

    multi-cloud-infra/
    ├── main.tf              # 메인 진입점, 프로바이더 설정
    ├── variables.tf         # 공통 변수 정의
    ├── outputs.tf           # 출력값 정의
    ├── terraform.tfvars     # 실제 변수값 (git에 올리지 마세요!)
    ├── backend.tf           # State 저장소 설정
    └── modules/
        ├── aws/
        │   ├── main.tf
        │   ├── variables.tf
        │   └── outputs.tf
        ├── gcp/
        │   ├── main.tf
        │   ├── variables.tf
        │   └── outputs.tf
        └── azure/
            ├── main.tf
            ├── variables.tf
            └── outputs.tf

    Step 1: 멀티 프로바이더 선언 (main.tf)

    여기가 핵심이에요. 세 개의 프로바이더를 동시에 선언합니다. alias(별칭)를 사용하면 같은 프로바이더를 여러 리전에서 사용할 수도 있어요.

    terraform {
      required_version = ">= 1.3.0"
    
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.0"
        }
        google = {
          source  = "hashicorp/google"
          version = "~> 5.0"
        }
        azurerm = {
          source  = "hashicorp/azurerm"
          version = "~> 3.0"
        }
      }
    }
    
    # AWS 프로바이더
    provider "aws" {
      region = var.aws_region
      # 환경변수 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 사용 권장
    }
    
    # GCP 프로바이더
    provider "google" {
      project = var.gcp_project_id
      region  = var.gcp_region
      # GOOGLE_APPLICATION_CREDENTIALS 환경변수 사용 권장
    }
    
    # Azure 프로바이더
    provider "azurerm" {
      features {}
      subscription_id = var.azure_subscription_id
      # az login으로 인증된 정보 사용
    }

    Step 2: 변수 정의 (variables.tf)

    variable "aws_region" {
      description = "AWS 배포 리전"
      type        = string
      default     = "ap-northeast-2"  # 서울 리전
    }
    
    variable "gcp_project_id" {
      description = "GCP 프로젝트 ID"
      type        = string
    }
    
    variable "gcp_region" {
      description = "GCP 배포 리전"
      type        = string
      default     = "asia-northeast3"  # 서울 리전
    }
    
    variable "azure_subscription_id" {
      description = "Azure 구독 ID"
      type        = string
      sensitive   = true
    }
    
    variable "environment" {
      description = "배포 환경 (dev/staging/prod)"
      type        = string
      default     = "dev"
    }
    
    variable "project_name" {
      description = "프로젝트명 (리소스 태그에 사용)"
      type        = string
    }

    ▲ 모듈화된 Terraform 프로젝트 구조 — 각 클라우드별 모듈이 독립적으로 관리되는 모습

    Step 3: 각 클라우드 모듈 구현

    이제 각 클라우드별 리소스를 모듈로 만들어볼게요. 예시로 각 클라우드에 VPC/네트워크와 기본 컴퓨팅 리소스를 만드는 구성입니다.

    AWS 모듈 (modules/aws/main.tf)

    # AWS VPC 생성
    resource "aws_vpc" "main" {
      cidr_block           = var.vpc_cidr
      enable_dns_hostnames = true
      enable_dns_support   = true
    
      tags = {
        Name        = "${var.project_name}-vpc"
        Environment = var.environment
        ManagedBy   = "terraform"
      }
    }
    
    # 퍼블릭 서브넷
    resource "aws_subnet" "public" {
      vpc_id                  = aws_vpc.main.id
      cidr_block              = var.public_subnet_cidr
      availability_zone       = "${var.aws_region}a"
      map_public_ip_on_launch = true
    
      tags = {
        Name        = "${var.project_name}-public-subnet"
        Environment = var.environment
      }
    }
    
    # S3 버킷 (State 저장용 또는 앱 스토리지)
    resource "aws_s3_bucket" "app_storage" {
      bucket = "${var.project_name}-${var.environment}-storage"
    
      tags = {
        Name        = "${var.project_name}-storage"
        Environment = var.environment
      }
    }
    
    resource "aws_s3_bucket_versioning" "app_storage" {
      bucket = aws_s3_bucket.app_storage.id
      versioning_configuration {
        status = "Enabled"
      }
    }

    GCP 모듈 (modules/gcp/main.tf)

    # GCP VPC 네트워크
    resource "google_compute_network" "main" {
      name                    = "${var.project_name}-network"
      auto_create_subnetworks = false
      project                 = var.gcp_project_id
    }
    
    # GCP 서브넷
    resource "google_compute_subnetwork" "main" {
      name          = "${var.project_name}-subnet"
      ip_cidr_range = var.subnet_cidr
      region        = var.gcp_region
      network       = google_compute_network.main.id
      project       = var.gcp_project_id
    }
    
    # GCS 버킷 (Google Cloud Storage)
    resource "google_storage_bucket" "app_storage" {
      name          = "${var.project_name}-${var.environment}-gcs"
      location      = "ASIA"
      project       = var.gcp_project_id
      force_destroy = false
    
      versioning {
        enabled = true
      }
    
      labels = {
        environment = var.environment
        managed_by  = "terraform"
      }
    }

    Azure 모듈 (modules/azure/main.tf)

    # Azure 리소스 그룹
    resource "azurerm_resource_group" "main" {
      name     = "${var.project_name}-${var.environment}-rg"
      location = var.azure_location
    
      tags = {
        environment = var.environment
        managed_by  = "terraform"
      }
    }
    
    # Azure Virtual Network
    resource "azurerm_virtual_network" "main" {
      name                = "${var.project_name}-vnet"
      address_space       = [var.vnet_cidr]
      location            = azurerm_resource_group.main.location
      resource_group_name = azurerm_resource_group.main.name
    
      tags = {
        environment = var.environment
      }
    }
    
    # Azure 서브넷
    resource "azurerm_subnet" "main" {
      name                 = "${var.project_name}-subnet"
      resource_group_name  = azurerm_resource_group.main.name
      virtual_network_name = azurerm_virtual_network.main.name
      address_prefixes     = [var.subnet_cidr]
    }
    
    # Azure Storage Account
    resource "azurerm_storage_account" "main" {
      name                     = "${replace(var.project_name, "-", "")}${var.environment}sa"
      resource_group_name      = azurerm_resource_group.main.name
      location                 = azurerm_resource_group.main.location
      account_tier             = "Standard"
      account_replication_type = "LRS"
    
      tags = {
        environment = var.environment
      }
    }

    Step 4: 모듈 호출 (루트 main.tf에 추가)

    # AWS 모듈 호출
    module "aws_infra" {
      source = "./modules/aws"
    
      project_name       = var.project_name
      environment        = var.environment
      aws_region         = var.aws_region
      vpc_cidr           = "10.0.0.0/16"
      public_subnet_cidr = "10.0.1.0/24"
    }
    
    # GCP 모듈 호출
    module "gcp_infra" {
      source = "./modules/gcp"
    
      project_name    = var.project_name
      environment     = var.environment
      gcp_project_id  = var.gcp_project_id
      gcp_region      = var.gcp_region
      subnet_cidr     = "10.1.0.0/24"
    }
    
    # Azure 모듈 호출
    module "azure_infra" {
      source = "./modules/azure"
    
      project_name   = var.project_name
      environment    = var.environment
      azure_location = "koreacentral"
      vnet_cidr      = "10.2.0.0/16"
      subnet_cidr    = "10.2.1.0/24"
    }

    Step 5: State Backend 설정 (backend.tf)

    멀티 클라우드에서 State 관리는 정말 중요해요. 팀 작업을 한다면 로컬 state 파일은 절대 안 됩니다. 저는 AWS S3를 State 저장소로, DynamoDB를 락(Lock) 관리에 사용했어요.

    terraform {
      backend "s3" {
        bucket         = "your-terraform-state-bucket"
        key            = "multi-cloud/terraform.tfstate"
        region         = "ap-northeast-2"
        encrypt        = true
        dynamodb_table = "terraform-state-lock"
      }
    }

    Step 6: 배포 실행

    # 초기화 (프로바이더 플러그인 다운로드)
    terraform init
    
    # 변경사항 미리보기
    terraform plan -var-file="terraform.tfvars"
    
    # 실제 배포
    terraform apply -var-file="terraform.tfvars"
    
    # 특정 모듈만 배포하고 싶을 때
    terraform apply -target=module.aws_infra
    
    # 리소스 삭제
    terraform destroy -var-file="terraform.tfvars"

    ⚠️ 삽질 기록: 이런 문제들 겪었습니다

    제가 처음 멀티 클라우드 Terraform 구성할 때 겪었던 문제들이에요. 여러분은 같은 삽질 안 하셨으면 해서 솔직하게 공유합니다.

    문제 1: Provider 버전 충돌

    terraform init 할 때 프로바이더 버전 충돌 에러가 났어요. ~> 연산자로 버전 범위를 지정하면 대부분 해결됩니다. 너무 엄격하게 고정하면 나중에 업그레이드할 때 고생해요.

    # 이렇게 하면 마이너 버전 업그레이드는 허용
    version = "~> 5.0"   # 5.x 버전 허용, 6.0은 안 됨
    
    # 이렇게 하면 패치 버전만 허용
    version = "~> 5.1.0" # 5.1.x만 허용

    문제 2: Azure Storage Account 이름 규칙

    Azure Storage Account 이름은 소문자와 숫자만 되고, 하이픈(-) 사용이 불가예요. 이거 모르고 프로젝트명에 하이픈 넣었다가 에러 폭탄 맞았습니다… replace() 함수로 해결했어요.

    # 하이픈 제거
    name = "${replace(var.project_name, "-", "")}${var.environment}sa"

    문제 3: GCP API 활성화 누락

    GCP는 사용하려는 API를 사전에 활성화해야 해요. Terraform으로 리소스 만들려는데 API가 비활성화 상태면 에러가 납니다. 이것도 Terraform으로 관리할 수 있어요.

    resource "google_project_service" "compute" {
      project = var.gcp_project_id
      service = "compute.googleapis.com"
    
      disable_on_destroy = false
    }
    
    resource "google_project_service" "storage" {
      project = var.gcp_project_id
      service = "storage.googleapis.com"
    
      disable_on_destroy = false
    }
    
    # 리소스가 API 활성화 후 생성되도록 의존성 설정
    resource "google_compute_network" "main" {
      depends_on = [google_project_service.compute]
      # ...
    }

    문제 4: State 파일 잠금(Lock) 충돌

    팀원이랑 동시에 terraform apply를 실행했다가 State Lock 에러가 났어요. DynamoDB 락 테이블을 꼭 설정하세요. 혼자 작업해도 비정상 종료 후 락이 안 풀리는 경우가 있는데, 이때는 terraform force-unlock 명령어로 해결합니다.

    # 락 강제 해제 (Lock ID는 에러 메시지에서 확인)
    terraform force-unlock LOCK_ID

    검증: 제대로 만들어졌는지 확인하기

    드디어 됐다! 싶을 때 꼭 확인해야 할 것들이 있어요. terraform apply가 성공했다고 끝이 아니거든요.

    # 현재 State 확인
    terraform show
    
    # 특정 리소스 상세 확인
    terraform state show module.aws_infra.aws_vpc.main
    
    # 모든 리소스 목록 확인
    terraform state list
    
    # 출력값 확인
    terraform output

    각 클라우드 콘솔에서도 직접 확인해보세요. AWS 콘솔에서 VPC가 생겼는지, GCP 콘솔에서 네트워크가 보이는지, Azure 포털에서 리소스 그룹이 만들어졌는지 확인하는 거예요. 코드만 믿지 말고 눈으로 직접 확인하는 습관이 정말 중요합니다.

    ▲ terraform apply 완료 후 각 클라우드 콘솔에서 리소스가 정상 생성된 결과 확인 화면

    Outputs으로 중요 정보 추출하기

    # outputs.tf
    output "aws_vpc_id" {
      description = "생성된 AWS VPC ID"
      value       = module.aws_infra.vpc_id
    }
    
    output "gcp_network_name" {
      description = "생성된 GCP 네트워크 이름"
      value       = module.gcp_infra.network_name
    }
    
    output "azure_resource_group" {
      description = "생성된 Azure 리소스 그룹 이름"
      value       = module.azure_infra.resource_group_name
    }

    💡 실무에서 쓰는 Best Practice 몇 가지

    13년 동안 인프라 일 하면서 쌓인 노하우를 좀 더 공유할게요.

    • terraform.tfvars는 절대 Git에 올리지 마세요. .gitignore에 꼭 추가하고, 민감 정보는 환경변수나 Vault로 관리하세요.
    • 환경별 tfvars 파일을 분리하세요. dev.tfvars, staging.tfvars, prod.tfvars처럼요. -var-file 옵션으로 지정해서 사용합니다.
    • Terraform Cloud 또는 Atlantis 도입을 고려하세요. 팀 규모가 커지면 PR 기반 Terraform 실행 환경이 필요해집니다.
    • 모든 리소스에 태그/라벨을 달아두세요. 멀티 클라우드에서 비용 관리하려면 태그 전략이 필수예요. ManagedBy = terraform, Environment = dev 같은 공통 태그부터 시작하세요.
    • Plan 결과는 팀원과 공유하세요. terraform plan -out=plan.tfplan으로 저장하고, terraform show plan.tfplan으로 리뷰받는 프로세스를 만들면 좋아요.

    ▲ Terraform 멀티 클라우드 운영을 위한 Best Practice 요약 — 보안, 협업, 비용 관리 핵심 포인트

    자주 묻는 질문 (FAQ)

    Q. Terraform과 Pulumi 중 어떤 걸 써야 하나요?

    Pulumi는 일반 프로그래밍 언어(Python, TypeScript 등)로 인프라를 정의할 수 있어서 개발자 친화적이에요. 반면 Terraform은 HCL이라는 전용 언어를 쓰지만 생태계가 훨씬 크고, 커뮤니티 모듈이 풍부하거든요. 처음 IaC를 시작한다면 Terraform을 추천해요.

    Q. 멀티 클라우드 구성에서 State를 어디에 저장해야 하나요?

    저는 AWS S3 + DynamoDB 조합을 주로 사용합니다. Terraform Cloud를 쓰면 State 관리, 락, 협업 기능을 한 번에 해결할 수 있어요. 팀 규모가 있다면 Terraform Cloud 무료 플랜부터 시작해보세요.

    Q. 멀티 클라우드 비용이 너무 많이 나올 것 같아요.

    맞아요, 이게 현실적인 고민이죠. 각 클라우드의 프리 티어(Free Tier)를 최대한 활용하고, 개발 환경은 작업이 끝나면 terraform destroy로 날리는 습관을 들이세요. 저도 홈랩에서는 퇴근 전에 꼭 destroy 합니다 😅

    마무리: 멀티 클라우드는 복잡하지만, Terraform이 있으면 달라요

    처음에 AWS IaC 하나 배우는 것도 버거웠는데, GCP IaC, Azure IaC까지 동시에 다루게 될 줄은 몰랐어요. 근데 Terraform 멀티 클라우드 구성을 제대로 한 번 해놓으면, 그다음부터는 정말 편하더라고요. 새로운 리소스 추가할 때 코드 몇 줄 추가하고 apply만 하면 되거든요.

    이 글에서 다룬 내용을 정리하면:

    1. Terraform에서 여러 Provider를 동시에 선언해 멀티 클라우드 관리 가능
    2. 모듈(Module) 구조로 각 클라우드 코드를 분리해 유지보수성 향상
    3. State는 반드시 원격 Backend(S3 + DynamoDB 등)에 저장
    4. 인증 정보, 민감 변수는 절대 코드에 하드코딩 금지
    5. 태그 전략을 처음부터 설계해야 멀티 클라우드 비용 관리가 됨

    다음 글에서는 Terraform과 GitHub Actions를 연동해서 CI/CD 파이프라인을 구축하는 방법을 다룰 예정이에요. Pull Request가 올라오면 자동으로 terraform plan 결과를 코멘트로 달아주는 그 워크플로우요. 꽤 실용적이니까 기대해주세요!

    질문이나 다른 삽질 경험 있으시면 댓글로 남겨주세요. 같이 고민해봐요 😊