Sau bài này bạn làm được: cài và cấu hình cert-manager với Let’s Encrypt; tạo Certificate resource cho Ingress; debug khi cert không được issue; hiểu rủi ro HSTS trước khi bật.
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:
Issue cert → cài lên server → đặt reminder 60 ngày → gia hạn thủ công → ...
↑ │
└──────────────── lặp lại mãi, dễ quên ───────────────────────┘
cert-manager tự động hóa toàn bộ vòng này trong Kubernetes:
[cert-manager controller]
│ watch Certificate resource
│ cert sắp hết hạn (còn <30 ngày) → auto-renew
▼
[ACME Challenge (Let's Encrypt)]
│ DNS-01 hoặc HTTP-01 challenge
▼
[Lưu cert vào Kubernetes Secret]
│
▼
[Ingress / TLS Secret mount] → NGINX dùng cert mới
ACME 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
Let's Encrypt: "Đặt file này tại http://example.com/.well-known/acme-challenge/TOKEN"
cert-manager: Tạo Pod/Ingress rule tạm thời để serve file đó
Let's Encrypt: GET file → verify → cấp cert
Yê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
Let's Encrypt: "Tạo TXT record _acme-challenge.example.com = TOKEN"
cert-manager: Gọi API DNS provider (Route53, Cloudflare…) tạo TXT record
Let's Encrypt: Query TXT record → verify → cấp cert
Yê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: [email protected]
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 |
| Rate limit exceeded | Quá nhiều request Let’s Encrypt prod | Dùng staging trước khi prod |
certificateRef not found | Ingress class sai | Kiểm tra ingressClassName |
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.