Cài cert-manager trên Kubernetes thường chỉ vài lệnh, nhưng khi sai thì thông báo rất khó đọc. Tài liệu này là sổ tay chẩn đoán lỗi cert-manager theo ba giai đoạn: trước khi cài (CRD không áp dụng được), lúc cài bằng Helm (lỗi ownership metadata) và lúc cấp chứng chỉ (lỗi ACME "public key must be different than account key"). Mỗi lỗi cert-manager có nguyên nhân, lệnh kiểm tra và cách xử lý đúng, kèm những chỗ các hướng dẫn trôi nổi trên mạng giải thích sai.
Bài về lỗi cert-manager này không lặp lại luồng cấp chứng chỉ đúng. Nếu bạn cần hiểu chuỗi Certificate, Order, Challenge, hãy đọc ingress-nginx và cert-manager: HTTPS tự động trên Kubernetes trước.
Ba giai đoạn xảy ra lỗi cert-manager
| Giai đoạn | Dấu hiệu | Hướng xử lý |
|---|---|---|
| Trước khi cài | no matches for kind CustomResourceDefinition in version apiextensions.k8s.io/v1beta1 | Dùng manifest CRD đúng phiên bản API |
| Lúc cài bằng Helm | invalid ownership metadata | Tìm tài nguyên cluster-scoped còn sót, dọn có kiểm soát |
| Lúc cấp chứng chỉ | Certificate Ready: False, Order errored | Đọc describe từ Certificate xuống Order, tách tên Secret |
Lỗi cert-manager trước khi cài: CRD apiextensions v1beta1 đã bị gỡ
Một lỗi cert-manager rất hay gặp là làm theo hướng dẫn cũ. Nhiều hướng dẫn yêu cầu kubectl apply -f một tệp CRD của cert-manager phiên bản cũ. Trên cluster mới, lệnh đó báo:
error: unable to recognize "cert-manager.crds.yaml": no matches for kind
"CustomResourceDefinition" in version "apiextensions.k8s.io/v1beta1"Nguyên nhân là API apiextensions.k8s.io/v1beta1 đã bị gỡ từ Kubernetes 1.22, chỉ còn apiextensions.k8s.io/v1 (xem deprecation guide). Tệp CRD của bản cert-manager đủ mới dùng v1 nên áp dụng được. Hãy chọn phiên bản cert-manager hỗ trợ phiên bản Kubernetes của bạn theo bảng tương thích trên trang phát hành của cert-manager. Việc một bản cụ thể (ví dụ 1.8) có tương thích với Kubernetes đời mới hay không chưa được kiểm chứng ở đây, hãy tự thử trên cluster thử.
Tuỳ chọn gọn nhất là để Helm cài CRD luôn: đặt crds.enabled=true (các bản cũ dùng installCRDs=true), và chọn một cách cài CRD, không trộn việc apply tay với cờ của chart.
Lỗi cert-manager lúc cài: Helm báo "invalid ownership metadata"
Đây là lỗi cert-manager lúc cài. Ví dụ:
helm install cert-manager jetstack/cert-manager -n cert-manager --version v1.x.y
Error: INSTALLATION FAILED: rendered manifests contain a resource that already exists.
Unable to continue with install: ClusterRole "cert-manager-cainjector" in namespace ""
exists and cannot be imported into the current release: invalid ownership metadata;
label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm";
annotation validation error: missing key "meta.helm.sh/release-name" ...Helm 3 chỉ cho phép một release sửa một tài nguyên nếu tài nguyên đó có ba khoá do Helm đặt: nhãn app.kubernetes.io/managed-by: Helm và hai annotation meta.helm.sh/release-name, meta.helm.sh/release-namespace. Một tài nguyên cùng tên đã tồn tại mà thiếu khoá (do apply tay từ manifest, hoặc do release khác sở hữu) thì Helm từ chối "nhận". ClusterRole, ClusterRoleBinding, webhook configuration là tài nguyên cluster-scoped, không nằm trong namespace nên dễ sót lại khi bạn xoá namespace.
Điều cần nhớ về lỗi cert-manager này: đổi phiên bản chart xuống thấp hơn không giải quyết được, vì lỗi nằm ở tài nguyên đang có trong cluster chứ không ở phiên bản. Hạ phiên bản CRD xuống rất cũ rồi đè lên CRD mới còn nguy hiểm, vì có thể làm mất trường của schema; không nên làm.
Lỗi tương tự ingress-nginx: ClusterRole trùng tên release
Lỗi tương tự xảy ra khi cài ingress-nginx vào namespace mới trong lúc đã có một bản ở namespace khác:
Error: ... ClusterRole "ingress-nginx" in namespace "" exists and cannot be imported
into the current release: invalid ownership metadata; annotation validation error:
key "meta.helm.sh/release-namespace" must equal "<namespace-moi>": current value is "default"Gốc ở đây là hai release cùng tên (ingress-nginx) muốn dùng chung một ClusterRole cùng tên, mà ClusterRole thuộc về release đầu tiên. Một cách xử lý thường được ghi lại là helm uninstall bản cũ rồi cài lại. Hãy cẩn thận: gỡ bản đang chạy là mất Service LoadBalancer và NodeBalancer đi kèm, tức là site mất địa chỉ vào. Nếu cần hai bản cùng tồn tại, chỉ đổi tên release và IngressClass chưa chắc đủ vì IngressClass cũng là tài nguyên cluster-scoped, mặc định tên nginx. Hãy đặt khác nhau release name, controller.ingressClassResource.name, controller.ingressClassResource.controllerValue và controller.electionID. Đây là giả thuyết cần kiểm chứng trên cluster thử. Nền tảng của ingress được nói ở Ingress và NodeBalancer: đưa traffic vào cluster Kubernetes.
Dọn tài nguyên sót khi lỗi cert-manager lặp lại
Trước khi xoá bất cứ thứ gì, kiểm tra xem có release cert-manager nào đang chạy không. Chỉ dọn khi không có release nào, hoặc bạn đã helm uninstall có chủ đích; nếu không, bạn sẽ làm hỏng bản đang dùng.
helm list -A | grep cert-manager
# liệt kê trước, đọc kỹ (tài nguyên cluster-scoped)
kubectl get clusterrole,clusterrolebinding,validatingwebhookconfiguration,mutatingwebhookconfiguration -o name | grep cert-manager
kubectl get role,rolebinding -n kube-system -o name | grep cert-manager
# xem một tài nguyên thuộc release nào trước khi xoá
kubectl get clusterrole cert-manager-cainjector -o yaml | grep -E "managed-by|release-"Chỉ xoá các mục có managed-by không phải Helm, hoặc có meta.helm.sh/release-name không khớp release bạn sắp cài, sau khi đã đọc -o yaml. Webhook configuration và Role leader-election trong kube-system là hai nhóm hay bị sót vì xoá namespace không xoá chúng. Dạng xoá đúng, từng loại một:
kubectl delete clusterrole cert-manager-cainjector
kubectl delete validatingwebhookconfiguration cert-manager-webhookMột lệnh dọn sai hay được chép lại là kubectl get | grep | xargs kubectl delete mà không dùng -o name: xargs truyền cả các cột làm tên tài nguyên và báo hàng loạt lỗi NotFound. Dạng -o name an toàn hơn nhưng xoá luôn cả bản đang chạy nếu grep quá rộng, nên đừng dùng nó khi chưa kiểm tra release. Đừng thêm -n <namespace> khi xoá tài nguyên cluster-scoped, kubectl chỉ cảnh báo và bỏ qua. Cũng đừng xoá CRD để "cho sạch": xoá CRD sẽ xoá toàn bộ Certificate, Issuer, Order trong cluster.
Một hướng khác là cho Helm "nhận" tài nguyên bằng cách gắn đúng ba khoá ownership đã nêu ở trên (app.kubernetes.io/managed-by, meta.helm.sh/release-name, meta.helm.sh/release-namespace). Hướng này chưa được kiểm chứng ở đây; hãy thử trên cluster thử trước. Về khái niệm release, xem tài liệu Helm về cách dùng và phần Helm trong bài cert-manager.
Lỗi cert-manager lúc cấp chứng chỉ: "public key must be different than account key"
Với lỗi cert-manager lúc cấp chứng chỉ, Certificate ở trạng thái Ready: False. Chạy kubectl describe certificate và đi xuống Order sẽ thấy:
Failed to finalize Order: 400 urn:ietf:params:acme:error:malformed:
Error finalizing order :: certificate public key must be different than account keySự kiện kèm theo cho gợi ý: Reusing private key stored in existing Secret resource. Cert-manager tái dùng khoá có sẵn trong Secret có tên secretName của Certificate. Nếu Secret đó chính là Secret của khoá tài khoản ACME, thì khoá chứng chỉ trùng khoá tài khoản và máy chủ ACME từ chối.
Cấu hình gây lỗi cert-manager này là hai tên Secret giống nhau và cùng namespace. Với ClusterIssuer, Secret khoá tài khoản nằm ở namespace cert-manager, nên Certificate cũng phải nằm ở đó thì mới va chạm:
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-key # khoá TÀI KHOẢN ACME
solvers:
- http01:
ingress:
ingressClassName: nginx
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: site-a
namespace: cert-manager # cùng namespace với khoá tài khoản: va chạm
spec:
secretName: letsencrypt-key # trùng tên: sai
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames: [site-a.example.com]Trường ingressClassName trong solver HTTP-01 cần cert-manager đủ mới; các bản cũ dùng class: nginx.
Sửa lỗi cert-manager về account key: privateKeySecretRef và secretName
Một giải thích hay gặp là privateKeySecretRef là khoá "dùng để ký CSR". Giải thích này không đúng. Theo tài liệu ACME của cert-manager:
privateKeySecretReftrong Issuer hoặc ClusterIssuer là Secret lưu khoá tài khoản ACME, dùng để đăng nhập với Let's Encrypt. Nó không ký chứng chỉ của website.secretNametrong Certificate là Secret sẽ chứa khoá riêng và chứng chỉ của website (tls.key,tls.crt).
Để tránh lỗi cert-manager này, hai thứ phải là hai khoá khác nhau. Hai Secret chỉ va chạm khi cùng tên và cùng namespace, nên chỉ cần phá một trong hai điều kiện là hết lỗi; khuyến nghị đổi tên cho rõ ràng. Với ClusterIssuer, Secret khoá tài khoản nằm ở namespace cluster-resource-namespace của cert-manager (mặc định cert-manager), còn Secret của Certificate nằm cùng namespace với Certificate, nên chúng chỉ trùng khi bạn đặt Certificate trong namespace cert-manager. Đó là lỗi hay gặp khi làm theo hướng dẫn đặt mọi thứ vào một namespace. Bản sửa đúng:
kind: Certificate
metadata:
name: site-a
namespace: site-a # namespace của Ingress
spec:
secretName: site-a-tls # khác tên Secret khoá tài khoảnSau khi sửa, apply lại Certificate với secretName mới và theo dõi bằng kubectl describe certificate. Đừng xoá Secret khoá tài khoản, vì cert-manager sẽ đăng ký tài khoản ACME mới. Cuối cùng tự chạy kubectl get certificate để xác nhận Ready: True.
Secret TLS cùng namespace với Ingress; Issuer và ClusterIssuer
Ingress chỉ đọc được Secret TLS trong cùng namespace của chính nó. Vì vậy Certificate phải tạo trong namespace của Ingress, và secretName ở hai nơi phải khớp:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: site-a
namespace: site-a
spec:
ingressClassName: nginx
tls:
- hosts: [site-a.example.com]
secretName: site-a-tls # khớp Certificate.spec.secretName
rules:
- host: site-a.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: app, port: { number: 80 } }Một ClusterIssuer dùng được cho mọi namespace, còn Issuer chỉ phục vụ namespace của nó. Metadata của ClusterIssuer không có namespace, nên một dòng namespace: trong ClusterIssuer là thừa. Bạn cũng đừng đặt email cá nhân thật vào ví dụ công khai.
Checklist xử lý lỗi cert-manager
- Xác định lỗi cert-manager thuộc giai đoạn nào: trước cài, lúc cài hay lúc cấp chứng chỉ.
- CRD báo v1beta1: dùng manifest hoặc chart đủ mới, chọn một cách cài CRD.
- Ownership metadata:
helm list -Atrước, rồi liệt kê, đọc-o yaml, xoá có kiểm soát, không xoá CRD. - Hai release cùng tên release (ví dụ cùng là
ingress-nginx): đổi tên release hoặc gỡ bản cũ có chủ đích. - Lỗi account key:
privateKeySecretRefvàsecretNamephải khác tên, không đặt Certificate trong namespace cert-manager. - Certificate và Ingress cùng namespace,
secretNamekhớp nhau. - Dùng
kubectl describetừ Certificate xuống CertificateRequest, Order, Challenge để thấy lỗi gốc.
Bài tiếp theo
Kiểm kê image Laravel cho Kubernetes: cache bị đóng băng, extension thiếu và opcache. Sau các lỗi cert-manager, bài sau quay lại với ứng dụng, soát lại image Laravel để tránh những lỗi cấu hình im lặng khi chạy trong container.



