
cert-manager 깔아두고 Let's Encrypt 인증서 받는 거, 처음엔 다들 HTTP01로 시작한다. 튜토리얼이 다 그렇게 되어 있고, 일단 ingress 붙이면 되니까. 근데 운영 좀 하다 보면 슬슬 짜증이 난다. 와일드카드가 안 된다거나, 클러스터 내부용 도메인인데 80포트가 안 뚫린다거나. 그래서 DNS01로 옮기는데, 이게 또 권한 관리가 따로 필요해서 처음엔 헤맨다.
이 글은 그 분기점에서 어떤 걸 쓰면 좋은지, 그리고 둘을 섞어 쓸 때 흔히 만나는 함정 몇 개를 정리해본다. 우리 팀에서 작년에 ingress 컨트롤러 갈아엎으면서 DNS01로 거의 다 옮겼는데, 그 과정에서 배운 것들이다.
HTTP01이 어울리는 상황
생각보다 단순한데, 두 가지 조건이 동시에 만족되면 HTTP01이 제일 편하다.
- 도메인이 퍼블릭하게 노출돼 있다 (80포트 도달 가능)
- 와일드카드가 필요 없다
이 둘 중 하나라도 깨지면 HTTP01은 못 쓰거나 억지로 쓰게 된다. 우리는 처음 1년 정도 HTTP01만 썼다. 클러스터에 ingress 하나만 두고 외부에서 80/443 받는 단순한 구조였으니까.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-http01
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-http01-key
solvers:
- http01:
ingress:
ingressClassName: nginx
이 정도로 끝난다. 도메인 늘어나면 Certificate 리소스 하나씩 만들면 되고, 컨트롤러가 알아서 임시 ingress route 만들어서 challenge 응답 띄워준다.
문제는 인프라가 조금만 복잡해져도 균열이 생긴다. 사내 전용 도메인 인증서 필요할 때 (외부에서 80을 못 뚫음), 와일드카드 받아서 한 인증서로 여러 서브도메인 커버하고 싶을 때, ingress 컨트롤러를 여러 개 쓸 때(ingressClassName이 솔버마다 하나씩이라 솔버를 여럿 정의해야 함).
그리고 한 가지 더. 2026년 들어서 ingress-nginx 커뮤니티 프로젝트가 사실상 EOL 상태로 들어갔다. 새 인프라는 Gateway API나 다른 컨트롤러로 옮기는 중인데, HTTP01 솔버가 Gateway API 지원을 한 게 비교적 최근이라(cert-manager 1.15+) 마이그레이션 중에는 좀 불안정한 케이스도 있다. 이 시점에 새로 깔고 있다면 DNS01이 더 안정적이긴 하다.
DNS01로 가는 게 나은 경우
DNS01은 ACME 서버가 TXT 레코드를 조회해서 도메인 소유권을 확인한다. 그래서 클러스터 네트워크가 외부에서 어떻게 보이는지 무관하다. 핵심 장점은 세 가지다.
첫째, 와일드카드 인증서가 된다. *.dev.example.com 같은 거. 서비스가 자주 늘어나는 환경에서는 이게 진짜 크다. 마이크로서비스마다 Certificate 리소스 만들 필요 없이 하나로 커버된다.
둘째, 80포트가 닫혀 있어도 된다. 사내망 전용 클러스터, VPC 안에서만 도는 ingress, 이런 경우에 HTTP01이 안 되거나 어색해진다.
셋째, 어떤 ingress 컨트롤러를 쓰든 솔버 설정이 독립적이다. ingress-nginx에서 Gateway API로 마이그레이션하는 중에도 DNS01 솔버는 그대로다.
대신 권한 관리가 따로 필요하다. cert-manager 컨트롤러가 DNS 영역의 TXT 레코드를 만들고 지울 수 있어야 한다. AWS Route53 기준으로는 보통 IRSA로 권한을 묶는다.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns01
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-dns01-key
solvers:
- dns01:
route53:
region: ap-northeast-2
hostedZoneID: Z0123456789ABCDEFGHIJ
selector:
dnsZones:
- example.com
hostedZoneID는 명시하는 게 좋다. 안 적으면 cert-manager가 SDK로 호스티드 존을 찾는데, 같은 이름의 퍼블릭/프라이빗 존이 둘 다 있으면 잘못 잡는 경우가 있다. 옛날에 한번 당했다. 스테이징 환경 인증서가 프로덕션 도메인의 프라이빗 존에 TXT 레코드를 만들려고 시도하다가 권한 거부 떨어졌는데, 이게 IRSA 정책 문제인 줄 알고 한참 헤맸다.
IRSA 정책은 최소 권한으로 이렇게 묶는다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "route53:GetChange",
"Resource": "arn:aws:route53:::change/*"
},
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets"
],
"Resource": "arn:aws:route53:::hostedzone/Z0123456789ABCDEFGHIJ"
}
]
}
ChangeResourceRecordSets를 특정 호스티드 존에 한정해두는 게 중요하다. *로 풀어두면 잘못된 issuer 설정이 다른 존을 건드릴 수 있다.
둘을 섞어 쓸 때
실제 운영에서는 한쪽만 쓰는 경우가 드물고, 보통 두 개를 같이 둔다. 와일드카드는 DNS01로, 단일 도메인 몇 개는 HTTP01로 — 식이다. 이때 솔버 selector를 정확하게 분리해두지 않으면 어떤 챌린지가 선택될지 예측이 안 된다.
cert-manager는 솔버를 순서대로 매칭하지 않고 가장 구체적인 selector를 우선한다. dnsZones가 있는 솔버가 dnsNames보다 우선이고, dnsNames는 matchLabels보다 우선이다. 그래서 보통은 이렇게 설계한다.
solvers:
- dns01:
route53:
region: ap-northeast-2
hostedZoneID: Z0123456789ABCDEFGHIJ
selector:
dnsZones:
- example.com # 이 존에 속한 건 다 DNS01
- http01:
ingress:
ingressClassName: nginx
# selector 없음 = 폴백
dnsZones에 매칭되는 도메인은 DNS01로, 그 외엔 HTTP01로 빠지는 구조. 폴백 솔버에 selector를 안 두는 게 포인트인데, 두면 매칭 안 되는 도메인이 인증서 발급에 실패하면서 한참 동안 원인을 못 찾는다.
디버깅이 막힐 때 보는 곳
challenge가 안 풀리면 보통 세 군데 본다.
kubectl describe certificate <name>: Ready 상태와 마지막 이벤트kubectl describe challenge: 어떤 솔버가 매칭됐고 왜 실패했는지- cert-manager 컨트롤러 로그 (cainjector, webhook 말고 controller pod)
DNS01에서 흔한 실패는 propagation 지연이다. ACME 서버가 TXT 레코드를 조회했는데 아직 전파가 안 됐다거나, 자기 권위 네임서버는 답을 주는데 public resolver는 옛날 캐시를 본다거나. cert-manager는 challenge 시작 전에 자기 self-check를 돌리는데, 이게 통과해도 ACME 서버가 보는 뷰가 다르면 또 깨진다. 이럴 땐 --dns01-recursive-nameservers 옵션으로 검증용 리졸버를 명시적으로 박아두면 좀 안정적이다.
HTTP01은 거꾸로 ingress 충돌이 흔하다. 같은 호스트에 다른 ingress 규칙이 먼저 매칭되면서 임시 솔버 경로(/.well-known/acme-challenge/)가 가려지는 케이스. 이건 ingress의 pathType이 Exact가 아닌 경우 잘 일어난다.
마무리
요약하면 이런 흐름이다. 인프라가 단순하고 외부 노출이 깔끔하면 HTTP01로 시작. 와일드카드가 필요하거나, 사내망 클러스터거나, ingress 마이그레이션 중이면 DNS01로 옮긴다. 둘을 같이 쓸 거면 selector를 명확하게 분리하고, DNS01 권한은 호스티드 존 단위로 좁힌다.
우리 팀은 작년 후반부에 거의 다 DNS01로 정리했는데, 솔직히 그 결정이 옳았다고 단언하긴 어렵다. 외부 공개 도메인에 굳이 DNS 권한을 cert-manager에게 주는 게 보안 측면에서 가장 깔끔한지 다시 보면 더 좋은 패턴이 있을 수도 있다. 혹시 다른 식으로 운영하는 분 있으면 댓글로 알려주시면 좋겠다.
'IT > Kubernets' 카테고리의 다른 글
| Cilium kube-proxy replacement 갈아탔다가 LoadBalancer 클라이언트 IP 다 날린 이야기 (0) | 2026.06.30 |
|---|---|
| 새벽 두 시에 깨운 Karpenter, disruption budget 시간 윈도우로 막은 이야기 (0) | 2026.06.29 |
| ingress-nginx EOL 통보를 받고 Gateway API로 옮긴 두 달의 기록 (0) | 2026.06.28 |
| Velero restic에서 Kopia로 옮기는 법 (0) | 2026.06.27 |
| kubectl events, 아직도 get events 쓰세요? (0) | 2026.06.26 |