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 Chart
    • resources/: 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.yaml
  • cert-manager/kustomize/base/resources/cloudflare-sops-secrets.yaml
  • cert-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 복제

  • reflector annotation으로 지정된 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 전환

  1. Staging에서 충분히 테스트
  2. Certificate 발급 성공 확인
  3. ClusterIssuer의 server URL을 Production으로 변경
  4. 기존 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 전환
💬 무료 컨설팅 신청