Tự động hóa chứng chỉ SSL/TLS trên Kubernetes với cert-manager và Let’s Encrypt: Cấu hình ClusterIssuer và tự động gia hạn Ingress

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Sự cố lúc 2:15 sáng mà chẳng ai muốn đối mặt

Cách đây vài tháng, kỹ sư on-call của chúng tôi nhận thông báo khẩn lúc 2:15 sáng. Cổng thanh toán production bất ngờ từ chối mọi giao dịch với lỗi ERR_CERT_DATE_INVALID. Chỉ trong vòng 90 giây, lượng giao dịch lao dốc không phanh từ 140 đơn mỗi phút xuống con số 0 tròn trĩnh. Các trình duyệt đồng loạt hiển thị cảnh báo bảo mật toàn màn hình, còn ứng dụng di động native lập tức ngắt toàn bộ kết nối API do cơ chế certificate pinning.

Chúng tôi vội vã mở cuộc họp khẩn, ban đầu đinh ninh rằng edge proxy bị sập hoặc một bản rollout vừa làm hỏng cấu hình routing. Phải mất 20 phút hoảng loạn sau đó, chúng tôi mới tìm ra thủ phạm thực sự: chứng chỉ SSL trên Ingress controller chính đã âm thầm hết hạn lúc nửa đêm. Kỹ sư platform – người từng tạo và rotate chứng chỉ wildcard đó – đã chuyển sang team khác từ 4 tháng trước, và lời nhắc định kỳ trên lịch của anh ấy vẫn nằm im lìm trong một hộp thư chưa từng được mở.

Quản lý vòng đời SSL/TLS bằng checklist thủ công trên các cluster luôn biến động là chiếc vé đảm bảo cho sự cố downtime. Vấn đề không phải là liệu nó có xảy ra hay không, mà là khi nào.

Nguyên nhân cốt lõi: Vì sao quản lý TLS thủ công thất bại trên Kubernetes

Việc rotate chứng chỉ thủ công có thể vẫn ổn nếu bạn chỉ có ba máy ảo tĩnh đặt sau một thiết bị phần cứng. Nhưng Kubernetes lại là một câu chuyện hoàn toàn khác. Giữa các microservice thay đổi liên tục, các namespace tách biệt và các môi trường preview tạm thời, cách xử lý TLS thủ công sẽ nhanh chóng bộc lộ điểm yếu:

  • Thời hạn chứng chỉ ngày càng ngắn: Các root program lớn đã giảm thời hạn hiệu lực tối đa từ 5 năm xuống 398 ngày, rồi tiếp tục xuống còn 90 ngày. Đề xuất hiện tại của CA/Browser Forum thậm chí còn hướng tới mốc 45 ngày. Việc gia hạn chứng chỉ thủ công nhiều lần trong năm sẽ làm tăng nguy cơ xảy ra sai sót con người theo cấp số nhân.
  • Tình trạng phân mảnh Secret giữa các namespace: Kubernetes cô lập secret theo từng namespace. Khi kỹ sư copy một wildcard Secret sang 15 namespace khác nhau, sự sai lệch (drift) sẽ xuất hiện rất nhanh. Kết quả là phiên bản không đồng nhất, các bản sao bị bỏ quên và hệ thống âm thầm gãy đổ khi đến kỳ rotate.
  • Quy trình vận hành phụ thuộc vào trí nhớ: Lời nhắc lịch, bảng tính và công cụ giám sát uptime bên ngoài chỉ mang tính phản ứng bị động. Ngay cả khi cảnh báo vang lên, kỹ sư vẫn phải tạo private key, sinh CSR, xác thực challenge record và mã hóa base64 payload để đẩy vào Kubernetes.
  • Lỗi Ingress âm thầm: Dán thiếu certificate chain đầy đủ hoặc quên kèm intermediate bundle sẽ dẫn đến lỗi TLS handshake khó phát hiện trên các thiết bị di động đời cũ, trong khi trạng thái pod vẫn báo xanh hoàn toàn bình thường.

