Sổ tay kỹ thuật

ingress-nginx + cert-manager + Let's Encrypt trên Kubernetes

how-to#kubernetes#tls#helm#nginx· cập nhật 2026-11-06

Dùng khi cần publish Service qua Ingress với chứng chỉ Let's Encrypt tự cấp và tự gia hạn bằng cert-manager ClusterIssuer.

Sửa trang này
Mục lục

Tóm tắt: Cài ingress-nginx và cert-manager bằng Helm, tạo ClusterIssuer ACME (staging trước, production sau), rồi gắn annotation cert-manager.io/cluster-issuer vào Ingress. cert-manager tự tạo Certificate, giải challenge HTTP-01 qua chính ingress-nginx, lưu cert vào Secret ở spec.tls[].secretName và gia hạn trước khi hết hạn.

Dự án ingress-nginx (kubernetes/ingress-nginx) đã ngừng phát triển: Kubernetes SIG Network thông báo chỉ bảo trì best-effort tới tháng 3/2026, sau đó không còn bản phát hành hay bản vá bảo mật. Cluster mới nên cân nhắc controller khác hoặc Gateway API (cert-manager cũng hỗ trợ Gateway). Phần cert-manager dưới đây áp dụng được cho các controller khác, chỉ cần đổi ingressClassName.

Luồng hoạt động

flowchart LR I["Ingress + annotation cluster-issuer"] --> C[Certificate] C --> O["Order / Challenge"] O -->|"HTTP-01 /.well-known/acme-challenge"| LE["Let's Encrypt"] LE -->|"GET qua ingress-nginx"| S["solver Pod tạm"] LE -->|"cấp cert"| K["Secret TLS"] K --> N["ingress-nginx dùng cert"]

Cấu hình

Cài ingress-nginx và cert-manager

helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  -n ingress-nginx --create-namespace
 
helm repo add jetstack https://charts.jetstack.io && helm repo update
helm upgrade --install cert-manager jetstack/cert-manager \
  -n cert-manager --create-namespace \
  --set crds.enabled=true
 
kubectl get svc -n ingress-nginx ingress-nginx-controller   # lấy EXTERNAL-IP để trỏ DNS
kubectl get pods -n cert-manager

crds.enabled=true là tên value từ cert-manager 1.15 (bản cũ dùng installCRDs=true). Trỏ bản ghi A/AAAA của app.example.com về EXTERNAL-IP của controller trước khi xin cert.

ClusterIssuer

cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: ops@example.com
    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: ops@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - http01:
          ingress:
            ingressClassName: nginx
kubectl apply -f cluster-issuer.yaml
kubectl get clusterissuer        # cột READY phải True

ClusterIssuer dùng cho mọi namespace; Issuer chỉ trong một namespace (annotation tương ứng là cert-manager.io/issuer).

Ingress

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app
  namespace: web
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
spec:
  ingressClassName: nginx
  tls:
    - hosts: [app.example.com]
      secretName: app-example-com-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app
                port:
                  number: 80

Chạy với staging tới khi Certificate READY, rồi đổi annotation sang letsencrypt-prod và xoá Secret staging để cert-manager cấp lại:

kubectl annotate ingress app -n web cert-manager.io/cluster-issuer=letsencrypt-prod --overwrite
kubectl delete secret app-example-com-tls -n web

Wildcard bằng DNS-01 (Cloudflare)

HTTP-01 không cấp được *.example.com. Dùng DNS-01, token API để trong Secret ở namespace cert-manager (namespace của cluster resource):

kubectl create secret generic cloudflare-api-token -n cert-manager --from-literal=api-token="$CF_API_TOKEN"
cluster-issuer-dns01.yaml
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token

Lệnh kiểm tra

Mục đích Lệnh
Certificate đã READY chưa kubectl get certificate -n web
Lý do chưa cấp kubectl describe certificate app-example-com-tls -n web
Đi xuống từng tầng kubectl get certificaterequest,order,challenge -n web
Chi tiết challenge đang kẹt kubectl describe challenge -n web
Log cert-manager kubectl logs -n cert-manager deploy/cert-manager --tail=100
Cert thực tế trong Secret kubectl get secret app-example-com-tls -n web -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -issuer -dates
Thử path challenge từ ngoài curl -sv http://app.example.com/.well-known/acme-challenge/test
Log controller ingress-nginx kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=100

Lỗi thường gặp

Triệu chứng Nguyên nhân Cách xử lý
Không có Certificate nào được tạo Sai tên annotation, hoặc Ingress thiếu spec.tls Annotation đúng là cert-manager.io/cluster-issuer, phải có tls.secretName
ClusterIssuer READY False Không đăng ký được account ACME (sai URL server, chặn egress) kubectl describe clusterissuer letsencrypt-prod
Challenge pending, lỗi wrong status code '404' DNS chưa trỏ về ingress, hoặc solver dùng sai ingress class Kiểm tra DNS, ingressClassName trong solver khớp controller
Challenge lỗi connection refused/timeout Port 80 bị firewall/LB chặn Mở port 80 từ internet (HTTP-01 bắt buộc dùng 80)
Trình duyệt báo cert (STAGING) không tin cậy Đang dùng issuer staging Đổi sang letsencrypt-prod, xoá Secret cũ
Lỗi too many certificates already issued / rateLimited Vượt rate limit Let's Encrypt production Thử trên staging, đợi hết thời gian giới hạn
Truy cập ra cert Kubernetes Ingress Controller Fake Certificate Secret chưa có hoặc tên không khớp secretName Chờ Certificate READY, kiểm tra tên Secret và namespace
Redirect loop sau Cloudflare proxy ingress-nginx ép HTTPS trong khi Cloudflare gửi HTTP tới origin Đặt SSL mode Full (strict) trên Cloudflare

Kiến thức chain/SAN khi đọc cert: TLS troubleshooting. Lệnh Helm: Helm – lệnh hay dùng.

Tham khảo