cert-manager란?
cert-manager는 Kubernetes 클러스터에서 TLS 인증서의 발급, 갱신, 관리를 자동화하는 컨트롤러입니다. Let’s Encrypt, Vault, Venafi 등 다양한 CA(Certificate Authority)와 연동하여 인증서 라이프사이클을 완전히 자동화합니다. 수동 인증서 관리의 만료 실수, 갱신 누락 등을 근본적으로 제거하며, Ingress와 자연스럽게 통합됩니다.
설치와 핵심 리소스
cert-manager는 CRD(Custom Resource Definition) 기반으로 동작하며, Issuer, Certificate, CertificateRequest 등의 커스텀 리소스를 사용합니다.
# Helm으로 설치
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager
--namespace cert-manager
--create-namespace
--set crds.enabled=true
--set prometheus.enabled=true
# 설치 확인
kubectl get pods -n cert-manager
# cert-manager-xxx Running
# cert-manager-cainjector-xxx Running
# cert-manager-webhook-xxx Running
| 리소스 | 역할 | 스코프 |
|---|---|---|
Issuer |
인증서 발급자 설정 (CA 연동) | 네임스페이스 |
ClusterIssuer |
클러스터 전역 인증서 발급자 | 클러스터 |
Certificate |
인증서 요청 및 관리 | 네임스페이스 |
CertificateRequest |
CSR 생성 및 CA 제출 (자동 생성) | 네임스페이스 |
Let’s Encrypt Issuer 설정
가장 일반적인 사용 사례인 Let’s Encrypt 연동입니다. HTTP-01과 DNS-01 두 가지 챌린지 방식을 지원합니다.
# Staging (테스트용 — 먼저 이것으로 검증!)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-staging-key
solvers:
# HTTP-01 챌린지: Ingress를 통해 검증
- http01:
ingress:
ingressClassName: nginx
---
# Production
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
ingressClassName: nginx
DNS-01 챌린지: 와일드카드 인증서
와일드카드 인증서(*.example.com)는 DNS-01 챌린지로만 발급 가능합니다. DNS 프로바이더 API를 통해 TXT 레코드를 자동 생성합니다.
# Cloudflare DNS-01 설정
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token
namespace: cert-manager
type: Opaque
stringData:
api-token: "your-cloudflare-api-token"
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-dns-key
solvers:
# DNS-01 챌린지: Cloudflare API
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
# 특정 도메인에만 DNS-01 적용
selector:
dnsZones:
- "example.com"
---
# 와일드카드 인증서 요청
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-cert
namespace: default
spec:
secretName: wildcard-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- "example.com"
- "*.example.com"
# 갱신 설정
renewBefore: 720h # 만료 30일 전 갱신
| 챌린지 | 검증 방식 | 와일드카드 | 요구 사항 |
|---|---|---|---|
| HTTP-01 | HTTP 경로에 토큰 파일 제공 | ❌ | 80 포트 접근, Ingress |
| DNS-01 | DNS TXT 레코드 생성 | ✅ | DNS 프로바이더 API 접근 |
Ingress 자동 인증서 발급
Ingress에 어노테이션을 추가하면 cert-manager가 자동으로 인증서를 발급하고 Secret에 저장합니다. 가장 간편한 방식입니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
annotations:
# cert-manager가 이 어노테이션을 감지하여 자동 발급
cert-manager.io/cluster-issuer: "letsencrypt-prod"
# 선택적: HTTP → HTTPS 리다이렉트
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
- admin.example.com
secretName: api-tls # cert-manager가 이 Secret 자동 생성
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 80
# cert-manager 동작 흐름:
# 1. Ingress 감지 → Certificate 리소스 자동 생성
# 2. CertificateRequest 생성 → ACME 챌린지 수행
# 3. 인증서 발급 → api-tls Secret에 저장
# 4. Ingress Controller가 Secret의 TLS 인증서 사용
# 5. 만료 전 자동 갱신
Certificate 리소스 상세 설정
Ingress 어노테이션 방식 외에, Certificate 리소스를 직접 생성하면 더 세밀한 제어가 가능합니다.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-cert
namespace: production
spec:
# TLS Secret 이름
secretName: api-tls
# 발급자 참조
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
group: cert-manager.io
# 도메인 목록
dnsNames:
- api.example.com
- api-v2.example.com
# 인증서 옵션
duration: 2160h # 90일 (Let's Encrypt 기본)
renewBefore: 720h # 만료 30일 전 갱신
# 개인 키 설정
privateKey:
algorithm: ECDSA
size: 256
rotationPolicy: Always # 갱신 시 키도 교체
# 추가 Subject 정보
subject:
organizations:
- "My Company"
# Secret 템플릿: 추가 레이블/어노테이션
secretTemplate:
labels:
app: api-server
annotations:
reloader.stakater.com/match: "true"
인증서 모니터링과 트러블슈팅
인증서 상태를 모니터링하고 문제를 진단하는 방법입니다.
# 인증서 상태 확인
kubectl get certificate -A
# NAME READY SECRET AGE
# api-cert True api-tls 30d
# web-cert False web-tls 5m ← 문제!
# 상세 이벤트 확인
kubectl describe certificate api-cert -n production
# Events:
# Type Reason Message
# Normal Issuing Issuing certificate as Secret does not exist
# Normal Generated Stored new private key
# Normal Requested Created new CertificateRequest
# Normal Issuing The certificate has been successfully issued
# CertificateRequest 상태 확인
kubectl get certificaterequest -A
kubectl describe certificaterequest api-cert-xxxxx
# ACME Order/Challenge 확인 (디버깅)
kubectl get order -A
kubectl get challenge -A
kubectl describe challenge api-cert-xxxxx
# 인증서 만료일 확인
kubectl get secret api-tls -o jsonpath='{.data.tls.crt}' |
base64 -d | openssl x509 -noout -dates
# notBefore=Mar 1 00:00:00 2026 GMT
# notAfter=May 30 00:00:00 2026 GMT
# Prometheus 메트릭으로 알림
# certmanager_certificate_expiration_timestamp_seconds
# certmanager_certificate_ready_status
Self-Signed CA와 내부 인증서
내부 서비스 간 mTLS나 개발 환경에서는 Self-Signed CA를 사용합니다.
# 1. Self-Signed Root CA 생성
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
---
# 2. CA 인증서 생성
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-ca
namespace: cert-manager
spec:
isCA: true
commonName: internal-ca
secretName: internal-ca-key
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: selfsigned-issuer
kind: ClusterIssuer
---
# 3. CA Issuer: 위 CA로 인증서 발급
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: internal-ca-issuer
spec:
ca:
secretName: internal-ca-key
---
# 4. 내부 서비스 인증서 발급
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: grpc-service-cert
namespace: production
spec:
secretName: grpc-tls
issuerRef:
name: internal-ca-issuer
kind: ClusterIssuer
dnsNames:
- grpc-service.production.svc.cluster.local
- grpc-service.production.svc
duration: 8760h # 1년
renewBefore: 720h # 30일 전 갱신
Gateway API 연동
Gateway API와 cert-manager를 연동하여 Gateway 리소스에서 자동 인증서를 발급받을 수 있습니다.
# cert-manager Gateway API 지원 활성화 (설치 시)
helm install cert-manager jetstack/cert-manager
--set featureGates="ExperimentalGatewayAPISupport=true"
# Gateway에서 자동 인증서 발급
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: api-gateway
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
gatewayClassName: istio
listeners:
- name: https
port: 443
protocol: HTTPS
hostname: "api.example.com"
tls:
mode: Terminate
certificateRefs:
- name: api-gateway-tls # cert-manager 자동 생성
운영 베스트 프랙티스
- Staging 먼저: 항상 Let’s Encrypt Staging으로 테스트 후 Production 전환하세요 — Rate Limit 방지
- DNS-01 선호: 와일드카드가 필요하거나 80포트 접근이 어려우면 DNS-01 챌린지를 사용하세요
- renewBefore 설정: 만료 30일 전 갱신으로 여유를 두세요 (Let’s Encrypt는 90일 유효)
- ECDSA 키 사용: RSA보다 키 크기가 작고 성능이 좋은 ECDSA P-256을 권장합니다
- Prometheus 알림:
certmanager_certificate_expiration_timestamp_seconds로 만료 임박 인증서를 감지하세요 - Secret 레이블링:
secretTemplate으로 Reloader 등과 연동하여 인증서 갱신 시 파드를 자동 재시작하세요