Ba hướng tiếp cận TLS phổ biến trên Kubernetes

Trước khi chuẩn hóa hạ tầng cluster, chúng tôi đã cân nhắc ba phương án quản lý mã hóa ingress thường gặp:

1. Edge Offloading tại Load Balancer đám mây (AWS ALB / Cloudflare / GCP HTTPS Proxy)

Chấm dứt (terminate) TLS ngay tại external load balancer của nhà cung cấp đám mây trước khi lưu lượng truy cập tới worker node.

  • Ưu điểm: Không cần manifest chứng chỉ nào bên trong Kubernetes; các public CA do nhà cung cấp đám mây tự xử lý.
  • Nhược điểm: Lưu lượng đi qua VPC dưới dạng HTTP không mã hóa; khó di chuyển linh hoạt trên các cluster hybrid hoặc bare-metal; phức tạp khi muốn tự động hóa cho các subdomain đa người thuê (multi-tenant) động.

2. CronJob định kỳ chạy Certbot

Chạy một container Certbot theo lịch bên trong cluster để giải quyết challenge HTTP-01, xuất key và thực thi lệnh kubectl create secret tls ... --dry-run=client -o yaml | kubectl apply -f -.

  • Ưu điểm: Không yêu cầu cài đặt Custom Resource Definition (CRD).
  • Nhược điểm: Shell script dễ thất bại trong âm thầm; phải cấp quyền RBAC quá rộng trên nhiều namespace cho job; không có vòng lặp đối soát (reconciliation loop) nếu ai đó vô tình xóa mất secret.

3. Tự động hóa Ingress-native với cert-manager

Chạy cert-manager dưới dạng controller nội bộ trong cluster. Nó mở rộng Kubernetes API với các tài nguyên như Issuer, ClusterIssuer và Certificate.

  • Ưu điểm: Chuẩn declarative và sẵn sàng cho GitOps; tích hợp trực tiếp qua annotation của Ingress; tự động gia hạn trước khi hết hạn 30 ngày; hỗ trợ cả phương thức HTTP-01 và DNS-01 challenge solver.
  • Nhược điểm: Thêm 3 pod controller gọn nhẹ và các CRD vào cluster.

Triển khai chi tiết: cert-manager + Let’s Encrypt

Kết hợp cert-manager với ClusterIssuer trên toàn cluster là giải pháp tối ưu và gọn gàng nhất. Controller sẽ theo dõi các tài nguyên Ingress và tự động cấp phát chứng chỉ bất cứ khi nào phát hiện quy tắc TLS host.

Bước 1: Cài đặt cert-manager bằng Helm

Triển khai cert-manager bằng Helm chart chính thức. Bật tùy chọn cài đặt CRD trực tiếp qua cờ values giúp đồng bộ các custom resource với phiên bản chart:

# Thêm Helm repository của Jetstack
helm repo add jetstack https://charts.jetstack.io
helm repo update

# Cài đặt cert-manager và các CRD đi kèm
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true

# Kiểm tra đảm bảo tất cả pod controller đang chạy
kubectl get pods -n cert-manager

Trong vòng 30 giây, bạn sẽ thấy 3 pod ở trạng thái running: cert-manager, cert-manager-cainjector và cert-manager-webhook.

Bước 2: Cấu hình Staging và Production ClusterIssuer

Một Issuer thông thường chỉ có phạm vi trong một namespace duy nhất. Trong khi đó, ClusterIssuer hoạt động trên toàn bộ cluster, phục vụ cho Ingress ở bất kỳ namespace nào. Let’s Encrypt áp dụng giới hạn tần suất (rate limit) production rất nghiêm ngặt: 50 chứng chỉ trên mỗi registered domain mỗi tuần và 5 chứng chỉ trùng lặp mỗi tuần. Hãy luôn kiểm tra pipeline ACME solver trên endpoint staging trước.

