证书管理:cert-manager 与自动轮转一句话定位:证书过期是集群的定时炸弹——这篇把 K8s 证书体系、cert-manager 自动轮转和过期巡检一次性配齐。写在前面我经历过一次惨剧:凌晨 3 点被告警叫醒,整个集群 apiserver 拒绝连接,kubelet 全报certificate has expired or not yet valid。原因很low——kubelet 客户端证书过期了,而自动轮转没开。那一夜我手动给 200 个节点续证书续到天亮。从那以后,我对证书管理的态度就一句话:能自动化的绝不手动,能监控的绝不靠人记。这篇把证书这套体系讲透,让你的集群不再栽在过期上。核心问题证书过期导致集群挂掉怎么办?apiserver/kubelet/etcd 证书一过期,控制面直接瘫痪,kubelet 无法注册。怎么避免手动续证书?kubelet 自带客户端证书轮转;cert-manager 管理 Ingress/内部服务证书。Let’s Encrypt 怎么用?cert-manager ACME HTTP01/DNS01 挑战,自动签发公网证书。一、原理剖析1.1 K8s 证书体系全景K8s 集群里证书多到让人头大,先理清楚谁跟谁通信:┌────────────────────────────────────────────────────────┐ │ K8s 证书体系 │ │ │ │ ┌──────────┐ server cert ┌──────────┐ │ │ │ kubelet │ ◄───────────────► │ apiserver│ │ │ │ │ client cert │ │ │ │ └──────────┘ └────┬─────┘ │ │ │ │ │ ┌──────────┐ client cert │ │ │ │ kubectl │ ────────────────────► │ │ │ │ (用户) │ │ │ │ └──────────┘ │ │ │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ apiserver │◄───┘ │ │ │ └─ apiserver-kubelet-client │ │ │ │ └─ apiserver-etcd-client │ │ │ └──────────────────────────────┘ │ │ │ │ ┌──────────┐ peer cert ┌──────────┐ │ │ │ etcd │ ◄─────────────► │ etcd │ │ │ └──────────┘ └──────────┘ │ └────────────────────────────────────────────────────────┘证书分类与过期影响:证书路径默认有效期过期影响apiserver serverapiserver.crt1 年(kubeadm)apiserver 不可访问apiserver-kubelet-clientapiserver-kubelet-client.crt1 年apiserver 无法连 kubeletapiserver-etcd-clientapiserver-etcd-client.crt1 年apiserver 无法读写 etcdkubelet serverkubelet.crt1 年kubelet 10250 端口不可访问kubelet client(轮转)kubelet-client-current.pem默认 1 年,轮转后自动续kubelet 无法注册到 apiserveretcd server/peerserver.crt/peer.crt1 年(kubeadm)etcd 集群通信失败aggregation CAfront-proxy-ca.crt10 年API 聚合失效最容易爆雷的是kubelet 客户端证书,因为它直接决定节点能不能留在集群里。1.2 kubelet 证书轮转机制kubelet 从 1.19 起支持客户端证书自动轮转:kube-controller-managerapiserverkubeletkube-controller-managerapiserverkubelet监听证书剩余有效期 30%启动,加载现有 client cert用现有证书注册发起 CSR(certificate signing request)CSR 进入 kubernetes.io/kube-apiserver-client-kubelet 签名器自动审批(若开启 RotateKubeletClientCertificate)签发新证书返回新证书写入 kubelet-client-current.pem,热加载开启轮转的关键配置(kubelet config):# /var/lib/kubelet/config.yamlrotateCertificates:true# 客户端证书自动轮转serverTLSBootstrap:true# 服务端证书 bootstrap(可选)serverTLSBootstrap开启后,kubelet 服务端证书也走 CSR,由 controller-manager 签发(默认还是自签)。生产建议开启,统一证书管理。1.3 cert-manager 架构cert-manager 是管理 Ingress/Service 证书的主力,基于 CRD:签发生成挂载申请挑战成功后Issuer/ClusterIssuerCertificateSecret TLSIngress/PodOrder ACMEChallenge HTTP01/DNS01CA:Lets Encrypt/内部核心 CRD:Issuer:命名空间级签发器,只能签同命名空间证书ClusterIssuer:集群级签发器,可签任意命名空间证书Certificate:声明想要的证书(域名、有效期、目标 Secret)Order/Challenge:ACME 流程的中间产物,一般不用管二、实战操作2.1 cert-manager Helm 生产部署# 添加 Jetstack 仓库helm repoaddjetstack https://charts.jetstack.io helm repo update# 安装 cert-manager(1.30 对应 cert-manager v1.15)helminstallcert-manager jetstack/cert-manager\--namespacecert-manager\--create-namespace\--versionv1.15.3\--setinstallCRDstrue\--setreplicaCount3\--setpodDisruptionBudget.minAvailable2\--setfeatureGatesAdditionalCertificateOutputFormatstrue\--setconfig.apiVersioncontroller.config.cert-manager.io/v1alpha1\--setconfig.kindControllerConfiguration\--setconfig.enableCertificateOwnerReftrue# 验证kubectl get pods-ncert-manager# 期望: cert-manager / webhook / cainjector 三个 Pod Running生产配置要点:replicaCount3:cert-manager 高可用podDisruptionBudget:保证升级时至少 2 个可用enableCertificateOwnerRef:删除 Certificate 时自动清理 Secret2.2 内部 CA 与自签证书内部服务互调用(如 mTLS),用自建 CA 签发证书,不依赖外部。Step 1:创建 CA 私钥和证书# 生成 CA 私钥openssl genrsa-outca.key4096# 生成 CA 证书(10 年)openssl req-x509-new-keyca.key-sha256-days3650\-outca.crt\-subj/CNinternal-ca/OMyOrgStep 2:创建 CA Secret(给 cert-manager 用)kubectl create namespace internal kubectl create secret tls internal-ca-secret\--certca.crt--keyca.key\-ninternalStep 3:创建 ClusterIssuer# cluster-issuer-internal.yamlapiVersion:cert-manager.io/v1kind:ClusterIssuermetadata:name:internal-ca-issuerspec:ca:secretName:internal-ca-secret# CA Secret 必须在 cert-manager 所在命名空间---# 或者用 SelfSigned(测试用,不适合生产)apiVersion:cert-manager.io/v1kind:ClusterIssuermetadata:name:selfsigned-issuerspec:selfSigned:{}kubectl apply-fcluster-issuer-internal.yaml kubectl get clusterissuer internal-ca-issuer-owideStep 4:申请内部证书# certificate-internal.yamlapiVersion:cert-manager.io/v1kind:Certificatemetadata:name:backend-tlsnamespace:internalspec:secretName:backend-tls-secretduration:2160h# 90 天renewBefore:360h# 提前 15 天续issuerRef:name:internal-ca-issuerkind:ClusterIssuercommonName:backend.internal.svcdnsNames:-backend-backend.internal-backend.internal.svc.cluster.localusages:-server auth-client auth# 生成 JKS/PEM 等多种格式additionalOutputFormats:-type:JKSprivateKey:algorithm:RSAencoding:PKCS8size:2048kubectl apply-fcertificate-internal.yaml kubectl get certificate-ninternal# READYTrue 表示签发成功kubectl get secret backend-tls-secret-ninternal-oyaml2.3 Let’s Encrypt ACME(HTTP01/DNS01)公网域名证书用 Let’s Encrypt,免费、自动续。两种挑战:挑战方式原理适用场景配置难度HTTP01在 Ingress 暴露 .well-known 验证文件有公网 IP 的 Ingress简单DNS01在 DNS 加 TXT 记录验证通配符证书/无公网 Ingress需 DNS API 凭证HTTP01 ClusterIssuer:# cluster-issuer-letsencrypt-http01.yamlapiVersion:cert-manager.io/v1kind:ClusterIssuermetadata:name:letsencrypt-prodspec:acme:server:https://acme-v02.api.letsencrypt.org/directoryemail:opsexample.comprivateKeySecretRef:name:letsencrypt-prod-account-keysolvers:-http01:ingress:class:nginx# 用指定 Ingress class 处理挑战DNS01 ClusterIssuer(以阿里云 DNS 为例):# cluster-issuer-letsencrypt-dns01.yamlapiVersion:cert-manager.io/v1kind:ClusterIssuermetadata:name:letsencrypt-dnsspec:acme:server:https://acme-v02.api.letsencrypt.org/directoryemail:opsexample.comprivateKeySecretRef:name:letsencrypt-dns-account-keysolvers:-dns01:webhook:groupName:acme.example.comsolverName:alidnsconfig:accessKeyIdRef:name:alidns-secretkey:access-key-idaccessKeySecretRef:name:alidns-secretkey:access-key-secretregion:cn-hangzhou# 阿里云 DNS 凭证kubectl create secret generic alidns-secret\--from-literalaccess-key-idLTAIxxx\--from-literalaccess-key-secretxxx\-ncert-manager申请通配符证书:# certificate-wildcard.yamlapiVersion:cert-manager.io/v1kind:Certificatemetadata:name:wildcard-tlsnamespace:nginx-ingressspec:secretName:wildcard-example-tlsissuerRef:name:letsencrypt-dnskind:ClusterIssuercommonName:*.example.comdnsNames:-*.example.com-example.comIngress 自动签发:# ingress-with-tls.yamlapiVersion:networking.k8s.io/v1kind:Ingressmetadata:name:app-ingressnamespace:internalannotations:cert-manager.io/cluster-issuer:letsencrypt-prod# 指定 issuer 即可自动签发spec:tls:-hosts:-app.example.comsecretName:app-tls-secretrules:-host:app.example.comhttp:paths:-path:/pathType:Prefixbackend:service:name:app-svcport:number:80cert-manager 看到 Ingress 有tls和 issuer 注解,自动签发证书到app-tls-secret。2.4 kubelet 证书轮转配置# 1. 检查 kubelet 配置cat/var/lib/kubelet/config.yaml|greprotate# 期望:# rotateCertificates: true# serverTLSBootstrap: true# 2. 如果没开,修改 kubelet configkubectl-nkube-system edit configmap kubelet-config# 加上:# rotateCertificates: true# serverTLSBootstrap: true# 3. 让所有节点应用新配置# ssh 到节点systemctl restart kubelet# 4. 开启 controller-manager 自动审批(生产谨慎)# 默认 kubelet client CSR 需要手动 kubectl certificate approve# 自动审批需配置 CSR approver(用 controller-manager 的签发器配置)# 或者用 auto approving controller(如 kubelet-csr-approver)自动审批方案(推荐kubelet-csr-approver):# 部署 kubelet-csr-approverhelm repoaddpostfinance https://postfinance.github.io/kubelet-csr-approver helminstallkubelet-csr-approver postfinance/kubelet-csr-approver\-nkube-system\--setproviderRegex^ip-10-0-0-.*\\.ec2\\.internal$\--setbypassDnsResolutiontrue2.5 证书过期巡检脚本#!/bin/bash# check-cert-expiry.sh - 巡检所有证书过期时间# 使用: ./check-cert-expiry.sh [days]THRESHOLD${1:-30}ALERT0echo 1. 控制面证书(kubeadm 管理) kubeadm certs check-expiration2/dev/null|grep-ECA|apiserver|kubelet|etcd|proxy|scheduler|controllerechoecho 2. 各节点 kubelet 客户端证书 fornodein$(kubectl get nodes-oname);doNODE$(echo$node|cut-d/-f2)# 通过 ssh 检查(需要 ssh 免密)EXPIRY$(ssh$NODEopenssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -enddate 2/dev/null | cut -d -f2)DAYS_LEFT$((($(date-d $EXPIRY%s)-$(date%s))/86400))if[$DAYS_LEFT-lt$THRESHOLD];thenecho[WARNING]$NODEkubelet client cert expires in$DAYS_LEFTdaysALERT1elseecho[OK]$NODEkubelet client cert:$DAYS_LEFTdays leftfidoneechoecho 3. cert-manager 证书 kubectl get certificate-A-ocustom-columnsNS:.metadata.namespace,NAME:.metadata.metadata.name,READY:.status.conditions[0].type,EXPIRY:.status.notAfter2/dev/nullechoecho 4. etcd 证书 formasterinmaster1 master2 master3;dossh$masteropenssl x509 -in /etc/kubernetes/pki/etcd/server.crt -noout -enddate2/dev/nulldoneif[$ALERT-eq1];thenechoecho[CRITICAL] 有证书即将过期,请立即处理!exit1fi# 定时巡检(每天)# crontab -e# 0 9 * * * /opt/check-cert-expiry.sh 30 | mail -s K8s cert expiry report opsexample.comPrometheus 告警(cert-manager 自带 metrics):# cert-manager 证书过期告警-alert:CertManagerCertExpiringSoonexpr:certmanager_certificate_expiration_timestamp_seconds (time() 86400 * 30)for:1hlabels:severity:warningannotations:summary:证书 {{ $labels.name }} 将在 30 天内过期-alert:CertManagerCertFailedexpr:certmanager_certificate_ready_status{conditionFalse} 1for:10mlabels:severity:criticalannotations:summary:证书签发失败: {{ $labels.name }}三、踩坑与排查坑1:cert-manager 证书一直 NotReady现象:Certificate 状态ReadyFalse,日志failed to solve challenge。原因:HTTP01 挑战要求 Ingress 有公网 IP,且 80 端口可达;内部集群没公网入口,挑战失败。解决:内网用内部 CA(ClusterIssuer);只有公网服务用 Let’s Encrypt。或者改用 DNS01。# 查看 Challenge 详情kubectl get challenge-ninternal kubectl describe challengename-ninternal# 看是 404、超时还是 DNS 没生效坑2:kubelet 证书过期,节点 NotReady现象:节点突然 NotReady,日志certificate has expired or not yet valid。原因:没开rotateCertificates,1 年到期没续。解决(应急):# 1. ssh 到节点# 2. 用 kubeadm 续期kubeadm certs renew kubelet.conf# 3. 重启 kubeletsystemctl restart kubelet# 4. 确认节点 Readykubectl getnodenode# 长期:开启 rotateCertificatestrue,见 2.4坑3:Let’s Encrypt 触发速率限制现象:签发证书失败,日志too many certificates already issued。原因:Let’s Encrypt 对每个域名每周限 50 张证书,测试环境反复签发会触发。解决:测试用--serverhttps://acme-staging-v02.api.letsencrypt.org/directory(staging 不限)合并用通配符证书(*.example.com)替代多个单域名证书善用 Certificate 的 renew,别反复删了重建坑4:CA 证书过期,全集群瘫痪现象:CA 过期(默认 10 年),所有证书失效,集群全挂。原因:平时只看叶证书,忘了 CA 也有期限。解决:提前重建 CA(复杂,需滚动替换):# 1. 生成新 CAopenssl genrsa-outnew-ca.key4096openssl req-x509-new-keynew-ca.key-days3650-outnew-ca.crt-subj/CNkubernetes# 2. 用 kubeadm 重建集群证书(会自动用新 CA)# 3. 滚动重启所有组件# 预防:监控 CA 过期,提前 1 年规划四、最佳实践kubelet 开启rotateCertificatestrue和serverTLSBootstraptrue部署 kubelet-csr-approver 自动审批 kubelet CSRcert-manager 3 副本 PDB公网证书用 Let’s Encrypt DNS01(支持通配符)内部服务用自建 CA(ClusterIssuer ca 类型)测试环境用 ACME staging endpoint,避免速率限制Certificate 设置renewBefore提前续(默认有效期 1/3)每日巡检脚本 Prometheus 证书过期告警(30 天/7 天分级)定期备份 CA 私钥(异地存储)CA 证书独立监控(10 年到期提前 1 年规划)证书 Secret 用 RBAC 限制访问,防泄露五、小结证书管理是运维里平时不起眼、出事就炸天的环节。核心就两条主线:一是 K8s 内部证书(kubeadm 管理),靠 kubelet 自动轮转兜底;二是业务证书(Ingress/内部服务),靠 cert-manager 自动化。把自动化和监控做扎实,证书过期这种低级错误就再也不会发生。记住:能自动续的绝不手动,能监控的绝不靠人记——运维的最高境界是忘了它的存在。思考题kubelet 的serverTLSBootstrap开了之后,服务端证书由谁签发?不开会怎样?Let’s Encrypt 的 staging 和 production 环境证书,浏览器信任度有什么区别?内部 mTLS 场景,CA 私钥存在哪里最安全?如果泄露怎么补救?延伸阅读cert-manager 官方文档:https://cert-manager.io/docs/K8s 证书管理:https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/kubelet 证书轮转:https://kubernetes.io/docs/reference/access-authn-authz/kubelet-tls-bootstrapping/
【K8S 运维实战】20-证书管理cert-manager
证书管理:cert-manager 与自动轮转一句话定位:证书过期是集群的定时炸弹——这篇把 K8s 证书体系、cert-manager 自动轮转和过期巡检一次性配齐。写在前面我经历过一次惨剧:凌晨 3 点被告警叫醒,整个集群 apiserver 拒绝连接,kubelet 全报certificate has expired or not yet valid。原因很low——kubelet 客户端证书过期了,而自动轮转没开。那一夜我手动给 200 个节点续证书续到天亮。从那以后,我对证书管理的态度就一句话:能自动化的绝不手动,能监控的绝不靠人记。这篇把证书这套体系讲透,让你的集群不再栽在过期上。核心问题证书过期导致集群挂掉怎么办?apiserver/kubelet/etcd 证书一过期,控制面直接瘫痪,kubelet 无法注册。怎么避免手动续证书?kubelet 自带客户端证书轮转;cert-manager 管理 Ingress/内部服务证书。Let’s Encrypt 怎么用?cert-manager ACME HTTP01/DNS01 挑战,自动签发公网证书。一、原理剖析1.1 K8s 证书体系全景K8s 集群里证书多到让人头大,先理清楚谁跟谁通信:┌────────────────────────────────────────────────────────┐ │ K8s 证书体系 │ │ │ │ ┌──────────┐ server cert ┌──────────┐ │ │ │ kubelet │ ◄───────────────► │ apiserver│ │ │ │ │ client cert │ │ │ │ └──────────┘ └────┬─────┘ │ │ │ │ │ ┌──────────┐ client cert │ │ │ │ kubectl │ ────────────────────► │ │ │ │ (用户) │ │ │ │ └──────────┘ │ │ │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ apiserver │◄───┘ │ │ │ └─ apiserver-kubelet-client │ │ │ │ └─ apiserver-etcd-client │ │ │ └──────────────────────────────┘ │ │ │ │ ┌──────────┐ peer cert ┌──────────┐ │ │ │ etcd │ ◄─────────────► │ etcd │ │ │ └──────────┘ └──────────┘ │ └────────────────────────────────────────────────────────┘证书分类与过期影响:证书路径默认有效期过期影响apiserver serverapiserver.crt1 年(kubeadm)apiserver 不可访问apiserver-kubelet-clientapiserver-kubelet-client.crt1 年apiserver 无法连 kubeletapiserver-etcd-clientapiserver-etcd-client.crt1 年apiserver 无法读写 etcdkubelet serverkubelet.crt1 年kubelet 10250 端口不可访问kubelet client(轮转)kubelet-client-current.pem默认 1 年,轮转后自动续kubelet 无法注册到 apiserveretcd server/peerserver.crt/peer.crt1 年(kubeadm)etcd 集群通信失败aggregation CAfront-proxy-ca.crt10 年API 聚合失效最容易爆雷的是kubelet 客户端证书,因为它直接决定节点能不能留在集群里。1.2 kubelet 证书轮转机制kubelet 从 1.19 起支持客户端证书自动轮转:kube-controller-managerapiserverkubeletkube-controller-managerapiserverkubelet监听证书剩余有效期 30%启动,加载现有 client cert用现有证书注册发起 CSR(certificate signing request)CSR 进入 kubernetes.io/kube-apiserver-client-kubelet 签名器自动审批(若开启 RotateKubeletClientCertificate)签发新证书返回新证书写入 kubelet-client-current.pem,热加载开启轮转的关键配置(kubelet config):# /var/lib/kubelet/config.yamlrotateCertificates:true# 客户端证书自动轮转serverTLSBootstrap:true# 服务端证书 bootstrap(可选)serverTLSBootstrap开启后,kubelet 服务端证书也走 CSR,由 controller-manager 签发(默认还是自签)。生产建议开启,统一证书管理。1.3 cert-manager 架构cert-manager 是管理 Ingress/Service 证书的主力,基于 CRD:签发生成挂载申请挑战成功后Issuer/ClusterIssuerCertificateSecret TLSIngress/PodOrder ACMEChallenge HTTP01/DNS01CA:Lets Encrypt/内部核心 CRD:Issuer:命名空间级签发器,只能签同命名空间证书ClusterIssuer:集群级签发器,可签任意命名空间证书Certificate:声明想要的证书(域名、有效期、目标 Secret)Order/Challenge:ACME 流程的中间产物,一般不用管二、实战操作2.1 cert-manager Helm 生产部署# 添加 Jetstack 仓库helm repoaddjetstack https://charts.jetstack.io helm repo update# 安装 cert-manager(1.30 对应 cert-manager v1.15)helminstallcert-manager jetstack/cert-manager\--namespacecert-manager\--create-namespace\--versionv1.15.3\--setinstallCRDstrue\--setreplicaCount3\--setpodDisruptionBudget.minAvailable2\--setfeatureGatesAdditionalCertificateOutputFormatstrue\--setconfig.apiVersioncontroller.config.cert-manager.io/v1alpha1\--setconfig.kindControllerConfiguration\--setconfig.enableCertificateOwnerReftrue# 验证kubectl get pods-ncert-manager# 期望: cert-manager / webhook / cainjector 三个 Pod Running生产配置要点:replicaCount3:cert-manager 高可用podDisruptionBudget:保证升级时至少 2 个可用enableCertificateOwnerRef:删除 Certificate 时自动清理 Secret2.2 内部 CA 与自签证书内部服务互调用(如 mTLS),用自建 CA 签发证书,不依赖外部。Step 1:创建 CA 私钥和证书# 生成 CA 私钥openssl genrsa-outca.key4096# 生成 CA 证书(10 年)openssl req-x509-new-keyca.key-sha256-days3650\-outca.crt\-subj/CNinternal-ca/OMyOrgStep 2:创建 CA Secret(给 cert-manager 用)kubectl create namespace internal kubectl create secret tls internal-ca-secret\--certca.crt--keyca.key\-ninternalStep 3:创建 ClusterIssuer# cluster-issuer-internal.yamlapiVersion:cert-manager.io/v1kind:ClusterIssuermetadata:name:internal-ca-issuerspec:ca:secretName:internal-ca-secret# CA Secret 必须在 cert-manager 所在命名空间---# 或者用 SelfSigned(测试用,不适合生产)apiVersion:cert-manager.io/v1kind:ClusterIssuermetadata:name:selfsigned-issuerspec:selfSigned:{}kubectl apply-fcluster-issuer-internal.yaml kubectl get clusterissuer internal-ca-issuer-owideStep 4:申请内部证书# certificate-internal.yamlapiVersion:cert-manager.io/v1kind:Certificatemetadata:name:backend-tlsnamespace:internalspec:secretName:backend-tls-secretduration:2160h# 90 天renewBefore:360h# 提前 15 天续issuerRef:name:internal-ca-issuerkind:ClusterIssuercommonName:backend.internal.svcdnsNames:-backend-backend.internal-backend.internal.svc.cluster.localusages:-server auth-client auth# 生成 JKS/PEM 等多种格式additionalOutputFormats:-type:JKSprivateKey:algorithm:RSAencoding:PKCS8size:2048kubectl apply-fcertificate-internal.yaml kubectl get certificate-ninternal# READYTrue 表示签发成功kubectl get secret backend-tls-secret-ninternal-oyaml2.3 Let’s Encrypt ACME(HTTP01/DNS01)公网域名证书用 Let’s Encrypt,免费、自动续。两种挑战:挑战方式原理适用场景配置难度HTTP01在 Ingress 暴露 .well-known 验证文件有公网 IP 的 Ingress简单DNS01在 DNS 加 TXT 记录验证通配符证书/无公网 Ingress需 DNS API 凭证HTTP01 ClusterIssuer:# cluster-issuer-letsencrypt-http01.yamlapiVersion:cert-manager.io/v1kind:ClusterIssuermetadata:name:letsencrypt-prodspec:acme:server:https://acme-v02.api.letsencrypt.org/directoryemail:opsexample.comprivateKeySecretRef:name:letsencrypt-prod-account-keysolvers:-http01:ingress:class:nginx# 用指定 Ingress class 处理挑战DNS01 ClusterIssuer(以阿里云 DNS 为例):# cluster-issuer-letsencrypt-dns01.yamlapiVersion:cert-manager.io/v1kind:ClusterIssuermetadata:name:letsencrypt-dnsspec:acme:server:https://acme-v02.api.letsencrypt.org/directoryemail:opsexample.comprivateKeySecretRef:name:letsencrypt-dns-account-keysolvers:-dns01:webhook:groupName:acme.example.comsolverName:alidnsconfig:accessKeyIdRef:name:alidns-secretkey:access-key-idaccessKeySecretRef:name:alidns-secretkey:access-key-secretregion:cn-hangzhou# 阿里云 DNS 凭证kubectl create secret generic alidns-secret\--from-literalaccess-key-idLTAIxxx\--from-literalaccess-key-secretxxx\-ncert-manager申请通配符证书:# certificate-wildcard.yamlapiVersion:cert-manager.io/v1kind:Certificatemetadata:name:wildcard-tlsnamespace:nginx-ingressspec:secretName:wildcard-example-tlsissuerRef:name:letsencrypt-dnskind:ClusterIssuercommonName:*.example.comdnsNames:-*.example.com-example.comIngress 自动签发:# ingress-with-tls.yamlapiVersion:networking.k8s.io/v1kind:Ingressmetadata:name:app-ingressnamespace:internalannotations:cert-manager.io/cluster-issuer:letsencrypt-prod# 指定 issuer 即可自动签发spec:tls:-hosts:-app.example.comsecretName:app-tls-secretrules:-host:app.example.comhttp:paths:-path:/pathType:Prefixbackend:service:name:app-svcport:number:80cert-manager 看到 Ingress 有tls和 issuer 注解,自动签发证书到app-tls-secret。2.4 kubelet 证书轮转配置# 1. 检查 kubelet 配置cat/var/lib/kubelet/config.yaml|greprotate# 期望:# rotateCertificates: true# serverTLSBootstrap: true# 2. 如果没开,修改 kubelet configkubectl-nkube-system edit configmap kubelet-config# 加上:# rotateCertificates: true# serverTLSBootstrap: true# 3. 让所有节点应用新配置# ssh 到节点systemctl restart kubelet# 4. 开启 controller-manager 自动审批(生产谨慎)# 默认 kubelet client CSR 需要手动 kubectl certificate approve# 自动审批需配置 CSR approver(用 controller-manager 的签发器配置)# 或者用 auto approving controller(如 kubelet-csr-approver)自动审批方案(推荐kubelet-csr-approver):# 部署 kubelet-csr-approverhelm repoaddpostfinance https://postfinance.github.io/kubelet-csr-approver helminstallkubelet-csr-approver postfinance/kubelet-csr-approver\-nkube-system\--setproviderRegex^ip-10-0-0-.*\\.ec2\\.internal$\--setbypassDnsResolutiontrue2.5 证书过期巡检脚本#!/bin/bash# check-cert-expiry.sh - 巡检所有证书过期时间# 使用: ./check-cert-expiry.sh [days]THRESHOLD${1:-30}ALERT0echo 1. 控制面证书(kubeadm 管理) kubeadm certs check-expiration2/dev/null|grep-ECA|apiserver|kubelet|etcd|proxy|scheduler|controllerechoecho 2. 各节点 kubelet 客户端证书 fornodein$(kubectl get nodes-oname);doNODE$(echo$node|cut-d/-f2)# 通过 ssh 检查(需要 ssh 免密)EXPIRY$(ssh$NODEopenssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -enddate 2/dev/null | cut -d -f2)DAYS_LEFT$((($(date-d $EXPIRY%s)-$(date%s))/86400))if[$DAYS_LEFT-lt$THRESHOLD];thenecho[WARNING]$NODEkubelet client cert expires in$DAYS_LEFTdaysALERT1elseecho[OK]$NODEkubelet client cert:$DAYS_LEFTdays leftfidoneechoecho 3. cert-manager 证书 kubectl get certificate-A-ocustom-columnsNS:.metadata.namespace,NAME:.metadata.metadata.name,READY:.status.conditions[0].type,EXPIRY:.status.notAfter2/dev/nullechoecho 4. etcd 证书 formasterinmaster1 master2 master3;dossh$masteropenssl x509 -in /etc/kubernetes/pki/etcd/server.crt -noout -enddate2/dev/nulldoneif[$ALERT-eq1];thenechoecho[CRITICAL] 有证书即将过期,请立即处理!exit1fi# 定时巡检(每天)# crontab -e# 0 9 * * * /opt/check-cert-expiry.sh 30 | mail -s K8s cert expiry report opsexample.comPrometheus 告警(cert-manager 自带 metrics):# cert-manager 证书过期告警-alert:CertManagerCertExpiringSoonexpr:certmanager_certificate_expiration_timestamp_seconds (time() 86400 * 30)for:1hlabels:severity:warningannotations:summary:证书 {{ $labels.name }} 将在 30 天内过期-alert:CertManagerCertFailedexpr:certmanager_certificate_ready_status{conditionFalse} 1for:10mlabels:severity:criticalannotations:summary:证书签发失败: {{ $labels.name }}三、踩坑与排查坑1:cert-manager 证书一直 NotReady现象:Certificate 状态ReadyFalse,日志failed to solve challenge。原因:HTTP01 挑战要求 Ingress 有公网 IP,且 80 端口可达;内部集群没公网入口,挑战失败。解决:内网用内部 CA(ClusterIssuer);只有公网服务用 Let’s Encrypt。或者改用 DNS01。# 查看 Challenge 详情kubectl get challenge-ninternal kubectl describe challengename-ninternal# 看是 404、超时还是 DNS 没生效坑2:kubelet 证书过期,节点 NotReady现象:节点突然 NotReady,日志certificate has expired or not yet valid。原因:没开rotateCertificates,1 年到期没续。解决(应急):# 1. ssh 到节点# 2. 用 kubeadm 续期kubeadm certs renew kubelet.conf# 3. 重启 kubeletsystemctl restart kubelet# 4. 确认节点 Readykubectl getnodenode# 长期:开启 rotateCertificatestrue,见 2.4坑3:Let’s Encrypt 触发速率限制现象:签发证书失败,日志too many certificates already issued。原因:Let’s Encrypt 对每个域名每周限 50 张证书,测试环境反复签发会触发。解决:测试用--serverhttps://acme-staging-v02.api.letsencrypt.org/directory(staging 不限)合并用通配符证书(*.example.com)替代多个单域名证书善用 Certificate 的 renew,别反复删了重建坑4:CA 证书过期,全集群瘫痪现象:CA 过期(默认 10 年),所有证书失效,集群全挂。原因:平时只看叶证书,忘了 CA 也有期限。解决:提前重建 CA(复杂,需滚动替换):# 1. 生成新 CAopenssl genrsa-outnew-ca.key4096openssl req-x509-new-keynew-ca.key-days3650-outnew-ca.crt-subj/CNkubernetes# 2. 用 kubeadm 重建集群证书(会自动用新 CA)# 3. 滚动重启所有组件# 预防:监控 CA 过期,提前 1 年规划四、最佳实践kubelet 开启rotateCertificatestrue和serverTLSBootstraptrue部署 kubelet-csr-approver 自动审批 kubelet CSRcert-manager 3 副本 PDB公网证书用 Let’s Encrypt DNS01(支持通配符)内部服务用自建 CA(ClusterIssuer ca 类型)测试环境用 ACME staging endpoint,避免速率限制Certificate 设置renewBefore提前续(默认有效期 1/3)每日巡检脚本 Prometheus 证书过期告警(30 天/7 天分级)定期备份 CA 私钥(异地存储)CA 证书独立监控(10 年到期提前 1 年规划)证书 Secret 用 RBAC 限制访问,防泄露五、小结证书管理是运维里平时不起眼、出事就炸天的环节。核心就两条主线:一是 K8s 内部证书(kubeadm 管理),靠 kubelet 自动轮转兜底;二是业务证书(Ingress/内部服务),靠 cert-manager 自动化。把自动化和监控做扎实,证书过期这种低级错误就再也不会发生。记住:能自动续的绝不手动,能监控的绝不靠人记——运维的最高境界是忘了它的存在。思考题kubelet 的serverTLSBootstrap开了之后,服务端证书由谁签发?不开会怎样?Let’s Encrypt 的 staging 和 production 环境证书,浏览器信任度有什么区别?内部 mTLS 场景,CA 私钥存在哪里最安全?如果泄露怎么补救?延伸阅读cert-manager 官方文档:https://cert-manager.io/docs/K8s 证书管理:https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/kubelet 证书轮转:https://kubernetes.io/docs/reference/access-authn-authz/kubelet-tls-bootstrapping/