8、NGINX-ingress cert-manager letsencrypt
在 kubernetes 集群中使用 cert-manager 管理证书很方便,cert-manager 可以从多种机构获取证书,包括 Let's Encrypt, HashiCorp Vault, CyberArk Certificate Manager and private PKI。一般来说,开发、测试以及要求不严格的生产环境使用 Let's Encrypt 是个不错的选择。
官方文档:https://cert-manager.io/docs/
本文参考:https://cert-manager.io/docs/tutorials/acme/nginx-ingress/
8.1、准备环境
-
一个正常运行的 kubernetes 集群
-
kubeclt 要有管理员权限,可以管理 kubernetes 集群
-
一个域名,可以配置 DNS 记录指向 kubernetes 集群的 Ingress IP
8.2、配置思路
1、安装 Nginx Ingress Controller
2、安装 Cert-Manager
3、创建 ClusterIssuer 从 Let’s Encrypt 获取证书
4、配置 Ingress 服务向 ClusterIssuer 索要证书,提供 HTTPS 服务
8.2.1、安装 Nginx Ingress Controller
Nginx Ingress Controller 的功能是把客户端请求路由到 k8s 集群中对应的服务上去,通过 helm 安装比较方便:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace
安装完成之后,验证一下服务是否启来了:
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
- 从云服务提供商那里拿到的 k8s 集群一般带有 LoadBalancer,把域名的 A 记录指向这个 LoadBalancer 的公网 IP 即可。
- 如果是自建的 k8s 集群,可能需要使用 NodePort 暴露 ingress service,不管使用什么方式,域名的 A 记录指向的 IP 要可以通往 ingress service。
8.2.2、安装 Cert-Manager
Cert-Manager 的功能是实现证书的周期性管理,同样使用 helm 安装:
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--version v1.14.1 \
--set installCRDs=true
安装完成后,你可以在 cert-manager 名称空间看到以下三个 Pods 处于 Running 状态:
cert-manager
cert-manager-cainjector
cert-manager-webhook
这里的步骤一般不会有问题,但是在比较老的 kubernetes 集群上,可能在安装的时候会报错:
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.14.1 --set installCRDs=true --set controller.nodeSelector.invoke-url=internet --set webhook.nodeSelector.invoke-url=internet --set cainjector.nodeSelector.invoke-url=internet Error: INSTALLATION FAILED: chart requires kubeVersion: >= 1.22.0-0 which is incompatible with Kubernetes v1.19.13 这个错误信息非常明确:您当前的 Kubernetes 集群版本是 v1.19.13,但 cert-manager v1.14.1 要求集群版本至少为 v1.22.0。
这是版本的兼容问题,必须参照官方的版本匹配矩阵 https://cert-manager.io/docs/releases/
我们使用的兼容命令是:
helm install cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --version v1.8.2 \ # 修改为兼容的版本 --set installCRDs=true \ --set controller.nodeSelector.invoke-url=internet \ # 我的集群只有标签为invoke-url=internet的节点才能上网 --set webhook.nodeSelector.invoke-url=internet \ --set cainjector.nodeSelector.invoke-url=internet
更稳妥的做法,先下载 Values 文件检查兼容性:
# 查看 v1.10.1 的 chart 信息,包括要求的 kubeVersion helm show chart jetstack/cert-manager --version v1.10.1 helm show chart jetstack/cert-manager --version v1.8.2 在输出中,您可以找到 kubeVersion 字段,确认它是否支持您的集群。
helm show values jetstack/cert-manager --version v1.8.2 > cert-manager-values-v1.8.2.yaml
修改完后,再安装:
helm install cert-manager jetstack/cert-manager \ -n cert-manager --create-namespace \ --version v1.8.2 \ -f cert-manager-values-v1.8.2.yaml
8.2.3、创建 ClusterIssuer for Let’s Encrypt
接下来,要创建 ClusterIssuer 来为整个 k8s 集群处理证书请求,官方建议先使用 Let’s Encrypt’s staging 环境进行测试,降低 rate-limits 风险
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: youremail@example.com
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- http01:
ingress:
class: nginx
创建到 k8s 集群:
kubectl apply -f letsencrypt-staging.yaml
验证 letsencrypt 账户是否注册成功:
kubectl describe clusterissuers.cert-manager.io letsencrypt-staging
Status:
Acme:
Last Registered Email: brinnatt@gmail.com
Conditions:
Last Transition Time: 2025-09-11T08:12:29Z
Message: The ACME account was registered with the ACME server
Observed Generation: 1
Reason: ACMEAccountRegistered
Status: True
Type: Ready
-
版本不同命令有些差别,我们的环境是 k8s v1.19.13 + cert-manager v1.8.2,使用上面命令查看,最新的 cert-manager v1.18 命令如下:
kubectl describe issuer letsencrypt-staging Status: Acme: Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/7374163 Conditions: Last Transition Time: 2018-11-17T18:04:00Z Message: The ACME account was registered with the ACME server Reason: ACMEAccountRegistered Status: True Type: Ready
一旦测试成功,可以创建生产环境的 ClusterIssuer,server 字段换成 https://acme-v02.api.letsencrypt.org/directory
,取一个新的 secret 名称,比如:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: youremail@example.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx
创建到 k8s 集群:
kubectl apply -f letsencrypt-production.yaml
现在同时有 staging 和 production ClusterIssuer。
8.2.4、部署测试应用
部署一个 “Hello World”:
kubectl create namespace demo
kubectl create deployment hello-world --image=nginx -n demo
kubectl expose deployment hello-world --port=80 --type=ClusterIP -n demo
这个 deployment 和 service 会在 demo 名称空间跑一个端口为 80 的 web 服务。
8.2.5、创建 Ingress
ingress 作用:
1、把 HTTP 流量从 example.com 路由到 hello-world 服务
2、通过 Cert-Manager 从 Let’s Encrypt 请求一个 TLS 证书
3、强制 HTTPS(通过 Nginx annotation)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello-world-ingress
namespace: demo
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-production"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
secretName: hello-world-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-world
port:
number: 80
创建到 k8s 集群:
kubectl apply -f hello-world-ingress.yaml
把
example.com
替换成你自己的域名
8.2.6、多 Hosts
如果你有多个域名,你可以定义多个 rules 和 tls 条目,比如:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host-ingress
namespace: demo
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-production"
spec:
ingressClassName: nginx
tls:
- hosts:
- app1.example.com
secretName: app1-tls
- hosts:
- app2.example.com
secretName: app2-tls
rules:
- host: app1.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-world
port:
number: 80
- host: app2.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-world
port:
number: 80
每个域名(app1.example.com 和 app2.example.com)将会获得它们自己的 certificate 和 secret。
8.2.7、更新 DNS
把你自己的域名(example.com
)指向 Ingress service 的 IP;如果是多个 hosts(app1.example.com
, app2.example.com
),把他们的 A 记录都指向 Ingress service 的 IP。
8.2.8、验证
验证 ingress:
kubectl describe ingress hello-world-ingress -n demo
确认域名、服务映射、annotations 都是正确的。
验证 Cert-Manager 日志:
kubectl logs -n cert-manager deploy/cert-manager
找到相关的证书是不是成功发布了,注意,cert-manager 在 ACME 流程中会临时创建 Challenge
对象,完成验证后会自动删除。在 controller 并发或重试时,可能还在处理队列里的旧任务,所以日志里会看到 "not found"。
E0912 03:04:40.895208 controller.go:234] cert-manager/challenges "msg"="challenge in work queue no longer exists" "error"="challenge.acme.cert-manager.io \"tls-from-letsencrypt-xt7hf-401890366-2965347012\" not found"
"error"="Operation cannot be fulfilled on certificates.cert-manager.io \"tls-from-letsencrypt\": the object has been modified; please apply your changes to the latest version and try again"
👉 说明 cert-manager 正在处理的 ACME challenge 对象 不存在了(可能被清理了,或者重建时换了名字)。
👉 这是 乐观锁冲突,常见于 cert-manager 内部 controller 并发修改资源时,会自动重试,通常不是致命错误。
Found status change for Certificate "tls-from-letsencrypt" condition "Ready": "False" -> "True"
👉 这个关键:表示 tls-from-letsencrypt
证书的状态已经变为 Ready=True,也就是证书实际上签发成功了。
8.2.9、问题
HTTP challenge fails?
你的 DNS A 记录一定要可以引流到 Ingress IP,有的时候你的环境比较复杂,有 elb 和 nginx 负载均衡器,在配置的时候难免出错,必须断点调试,一步步确认从域名可以到达 ingress service。
Wrong Ingress class?
一定要注意,ingressClassName: nginx
,这个字段必须配置,早期的版是在 annotation 里面配置 kubernetes.io/ingress.class: nginx
,没有这个字段,ingress 的 rules 很有可能不会被注入到 ingress controller 中去,会报 404 的错误。
Rate limits
Let's encrypt 有请求次数的限制,为了降低 rate-limits 风险,请先使用 letsencrypt-staging
测试。
Logs & events
如果还有问题,查看日志,找一些线索定位问题
kubectl describe certificate <certificate-name>
kubectl describe challenge <challenge-name>