안녕하세요, Cloudraw입니다!
Terraform으로 Kubernetes 다루기 네 번째입니다.
- Kubernetes Provider 사용 방법, 기본 오브젝트 생성하기(Namespace, Pod, Service, Secret)
- 볼륨 리소스 다루기(CSP별 Blob 및 configmap mount)
- 네트워크 리소스 다루기(Ingress, Nginx-Ingress(Helm Chart))
- DNS 연동 및 인증서 발급(Let's Encrypt 및 Cert Manager(Helm))
- 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

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
'IaC' 카테고리의 다른 글
[IaC] Terraform으로 Kubernetes 다루기 - 3/5 (0) | 2025.05.22 |
---|---|
[PaC] HCP Terraform Sentinel을 활용한 배포 정책 적용 (0) | 2025.05.12 |
[IaC] Terraform으로 Kubernetes 다루기 - 2/5 (0) | 2025.04.17 |
[IaC] Terraform Enterprise AKS에 배포하기 (0) | 2025.02.07 |
[IaC] Terraform Module 작성 및 사용방법 (2) | 2024.12.03 |