Tạo một file có tên cluster-issuers.yaml:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
    - http01:
        ingress:
          ingressClassName: nginx
---
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-account-key
    solvers:
    - http01:
        ingress:
          ingressClassName: nginx

Áp dụng các issuer vào cluster:

kubectl apply -f cluster-issuers.yaml

# Kiểm tra trạng thái đăng ký
kubectl get clusterissuer

Xác nhận rằng cả hai issuer đều hiển thị READY: True ở cột trạng thái.

Bước 3: Thêm annotation vào tài nguyên Ingress

Bạn hiếm khi cần phải tự tay viết các manifest Certificate riêng lẻ. Chỉ cần gắn annotation cert-manager.io/cluster-issuer vào Ingress. Controller sẽ tự đọc khối TLS, gửi challenge và lưu trữ cặp key kết quả vào secret đã chỉ định.

Dưới đây là manifest Ingress mẫu (payment-ingress.yaml):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: payment-service-ingress
  namespace: production
  annotations:
    # Chỉ định cert-manager cấp phát chứng chỉ qua Let's Encrypt Production
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    # Bắt buộc chuyển hướng từ HTTP sang HTTPS
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.example.com
    secretName: payment-service-tls
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: payment-service
            port:
              number: 8080

Áp dụng manifest:

kubectl apply -f payment-ingress.yaml

Bước 4: Kiểm tra ACME Challenge và quá trình tạo Secret

cert-manager theo dõi annotation và khởi tạo một pod challenge tạm thời cùng một đường dẫn Ingress ngắn hạn tại /.well-known/acme-challenge/<token>. Let’s Encrypt sẽ xác minh quyền sở hữu tên miền qua endpoint đó, kiểm tra tính hợp lệ của challenge và trả về bundle chứng chỉ đã ký.

Theo dõi tiến trình cấp phát chứng chỉ qua từng giai đoạn đối soát:

# 1. Kiểm tra trạng thái cấp phát
kubectl get certificate -n production

# 2. Kiểm tra CertificateRequest của ACME
kubectl get certificaterequest -n production

# 3. Xem trạng thái challenge (thường hoàn tất trong 15-45 giây)
kubectl get challenges -n production

# 4. Debug lỗi nếu challenge vẫn ở trạng thái pending
kubectl describe order -n production

Khi quá trình cấp phát hoàn tất, lệnh kubectl get certificate -n production sẽ trả về READY: True. Kiểm tra secret TLS vừa được tạo:

kubectl get secret payment-service-tls -n production -o yaml

Secret này chứa tls.crt và tls.key. Ingress-NGINX sẽ phát hiện secret vừa cập nhật ngay lập tức thông qua informer cache và nạp lại SSL context mà không làm rớt bất kỳ kết nối nào.

Bước 5: Thử nghiệm tự động gia hạn

Chứng chỉ Let’s Encrypt hết hạn sau 90 ngày. cert-manager sẽ tự động kích hoạt tiến trình gia hạn ngầm khi thời hạn hiệu lực còn lại 30 ngày (vào ngày thứ 60). Không cần cron job. Không cần can thiệp thủ công.

Bạn có thể thử nghiệm cơ chế gia hạn ngay mà không cần đợi hai tháng. Kích hoạt gia hạn tức thì bằng công cụ CLI cmctl:

# Kích hoạt gia hạn cho chứng chỉ mục tiêu
cmctl renew payment-service-tls -n production

Xem log trực tiếp từ controller để theo dõi vòng lặp gia hạn hoàn tất:

kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager -f

ClusterIssuer theo chuẩn declarative, annotation tự động trên Ingress và các reconciler của Kubernetes giúp loại bỏ hoàn toàn yếu tố con người khỏi việc rotate chứng chỉ. Thiết lập một lần, và đội ngũ của bạn có thể kê cao gối ngủ ngon mỗi đêm.

Share: