K8s Security - 07. cert-manager
인증서 만료는 클러스터의 타이머 폭탄이다 — cert-manager가 자동화하는 것
14장에서 kubeadm 인증서가 1년 만료라고 했다. 잊고 있던 어느 날, apiserver 인증서가 만료돼 클러스터가 멈췄다 — 아무것도 연결이 안 됐다. 인증서 만료는 Kubernetes의 타이머 폭탄이다. cert-manager가 그 폭탄을 자동 해체한다 — 발급·갱신·배포를 코드로. 단지 kubeadm 시스템 인증서가 아니라, 앱의 TLS 인증서까지.
이 글이 푸는 것은: cert-manager가 인증서 생명주기를 어떻게 자동화하고, Issuer/ClusterIssuer가 무엇인가다.
인증서가 Kubernetes에서 왜 그렇게 흔한가
flowchart LR
USE["인증서 사용처"] --> A1["apiserver/kubelet 상호 인증<br/>(kubeadm)"]
USE --> A2["Service Mesh mTLS<br/>(k8s networking Istio)"]
USE --> A3["Ingress/Gateway TLS<br/>(외부 HTTPS)"]
USE --> A4["Webhook 어드미션<br/>(k8s security)"]
Kubernetes 곳곳이 인증서. 각각 만료가 있고, 만료되면 해당 구성요소가 멈춘다. 수동 관리는 불가능에 가깝다 — 몇십 장의 인증서 만료일을 사람이 추적할 수 없다. 자동화가 생존 조건.
cert-manager — 인증서를 CRD로
cert-manager는 인증서를 Kubernetes 객체로 다룬다. 세 핵심 CRD:
- Issuer: 한 namespace의 인증서 발급자.
- ClusterIssuer: 클러스터 전체 발급자.
- Certificate: 발급받을 인증서 요청 + 결과.
# Kubernetes 1.36 — Let's Encrypt ClusterIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata: {name: letsencrypt}
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef: {name: letsencrypt-key}
solvers:
- http01: {ingress: {class: nginx}}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata: {name: app-tls, namespace: app}
spec:
secretName: app-tls-secret # 결과 Secret
issuerRef: {name: letsencrypt, kind: ClusterIssuer}
dnsNames: [app.example.com]
cert-manager가 이 Certificate를 보고 Issuer에 발급 요청(ACME), 결과를 Secret(kubernetes.io/tls)에 저장. Ingress/Gateway가 이 Secret을 TLS에 쓴다.
자동 갱신 — 타이머 폭탄 해체
cert-manager의 핵심 가치: 자동 갱신. Certificate에 만료일이 다가오면(기본 2/3 지점), cert-manager가 재발급. 사람의 개입 없이. 인증서 만료로 클러스터가 멈추는 사태를 영구적으로 막는다.
flowchart LR
CERT["Certificate 객체<br/>(만료일 추적)"] -->|"만료 임박"| RENEW["자동 재발급 요청"]
RENEW --> ISSUER["Issuer/CA"]
ISSUER --> SEC["Secret 갱신"]
SEC --> USE["Ingress/Gateway/mTLS 사용"]
발급 소스의 종류 — 어디서 인증서를 받나
- ACME(Let's Encrypt): 외부 공개 CA. HTTP-01/DNS-01 챌린지로 도메인 소유 증명. 외부 서비스용.
- 자체 서명 CA(SelfSigned/CA): 클러스터 내부 CA. 내부 서비스/mTLS용.
- Vault PKI: Vault를 CA로. 엔터프라이즈 내부.
- 클라우드(AWS Private CA 등): 클라우드 관리형 CA.
Issuer 종류에 따라 발급 소스 선택. 외부 인터넷 서비스 → Let's Encrypt, 내부 메시 mTLS → 자체 CA/Vault.
인증서와의 연결 — 어디에 쓰나
발급된 인증서(Secret)는 여러 곳에서 참조:
- Ingress/Gateway:
tls.secretName. 외부 HTTPS 종단. - Istio mTLS: Istiod가 발급하거나 cert-manager와 통합.
- Webhook 어드미션: webhook 서버가 TLS 필요 → cert-manager로.
- 커스텀 컨트롤러: HTTPS를 쓰는 모든 컴포넌트.
cert-manager 자체는 Kubernetes 시스템 인증서(kubeadm이 만든 apiserver 인증서 등)를 직접 관리하지 않는다 — 그건 여전히 kubeadm의 영역(14장,
kubeadm certs renew). cert-manager는 앱/부가 서비스 인증서를 자동화. 단, 일부 환경에선 시스템 인증서까지 cert-manager로 통합하기도.
직접 확인하기
# Kubernetes 1.36 — cert-manager 설치 후
kubectl get pods -n cert-manager
kubectl get clusterissuer,certificate -A
cert-manager 별도 설치(helm). kind 클러스터에서 동작. 미검증 환경은 노트.
# 인증서 상태와 갱신 추적
kubectl describe certificate app-tls -n app | grep -E 'Not After|Status'
확인할 것: 만료일(Not After)과 갱신 상태(Renewal). cert-manager가 만료 임박 시 자동 갱신.
ACME 챌린지의 두 종류 — HTTP-01과 DNS-01
Let's Encrypt(ACME)가 도메인 소유를 증명받는 방식이 두 가지:
| 챌린지 | 방식 | 요구 | 적합 |
|---|---|---|---|
| HTTP-01 | http://도메인/.well-known/acme-challenge/...로 응답 |
공인 IP, 80 포트 노출 | 단순, 일반적 |
| DNS-01 | _acme-challenge.도메인 TXT 레코드에 토큰 |
DNS provider API 접근 | 와일드카드(*.domain), 80 안 열어도 됨 |
# HTTP-01 solver (Ingress 경유)
solvers:
- http01: {ingress: {class: nginx}}
# DNS-01 solver (DNS provider API)
solvers:
- dns01: {route53: {region: ap-northeast-2, ...}}
DNS-01이 더 강력하다 — 와일드카드 인증서(*.app.example.com 한 장으로 모든 서브도메인 커버)를 발급받으려면 DNS-01 필수. 단 DNS provider API 자격증명이 필요(AWS Route 53, Cloudflare 등). HTTP-01은 단순하지만 80 포트 노출이 전제.
Certificate 객체의 생명주기 — 발급에서 갱신까지
cert-manager가 관리하는 Certificate 객체의 상태 기계:
flowchart LR
CREATE["Certificate 생성"] --> REQ["발급 요청 (Order)"]
REQ --> CHAL["ACME 챌린지 수행<br/>(HTTP-01/DNS-01)"]
CHAL --> ISSUED["인증서 발급 → Secret"]
ISSUED --> WATCH["만료일 감시"]
WATCH -. 만료 2/3 임박 .-> RENEW["재발급"]
RENEW --> ISSUED
- Order: ACME 주문 객체. 챌린지 → 발급의 단위.
- Challenge: 도메인 소유 증명 객체(HTTP-01/DNS-01).
- Certificate: 최종 인증서(Secret에 저장). 만료일 추적.
- 자동 갱신: 만료일의 2/3 시점에 재발급 트리거. 예: 90일 인증서면 60일째에 재발급.
이 생명주기가 "인증서 만료 타이머 폭탄"을 영구 해체 — 사람이 만료일을 추적할 필요 없이 cert-manager가 알아서.
Ingress/Gateway와 cert-manager 통합 — 한 매니페스트로
Ingress에 annotation을 붙이면 cert-manager가 자동으로 Certificate를 생성:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt # 이 한 줄로 인증서 자동 발급
spec:
tls:
- hosts: [app.example.com]
secretName: app-tls
이 annotation을 보면 cert-manager가 Certificate 객체를 자동 생성 → 발급 → app-tls Secret 채움. Ingress는 그 Secret을 TLS에. 인증서 관리가 Ingress 정의 한 곳에서 끝. Gateway API도 비슷한 통합 지원.
Vault PKI — 내부 인증 기관으로
cert-manager가 Let's Encrypt(공개 CA) 외에 Vault PKI를 CA로 쓸 수 있다. Vault가 인증 기관 역할:
apiVersion: cert-manager.io/v1
kind: Issuer
metadata: {name: vault-issuer}
spec:
vault:
server: https://vault.example.com
path: pki/sign/internal # Vault PKI 엔진 경로
auth: {kubernetes: {mountPath: /v1/auth/kubernetes, role: cert-manager}}
이제 인증서가 Vault가 서명한 내부 CA 체인으로 발급. 엔터프라이즈 내부 서비스(mTLS, 내부 HTTPS)에 적합 — 외부 CA(Let's Encrypt)는 외부 도메인만, 내부 서비스는 Vault/자체 CA가 맞음.
자체 서명 CA로 시작 — 개발/테스트
cert-manager의 SelfSigned issuer로 자체 서명 CA를 만들고, 그 CA로 다른 인증서를 서명:
# 1. 자체 서명 CA 인증서
# 2. CA Issuer (그 CA로 서명)
# 3. 일반 인증서 (CA Issuer에서 발급)
이 3단계가 "클러스터 안 자체 CA 풀"을 만드는 패턴. 개발/테스트 환경에 적합 — 외부 의존 없이 내부 mTLS/TLS를. 단 자체 서명은 공개 신뢰가 아니므로, 외부 서비스엔 부적합(브라우저 경고 등).
인증서 갱신 트리거와 연쇄 — 인증서가 바뀌면 무엇이 갱신되나
cert-manager가 인증서를 갱신하면 Secret이 갱신. 그 Secret을 참조하는 리소스들이 자동으로 갱신되나?
- Ingress/Gateway: 새 Secret을 읽음(TLS 인증서). 보통 자동.
- Pod(볼륨 마운트): 10장처럼 kubelet이 수분 단위로 갱신.
- Pod(환경 변수): 재시작까지 안 바뀜.
그래서 갱신의 효과가 참조 방식에 따라 다르다. 인증서 회전이 모든 소비자에 즉시 전파되지 않음 — 이것이 06장(secrets-management)의 회전→갱신 문제와 같은 맥락.
흔히 묻는 것, 흔히 틀리는 것
| 오해 | 정정 |
|---|---|
| "cert-manager는 kubeadm 인증서도 관리한다" | 주로 앱/부가 인증서. 시스템은 kubeadm |
| "인증서는 한 번 발급이면 끝" | 만료 있음. 자동 갱신이 핵심 가치 |
| "Issuer와 ClusterIssuer는 같다" | scope 다름. Issuer는 namespace, ClusterIssuer는 전체 |
| "Let's Encrypt는 내부 mTLS에 쓴다" | 외부 도메인용. 내부는 자체 CA/Vault |
| "인증서 Secret은 평문 비밀이다" | TLS 개인키. Secret 보안 모델 적용 대상 |
| "갱신은 수동 트리거해야" | cert-manager가 만료 임박 시 자동 |
요약 — 이 글의 결론
- 인증서가 Kubernetes 곳곳 — apiserver, mTLS, Ingress/Gateway TLS, webhook. 각각 만료, 만료되면 해당 구성요소 멈춤. 수동 관리 불가.
- cert-manager가 인증서를 CRD로 — Issuer/ClusterIssuer(발급자) + Certificate(요청/결과). 결과는 Secret으로.
- 자동 갱신이 핵심 가치 — 만료 임박 시 재발급. 인증서 만료 타이머 폭탄 영구 해체.
- 발급 소스: ACME(Let's Encrypt, 외부)/자체 CA(내부 mTLS)/Vault/클라우드 CA. 용도에 따라.
- cert-manager는 앱/부가 인증서 중심. 시스템(kubeadm) 인증서는 여전히 kubeadm 영역. 단 일부 환경은 통합.
- 발급된 Secret을 Ingress/Gateway/mTLS/webhook이 참조. 인증서 생명주기가 클러스터 전체에 영향.
생각해 볼 문제
- Let's Encrypt 인증서가 90일마다 만료다. cert-manager 없이 수동 관리 시 생기는 일을 시나리오로.
- 내부 mTLS(메시)용과 외부 HTTPS용 발급 소스를 왜 다르게 잡나?
- cert-manager가 다운됐다. 이미 발급된 인증서는? 만료 임박 것은?
- ClusterIssuer를 쓰는 Certificate가 갱신됐다. Secret은 어떻게 갱신되나? (참조자에게 전파?)
- 웹hook 어드미션 서버의 TLS 인증서를 cert-manager로 관리하면 얻는 이점은?
- kubeadm 시스템 인증서 만료와 cert-manager 관리 인증서 만료의 차이를 영향 범위로 비교하라.
참고
- cert-manager 공식 문서 - 접근 2026-07-12
- cert-manager - Issuer/ClusterIssuer - 접근 2026-07-12
- cert-manager - ACME (Let's Encrypt) - 접근 2026-07-12
- Kubernetes 공식 문서 - TLS for Ingress (cert-manager 연계) - 접근 2026-07-12
- kubeadm 인증서 관리(14장 연계) - 접근 2026-07-12
'Tech Artifacts > K8s Security' 카테고리의 다른 글
| K8s Security - 06. secrets management (0) | 2026.07.16 |
|---|---|
| K8s Security - 05. pod-security-admission (0) | 2026.07.16 |
| K8s Security - 04. admission-control (0) | 2026.07.16 |
| K8s Security - 03. fine-grained kubelet API authorization (0) | 2026.07.16 |
| K8s Security - 02. authentication (0) | 2026.07.16 |