cert-manager 설치 가이드
1. 개요
본 문서는 gitops 저장소의 /cert-manager 디렉터리 구조를 기반으로 cert-manager Kubernetes 클러스터에 설치하고, Cloudflare DNS-01 방식으로 Let’s Encrypt Wildcard 인증서(*.cnapcloud.com)를 발급받아 여러 Namespace에서 공유하는 방법을 다룹니다.
2. 사전 요구사항
- Kubernetes 클러스터: 1.30 이상,
security네임스페이스 접근 권한 - 로컬 도구: kubectl, kustomize, helm 설치 완료
- 사전 설치: Prometheus (모니터링), Reflector (Secret 복제)
- Cloudflare: DNS API Token (DNS-01 Challenge용)
3. 디렉터리 구조 및 역할
Makefile:apply-helm,apply,delete,pull등 배포 자동화 스크립트kustomize/base/: 공통 리소스 및 Helm Chartresources/: ClusterIssuer 정의 파일들secrets/: Cloudflare API Token (SOPS 암호화)
kustomize/overlays/dev/: dev 환경 설정helm/values.yaml: cert-manager Helm 설정resources/: Certificate 리소스
4. ClusterIssuer 구성
4.1 Self-Signed Issuer (비공인)
# selfsigned-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
- 테스트/개발용 자체 서명 인증서
- 브라우저에서 신뢰하지 않음
4.2 Let’s Encrypt HTTP-01 (비공인)
# nginx-letsencrypt-dev.yaml
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
solvers:
- http01:
ingress:
class: nginx
- HTTP-01: Let’s Encrypt가
http://도메인/.well-known/...접속으로 확인 - 제한: 공개 서비스만 가능, 와일드카드 불가
4.3 Let’s Encrypt DNS-01 + Cloudflare (공인, 권장)
# cloudflare-letsencrypt.yaml
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
- DNS-01:
_acme-challenge.<도메인>TXT 레코드로 소유권 확인 - 장점: 내부망 가능, 와일드카드 가능, 가장 안정적
4.4 DNS-01 + Cloudflare를 사용하지 않는 경우
DNS-01 + Cloudflare 방식을 사용하지 않는 경우, 다음 파일들을 제거하세요:
cert-manager/kustomize/base/resources/cloudflare-letsencrypt.yamlcert-manager/kustomize/base/resources/cloudflare-sops-secrets.yamlcert-manager/kustomize/overlays/dev/resources/cnapcloud-certificate.yaml
이 파일들은 Cloudflare DNS-01 인증을 위한 리소스이므로, 다른 인증 방식을 사용할 때는 불필요합니다. 이 경우, 이 GitOps 구성에서는 모든 모듈이 wildcard 인증서를 공유하므로, 다른 인증 방식을 사용하여 wildcard 인증서를 발급받고 Reflector를 통해 네임스페이스 간 공유하는 구성을 직접 설정해야 합니다.
5. 설치 단계
5.1 사전 준비
5.1.1 Cloudflare API Token 생성
Step 1: Cloudflare 토큰 생성
- Cloudflare Dashboard → My Profile → API Tokens → Create Token
- Template: “Edit zone DNS” 선택
- Zone Resources: 특정 도메인(cnapcloud.com) 선택
- 생성된 Token 복사
Step 2: SOPS로 암호화
# cloudflare-api-token.env 작성
api-token=<Cloudflare API Token>
# SOPS 암호화
sops -e --output kustomize/base/secrets/sops/cloudflare-api-token.enc.env cloudflare-api-token.env
# 원본 삭제
rm cloudflare-api-token.env
5.1.2 values.yaml 커스터마이징
kustomize/overlays/dev/helm/values.yaml 확인:
installCRDs: true
prometheus:
enabled: true
servicemonitor:
enabled: true
5.2 배포 실행
Step 1: Helm Chart 다운로드
cd cert-manager
make pull
Step 2: cert-manager 설치 (Helm) cert-manager 설치는 Webhook validation 문제로 인해 Helm 설치와 ClusterIssuer, Certificate 설치 분리하여 진행합니다.
make apply-helm
security네임스페이스에 cert-manager 설치- CRD 자동 설치 (
installCRDs: true)
Step 3: ClusterIssuer 및 Certificate 배포
make apply DEPLOY_ENV=dev
⚠️ 주의: DNS-01 ClusterIssuer는 하나의 프로세스만 존재해야 합니다. 여러 개의 DNS-01 ClusterIssuer가 동시에 활성화되면 DNS 챌린지 충돌이 발생할 수 있습니다. 하나의 ClusterIssuer만 사용하세요.
6. 인증서 발급 및 Namespace 공유
6.1 Certificate 리소스
# cnapcloud-certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: cnapcloud-wildcard
namespace: security
spec:
secretName: cnapcloud.com-tls
issuerRef:
name: letsencrypt-cloudflare
kind: ClusterIssuer
dnsNames:
- "*.cnapcloud.com"
- "cnapcloud.com"
secretTemplate:
annotations:
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "security,cicd,default,gateway,database,lma"
6.2 Reflector를 통한 Secret 복제
reflectorannotation으로 지정된 Namespace에 자동 복제- 인증서 갱신 시 모든 복제본도 자동 업데이트
- 복제 대상:
security,cicd,default,gateway
7. 설치 후 검증
7.1 cert-manager Pod 상태 확인
kubectl get pods -n security -l app.kubernetes.io/instance=cert-manager
# cert-manager, cert-manager-webhook, cert-manager-cainjector Running 상태
7.2 ClusterIssuer 상태 확인
kubectl get clusterissuer
kubectl describe clusterissuer letsencrypt-cloudflare
# Status: Ready=True 확인
7.3 Certificate 발급 확인
kubectl get certificate -n security
kubectl describe certificate cnapcloud-wildcard -n security
# Status: Ready=True 확인
7.4 Secret 복제 확인
# 원본
kubectl get secret cnapcloud.com-tls -n security
# 복제본 확인
kubectl get secret cnapcloud.com-tls -n cicd
kubectl get secret cnapcloud.com-tls -n gateway
8. Let’s Encrypt Staging vs Production
8.1 Staging 환경 (테스트용)
server: https://acme-staging-v02.api.letsencrypt.org/directory
- 인증서 발급 실패해도 계속 시도 가능
- Insecure Certificate 발급 (브라우저 경고)
- 설정 테스트용
8.2 Production 환경 (Rate Limit 주의)
server: https://acme-v02.api.letsencrypt.org/directory
- Rate Limit 엄격:
- 동일 도메인 인증서: 주당 5개 제한
- Invalid 요청 반복 시 계정 전체 제한
- 설정 오류 시 며칠간 발급 불가 → 서비스 HTTPS 불가
8.3 Staging → Production 전환
- Staging에서 충분히 테스트
- Certificate 발급 성공 확인
- ClusterIssuer의
serverURL을 Production으로 변경 - 기존 Certificate 삭제 후 재생성
9. 문제 해결
9.1 Reflector Secret 복제 문제
이미 존재했던 Secret이 대상 네임스페이스에서 삭제된 경우, Reflector는 자동으로 재생성하지 않습니다. reflection-auto-enabled: "true" 설정은 원본 Secret의 생성/업데이트 이벤트에만 반응합니다.
해결 방법: 원본 Secret에 변경 이벤트를 발생시켜 Reflector가 다시 복제하도록 유도합니다.
kubectl -n security annotate secret cnapcloud.com-tls \
reflector.v1.k8s.emberstack.com/force-refresh="$(date +%s)"
확인:
kubectl -n gateway get secret cnapcloud.com-tls
9.2 Certificate Pending 상태
# Challenge 상태 확인
kubectl get challenges -A
kubectl describe challenge <challenge-name> -n security
# cert-manager 로그 확인
kubectl logs -n security -l app=cert-manager
9.3 DNS-01 Challenge 실패
# Cloudflare API Token 확인
kubectl get secret cloudflare-api-token -n security -o yaml
# DNS TXT 레코드 확인
dig TXT _acme-challenge.cnapcloud.com
9.4 Kind 환경 CoreDNS SOA 조회 오류
Kind의 Docker DNS는 SOA 질의를 제대로 처리하지 못함:
# 증상: A, TXT, NS는 성공, SOA만 SERVFAIL
# cert-manager DNS-01은 반드시 SOA 확인 필요
해결: CoreDNS forward를 Cloudflare DNS로 변경
# CoreDNS ConfigMap 수정
forward . 1.1.1.1 1.0.0.1 {
max_concurrent 1000
}
kubectl -n kube-system rollout restart deployment coredns
9.5 Webhook CA 인증서 갱신 오류
간헐적으로 cert-manager-webhook-ca Secret 갱신 실패로 webhook 호출 에러 발생:
Error: Internal error occurred: failed calling webhook "webhook.cert-manager.io"
원인: cainjector가 webhook CA 인증서를 제때 갱신하지 못함
해결 방법 1: Secret 삭제 후 재생성
# webhook CA Secret 삭제 (자동 재생성됨)
kubectl delete secret cert-manager-webhook-ca -n security
# cert-manager Pod 재시작
kubectl rollout restart deployment cert-manager -n security
kubectl rollout restart deployment cert-manager-webhook -n security
kubectl rollout restart deployment cert-manager-cainjector -n security
해결 방법 2: 전체 재설치
make apply-helm
확인:
kubectl get secret cert-manager-webhook-ca -n security
kubectl logs -n security -l app=cert-manager-cainjector
9.5 Reflector Secret 복제 문제
이미 존재했던 Secret이 대상 네임스페이스에서 삭제된 경우, Reflector는 자동으로 재생성하지 않습니다. reflection-auto-enabled: "true" 설정은 원본 Secret의 생성/업데이트 이벤트에만 반응합니다.
해결 방법: 원본 Secret에 변경 이벤트를 발생시켜 Reflector가 다시 복제하도록 유도합니다.
kubectl -n security annotate secret cnapcloud.com-tls \
reflector.v1.k8s.emberstack.com/force-refresh="$(date +%s)"
확인:
kubectl -n gateway get secret cnapcloud.com-tls
10. 배포 관리
10.1 업데이트
make apply-helm
make apply DEPLOY_ENV=dev
10.2 삭제
make delete
11. 참고 자료
최종 체크리스트
- Cloudflare API Token 생성 완료
- SOPS 암호화 완료
- Reflector 사전 설치 완료
make pull완료make apply-helm완료 (cert-manager 설치)make apply완료 (ClusterIssuer, Certificate)- ClusterIssuer Ready 확인
- Certificate Ready 확인
- Secret 복제 확인 (cicd, gateway 등)
- Staging 테스트 완료 후 Production 전환