# Ingress + TLS 证书自动化 --- ## 场景 生产环境对外服务必须 HTTPS。手动申请、续期、分发证书太麻烦,尤其多域名、多环境时。 **cert-manager + Let's Encrypt** 实现:自动申请、自动续期、自动分发到 Ingress。 ## 前置条件 * K8s 1.19+,已安装 Ingress Controller(nginx-ingress / traefik / alb) * 域名已解析到 Ingress 的公网 IP / LB * 集群能访问外网(访问 Let's Encrypt API:`acme-v02.api.letsencrypt.org:443`) * Helm 3 已安装 ## 配置 ### 1\. 安装 cert\-manager ```bash # 方式 1:Helm(推荐) helm repo add jetstack https://charts.jetstack.io helm repo update helm install cert-manager jetstack/cert-manager \ -n cert-manager --create-namespace \ --version v1.13.0 \ --set installCRDs=true \ --set prometheus.enabled=false # 验证 kubectl get pods -n cert-manager kubectl get crd | grep cert-manager ``` ### 2\. 配置 Let's Encrypt ClusterIssuer ```yaml # cluster-issuer.yaml 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-key solvers: - http01: ingress: class: nginx # 对应 IngressClass 名称 --- # staging 环境用(测试用,不受速率限制) 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-key solvers: - http01: ingress: class: nginx ``` ```bash kubectl apply -f cluster-issuer.yaml kubectl get clusterissuer # READY 列应为 True ``` ### 3\. Ingress 自动申请证书 ```yaml # ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp namespace: prod annotations: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/proxy-body-size: "50m" spec: ingressClassName: nginx tls: - hosts: - app.example.com - api.example.com secretName: myapp-tls # cert-manager 自动创建 rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: myapp-svc port: number: 80 - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: api-svc port: number: 8080 ``` ```bash kubectl apply -f ingress.yaml # 观察证书申请过程 kubectl get certificate -n prod kubectl describe certificate myapp-tls -n prod kubectl describe certificaterequest -n prod ``` ### 4\. 通配符证书(DNS\-01 挑战) 适用于:子域名多、不想暴露 HTTP 端口、内网域名。 ```yaml # 需要 DNS 提供商 API Token(以 Cloudflare 为例) # cluster-issuer-dns.yaml apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-dns spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: ops@example.com privateKeySecretRef: name: letsencrypt-dns-key solvers: - dns01: cloudflare: email: ops@example.com apiTokenSecretRef: name: cloudflare-api-token key: api-token --- # Secret 存 API Token apiVersion: v1 kind: Secret metadata: name: cloudflare-api-token namespace: cert-manager type: Opaque stringData: api-token: "your-cloudflare-api-token" ``` ```yaml # Ingress 使用通配符证书 metadata: annotations: cert-manager.io/cluster-issuer: letsencrypt-dns spec: tls: - hosts: - "*.example.com" secretName: wildcard-example-com ``` ### 5\. 多环境证书隔离 ```yaml # staging 环境用 staging issuer(不受速率限制) metadata: annotations: cert-manager.io/cluster-issuer: letsencrypt-staging ``` ### 6\. 常用排查 ```bash # 证书没生成 kubectl describe certificate myapp-tls -n prod kubectl describe certificaterequest -n prod kubectl logs -n cert-manager -l app=cert-manager -c cert-manager # Ingress 没挂上证书 kubectl get ingress -n prod kubectl describe ingress myapp -n prod # cert-manager 日志 kubectl logs -n cert-manager -l app=cert-manager -c cert-manager --tail=100 # 常见报错 # "Failed to get ACME directory" -> 网络不通、DNS 解析失败 # "No solver configured" -> Issuer 没配 solver # "No ingresses found matching" -> IngressClass 不匹配 / 没加 annotation # "Rate limited" -> 生产环境频率限制,改用 staging ``` ## 验证 ```bash # 证书状态 kubectl get certificate -A # READY 为 True 即成功 # 查看证书详情 kubectl get secret myapp-tls -n prod -o yaml # 解码证书 echo "" | base64 -d | openssl x509 -text -noout # 浏览器验证 curl -vI https://app.example.com openssl s_client -connect app.example.com:443 -servername app.example.com ``` ## 回滚 ```bash # 证书异常,强制重签 kubectl delete certificate myapp-tls -n prod # cert-manager 会按 Issuer 重新申请 # 紧急回滚到手动证书 kubectl delete secret myapp-tls -n prod kubectl create secret tls myapp-tls \ --cert=/path/to/cert.pem --key=/path/to/key.pem -n prod # Ingress 自动挂载新 secret ``` ## 坑 | 坑 | 现象 | 避坑 | | --- | --- | --- | | 证书一直 Pending | 网络不通 / IngressClass 不对 | 先 curl acme 服务器,检查 IngressClass 名称 | | 证书一直 Pending (DNS-01) | DNS 记录没生效 / API Token 无权限 | 先手动 dig txt 验证,检查 API Token 权限 | | 证书自动续期失败 | cert-manager 重启丢失 lease / 网络抖动 | 设置 `--feature-gates=ExperimentalCertificateSigningRequestControllers=true` | | 多域名证书其中一个失败导致全部失败 | Let's Encrypt 多域名是全或无 | 关键域名单独申请证书 | | HTTP-01 端口 80 被占用 / 防火墙拦截 | 80 端口没暴露 / 防火墙拦截 | 确保 80 端口直通 Ingress Controller | | 证书续期后 Ingress 没重新加载 | Ingress Controller 没监听 secret 变更 | nginx-ingress 默认 watch secret,检查 `--watch-ingress-without-class` | | staging 证书信任报错 | 浏览器不信任 staging 根证书 | 仅测试用,生产必须用 prod issuer |