작년 말에 우리 팀은 사내 서비스용 도메인의 TLS 인증서를 ACM에서 cert-manager로 옮겼다. EKS 클러스터가 늘어나면서 ALB마다 ACM 인증서 attach하고 갱신 알람 처리하는 게 점점 귀찮아진 게 직접적인 이유였고, 비용보다는 운영 부담 쪽이 컸다. 6개월쯤 굴려보니 마이그레이션 직후엔 안 보이던 문제들이 슬슬 보이기 시작해서, 정리해 두면 누군가는 덜 헤맬 것 같아 글로 남긴다.
먼저 짚고 가야 할 거 하나. Wildcard 인증서(*.internal.example.com)는 HTTP01 challenge로 못 받는다. DNS01만 된다. 이건 Let's Encrypt 정책이라 우회할 방법이 없다. 그래서 cert-manager + Route53(우리 환경 기준) 조합이 사실상 표준 답안이 된다.
기본 셋업과 staging 분리
IAM access key를 Secret에 박아두는 방식 말고, IRSA(또는 EKS Pod Identity)로 권한을 주는 편을 권한다. 처음에 키 방식으로 시작했다가 키 로테이션이 귀찮아져서 결국 IRSA로 갈아탔다.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- selector:
dnsZones:
- "internal.example.com"
dns01:
route53:
region: ap-northeast-2
hostedZoneID: Z0123456789ABC
role: arn:aws:iam::111111111111:role/cert-manager-route53
hostedZoneID는 명시하는 편을 추천한다. 빼면 cert-manager가 ListHostedZones 권한이 필요해서 IAM policy가 더 넓어진다.
그리고 더 중요한 건 staging Issuer를 분리해 두는 것이다. Let's Encrypt는 동일 도메인에 주당 50건 발급 제한이 있고, 실패한 ACME order도 카운트에 잡힌다. 처음 셋업하면서 ClusterIssuer 설정 잘못해서 5분마다 retry가 도는 걸 한 시간쯤 방치한 적이 있었는데, prod 발급이 막혀서 새벽에 staging issuer로 갈아끼웠다. 그날 이후 규칙으로 정했다. 새 도메인 추가나 issuer 변경은 무조건 staging부터.
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
staging 인증서는 브라우저에서 신뢰 안 하지만, ACME flow가 끝까지 도는지 확인하기엔 충분하다.
DNS propagation timeout — split-horizon에서 자주 터진다
Waiting for DNS-01 challenge propagation 상태에서 멈춰 있는 경우가 종종 있다. cert-manager 기본 동작은 도메인의 authoritative name server를 직접 쿼리해서 TXT 레코드가 보이면 ACME에 알리는 식이다. 그런데 사내 split-horizon DNS가 끼면 일부 NS가 늦게 동기화돼서 propagation 체크가 실패한다. 우리도 사내 Route53 inbound resolver 때문에 한참 헤맸다.
해결책은 dns01-recursive-nameservers 옵션으로 public resolver만 쓰게 강제하는 것:
extraArgs:
- --dns01-recursive-nameservers-only
- --dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53
이렇게 하면 cert-manager가 authoritative를 안 보고 8.8.8.8/1.1.1.1을 통해 propagation을 확인한다. split-horizon 환경에서는 거의 필수다.
Cross-account Route53 — trust policy 함정
DNS zone은 networking 계정에 있고, EKS는 workload 계정에 있는 구조. 회사가 어느 정도 규모가 되면 흔한 패턴인데, 이 경우 cert-manager가 networking 계정의 role을 assume해야 한다.
ClusterIssuer의 solvers.dns01.route53.role에 networking 계정 role ARN을 적고, 그 role의 trust policy에 workload 계정의 cert-manager IRSA role을 sts:AssumeRole 가능하게 등록한다. 한 번 잘못 설정하면 에러 메시지가 그리 친절하지 않아서 한참 헤맨다. cert-manager Pod 로그에서 AccessDenied가 보이면 십중팔구 trust policy 문제다. networking 쪽 인프라 팀에 IAM 변경 PR을 보내는 흐름이라 PR review에 하루 걸리는 것도 미리 생각해 두자.
갱신은 자동, 모니터링은 직접
cert-manager는 만료 30일 전쯤 자동 renewal을 시도한다. 잘 동작하면 좋은데, 갱신이 조용히 실패하는 케이스가 있다. 대표적으로 IAM role 변경, ClusterIssuer 변경, Let's Encrypt rate limit hit. 갱신 실패는 인증서가 살아있는 동안엔 알람이 안 울려서 만료 직전에 발견되곤 한다.
우리는 결국 Prometheus rule을 직접 만들었다. cert-manager가 노출하는 certmanager_certificate_expiration_timestamp_seconds를 보고 14일 이내 만료될 인증서가 있으면 알람을 띄운다.
(certmanager_certificate_expiration_timestamp_seconds - time()) / 86400 < 14
이걸 슬랙으로 보내게 했더니 그 뒤로 인증서 만료로 새벽에 깨는 일은 없어졌다. 추가로 certmanager_certificate_ready_status{condition="False"} 도 같이 보면 갱신 실패 자체를 미리 잡을 수 있다.
마무리
cert-manager는 깔아두면 잊고 살 수 있는 도구처럼 보이지만, 실제로는 위 함정들 때문에 한 번씩은 새벽에 깨게 된다. staging 분리, recursive nameserver 설정, cross-account trust policy 점검, 갱신 모니터링. 이 네 가지만 셋업 단계에서 미리 챙겨두면 운영 중 사고 확률이 크게 줄어든다.
그리고 올해 초에 Let's Encrypt가 발표한 DNS-PERSIST-01도 한 번 봐둘 만하다. 한 번 검증한 도메인에 대해 일정 기간 검증 상태를 유지하는 방식인데, staging은 Q1 2026 말, 프로덕션은 Q2 2026 중 예정이다. cert-manager 쪽에도 이슈가 열려 있고 1.20쯤에 들어올 가능성이 있다. 들어오면 DNS propagation 관련 함정 자체가 줄어들지 않을까 기대 중이다.
혹시 다른 함정 만나신 분 있으면 댓글로 공유 부탁드린다. 다음에는 cert-manager를 multi-cluster에서 어떻게 배포하는지(centralized vs per-cluster) 정리해보려고 한다.
'IT > DevSecOps' 카테고리의 다른 글
| Falco vs Tetragon, 둘 다 6개월 써본 결정 (0) | 2026.05.28 |
|---|---|
| Kyverno cluster policy 하나 올렸다가 API 서버가 죽은 새벽 (0) | 2026.05.27 |
| ClusterSecretStore 쓸 거면 namespaceSelector는 꼭 걸어두자 (0) | 2026.05.23 |
| Tetragon vs Falco, 런타임 보안 뭘 쓸까 (0) | 2026.05.21 |
| OpenBao 내부 들여다보기 - 포크 이후 2년, 진짜로 Vault를 대체할 수 있나 (0) | 2026.05.20 |