Oamc

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

# 方式 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

# 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
kubectl apply -f cluster-issuer.yaml
kubectl get clusterissuer
# READY 列应为 True

3. Ingress 自动申请证书

# 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
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 端口、内网域名。

# 需要 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"
# Ingress 使用通配符证书
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-dns
spec:
  tls:
    - hosts:
        - "*.example.com"
      secretName: wildcard-example-com

5. 多环境证书隔离

# staging 环境用 staging issuer(不受速率限制)
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging

6. 常用排查

# 证书没生成
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

验证

# 证书状态
kubectl get certificate -A
# READY 为 True 即成功

# 查看证书详情
kubectl get secret myapp-tls -n prod -o yaml
# 解码证书
echo "<base64>" | base64 -d | openssl x509 -text -noout

# 浏览器验证
curl -vI https://app.example.com
openssl s_client -connect app.example.com:443 -servername app.example.com

回滚

# 证书异常,强制重签
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