본문 바로가기
IaC

[IaC] Terraform으로 Kubernetes 다루기 - 4/5

by cloudraw 2025. 6. 10.

안녕하세요, Cloudraw입니다!

Terraform으로 Kubernetes 다루기 네 번째입니다.

  1. Kubernetes Provider 사용 방법, 기본 오브젝트 생성하기(Namespace, Pod, Service, Secret)
  2. 볼륨 리소스 다루기(CSP별 Blob 및 configmap mount)
  3. 네트워크 리소스 다루기(Ingress, Nginx-Ingress(Helm Chart))
  4. DNS 연동 및 인증서 발급(Let's Encrypt 및 Cert Manager(Helm))
  5. CloudStudio로 위 1~4 구성하기

 

네 번째, DNS 연동 및 인증서 발급 (Let's Encrypt + Cert Manager)

이전 글에서는 Ingress를 구성하여 외부에서 클러스터 내부의 Pod로 트래픽을 라우팅할 수 있도록 만들었습니다.

이번 글에서는 도메인(DNS)을 연동하고, HTTPS 인증서(TLS)를 자동으로 발급받아 보안 연결(🔒)을 구성하는 과정을 Terraform과 Helm으로 구현합니다.

 

 

📎 이번 단계에서 구현할 목표

  • https://test.cloudraw.kr(예시)등 도메인 기반 접속 가능
  • Ingress + Nginx-Ingress Controller를 통한 트래픽 라우팅
  • Let's Encrypt 기반 TLS 인증서 자동 발급
  • Cert Manager를 이용한 인증서 갱신 자동화
  • 결과적으로 HTTPS 접속 시 🔒 "보안 연결됨" 상태 달성

 

🧾 사전 준비 사항

항목 설명
1. Ingress Controller 설치 완료 앞선 글(3편)에서 Helm으로 설치한 nginx-ingress
2. 외부 접속 가능한 DNS 도메인 예시: test.cloudraw.kr
3. DNS 레코드 등록 도메인 → Ingress LoadBalancer의 EXTERNAL-IP 또는 DNS
4. Helm CLI 및 Terraform 환경 Helm Chart 설치 및 CRD 적용 필요
5. ingressClassName 확인 cert-manager와 ingress 리소스 모두 동일한 클래스 사용 필요 (예: cloudraw)
 
⚠️ 중요
DNS → EXTERNAL-IP 또는 DNS 주소가 연결되어 있지 않으면 인증서 발급이 실패합니다.

 

 

1. cert-manager 설치 (Helm)

Let's Encrypt와 ACME 프로토콜을 통해 인증서를 자동 발급받는 컨트롤러입니다.

resource "helm_release" "cert_manager" {
  name             = "cert-manager"
  namespace        = "cert-manager"
  repository       = "https://charts.jetstack.io"
  chart            = "cert-manager"
  version          = "v1.14.2"

  create_namespace = true    # 네임스페이스가 없는 경우 생성

  set {
    name  = "installCRDs"
    value = "true"    # Cert Manager 설치 시 CRDs도 함께 설치
  }
}

 

✅ 설치 후 생성되는 주요 CRD

리소스 설명
Certificate 인증서 객체 정의
Issuer 인증서 발급자
Challenge 도메인 소유 인증 요청
Order 인증서 발급 진행 상태

 

 

2. CRD 적용 대기 및 Issuer 생성

cert-manager는 설치 완료 후에도 CRD 리소스(Issuer, Certificate)가 즉시 생성되지 않을 수 있습니다.
Terraform에서는 이를 자동으로 기다리지 않기 때문에 수동 대기 로직을 추가해야 안정적으로 동작합니다.

 

CRD 대기

resource "null_resource" "wait_for_crd" {
  depends_on = [helm_release.cert_manager]

  provisioner "local-exec" {
    command = <<EOT
      for i in {1..30}; do
        kubectl get crd issuers.cert-manager.io && exit 0
        echo "Waiting for Issuer CRD to be created..."
        sleep 5
      done
      echo "Timeout waiting for Issuer CRD" >&2
      exit 1
    EOT
  }
}

 

💡 CRD 대기가 필요한 이유
cert-manager Helm Chart 설치 시, 내부적으로 Kubernetes Custom Resource Definition(CRD)을 생성합니다.
이 CRD는 Issuer, Certificate 등의 리소스를 정의하는 메타 구조이며, 생성되기 전에는 관련 리소스를 Terraform이나 kubectl로 생성할 수 없습니다.

 

 

Issuer 정의 

resource "kubernetes_manifest" "letsencrypt_issuer" {
  depends_on = [
    null_resource.wait_for_crd,
    helm_release.nginx_ingress
  ]

  manifest = {
    apiVersion = "cert-manager.io/v1"
    kind       = "Issuer"
    metadata = {
      name      = "letsencrypt-test"
      namespace = kubernetes_namespace_v1.cloudraw.metadata[0].name
    }
    spec = {
      acme = {
        email  = "help@cloudraw.kr"
        server = "https://acme-v02.api.letsencrypt.org/directory"
        privateKeySecretRef = {
          name = "letsencrypt-test-key"
        }
        solvers = [
          {
            http01 = {
              ingress = {
                class = "cloudraw"
              }
            }
          }
        ]
      }
    }
  }
}
💡 Issuer란?
- cert-manager에서 인증서를 자동 발급하기 위해 사용되는 리소스
- Issuer: 특정 네임스페이스에서만 사용
- ClusterIssuer: 전체 클러스터에서 사용 가능 
ACME(Automatic Certificate Management Environment)는 인증서 발급 및 갱신을 자동화하기 위한 표준 프로토콜이며, Let's Encrypt는 이 ACME 프로토콜을 지원합니다.

 

 

3. 인증서 적용을 위한 설정

이전 글에서 작성했던 Ingress 리소스와 helm_release 리소스에 인증서 적용을 위한 설정을 추가합니다.

 

helm_release 리소스에 controller.ingressClassResource.controllerValue 추가

 set {
    name  = "controller.ingressClassResource.controllerValue"
    value = "k8s.io/cloudraw"
  }
이 값은 Kubernetes 리소스 IngressClass 안에 정의되는 컨트롤러 식별자입니다. cert-manager는 Ingress 리소스에 지정된 ingress.class와 이 controllerValue가 일치하는지를 기준으로 HTTP-01 Challenge의 solver 대상 여부를 판단합니다.
이 설정이 없으면 cert-manager가 해당 ingressClass를 cert-manager용 solver로 인식하지 않을 수 있습니다.

 

설정이 추가된 helm_release 리소스

resource "helm_release" "nginx_ingress" {
  name       = "nginx-ingress"
  namespace  = "ingress-nginx"
  repository = "https://kubernetes.github.io/ingress-nginx"
  chart      = "ingress-nginx"
  version    = "4.12.2"

  create_namespace = true

  set {
    name  = "controller.service.type"
    value = "LoadBalancer"
  }

  set {
    name  = "controller.publishService.enabled"
    value = "true"
  }
  
  set {
    name  = "controller.ingressClass"
    value = "cloudraw"
  }

  set {
    name  = "controller.ingressClassResource.name"
    value = "cloudraw"
  }

  set {
    name  = "controller.ingressClassResource.controllerValue"
    value = "k8s.io/cloudraw"
  }
}

Ingress 리소스에 cert-manager.io/issuer annotation 추가

annotations = {
  "kubernetes.io/ingress.class"    = "cloudraw"
  "cert-manager.io/issuer" = "letsencrypt-test"
}

 

Ingress 리소스에 spec.tls 블록 추가

spec {
  tls {
    hosts       = ["test.cloudraw.kr"]
    secret_name = "cloudraw-tls-secret"
  }

  rule {
    host = "cloudraw.kr"
    ...
  }
}

 

 

두 가지 설정이 추가된 Ingress 리소스

resource "kubernetes_ingress_v1" "cloudraw_ingress" {
  metadata {
    name      = "cloudraw-ingress"
    namespace = kubernetes_namespace_v1.cloudraw.metadata[0].name

    annotations = {
      "kubernetes.io/ingress.class" = "cloudraw"            # 사용할 Ingress Class 이름
      "cert-manager.io/issuer"      = "letsencrypt-test"    # 사용할 인증서 발급자(Issuer) 이름
    }
  }

  spec {
    tls {
      hosts       = ["test.cloudraw.kr"]       # HTTPS 인증서를 적용할 도메인
      secret_name = "cloudraw-tls-secret"       # 발급된 인증서가 저장될 Secret 이름
    }

    rule {
      host = "test.cloudraw.kr"        # 이 호스트로 접근 시 트래픽을 라우팅할 규칙 정의

      http {
        path {
          path      = "/"
          path_type = "Prefix"

          backend {
            service {
              name = kubernetes_service_v1.cloudraw.metadata[0].name
              port {
                number = 80
              }
            }
          }
        }
      }
    }
  }
}

 

🧾 주요 필드 정리

필드 설명
annotations.cert-manager.io/issuer 사용할 인증 발급자 지정
tls.hosts 인증서가 적용될 도메인 이름
tls.secret_name 인증서가 저장될 Secret 명칭
ingressClassName / annotation Ingress Controller와 일치해야 함

 

 

4. DNS 연결 설정 (도메인 → Ingress LoadBalancer)

도메인 접속을 위해 먼저 DNS 레코드가 적절히 연결되어야 합니다.
Ingress Controller가 LoadBalancer 방식으로 EXTERNAL-IP 또는 DNS 주소를 갖고 있어야 합니다.

예시)

도메인 타입 연결 대상
A 레코드 고정 EXTERNAL-IP (예: 203.0.113.10)
CNAME 레코드 클라우드 제공 DNS 주소 (예: abc.cloudapp.azure.com)

 

DNS 연결을 시켜주는 방식은 크게 2가지로 Azure Console에서 연결시키는 방식Terraform을 이용한 방법으로 설명하겠습니다.

✅ 도메인 발급 및 Name server를 연결했다는 가정하에 진행하겠습니다.

 

 

4-1. Azure Console을 통한 연결방법

 

DNS Zone을 서비스 -> Add Record Sets를 통해 레코드를 추가해줍니다.

 

 

순서대로 진행해줍니다.
1️⃣ 사용할 도메인명 입력(ex: test)
2️⃣ Ingress Controller EXTERNAL-IP 입력 (⚠️Type에서 A레코드 유형을 선택해 줘야합니다.)

3️⃣ Add를 눌려 레코드 집합 추가 완료

 

추가 완료 시 이미지

 

 

 

4-2. Terraform을 이용한 연결 방법 

Terraform Registry에 안내되어 있는 사용 방법을 참조하며 진행하겠습니다.

 

1️⃣ Data Source인 azurerm_dns_zone을 통해 Azure에서 생성한 DNS 정보를 읽어옵니다.

# Azure Portal에서 이미 생성한 DNS Zone 정보를 가져옵니다.
data "azurerm_dns_zone" "cloudraw_zone" {
  name                = "cloudraw.kr"           # 최상위 도메인
  resource_group_name = "RG-DNS-ZONE-test"      # DNS Zone이 속한 리소스 그룹명
}

 

 

2️⃣ azurerm_dns_a_record Resource를 통해 A 레코드 추가 (test.cloudraw.kr → Ingress IP 연결)

records에 추가할 Ingress EXTERNAL-IP를 가져오는 방식은 두 가지를 선택해서 사용하시면 됩니다.

 

2-1. variables 방식 사용

variable "ingress_ip" {
  description = "Ingress LoadBalancer의 외부 IP 주소"
  type        = string
}

terraform.tfvars 또는 CLI로 -var="ingress_ip=xxx.xxx.xxx.xxx" 형태로 전달하여 사용.

 

variables 방식을 사용한 A 레코드 추가

resource "azurerm_dns_a_record" "cloudraw_test" {
  name                = "test"                     # 'test.cloudraw.kr' 도메인 앞부분
  zone_name           = data.azurerm_dns_zone.cloudraw_zone.name
  resource_group_name = data.azurerm_dns_zone.cloudraw_zone.resource_group_name
  ttl                 = 300
  records             = ["${var.ingress_ip}"]      # Ingress EXTERNAL-IP
}

 


 

2-2. kubernetes_service_v1 데이터 리소스로 Ingress Controller IP 가져오기

 

kubernetes_service_v1 Data Resource 가져오기 

# nginx-ingress 컨트롤러 서비스 정보를 가져옵니다.
data "kubernetes_service_v1" "nginx_ingress" {
  metadata {
    name      = "nginx-ingress-ingress-nginx-controller"
    namespace = "ingress-nginx"
  }
}
⚠️ 이 이름은 Helm 설치 시 자동 생성되는 서비스 이름이므로, 실제 클러스터에 kubectl get svc -n ingress-nginx로 확인해 정확히 일치해야 함!

 

kubernetes_service_v1 데이터 리소스에서 추출하는 방식을 사용한 A 레코드 추가

resource "azurerm_dns_a_record" "cloudraw_dns" {
  name                = "test"  
  zone_name           = data.azurerm_dns_zone.cloudraw_zone.name
  resource_group_name = data.azurerm_dns_zone.cloudraw_zone.resource_group_name
  ttl                 = 300

  # LoadBalancer EXTERNAL-IP 추출
  records = [data.kubernetes_service_v1.nginx_ingress.status.0.load_balancer.0.ingress.0.ip]

  depends_on = [helm_release.nginx_ingress]
}

 

💡AWS 환경에서 진행하는 방법
AWS 환경에서는 Route53을 활용하여 도메인 배포 및 연결을 할 수 있습니다.
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record

 

 

 

5. 인증 상태 확인 및 접속 테스트

terraform init
terraform apply

적용 후

cm-acme-http-solver-snmpf 라는 pod는 HTTP 요청에 대한 응답을 반환하는 nginx 서버 역할을 하며, Let's Encrypt가  http://cloudraw.kr/.well-known/acme-challenge/<token>경로로 접속해 정상 응답을 받으면 인증서를 발급해줍니다.
(인증 성공 또는 실패 후 cert-manager가 자동 삭제)

 

kubectl get certificate -A
kubectl describe certificate -n <namespace>
kubectl get secret -A | grep tls
 
연결 성공 시 READY에 True로 되어있습니다.
 
 
브라우저 접속 테스트
https://test.cloudraw.kr/

 

 

정상적으로 보안 연결 설정이 되었을 때

 

 

위와 같은 과정까지 진행이 되었으면 TLS 구성이 성공적으로 완료된 것입니다.


 

⚠️ 인증서 오류 해결 가이드

문제 원인 해결 방법
인증서가 발급되지 않음 DNS 미연결, IP 불일치 도메인 → EXTERNAL-IP 확인
인증이 실패함 HTTP-01 Challenge 실패 .well-known/acme-challenge 경로 응답 확인
Secret 생성 안 됨 cert-manager 미설치 or CRD 누락 installCRDs = true 설정 확인
HTTPS 적용 안 됨 Ingress에 TLS 블록 누락 tls, annotations, Issuer 확인

 

✅ 체크리스트

단계 완료 여부
DNS → EXTERNAL-IP 연결
cert-manager Helm 설치
Issuer 생성
Ingress 리소스에 TLS 설정
인증서 발급 후 Secret 저장
브라우저 접속 시 HTTPS 확인
 

 

CloudStudio는 Terraform 기반 인프라 구성을 GUI로 시각적으로 설계하고, 이를 자동으로 코드(IaC)로 변환해주는 도구입니다. 복잡한 코드를 직접 작성하지 않아도 블록을 드래그하듯 직관적으로 인프라 리소스를 구성할 수 있으며, 생성된 코드는 그대로 Terraform으로 적용할 수 있습니다.

 

 

다음 단계에서는, CloudStudio를 활용하여 Terraform 구조를 보다 쉽게 구성하는 방법을 소개합니다.
지금까지 직접 작성해 온 코드들을 

  • GUI 기반으로 시각적으로 재구성하고
  • 생성된 코드를 그대로 활용하거나
  • 커스터마이징하여 실무에 맞게 확장

할 수 있는 과정을 통해, 코드 작성의 진입장벽을 낮추고 실무 적용 속도를 높이는 방법을 함께 다뤄보겠습니다.

 

 

 


 

    

 

Cloudraw는 쉽게 클라우드 인프라를 그리고 사용할 수 있는 서비스를 제공하기 위해 노력하고 있습니다.

 

클라우드가 있는 곳 어디든 Cloudraw가 함께합니다.

 

📨 help@cloudraw.kr