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

class vs ingressClassName: field class cũ (dùng annotation kubernetes.io/ingress.class) đã deprecated từ K8s 1.18; cert-manager v1.12+ đọc ingressClassName khớp với IngressClass resource chính thức. Nếu thấy challenge Pod không được route đúng, nguyên nhân thường là dùng class sai tên hoặc Ingress mới khai báo spec.ingressClassName khá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ỗiNguyên nhânGiải pháp
Challenge pending mãiDNS chưa trỏ đúng hoặc cổng 80 bị chặnKiểm tra DNS và SG/firewall
Rate limit exceededQuá nhiều request Let’s Encrypt prodDùng staging trước khi prod
certificateRef not foundIngress class saiKiể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 includeSubDomains mà có subdomain chưa có HTTPS → toàn bộ subdomain đó không truy cập được.
  • Nếu bật preload và 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-agekhô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ự: CertificateCertificateRequestOrderChallenge.
  • 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 CertificateRequestOrderChallenge (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.