Bài toán: vòng đời chứng chỉ TLS
Cert TLS có hạn sử dụng (Let’s Encrypt: 90 ngày). Quản lý thủ công:
[1] Cấp / mua cert
│
▼
[2] Cài lên server
│
▼
[3] Đặt nhắc gia hạn (~60 ngày trước khi hết hạn)
│
▼
[4] Gia hạn thủ côngcert-manager tự động hóa toàn bộ vòng này trong Kubernetes:
[1] cert-manager watch Certificate
│
▼
[2] Cert sắp hết hạn (<~30 ngày) → auto-renew
│
▼
[3] ACME challenge (DNS-01 / HTTP-01)
│
▼
[4] Lưu cert vào Kubernetes Secret
│
▼
[5] Ingress / workload mount TLS SecretACME và hai loại challenge
Let’s Encrypt dùng giao thức ACME để xác minh bạn sở hữu domain:
HTTP-01 challenge
[1] Let's Encrypt bảo cert-manager: phục vụ `/.well-known/acme-challenge/TOKEN`
│
▼
[2] cert-manager tạo rule Ingress/Pod tạm để serve file
│
▼
[3] LE GET công khai vào domain → thấy file → verify → cấp certYêu cầu: domain phải trỏ vào cluster và cổng 80 phải mở công khai. Không dùng được với wildcard cert.
DNS-01 challenge
[1] Let's Encrypt bảo cert-manager: tạo TXT `_acme-challenge.example.com = TOKEN`
│
▼
[2] cert-manager gọi API DNS provider (Route53, Cloudflare…) tạo bản ghi
│
▼
[3] LE query DNS → thấy TXT → verify → cấp certYêu cầu: cert-manager cần credential API của DNS provider. Dùng được với wildcard cert và với domain không expose HTTP.
Cài cert-manager
# Cài bằng Helm
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--set installCRDs=true
# Kiểm tra
kubectl get pods -n cert-manager
Tạo ClusterIssuer (Let’s Encrypt)
# HTTP-01 challenge
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
classvsingressClassName: fieldclasscũ (dùng annotationkubernetes.io/ingress.class) đã deprecated từ K8s 1.18; cert-manager v1.12+ đọcingressClassNamekhớp vớiIngressClassresource chính thức. Nếu thấy challenge Pod không được route đúng, nguyên nhân thường là dùngclasssai tên hoặc Ingress mới khai báospec.ingressClassNamekhác.
Lưu ý: Dùng letsencrypt-staging khi test để tránh rate limit của production. Staging cert không được browser tin nhưng không có rate limit.
Certificate resource và tích hợp với Ingress
Cách 1: Annotation trên Ingress (đơn giản hơn)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-api
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-example-com-tls # cert-manager tự tạo Secret này
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-api
port:
number: 80
Cách 2: Certificate resource độc lập
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-example-com
namespace: default
spec:
secretName: api-example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- api.example.com
- www.example.com
Debug khi cert không được issue
# Xem trạng thái Certificate
kubectl describe certificate api-example-com
# Xem CertificateRequest (giai đoạn trung gian)
kubectl get certificaterequest -n default
# Xem Order (ACME order)
kubectl get order -n default
kubectl describe order api-example-com-xxxxx
# Xem Challenge (HTTP-01 hay DNS-01)
kubectl get challenge -n default
kubectl describe challenge api-example-com-xxxxx
# Log của cert-manager controller
kubectl logs -n cert-manager deployment/cert-manager -f
Lỗi thường gặp:
| Lỗi | Nguyên nhân | Giải pháp |
|---|---|---|
| Challenge pending mãi | DNS chưa trỏ đúng hoặc cổng 80 bị chặn | Kiểm tra DNS và SG/firewall |
Waiting for HTTP-01 challenge propagation | Ingress controller không route .well-known path | Kiểm tra ingressClassName khớp và không bị middleware chặn |
| Rate limit exceeded | Quá nhiều request Let’s Encrypt prod (5 cert/tuần/domain) | Dùng staging trước khi prod |
no configured challenge solvers | Solver không match domain/ingressClass | Kiểm tra ClusterIssuer solvers selector |
Order is invalid | Domain không resolve tới cluster hoặc challenge fail nhiều lần | Kiểm tra DNS A/CNAME và firewall |
context deadline exceeded | cert-manager không kết nối được ACME server | Kiểm tra egress, NAT, proxy |
Debug tip: cert-manager tạo resource theo chuỗi
Certificate→CertificateRequest→Order→Challenge. Lỗi thường nằm ở bước cuối (Challenge),kubectl describe challengelà lệnh hữu ích nhất.
ACME ARI và renewal jitter (2025+)
ARI (ACME Renewal Information) là extension mới cho phép CA gợi ý thời điểm renew tối ưu:
- Let’s Encrypt hỗ trợ ARI từ 2024; cert-manager 1.15+ hỗ trợ đọc ARI.
- Khi CA cần revoke sớm (ví dụ: CA incident), ARI báo cert-manager renew ngay thay vì chờ 60 ngày.
- Giảm tình trạng “thundering herd” khi nhiều cert cùng renew lúc.
Renewal jitter: cert-manager 1.14+ thêm random jitter vào thời điểm renew (đến 10% của renewal window). Cluster 500+ cert không còn đồng loạt request ACME cùng giây.
# cert-manager 1.15+: ARI tự động bật khi CA hỗ trợ
# Kiểm tra Certificate status:
kubectl get certificate api-example-com -o yaml | grep -A5 renewalTime
HSTS, bật cẩn thận
HSTS (HTTP Strict Transport Security) nói với browser: “Chỉ kết nối HTTPS với domain này, trong X giây, và ghi nhớ.”
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Rủi ro:
- Nếu bật
includeSubDomainsmà có subdomain chưa có HTTPS → toàn bộ subdomain đó không truy cập được. - Nếu bật
preloadvà bị vào danh sách preload browser → không thể tắt trong vài tháng dù bạn muốn. - Rollback về HTTP trong thời gian
max-agelà không thể với browser đã cache.
Khởi đầu an toàn:
max-age=300 # 5 phút, dễ rollback khi test
→ max-age=86400 # 1 ngày sau khi ổn
→ max-age=31536000 # 1 năm, thêm includeSubDomains khi chắc chắn
Tóm tắt
- cert-manager tự động issue, renew và rotate cert, không cần cronjob thủ công.
- HTTP-01: đơn giản nhưng cần HTTP công khai; DNS-01: linh hoạt hơn, dùng cho wildcard.
- Debug theo thứ tự:
Certificate→CertificateRequest→Order→Challenge. - HSTS với max-age lớn và preload là quyết định không dễ rollback, bắt đầu nhỏ.
Câu hỏi hay gặp
Wildcard *.example.com với Let’s Encrypt, HTTP-01 hay DNS-01?
Trả lời: DNS-01 (Let’s Encrypt không cấp wildcard qua HTTP-01). Cần issuer có solver DNS (Route53, Cloudflare…) và quyền tạo TXT _acme-challenge.
Certificate mãi Issuing, xem resource nào tiếp?
Trả lời: Chuỗi CertificateRequest → Order → Challenge (kubectl describe challenge), log cert-manager, và kiểm tra DNS/HTTP reachability tới challenge.
Bật HSTS dài hạn + preload rồi cần HTTP port 80 lại, vì sao khó?
Trả lời: Browser ép HTTPS theo HSTS đã cache; preload khó revert. Tránh: bật max-age nhỏ khi thử nghiệm, hiểu rõ includeSubDomains/preload trước khi bật.
Bài tiếp theo (Giai đoạn IV): Quan sát mạng (Observability), sau khi hạ tầng mạng chạy, cần đo đạc để biết nó chạy tốt không.