13년차의 서버실

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

[태그:] 멀티 클라우드

  • [클라우드] AWS GCS vs Azure Blob Storage, 실제 사용 비교와 마이그레이션 전략

    [클라우드] AWS GCS vs Azure Blob Storage, 실제 사용 비교와 마이그레이션 전략

    [클라우드] AWS GCS vs Azure Blob Storage, 실제 사용 비교와 마이그레이션 전략

    AWS GCS vs Azure Blob Storage 같은 검색어로 들어오시는 분들이 꽤 많습니다. 근데 여기서 먼저 짚고 갈 게 하나 있네요. AWS에는 Google Cloud Storage(GCS)가 없습니다. AWS의 객체 스토리지(Object Storage, 오브젝트 저장소)는 Amazon S3이고, GCS는 Google Cloud Storage입니다. 저도 현업에서 회의하다 보면 이 표현이 자주 섞이더라고요. 처음엔 ‘이게 뭔가 싶었는데’, 막상 마이그레이션(Migration, 데이터 이전) 설계 들어가면 이 용어 정리가 꽤 중요합니다.

    그래서 이 글에서는 검색 의도를 살려 AWS GCS vs Azure Blob Storage라는 키워드를 기준으로 설명하되, 실제 비교는 Amazon S3, Google Cloud Storage, Azure Blob Storage를 함께 놓고 보겠습니다. 특히 클라우드 스토리지 비교, 데이터 마이그레이션, 비용 최적화 관점에서 제가 직접 운영하면서 부딪혔던 포인트를 중심으로 정리해보겠습니다.

    AWS GCS vs Azure Blob Storage 비교를 위한 멀티 클라우드 아키텍처 이미지

    Amazon S3, Google Cloud Storage, Azure Blob Storage를 한눈에 비교하는 멀티 클라우드 아키텍처 개요 이미지입니다.

    1. 클라우드 스토리지 비교가 실무에서 중요한 이유

    객체 스토리지는 그냥 파일 올려두는 공간처럼 보이지만, 실제로는 백업(Backup, 백업 저장), 로그 적재(Log Ingestion, 로그 수집), 정적 웹 호스팅(Static Hosting, 정적 파일 제공), 데이터 레이크(Data Lake, 대용량 데이터 저장소)까지 거의 다 닿습니다. 한 번 올바르지 않게 설계하면 나중에 옮기는 비용이 꽤 커요. 진짜입니다.

    제가 홈랩(Home Lab, 개인 실험용 인프라)과 업무 환경에서 공통으로 느낀 건 이겁니다. 처음엔 API만 맞추면 다 비슷해 보이는데, 운영 들어가면 IAM(Identity and Access Management, 권한 관리), 네트워크 요금, 티어링(Tiering, 저장 계층 분리), 마이그레이션 툴에서 차이가 크게 납니다. 특히 여러 클라우드를 같이 쓰는 조직은 ‘어디에 원본을 두고 어디로 복제할지’부터 정책이 갈리거든요.

    2. 쉽게 말해, S3/GCS/Azure Blob은 무엇이 다를까요?

    쉽게 말해 셋 다 객체 스토리지입니다. 폴더처럼 보여도 실제로는 키(Key, 객체 이름) 기반으로 파일을 저장합니다. 서버에 디스크 마운트하듯 접근하는 블록 스토리지(Block Storage)와는 성격이 다르죠.

    • Amazon S3: AWS 생태계와 가장 자연스럽게 붙습니다. Lambda, Athena, CloudFront 같은 서비스와 연결이 편하더라고요.
    • Google Cloud Storage: GCP 데이터 분석 쪽, 특히 BigQuery나 데이터 파이프라인과 조합할 때 깔끔합니다.
    • Azure Blob Storage: Microsoft 생태계, 특히 Azure AD 기반 권한 관리와 기업 환경 연동에서 강점이 있습니다.

    여기서 중요한 포인트! 기능이 겹친다고 해서 운영 경험까지 같은 건 아닙니다. 예를 들어 버킷(Bucket, 객체 저장 컨테이너) 만들고 업로드하는 것까지는 다 쉬워요. 근데 권한 분리, 수명 주기(Lifecycle, 자동 보관 정책), 리전 간 복제(Replication, 데이터 복제)부터 슬슬 차이가 보입니다.

    3. 클라우드 스토리지 비교: 실무 기준으로 보면

    비교 항목 Amazon S3 Google Cloud Storage Azure Blob Storage
    기본 개념 Bucket 중심 객체 저장 Bucket 중심 객체 저장 Storage Account + Container 구조
    권한 모델 IAM, Bucket Policy, ACL IAM 중심 RBAC, SAS, Access Key
    생태계 연동 AWS 서비스와 강함 GCP 분석 서비스와 강함 Microsoft/Azure 서비스와 강함
    마이그레이션 난이도 도구 다양 gsutil 등 단순함 AzCopy 강력
    운영 포인트 정책 세분화 유리 구조 단순 기업 권한 체계와 잘 맞음

    제가 실제로 써보니까, S3는 유연성, GCS는 단순함, Azure Blob은 조직형 권한 관리 쪽으로 체감됐습니다. 물론 이건 워크로드(Workload, 실제 실행 업무)마다 다릅니다. 이미지 백업 위주인지, 데이터 분석용인지, 사내 파일 아카이브인지에 따라 느낌이 달라져요.

    3-1. 권한 관리가 생각보다 큰 차이를 만듭니다

    초기에는 업로드/다운로드만 되면 끝인 줄 알았는데, 운영하다 보면 누가 읽고 누가 쓰고 누가 삭제할 수 있는지가 가장 골치 아픕니다. S3는 Bucket Policy와 IAM Role 조합이 강력하지만 초반엔 살짝 복잡합니다. Azure Blob은 SAS(Shared Access Signature, 서명 기반 임시 접근 토큰)를 잘 쓰면 외부 공유가 편하더라고요. 다만 만료 시간과 권한 범위 관리를 대충 잡으면 나중에 추적이 어려워집니다.

    3-2. 비용 최적화는 저장 요금보다 전송 요금이 더 아픕니다

    이거 진짜 많이 놓칩니다. 많은 분이 GB당 저장 비용만 보시는데, 실제 청구서 보면 데이터 송신(Egress, 외부 전송) 비용이 체감상 더 아프게 들어올 때가 있습니다. 특히 멀티 클라우드 구조에서 한쪽에 저장하고 다른 쪽 컴퓨트에서 계속 읽는 구조는 생각보다 비효율적입니다. 비용 최적화 하려면 저장 위치와 소비 위치를 같이 봐야 합니다.

    4. 마이그레이션 전략: 제가 보통 이렇게 나눕니다

    데이터 마이그레이션은 무조건 한 번에 크게 옮기기보다, 단계별로 나누는 게 안전합니다. 저도 처음엔 ‘주말에 한 번에 끝내죠’ 했다가 삽질 좀 했습니다. 결국 안정적인 방식은 아래 순서더라고요.

    1. 데이터 분류: 정적 파일, 로그, 백업, 아카이브를 나눕니다.
    2. 권한 모델 정리: 기존 접근 정책을 새 환경에서 어떻게 매핑할지 정합니다.
    3. 소량 검증: 일부 Prefix(프리픽스, 객체 경로 묶음)만 먼저 옮깁니다.
    4. 동기화 반복: 초기 전체 복사 후 증분 동기화(Incremental Sync, 변경분만 반영)를 돌립니다.
    5. 애플리케이션 전환: 읽기 경로를 새 스토리지로 바꿉니다.
    6. 정합성 검증: 객체 수, 체크섬(Checksum, 무결성 검증값), 샘플 다운로드를 확인합니다.

    혹시 이런 경험 있으신가요? 데이터는 다 옮겼는데 애플리케이션이 예전 엔드포인트(Endpoint, 접속 주소)를 계속 바라보는 경우요. 이게 진짜 흔합니다. 그래서 저는 복사보다 전환 계획을 더 오래 잡는 편입니다.

    5. 실전 구현: S3에서 Azure Blob으로 옮길 때 기본 흐름

    여기서는 가장 많이 물어보시는 패턴 중 하나인 Amazon S3 → Azure Blob Storage 예시로 보겠습니다. 도구는 여러 가지가 있지만, 기본 원리는 비슷합니다. 중요한 건 사전 검증과 증분 동기화입니다.

    5-1. 사전 점검 체크리스트

    • 버킷/컨테이너 이름 규칙 확인
    • 객체 메타데이터(Metadata, 부가 속성) 유지 필요 여부 확인
    • 애플리케이션이 S3 API 의존인지 확인
    • 대용량 객체와 소형 파일 비율 확인
    • 암호화(Encryption, 데이터 암호화) 정책 확인

    5-2. AWS CLI로 원본 목록 확인

    aws s3 ls s3://example-bucket --recursive --summarize

    이 단계에서 총 객체 수와 대략적인 용량 감을 먼저 잡습니다. 저는 항상 이 결과를 따로 저장해둡니다. 나중에 검증할 때 기준점이 되거든요.

    5-3. Azure 쪽 컨테이너 준비

    az storage container create \
      --account-name mystorageaccount \
      --name archive-data \
      --auth-mode login

    Azure Blob은 Storage Account 아래에 Container를 두는 구조입니다. S3 버킷만 생각하고 접근하면 처음엔 약간 헷갈릴 수 있습니다.

    AWS GCS vs Azure Blob Storage 마이그레이션 흐름을 설명하는 동기화 구성 이미지

    S3 버킷에서 Azure Blob 컨테이너로 복사되는 흐름과 검증 단계를 표현한 구성 이미지입니다.

    5-4. AzCopy로 Azure 업로드 작업 예시

    실제로는 중간 서버를 두고 내려받았다가 다시 올리는 방식보다, 가능하면 공식 도구와 직접 경로를 검토하는 게 좋습니다. 예시는 로컬 검증용 흐름입니다.

    azcopy copy \
      "/data/migration" \
      "https://mystorageaccount.blob.core.windows.net/archive-data?<SAS_TOKEN>" \
      --recursive=true

    대규모 이전에서는 한 번에 끝내려 하지 말고, 테스트 Prefix부터 돌려보세요. 정말입니다. 저도 처음엔 전체부터 밀었다가 메타데이터 누락 확인하느라 다시 했었거든요.

    5-5. 매핑 정보를 남겨두는 설정 예시

    migration:
      source:
        type: s3
        bucket: example-bucket
        prefix: app-data/
      destination:
        type: azure-blob
        account: mystorageaccount
        container: archive-data
        prefix: app-data/
      validation:
        compare_object_count: true
        sample_download: true
        checksum_when_available: true

    이런 식으로라도 문서화해두면 팀 작업이 훨씬 편합니다. 나중에 ‘어디까지 옮겼죠?’ 하는 순간을 줄여주거든요.

    6. GCS에서 Azure Blob으로 옮길 때는 뭐가 다를까요?

    Google Cloud Storage에서 Azure Blob으로 이동할 때도 핵심은 비슷합니다. 다만 운영 도구가 달라집니다. GCS 쪽은 보통 gsutil이나 GCP 콘솔 기반 검증을 많이 쓰죠.

    gsutil ls -r gs://example-bucket

    GCS는 구조가 단순해서 처음 다루기 편한 편입니다. 근데 여기서도 조심할 게 있습니다. 애플리케이션이 GCS SDK나 이벤트 모델에 직접 의존하고 있으면, 저장소만 바꿔도 끝나지 않습니다. 저장 위치 이전과 애플리케이션 의존성 제거는 별개입니다.

    그래서 저는 마이그레이션 전략을 두 갈래로 봅니다.

    1. 저장소만 이전: 파일 위치만 바꾸고 앱은 최소 수정
    2. 플랫폼 의존성까지 제거: SDK, 이벤트, 권한 구조까지 같이 재설계

    시간이 없으면 1번으로 가되, 장기적으로는 2번을 준비하는 게 맞습니다. 멀티 클라우드 운영에서는 이 차이가 꽤 큽니다.

    7. ⚠️ 실제 겪었던 문제와 트러블슈팅

    7-1. 권한은 맞는데 접근이 안 되는 경우

    이거 정말 자주 봅니다. 키나 토큰은 있는데 네트워크 제한이나 퍼블릭 액세스 차단 정책 때문에 막히는 경우요. 특히 Azure는 계정 레벨 설정과 컨테이너 레벨 설정을 같이 봐야 할 때가 있습니다.

    • 확인 포인트: 계정 수준 퍼블릭 차단 여부
    • 확인 포인트: SAS 만료 시간
    • 확인 포인트: 방화벽/허용 IP 정책

    7-2. 파일은 다 갔는데 애플리케이션이 깨지는 경우

    원인은 대부분 경로 규칙 차이, 메타데이터 처리, 캐시(Cache, 임시 저장)입니다. 예를 들어 정적 웹 리소스는 Content-Type이 틀어지면 브라우저에서 바로 티가 납니다. 저는 이 부분 때문에 이미지가 다운로드로 열리는 문제를 겪었었는데, 메타데이터 재설정으로 해결했습니다.

    7-3. 작은 파일이 너무 많아서 속도가 안 나오는 경우

    대용량 단일 파일보다, 작은 파일 수십만 개가 더 까다롭습니다. API 호출 수가 많아지고 병렬 처리 튜닝이 필요하거든요. 이럴 땐 샘플 구간으로 성능을 먼저 재보고, 작업 창(Window, 이전 가능 시간)을 계산하는 게 안전합니다.

    AWS GCS vs Azure Blob Storage 이전 중 권한 오류와 검증 포인트를 보여주는 이미지

    권한 오류, 메타데이터 누락, 경로 불일치 같은 대표적인 마이그레이션 문제를 요약한 트러블슈팅 이미지입니다.

    8. 검증과 결과 확인: 여기까지 해야 진짜 끝입니다

    복사가 끝났다고 끝이 아닙니다. 검증(Validation, 결과 확인)이 빠지면 운영 전환 후 사고가 납니다. 저는 최소한 아래 네 가지는 꼭 봅니다.

    1. 객체 수 비교: 원본과 대상의 총 개수 확인
    2. 샘플 다운로드: 랜덤 파일 몇 개 직접 열어보기
    3. 애플리케이션 테스트: 실제 서비스 경로에서 읽기 확인
    4. 로그 모니터링: 403, 404, 타임아웃 여부 확인
    aws s3 ls s3://example-bucket --recursive | wc -l
    az storage blob list \
      --account-name mystorageaccount \
      --container-name archive-data \
      --num-results "*" \
      --auth-mode login

    실제로 써보니까, 숫자만 맞는다고 안심하면 안 되더라고요. 샘플 파일 열어보면 메타데이터나 인코딩 문제를 발견하는 경우가 있습니다. 드디어 됐다! 싶었는데 마지막 검증에서 다시 되돌아간 적도 있었네요.

    AWS GCS vs Azure Blob Storage 비교 후 마이그레이션 결과 검증 대시보드 이미지

    객체 수, 오류율, 전환 완료 상태를 보여주는 운영 검증 대시보드 이미지입니다.

    9. 정리: 어떤 선택이 더 나을까요?

    결론부터 말씀드리면, 어느 스토리지가 무조건 더 좋다기보다 현재 워크로드와 조직 구조에 더 잘 맞는 쪽이 있습니다.

    • AWS 중심 운영이면 Amazon S3가 가장 자연스럽습니다.
    • GCP 분석 워크로드가 많으면 Google Cloud Storage가 편합니다.
    • 기업 권한 체계와 Microsoft 생태계가 중요하면 Azure Blob Storage가 강점이 있습니다.

    다만 AWS GCS vs Azure Blob Storage처럼 검색하신 분이라면, 실제 의사결정에서는 S3 vs GCS vs Azure Blob 세 축으로 다시 정리해보시는 걸 권합니다. 이름이 섞인 상태로 비교를 시작하면 요구사항도 같이 꼬이거든요.

    제가 직접 해보니 마이그레이션 전략에서 제일 중요한 건 화려한 도구보다 정합성 검증, 권한 설계, 전환 순서였습니다. 이 세 가지만 잡아도 실패 확률이 확 줄어듭니다. 다음 글에서는 S3 호환(Object Storage Compatibility) 스토리지를 포함해서 MinIO 같은 대안까지 비교해볼 예정입니다. 이전 글에서 다뤘던 백업 정책 설계 글과 함께 보시면 더 연결이 잘 되실 겁니다.

    FAQ

    AWS GCS라는 서비스가 정말 있나요?

    아닙니다. AWS의 대표 객체 스토리지는 Amazon S3이고, GCS는 Google Cloud Storage입니다. 검색어에서 두 이름이 섞여 쓰이는 경우가 많습니다.

    마이그레이션 시 가장 먼저 볼 항목은 뭔가요?

    저는 권한 모델과 데이터 전송 경로를 먼저 봅니다. 저장 요금보다 전송 요금과 접근 실패가 더 큰 문제를 만들 때가 많거든요.

    비용 최적화는 어떤 방식으로 접근하면 좋나요?

    저장 계층(Tier)만 보지 말고, 데이터가 어디서 생성되고 어디서 읽히는지를 같이 봐야 합니다. 특히 멀티 클라우드에서는 Egress 비용을 꼭 확인하세요.

    워크로드, 권한, 비용, 마이그레이션 난이도 기준으로 세 스토리지를 요약 비교한 인포그래픽입니다.

  • [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] Ansible로 멀티 클라우드 인프라 자동화: AWS, Azure, GCP 통합 관리 가이드

    [Cloud] Ansible로 멀티 클라우드 인프라 자동화: AWS, Azure, GCP 통합 관리 가이드

    Ansible로 멀티 클라우드 인프라 자동화: AWS, Azure, GCP 통합 관리 가이드

    안녕하세요, 13년차 서버실 지킴이입니다. 요즘 인프라 환경을 보면 단순히 한 클라우드에만 갇혀 있지 않죠? 멀티 클라우드(Multi-Cloud)는 이제 선택이 아니라 필수가 되어가는 시대인 것 같습니다. 저도 처음엔 AWS만 주력으로 썼었는데, 프로젝트가 커지고 요구사항이 다양해지면서 Azure나 GCP도 함께 다루게 되더라고요. 근데 이게 여러 클라우드를 동시에 관리하려니 여간 복잡한 게 아니었습니다. 각 클라우드마다 CLI도 다르고, API도 다르고… 수동으로 관리하다가는 퇴근은커녕 야근만 늘겠다 싶었죠. 😭

    이런 고민을 하던 중에 저의 든든한 동반자, Ansible(앤서블)을 떠올렸습니다. Ansible은 이미 온프레미스 환경에서 서버 자동화에 요긴하게 써왔던 도구거든요. ‘이걸로 멀티 클라우드도 통합 관리할 수 있지 않을까?’ 하는 생각에 홈랩에서 이것저것 실험해봤습니다. 결과는 대만족이었습니다! 오늘은 제가 직접 경험하며 삽질했던 내용과 함께, Ansible로 AWS, Azure, GCP 인프라를 한 번에 자동화하는 방법을 여러분께 알려드리려고 합니다. 이 글을 통해 멀티 클라우드 관리의 복잡성을 확 줄여보시길 바랍니다. 자, 그럼 시작해볼까요? 🎉

    Ansible을 활용한 멀티 클라우드 통합 관리 아키텍처 다이어그램.

    Ansible, 왜 멀티 클라우드 자동화에 최적일까요?

    먼저 Ansible이 어떤 녀석인지, 그리고 왜 멀티 클라우드 환경에 딱 맞는지 간단히 짚고 넘어가죠. 쉽게 말해 Ansible(앤서블)은 에이전트리스(Agentless) 기반의 자동화 도구(Automation Tool)입니다. 관리 대상 서버에 별도의 에이전트를 설치할 필요 없이 SSH(Secure Shell)나 WinRM(Windows Remote Management) 프로토콜을 이용해 명령을 실행하죠. 이게 멀티 클라우드 환경에서 엄청난 강점입니다.

    • 단일 제어 플레인(Single Control Plane): 각 클라우드 콘솔이나 CLI를 오갈 필요 없이, Ansible 컨트롤러 노드(Control Node) 하나에서 모든 클라우드 인프라를 관리할 수 있습니다.
    • 모듈(Modules) 기반의 추상화: Ansible은 각 클라우드 프로바이더(Cloud Provider)별로 수많은 모듈을 제공합니다. 예를 들어, AWS EC2 인스턴스를 만들 때는 amazon.aws.ec2_instance 모듈을, Azure VM을 만들 때는 azure.azcollection.azure_rm_virtualmachine 모듈을 사용하죠. 각 클라우드 API의 복잡성을 몰라도 모듈만 잘 사용하면 됩니다.
    • 멱등성(Idempotency): 플레이북(Playbook)을 여러 번 실행해도 항상 동일한 최종 상태(Desired State)를 보장합니다. 이미 생성된 리소스는 건드리지 않고, 필요한 변경사항만 적용되니 안심하고 재실행할 수 있습니다.
    • YAML(YAML Ain’t Markup Language) 기반의 쉬운 학습 곡선: 복잡한 프로그래밍 언어 대신 사람이 읽고 쓰기 쉬운 YAML 문법으로 플레이북을 작성합니다. 인프라 엔지니어에게는 정말 친숙한 방식이죠.

    결론적으로 Ansible은 코드로서의 인프라(Infrastructure as Code, IaC)를 구현하기에 아주 적합하며, 특히 이종 클라우드 환경을 통합 관리하는 데 강력한 성능을 발휘합니다.

    실전 구현: AWS, Azure, GCP 인프라 자동화

    자, 이제 직접 Ansible로 멀티 클라우드 인프라를 구축해볼 차례입니다. 목표는 각 클라우드에 웹 서버 역할을 할 가상 머신(Virtual Machine)을 하나씩 생성하고, 간단한 웹 서비스(Nginx)를 설치하는 것입니다.

    1. Ansible 및 클라우드 연동 환경 설정

    먼저 Ansible 컨트롤러 노드에 필요한 도구들을 설치해야 합니다. 저는 Ubuntu 환경에서 진행했습니다.

    #!/bin/bash
    # Ansible 설치
    sudo apt update && sudo apt install -y ansible python3-pip
    
    # AWS 연동을 위한 boto3 라이브러리 설치
    pip3 install boto3 botocore
    
    # Azure 연동을 위한 Azure CLI 및 모듈 설치
    sudo apt install -y azure-cli
    ansible-galaxy collection install azure.azcollection
    
    # GCP 연동을 위한 Google Cloud SDK 및 모듈 설치
    echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
    sudo apt update && sudo apt install -y google-cloud-sdk
    ansible-galaxy collection install google.cloud
    

    각 클라우드 인증 정보 설정도 중요합니다:

    • AWS: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 환경 변수 설정 또는 ~/.aws/credentials 파일 설정
    • Azure: az login 명령으로 로그인 또는 서비스 주체(Service Principal) 생성 후 환경 변수 설정
    • GCP: 서비스 계정(Service Account) 키 파일 다운로드 후 GOOGLE_APPLICATION_CREDENTIALS 환경 변수 설정

    ⚠️ 인증 정보 관리 중요: 실제 운영 환경에서는 AWS Secrets Manager, Azure Key Vault, GCP Secret Manager 같은 비밀 관리 서비스(Secret Management Service)를 활용하거나 Ansible Vault를 사용하여 인증 정보를 안전하게 보관해야 합니다. 절대 민감한 정보를 플레이북에 직접 넣지 마세요!

    2. 동적 인벤토리(Dynamic Inventory) 설정

    클라우드 환경은 리소스가 수시로 생성/삭제되기 때문에 고정된 인벤토리 파일보다는 동적 인벤토리를 사용하는 것이 효율적입니다. Ansible은 각 클라우드 프로바이더를 위한 동적 인벤토리 플러그인을 제공하거든요.

    # aws_ec2.yml (AWS EC2 인스턴스 동적 인벤토리)
    plugin: amazon.aws.ec2
    regions:
      - ap-northeast-2
    keyed_groups:
      - key: tags.env
        prefix: env_
    
    # azure_rm.yml (Azure VM 동적 인벤토리)
    plugin: azure.azcollection.azure_rm
    include_vm_resource_groups:
      - my-ansible-rg
    
    # gcp_compute.yml (GCP Compute Engine 동적 인벤토리)
    plugin: google.cloud.gcp_compute
    projects:
      - your-gcp-project-id
    zones:
      - asia-northeast3-a
    

    이제 ansible-inventory -i aws_ec2.yml --list 등으로 각 클라우드의 인벤토리를 확인할 수 있습니다.

    Ansible으로 생성된 각 클라우드 가상 머신 목록.

    3. 클라우드 리소스 생성 플레이북 작성

    각 클라우드에 가상 머신을 생성하고 Nginx를 설치하는 플레이북입니다. 하나의 플레이북에서 여러 클라우드를 대상으로 할 수 있다는 점이 핵심입니다.

    ---
    # multi_cloud_infra.yml
    # AWS, Azure, GCP에 동시에 웹 서버를 배포하는 플레이북
    
    - name: AWS 인프라 준비
      hosts: localhost
      connection: local
      gather_facts: no
    
      vars:
        aws_region: ap-northeast-2
        ssh_key_path: ~/.ssh/id_rsa.pub
    
      tasks:
        # AWS Security Group 먼저 생성
        - name: AWS 보안 그룹 생성
          amazon.aws.ec2_security_group:
            name: ansible-sg-aws
            description: "Ansible managed AWS webserver security group"
            region: "{{ aws_region }}"
            rules:
              - proto: tcp
                ports: 22
                cidr_ip: 0.0.0.0/0
              - proto: tcp
                ports: 80
                cidr_ip: 0.0.0.0/0
            rules_egress:
              - proto: all
                cidr_ip: 0.0.0.0/0
          register: aws_sg_output
    
        # AWS EC2 인스턴스 생성
        - name: AWS EC2 인스턴스 생성
          amazon.aws.ec2_instance:
            name: ansible-aws-webserver
            image_id: ami-0c802847a7dd848c0  # Ubuntu 22.04 LTS (ap-northeast-2)
            instance_type: t2.micro
            region: "{{ aws_region }}"
            security_group: ansible-sg-aws
            key_name: your_aws_keypair
            tags:
              env: dev
              project: ansible-multi-cloud
            state: running
            wait: yes
          register: aws_ec2_output
    
    - name: Azure 인프라 준비
      hosts: localhost
      connection: local
      gather_facts: no
    
      vars:
        azure_resource_group: my-ansible-rg
        azure_location: koreacentral
        ssh_key_path: ~/.ssh/id_rsa.pub
    
      tasks:
        # Azure Virtual Network 생성 (필수 선행 작업)
        - name: Azure Virtual Network 생성
          azure.azcollection.azure_rm_virtualnetwork:
            resource_group: "{{ azure_resource_group }}"
            name: ansible-vnet
            location: "{{ azure_location }}"
            address_prefixes:
              - "10.0.0.0/16"
          register: azure_vnet_output
    
        # Azure Subnet 생성
        - name: Azure Subnet 생성
          azure.azcollection.azure_rm_subnet:
            resource_group: "{{ azure_resource_group }}"
            name: default
            address_prefix: "10.0.0.0/24"
            virtual_network: ansible-vnet
          register: azure_subnet_output
    
        # Azure NSG 생성
        - name: Azure 네트워크 보안 그룹 생성
          azure.azcollection.azure_rm_networksecuritygroup:
            resource_group: "{{ azure_resource_group }}"
            name: ansible-azure-nsg
            location: "{{ azure_location }}"
            rules:
              - name: AllowSSH
                protocol: Tcp
                destination_port_range: 22
                access: Allow
                priority: 100
                direction: Inbound
              - name: AllowHTTP
                protocol: Tcp
                destination_port_range: 80
                access: Allow
                priority: 110
                direction: Inbound
          register: azure_nsg_output
    
        # Azure VM 생성
        - name: Azure VM 생성
          azure.azcollection.azure_rm_virtualmachine:
            resource_group: "{{ azure_resource_group }}"
            name: ansible-azure-webserver
            vm_size: Standard_B1s
            admin_username: azureuser
            ssh_password_enabled: false
            ssh_public_keys:
              - path: /home/azureuser/.ssh/authorized_keys
                key_data: "{{ lookup('file', ssh_key_path) }}"
            image:
              offer: 0001-com-ubuntu-server-jammy
              publisher: Canonical
              sku: 22_04-lts-gen2
              version: latest
            location: "{{ azure_location }}"
          register: azure_vm_output
    
    - name: GCP 인프라 준비
      hosts: localhost
      connection: local
      gather_facts: no
    
      vars:
        gcp_project: your-gcp-project-id
        gcp_zone: asia-northeast3-a
        gcp_network: default
        ssh_key_path: ~/.ssh/id_rsa.pub
        gcp_user: ansible
    
      tasks:
        # GCP Firewall 규칙 생성
        - name: GCP 방화벽 규칙 생성
          google.cloud.gcp_compute_firewall:
            name: ansible-gcp-allow-http-ssh
            project: "{{ gcp_project }}"
            allowed:
              - IPProtocol: tcp
                ports: [ '22', '80' ]
            source_ranges: [ '0.0.0.0/0' ]
            target_tags: [ 'http-server' ]
            state: present
          register: gcp_fw_output
    
        # GCP Compute Engine 인스턴스 생성
        - name: GCP Compute Engine 인스턴스 생성
          google.cloud.gcp_compute_instance:
            name: ansible-gcp-webserver
            project: "{{ gcp_project }}"
            zone: "{{ gcp_zone }}"
            machine_type: e2-micro
            disks:
              - auto_delete: 'true'
                boot: 'true'
                initialize_params:
                  source_image: projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts
                  disk_size_gb: 20
            network_interfaces:
              - name: "{{ gcp_network }}"
                access_configs:
                  - name: "External NAT"
                    type: "ONE_TO_ONE_NAT"
            metadata:
              ssh-keys: "{{ gcp_user }}:{{ lookup('file', ssh_key_path) }}"
            tags:
              items:
                - http-server
            state: present
          register: gcp_instance_output
    
    - name: 모든 클라우드 웹서버에 Nginx 설치
      hosts: all
      gather_facts: yes
    
      pre_tasks:
        - name: SSH 연결 대기
          ansible.builtin.wait_for_connection:
            timeout: 300
    
      tasks:
        - name: Nginx 설치
          ansible.builtin.apt:
            name: nginx
            state: present
            update_cache: yes
          become: yes
    
        - name: Nginx 서비스 시작
          ansible.builtin.service:
            name: nginx
            state: started
            enabled: yes
          become: yes
    

    위 플레이북은 각 클라우드별로 분리된 구조로 작성되어 있습니다. 첫 번째부터 세 번째까지는 각각 AWS, Azure, GCP 인프라를 생성하는 hosts: localhost 플레이북입니다. 마지막 플레이북은 동적 인벤토리에서 수집된 모든 호스트에 Nginx를 설치합니다.

    플레이북 실행은 간단합니다:

    ansible-playbook -i aws_ec2.yml -i azure_rm.yml -i gcp_compute.yml multi_cloud_infra.yml
    

    ⚠️ 삽질 경험 및 트러블슈팅

    제가 이 과정을 진행하면서 겪었던 몇 가지 삽질 경험과 해결 방법을 공유합니다. 여러분은 저처럼 헤매지 마시길 바랍니다. ㅎㅎ

    1. 클라우드 인증 실패

    문제: 플레이북 실행 시 Unauthorized, Access Denied, Credential Error 같은 메시지가 뜨면서 클라우드 리소스 생성이 안 되는 경우가 많았습니다.

    해결:

    • AWS: ~/.aws/credentials 파일의 내용이 정확한지, 또는 환경 변수(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)가 올바르게 설정되었는지 확인했습니다. 그리고 해당 IAM 사용자에게 EC2 생성, Security Group 관리 등 필요한 권한이 제대로 부여되었는지 IAM 정책을 검토했습니다.
    • Azure: az login으로 로그인 세션이 유효한지 확인하거나, 서비스 주체(Service Principal)를 사용한다면 AZURE_CLIENT_ID, AZURE_SECRET, AZURE_TENANT, AZURE_SUBSCRIPTION_ID 환경 변수가 정확한지 확인했습니다. 해당 서비스 주체에 기여자(Contributor) 또는 그에 준하는 역할이 할당되어야 합니다.
    • GCP: 서비스 계정 키 파일(JSON)의 경로가 GOOGLE_APPLICATION_CREDENTIALS 환경 변수에 올바르게 지정되었는지 확인했습니다. 또한, 서비스 계정에 Compute Engine Instance Admin (v1), Service Account User 등 필요한 역할이 부여되어 있는지 GCP IAM 콘솔에서 확인했습니다.

    2. SSH 접속 문제 (Nginx 설치 단계)

    문제: 인스턴스는 잘 생성됐는데, Nginx 설치 단계에서 SSH Connection refused 또는 Timeout 에러가 발생했습니다.

    해결:

    • 보안 그룹/방화벽 규칙: 각 클라우드에서 생성된 가상 머신의 보안 그룹(AWS), 네트워크 보안 그룹(Azure), 방화벽 규칙(GCP)에 Ansible 컨트롤러 노드의 IP 주소 또는 0.0.0.0/0(테스트용)에서 22번 포트(SSH) 접속을 허용하는 규칙이 있는지 확인했습니다.
    • SSH 키페어: 플레이북에서 지정한 ssh_key_path의 퍼블릭 키가 각 클라우드 인스턴스에 올바르게 등록되었는지 확인했습니다. AWS는 키페어 이름을, Azure/GCP는 직접 퍼블릭 키 내용을 전달하는 방식이므로 차이에 유의했습니다.
    • 사용자 이름: 각 클라우드 인스턴스의 기본 사용자 이름이 다릅니다. AWS Ubuntu는 ubuntu, Azure Ubuntu는 azureuser, GCP Ubuntu는 ubuntu로 설정했습니다. 플레이북에 ansible_user: 사용자명으로 명시하면 더 안전합니다.
    • 인스턴스 부팅 시간: 가끔 인스턴스가 완전히 부팅되기 전에 Ansible이 SSH 접속을 시도하여 실패하는 경우가 있습니다. ansible.builtin.wait_for_connection 모듈을 사용해 SSH 포트가 열릴 때까지 기다리도록 플레이북에 추가했습니다. 이건 정말 중요해요!

    3. Azure 네트워크 구성 누락

    문제: Azure VM을 생성하려니 Virtual Network와 Subnet이 없다고 에러가 나왔습니다.

    해결: Azure는 AWS나 GCP와 달리 기본 네트워크 구조가 없으므로, VM 생성 전에 Virtual Network와 Subnet을 명시적으로 생성해야 합니다. 위의 플레이북에서 이를 추가했으니 참고하세요.

    검증 및 결과 확인 ✅

    플레이북 실행이 성공적으로 완료되었다면, 이제 각 클라우드 콘솔에 접속하여 리소스가 잘 생성되었는지 확인해볼 차례입니다. AWS EC2, Azure VM, GCP Compute Engine 목록에서 ansible-aws-webserver, ansible-azure-webserver, ansible-gcp-webserver라는 이름의 인스턴스가 실행 중인 것을 볼 수 있을 겁니다.

    각 인스턴스의 퍼블릭 IP 주소로 웹 브라우저에서 접속해보세요. Nginx 기본 페이지가 보인다면 성공입니다! 드디어 됐다! 🎉

    이 과정에서 여러분이 직접 각 클라우드의 콘솔을 열어 인스턴스를 생성하고, 보안 그룹을 설정하고, SSH로 접속해서 Nginx를 설치하는 수고를 덜 수 있었다는 것을 체감할 수 있을 겁니다. 자동화의 힘은 정말 대단하죠.

    성공적으로 배포된 Nginx 웹 서버 페이지.

    마무리하며: 클라우드 여정의 다음 단계

    오늘은 Ansible을 활용해서 AWS, Azure, GCP 멀티 클라우드 환경에 인프라를 자동화하고 웹 서버를 배포하는 과정을 함께 해봤습니다. 제가 직접 해보니, 처음엔 각 클라우드 모듈과 인증 방식에 적응하는 데 시간이 좀 걸렸지만, 한 번 체계를 잡아두니 그 이후부터는 정말 편하더라고요. 삽질 끝에 얻은 귀한 경험이었습니다. 💡

    이 글이 여러분의 멀티 클라우드 여정에 작은 등대가 되었기를 바랍니다. 여기서 멈추지 않고, 더 나아가 다음과 같은 내용들을 탐구해보시면 좋을 것 같습니다:

    • Ansible Vault: 민감한 정보를 안전하게 관리하는 방법.
    • CI/CD 파이프라인 통합: GitOps 워크플로우를 통해 변경 사항이 자동으로 배포되도록 구성.
    • Terraform과의 연동: Terraform으로 인프라를 프로비저닝하고, Ansible로 프로비저닝된 인스턴스에 소프트웨어를 구성하는 하이브리드 접근 방식.
    • 삭제 플레이북: 생성된 리소스를 깔끔하게 정리하는 삭제 플레이북 작성 (state: absent 활용).

    저도 홈랩에서 이런저런 실험을 계속하면서 새로운 인사이트를 얻고 있습니다. 다음번에는 더 재미있고 유익한 경험담으로 찾아뵙겠습니다. 혹시 이 글을 읽으시면서 궁금한 점이나 공유하고 싶은 삽질 경험이 있으시다면 언제든지 댓글로 남겨주세요! 감사합니다!

    Ansible을 통한 멀티 클라우드 자동화 요약.

  • [Cloud] Terraform 멀티 클라우드 IaC 자동화 가이드 — AWS/GCP/Azure 통합 관리

    [Cloud] Terraform 멀티 클라우드 IaC 자동화 가이드 — AWS/GCP/Azure 통합 관리

    멀티 클라우드, 왜 이렇게 복잡한 걸까요?

    솔직히 말씀드리면, 저도 처음 멀티 클라우드 환경을 맡았을 때 머리가 좀 아팠습니다. AWS 콘솔 따로, GCP 콘솔 따로, Azure 포털 따로… 각각 로그인하고, 각각 다른 방식으로 리소스 만들고, 나중에 뭘 어디에 만들었는지 파악도 안 되는 그 상황 말이에요. 혹시 이런 경험 있으신가요?

    13년 동안 인프라 엔지니어로 일하면서 클라우드 환경이 단일 벤더에서 멀티 클라우드로 넘어가는 걸 직접 겪었는데요. 이게 비즈니스 연속성 확보나 벤더 종속(Vendor Lock-in) 방지 측면에서는 분명히 좋은 전략이에요. 근데 관리가 지옥이 되기 시작하거든요.

    그 해결책이 바로 Terraform 멀티 클라우드 IaC 자동화입니다. 오늘은 Terraform을 이용해서 AWS, GCP, Azure를 하나의 코드베이스로 관리하는 방법을 실제 경험 기반으로 풀어드릴게요.

    Terraform 멀티 클라우드 아키텍처 다이어그램 — AWS, GCP, Azure 동시 관리

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

    Terraform IaC가 뭔지 먼저 짚고 넘어가요

    IaC(Infrastructure as Code, 코드로 인프라를 정의하는 방식)는 이름 그대로 서버, 네트워크, 데이터베이스 같은 인프라를 코드 파일로 정의하고 버전 관리하는 방식입니다. 쉽게 말해, 클릭클릭으로 콘솔에서 만들던 걸 코드로 적어두는 거예요.

    Terraform은 HashiCorp가 만든 오픈소스 IaC 도구인데요. 가장 큰 장점이 프로바이더(Provider) 개념입니다. AWS용 프로바이더, GCP용 프로바이더, Azure용 프로바이더를 각각 선언하면, 하나의 Terraform 코드베이스에서 세 클라우드를 동시에 다룰 수 있어요. 이게 진짜 강력한 포인트예요.

    비교 항목 콘솔 수동 관리 Terraform IaC 자동화
    반복 작업 매번 클릭 필요 코드 한 번 작성 후 재사용
    버전 관리 불가능 (변경 이력 추적 어려움) Git으로 완전한 이력 관리
    멀티 클라우드 각 콘솔 별도 접근 필요 단일 코드베이스로 통합 관리
    팀 협업 “누가 뭘 만들었는지” 파악 어려움 코드 리뷰로 변경 사항 투명 공유
    재현성 동일 환경 재현 어려움 동일 코드로 동일 환경 재현 보장

    프로젝트 구조 잡기 — 이게 제일 중요합니다

    처음 멀티 클라우드 Terraform을 짤 때 가장 많이 실수하는 게 디렉토리 구조거든요. 저도 처음엔 파일 다 때려넣고 나중에 엉망이 돼서 처음부터 다시 짠 적이 있습니다. 클라우드별로 분리하고, 환경(dev/prod)별로도 분리하는 구조를 추천드려요.

    multi-cloud-infra/
    ├── modules/                    # 재사용 가능한 모듈 모음
    │   ├── aws/
    │   │   ├── vpc/
    │   │   │   ├── main.tf
    │   │   │   ├── variables.tf
    │   │   │   └── outputs.tf
    │   │   └── ec2/
    │   │       ├── main.tf
    │   │       ├── variables.tf
    │   │       └── outputs.tf
    │   ├── gcp/
    │   │   ├── vpc/
    │   │   └── compute/
    │   └── azure/
    │       ├── vnet/
    │       └── vm/
    ├── environments/
    │   ├── dev/
    │   │   ├── main.tf
    │   │   ├── variables.tf
    │   │   └── terraform.tfvars
    │   └── prod/
    │       ├── main.tf
    │       ├── variables.tf
    │       └── terraform.tfvars
    └── backend.tf                  # 원격 상태 저장소 설정
    

    💡 팁: modules 디렉토리에 클라우드별로 공통 모듈을 만들어두면, 나중에 환경을 추가할 때 모듈만 호출하면 되니까 진짜 편해요. 처음 구조 잡는 데 시간 좀 써도 나중에 열 배로 돌아옵니다.

    실전 구현 — Terraform 멀티 클라우드 프로바이더 설정부터

    자, 이제 실제로 코드를 작성해볼게요. 먼저 세 클라우드의 프로바이더를 한 파일에 선언하는 것부터 시작합니다.

    1단계: 프로바이더(Provider) 설정

    # providers.tf
    
    terraform {
      required_version = ">= 1.5.0"
    
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.0"
        }
        google = {
          source  = "hashicorp/google"
          version = "~> 5.0"
        }
        azurerm = {
          source  = "hashicorp/azurerm"
          version = "~> 3.0"
        }
      }
    
      # 원격 백엔드 (Remote Backend) — 팀 협업 시 필수!
      backend "s3" {
        bucket         = "my-terraform-state-bucket"
        key            = "multi-cloud/terraform.tfstate"
        region         = "ap-northeast-2"
        encrypt        = true
        dynamodb_table = "terraform-state-lock"  # 상태 잠금(State Locking)용
      }
    }
    
    # AWS 프로바이더
    provider "aws" {
      region = var.aws_region
    
      default_tags {
        tags = {
          ManagedBy   = "Terraform"
          Environment = var.environment
          Project     = var.project_name
        }
      }
    }
    
    # GCP 프로바이더
    provider "google" {
      project = var.gcp_project_id
      region  = var.gcp_region
    }
    
    # Azure 프로바이더
    provider "azurerm" {
      features {}
      subscription_id = var.azure_subscription_id
    }
    

    여기서 중요한 포인트! 원격 백엔드(Remote Backend)는 팀으로 작업할 때 절대 빠지면 안 됩니다. 상태 파일(State File)을 로컬에 두면 팀원끼리 충돌이 생기거든요. 저도 초반에 이걸 몰라서 상태 파일 날려먹은 적이 있습니다.

    2단계: 변수 파일 설정

    # variables.tf
    
    variable "environment" {
      description = "배포 환경 (dev, staging, prod)"
      type        = string
      default     = "dev"
    }
    
    variable "project_name" {
      description = "프로젝트 이름"
      type        = string
    }
    
    # AWS 관련 변수
    variable "aws_region" {
      description = "AWS 리전"
      type        = string
      default     = "ap-northeast-2"  # 서울 리전
    }
    
    # GCP 관련 변수
    variable "gcp_project_id" {
      description = "GCP 프로젝트 ID"
      type        = string
    }
    
    variable "gcp_region" {
      description = "GCP 리전"
      type        = string
      default     = "asia-northeast3"  # 서울 리전
    }
    
    # Azure 관련 변수
    variable "azure_subscription_id" {
      description = "Azure 구독 ID"
      type        = string
      sensitive   = true  # 민감 정보 마스킹
    }
    
    variable "azure_location" {
      description = "Azure 지역"
      type        = string
      default     = "Korea Central"
    }
    
    Terraform 멀티 클라우드 프로바이더 설정 코드 — AWS GCP Azure 동시 구성

    ▲ 세 클라우드의 프로바이더를 하나의 코드베이스에서 관리하는 실제 구성 예시

    3단계: AWS VPC 모듈 작성

    # modules/aws/vpc/main.tf
    
    resource "aws_vpc" "main" {
      cidr_block           = var.vpc_cidr
      enable_dns_hostnames = true
      enable_dns_support   = true
    
      tags = {
        Name = "${var.project_name}-${var.environment}-vpc"
      }
    }
    
    resource "aws_subnet" "public" {
      count             = length(var.public_subnet_cidrs)
      vpc_id            = aws_vpc.main.id
      cidr_block        = var.public_subnet_cidrs[count.index]
      availability_zone = var.availability_zones[count.index]
    
      map_public_ip_on_launch = true
    
      tags = {
        Name = "${var.project_name}-public-subnet-${count.index + 1}"
        Type = "Public"
      }
    }
    
    resource "aws_internet_gateway" "main" {
      vpc_id = aws_vpc.main.id
    
      tags = {
        Name = "${var.project_name}-igw"
      }
    }
    

    4단계: GCP VPC 네트워크 모듈

    # modules/gcp/vpc/main.tf
    
    resource "google_compute_network" "main" {
      name                    = "${var.project_name}-${var.environment}-vpc"
      auto_create_subnetworks = false  # 커스텀 서브넷 사용
      project                 = var.project_id
    }
    
    resource "google_compute_subnetwork" "main" {
      name          = "${var.project_name}-subnet"
      ip_cidr_range = var.subnet_cidr
      region        = var.region
      network       = google_compute_network.main.id
      project       = var.project_id
    
      # Private Google Access — GCP 내부 서비스 접근용
      private_ip_google_access = true
    }
    
    # 방화벽 규칙 (Firewall Rule)
    resource "google_compute_firewall" "allow_internal" {
      name    = "${var.project_name}-allow-internal"
      network = google_compute_network.main.name
      project = var.project_id
    
      allow {
        protocol = "tcp"
        ports    = ["0-65535"]
      }
    
      allow {
        protocol = "udp"
        ports    = ["0-65535"]
      }
    
      allow {
        protocol = "icmp"
      }
    
      source_ranges = [var.subnet_cidr]
    }
    

    5단계: Azure VNet 모듈

    # modules/azure/vnet/main.tf
    
    resource "azurerm_resource_group" "main" {
      name     = "${var.project_name}-${var.environment}-rg"
      location = var.location
    
      tags = {
        ManagedBy   = "Terraform"
        Environment = var.environment
      }
    }
    
    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
    }
    
    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]
    }
    
    # NSG (Network Security Group, 네트워크 보안 그룹)
    resource "azurerm_network_security_group" "main" {
      name                = "${var.project_name}-nsg"
      location            = azurerm_resource_group.main.location
      resource_group_name = azurerm_resource_group.main.name
    }
    

    6단계: 환경별 메인 파일에서 모듈 호출

    # environments/dev/main.tf
    
    # AWS 모듈 호출
    module "aws_vpc" {
      source = "../../modules/aws/vpc"
    
      project_name         = var.project_name
      environment          = var.environment
      vpc_cidr             = "10.0.0.0/16"
      public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
      availability_zones   = ["ap-northeast-2a", "ap-northeast-2c"]
    }
    
    # GCP 모듈 호출
    module "gcp_vpc" {
      source = "../../modules/gcp/vpc"
    
      project_name = var.project_name
      environment  = var.environment
      project_id   = var.gcp_project_id
      region       = var.gcp_region
      subnet_cidr  = "10.1.0.0/16"
    }
    
    # Azure 모듈 호출
    module "azure_vnet" {
      source = "../../modules/azure/vnet"
    
      project_name = var.project_name
      environment  = var.environment
      location     = var.azure_location
      vnet_cidr    = "10.2.0.0/16"
      subnet_cidr  = "10.2.1.0/24"
    }
    

    ⚠️ 삽질 포인트 — 이것만 조심하세요

    제가 멀티 클라우드 Terraform 구축하면서 진짜 고생했던 부분들을 공유드릴게요. 여러분은 같은 실수 안 하셨으면 해서요.

    문제 1: 인증 정보 관리

    각 클라우드마다 인증 방식이 달라서 처음엔 환경변수를 어디다 어떻게 설정해야 하는지 헷갈렸습니다. 절대 코드에 크리덴셜(Credential, 인증 정보)을 하드코딩하지 마세요!

    # AWS 인증 — AWS CLI 프로파일 사용 권장
    export AWS_PROFILE=my-profile
    # 또는 환경변수로
    export AWS_ACCESS_KEY_ID="your-access-key"
    export AWS_SECRET_ACCESS_KEY="your-secret-key"
    
    # GCP 인증 — 서비스 계정 키 파일 사용
    export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
    # 또는 gcloud CLI 인증
    gcloud auth application-default login
    
    # Azure 인증 — Service Principal 사용
    export ARM_CLIENT_ID="your-client-id"
    export ARM_CLIENT_SECRET="your-client-secret"
    export ARM_TENANT_ID="your-tenant-id"
    export ARM_SUBSCRIPTION_ID="your-subscription-id"
    

    💡 팁: CI/CD 파이프라인에서는 각 클라우드의 OIDC(OpenID Connect) 방식 인증을 사용하면 시크릿 관리가 훨씬 깔끔해집니다. GitHub Actions랑 연동하면 특히 좋아요.

    문제 2: 상태 파일(State File) 충돌

    팀원이 동시에 terraform apply 돌리면 상태 파일이 꼬입니다. 이게 진짜 무서운 상황이에요. DynamoDB 테이블로 상태 잠금(State Locking)을 반드시 설정하세요.

    # 상태 잠금용 DynamoDB 테이블 생성
    resource "aws_dynamodb_table" "terraform_state_lock" {
      name           = "terraform-state-lock"
      billing_mode   = "PAY_PER_REQUEST"
      hash_key       = "LockID"
    
      attribute {
        name = "LockID"
        type = "S"
      }
    
      tags = {
        Name = "Terraform State Lock Table"
      }
    }
    

    문제 3: 프로바이더 버전 충돌

    이거 진짜 골치 아팠는데요. required_providers에 버전 범위를 명확히 지정하지 않으면 팀원마다 다른 버전이 설치돼서 동작이 달라지는 일이 생겨요. 반드시 .terraform.lock.hcl 파일을 Git에 커밋하세요. 이게 npm의 package-lock.json 같은 역할을 합니다.

    # 프로바이더 초기화 및 잠금 파일 생성
    terraform init
    
    # 잠금 파일 확인
    cat .terraform.lock.hcl
    
    # 잠금 파일을 Git에 커밋
    git add .terraform.lock.hcl
    git commit -m "chore: update terraform provider lock file"
    

    배포 및 결과 검증

    드디어 실제 배포 단계입니다! Terraform의 기본 워크플로우는 init → plan → apply 순서로 진행됩니다.

    # 1. 초기화 (프로바이더 다운로드)
    terraform init
    
    # 2. 플랜 확인 — 실제로 뭐가 만들어질지 미리 보기
    terraform plan -var-file="terraform.tfvars" -out=tfplan
    
    # 3. 플랜 파일 내용 사람이 읽을 수 있게 출력
    terraform show -json tfplan | jq '.'
    
    # 4. 실제 적용!
    terraform apply tfplan
    
    # 5. 결과 확인
    terraform output
    
    # 6. 상태 파일에서 특정 리소스 확인
    terraform state list
    terraform state show module.aws_vpc.aws_vpc.main
    

    🎉 apply가 성공하면 이런 출력이 나옵니다:

    Apply complete! Resources: 15 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    aws_vpc_id = "vpc-0a1b2c3d4e5f67890"
    aws_public_subnet_ids = [
      "subnet-0a1b2c3d4e5f67891",
      "subnet-0a1b2c3d4e5f67892",
    ]
    gcp_network_id = "projects/my-project/global/networks/myproject-dev-vpc"
    azure_vnet_id = "/subscriptions/.../resourceGroups/myproject-dev-rg/providers/Microsoft.Network/virtualNetworks/myproject-vnet"
    
    Terraform apply 성공 결과 화면 — 멀티 클라우드 리소스 동시 프로비저닝 완료

    ▲ terraform apply 성공 시 세 클라우드에 동시 프로비저닝된 결과 화면

    Terraform 멀티 클라우드 자동화 — 한 단계 더 나아가기

    여기까지 기본 구조를 잡았다면, 이제 실제 팀 환경에서 쓸 수 있는 수준으로 올려야죠. 제가 실제로 도입해서 효과 본 것들을 공유드릴게요.

    Terragrunt로 반복 코드 줄이기

    Terragrunt는 Terraform의 래퍼(Wrapper) 도구인데요. 환경별로 반복되는 backend 설정이나 공통 변수를 DRY(Don’t Repeat Yourself, 반복하지 않기) 원칙에 맞게 관리할 수 있게 해줍니다. 규모가 커지면 진짜 필요해져요.

    CI/CD 파이프라인 연동

    GitHub Actions나 GitLab CI와 연동해서 PR(Pull Request) 올릴 때 자동으로 terraform plan 결과를 코멘트로 달아주는 워크플로우를 구성하면, 코드 리뷰 단계에서 인프라 변경 사항을 팀 전체가 확인할 수 있어요. 이거 도입하고 나서 팀 내 사고가 확 줄었습니다.

    # .github/workflows/terraform.yml
    name: Terraform CI/CD
    
    on:
      pull_request:
        branches: [main]
      push:
        branches: [main]
    
    jobs:
      terraform:
        name: Terraform Plan & Apply
        runs-on: ubuntu-latest
        
        permissions:
          id-token: write   # OIDC 인증용
          contents: read
          pull-requests: write
    
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Setup Terraform
            uses: hashicorp/setup-terraform@v3
            with:
              terraform_version: "1.6.0"
    
          - name: Configure AWS Credentials (OIDC)
            uses: aws-actions/configure-aws-credentials@v4
            with:
              role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
              aws-region: ap-northeast-2
    
          - name: Terraform Init
            run: terraform init
            working-directory: environments/dev
    
          - name: Terraform Plan
            id: plan
            run: terraform plan -no-color
            working-directory: environments/dev
    
          - name: Comment PR with Plan
            uses: actions/github-script@v7
            if: github.event_name == 'pull_request'
            with:
              script: |
                const output = `#### Terraform Plan 결과 🗺️
                \`\`\`\n${{ steps.plan.outputs.stdout }}\n\`\`\`
                `;
                github.rest.issues.createComment({
                  issue_number: context.issue.number,
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  body: output
                })
    
          - name: Terraform Apply
            if: github.ref == 'refs/heads/main'
            run: terraform apply -auto-approve
            working-directory: environments/dev
    
    Terraform 멀티 클라우드 IaC 자동화 베스트 프랙티스 — 코드에서 배포까지 워크플로우 요약

    ▲ Terraform 멀티 클라우드 IaC 자동화 베스트 프랙티스 요약 — 코드부터 배포까지의 전체 워크플로우

    자주 묻는 질문 (FAQ)

    Q. Terraform Cloud를 써야 하나요, 아니면 자체 백엔드로 충분한가요?

    소규모 팀이라면 S3 + DynamoDB 조합의 자체 백엔드로 충분합니다. 팀이 커지거나 RBAC(역할 기반 접근 제어), 정책 관리가 필요해지면 HCP Terraform(구 Terraform Cloud) 유료 플랜을 고려해볼 만해요.

    Q. 각 클라우드 인증 정보를 어떻게 안전하게 관리하나요?

    CI/CD 환경에서는 각 클라우드의 OIDC 연동을 추천드립니다. 로컬 개발 환경에서는 각 클라우드 CLI 도구의 프로파일 기능을 활용하고, 절대 코드나 tfvars 파일에 시크릿을 하드코딩하지 마세요.

    Q. 멀티 클라우드에서 네트워크를 연결하려면 어떻게 하나요?

    VPN Gateway나 클라우드 간 피어링 서비스를 사용해야 하는데, 이 부분은 별도 글에서 자세히 다룰 예정이에요. 각 클라우드의 VPN 리소스도 Terraform으로 코드화할 수 있습니다.

    마무리 — Terraform 멀티 클라우드, 이제 시작해보세요

    처음 멀티 클라우드 Terraform 구조를 잡을 때는 진짜 막막했는데, 이제 돌아보면 이게 없던 시절로 돌아가기 싫을 만큼 편해졌습니다. 코드로 모든 인프라가 관리되니까 감사 추적(Audit Trail)도 되고, 실수로 뭔가 지워도 코드로 복구할 수 있고, 팀 온보딩도 훨씬 수월해졌어요.

    오늘 다룬 내용을 정리하면:

    • ✅ 프로젝트 구조: 클라우드별, 환경별 디렉토리 분리
    • ✅ 프로바이더 설정: AWS, GCP, Azure를 단일 코드베이스에서 선언
    • ✅ 모듈화: 재사용 가능한 모듈로 반복 코드 제거
    • ✅ 원격 백엔드: 상태 파일 중앙화 및 잠금 설정
    • ✅ CI/CD 연동: GitHub Actions로 자동화 파이프라인 구성

    다음 글에서는 Terraform 모듈 레지스트리 활용법과 실제 프로덕션 환경에서 쓰는 보안 설정들을 다뤄볼 예정이에요. 이전 글에서 Kubernetes 클러스터 구성을 다뤘으니 함께 참고하시면 더 좋을 것 같습니다.

    궁금한 점이나 다른 삽질 경험 있으시면 댓글로 공유해주세요. 같이 고민해봐요! 🎉