8、NGINX-ingress cert-manager letsencrypt

作者: Brinnatt 分类: ARM64 Linux CICD 研发 发布时间: 2025-09-12 16:04

在 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、创建 ClusterIssuerLet’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>
标签云