如何使用cert-manager、Let's Encrypt和内部TLS加密Kubernetes流量
TL;DR · AI 摘要
本文详细指导如何通过cert-manager、Let's Encrypt和内部TLS加密Kubernetes流量,涵盖证书管理、Ingress TLS配置及服务间加密实践。
核心要点
- cert-manager通过Issuer/ClusterIssuer与Certificate资源实现证书自动签发和轮换,避免因证书过期导致的停机
- 使用ACME Issuer配置Let's Encrypt需完成DNS验证,可自动化Ingress TLS配置并支持HTTP/HTTPS双协议
- 内部服务加密需自建私有CA,通过ClusterIssuer实现跨命名空间证书管理,Pod间通信需配合mTLS或服务网格
结构提纲
按章节快速跳转。
明确Kubernetes默认加密范围,指出Pod间通信和Ingress流量未加密的典型安全漏洞
介绍cert-manager作为Kubernetes Operator的工作原理,通过自定义资源实现证书全生命周期管理
对比命名空间级与集群级证书签发器的适用场景,强调ACME与私有CA的配置差异
分步演示Let's Encrypt Ingress TLS配置和内部服务加密方案的具体实施方法
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- Kubernetes流量加密方案
- 证书管理
- cert-manager
- Issuer/ClusterIssuer
- 自动轮换
- 加密场景
- Ingress TLS
- 服务间通信
- 私有CA
- 配置组件
- ACME协议
- Kubernetes Secret
- DNS验证
金句 / Highlights
值得收藏与分享的关键句。
Pod间通信默认为明文,需通过mTLS或服务网格加密
cert-manager自动处理证书轮换,确保Secret始终包含有效证书
ACME Issuer通过HTTP或DNS验证域名,Let's Encrypt要求证书有效期至少10天
Most engineers assume their Kubernetes cluster encrypts all of its traffic. It doesn't. The commands you run with kubectl are encrypted — your client and the API server speak TLS. The API server talking to etcd is usually encrypted too, depending on how the cluster was provisioned.
But traffic between your pods? Plaintext by default. Ingress traffic from the internet to your services? Only encrypted if you explicitly configure TLS. And certificates for internal services? You have to provision those yourself.
This is not a Kubernetes oversight. It's a deliberate design choice — Kubernetes provides the primitives and leaves the implementation to you. The problem is that certificate management is notoriously painful. Certificates expire. Provisioning them manually doesn't scale. Forgetting to rotate them causes outages.
cert-manager solves this. It runs as a controller inside your cluster, watches for Certificate resources, requests certificates from configured issuers, stores them in Kubernetes Secrets, and rotates them automatically before they expire. You declare what you want, cert-manager makes it happen and keeps it that way.
In this article you'll work through how cert-manager's core model works, automate public Ingress TLS using Let's Encrypt, set up an internal Certificate Authority for service-to-service encryption, and understand how certificate rotation works so outages caused by expired certificates become a thing of the past.
Prerequisites
- A kind cluster with the nginx Ingress controller installed
- Helm 3 installed
- A domain name with DNS you control — needed for the Let's Encrypt demo
- Basic understanding of TLS: you know what a certificate, a private key, and a CA are
All demo files are in the DevOps-Cloud-Projects GitHub repository.
Table of Contents
Kubernetes 中哪些流量是加密的,哪些不是?
在安装任何工具之前,先明确集群已有的保护机制和存在的安全缺口:
| 流量路径 | 默认加密? | 说明 | | --- | --- | --- | | kubectl → API 服务器 | 是 | 通过集群 CA 进行 TLS 加密 | | API 服务器 → etcd | 通常加密 | 取决于集群创建方式 — 需验证具体配置 | | API 服务器 → kubelet | 是 | TLS 加密,但 kubelet 证书验证依赖配置 | | Pod → Pod(同集群) | 否 | 默认明文传输,需添加服务网格或 mTLS | | 互联网 → Ingress | 否 | 需主动配置 — 在 Ingress 资源中设置 TLS | | Pod → Kubernetes API | 是 | 通过服务账户令牌和集群 CA 验证 |
实践中最需要关注的两个缺口是 Pod 到 Pod 的流量和 Ingress TLS 加密。本文将覆盖通过 Let's Encrypt 实现 Ingress TLS 加密,以及使用私有 CA 实现服务间加密。
cert-manager 是一个 Kubernetes Operator。它通过自定义资源扩展 Kubernetes API,表示证书请求及其配置。当你创建 Certificate 资源时,cert-manager 控制器会自动处理:向配置的签发方请求证书,将证书和私钥存储到 Kubernetes Secret,并在到期前自动续期。应用程序只需读取 Secret,无需关心证书管理细节。
四大核心资源
cert-manager 引入了四个常用自定义资源:
| 资源 | 代表内容 | | --- | --- | | Issuer | 证书颁发机构或 ACME 账户 — 命名空间作用域 | | ClusterIssuer | 与 Issuer 相同,但集群范围内可用 | | Certificate | 证书请求声明 — 定义所需证书规格 | | CertificateRequest | 单个签名请求 — 由 cert-manager 自动生成,极少直接操作 |
实际使用中主要配置 ClusterIssuer 和 Certificate。ClusterIssuer 定义证书来源,Certificate 定义所需证书规格及存储位置。
Issuer 和 ClusterIssuer
Issuer 仅能在所属命名空间内签发证书,而 ClusterIssuer 可跨命名空间使用。对于 Let's Encrypt 等共享基础设施,建议使用 ClusterIssuer;对于特定应用的内部 CA,则应使用限定在应用命名空间的 Issuer。
cert-manager 支持多种签发方类型,最常见的三种是:
ACME — 用于从 Let's Encrypt 或兼容 ACME 协议的 CA 获取公共证书。通过 HTTP-01 或 DNS-01 挑战验证域名所有权。
CA — 使用存储在 Kubernetes Secret 中私钥的 CA 签发内部证书。用于集群内的服务间 TLS 加密。
Self-signed — 生成自签名证书。单独使用场景较少,但作为创建内部 CA 的初始步骤不可或缺。
证书生命周期
创建 Certificate 资源后,cert-manager 会按以下流程处理:
- 创建包含 CSR(证书签名请求)的
CertificateRequest
- 将 CSR 提交给配置的签发方
- 对于 ACME 签发方:创建
Challenge资源并完成验证(后文详述)
- 从签发方获取已签名证书
- 将证书和私钥存储到
spec.secretName指定的 Kubernetes Secret
- 监控证书有效期 — 默认在有效期限的 2/3 时触发续期
您的应用程序挂载 Secret。cert-manager 会静默更新它。大多数能够监听文件变化的应用无需重启即可获取新证书。
ACME 挑战:HTTP-01 vs DNS-01
Let's Encrypt 需要验证您对域名的控制权才能签发证书。ACME 协议为此定义了两种挑战类型。
HTTP-01 通过让 cert-manager 在 http://<your-domain>/.well-known/acme-challenge/<token> 创建临时 HTTP 端点实现。Let's Encrypt 会请求该 URL,若响应与预期 token 匹配则挑战通过。这需要集群在 80 端口可从互联网访问。
DNS-01 通过让 cert-manager 在 _acme-challenge.<your-domain> 创建临时 DNS TXT 记录实现。Let's Encrypt 会验证该记录是否存在。此方法无需入向 HTTP 访问,适合私有集群,并且是获取通配符证书(如 *.example.com)的唯一方式。
权衡:HTTP-01 配置简单但仅适用于单域名且需要可公开访问的基础设施;DNS-01 需要 DNS 提供商的 API 访问权限,但支持内部集群和通配符证书。
演示 1 — 安装 cert-manager 并使用 Pebble 和 Let's Encrypt 签发证书
Pebble 是 Let's Encrypt 的本地 ACME 测试服务器。它运行在集群内部,使用与 Let's Encrypt 完全相同的 ACME 协议签发证书,且无需公开域名或互联网访问。通过 Pebble 可在普通 kind 集群上完整测试 cert-manager 的流程(挑战、签发、续期)。
理解本地流程后,切换到真实 Let's Encrypt 仅需一行配置修改:替换 ClusterIssuer 的服务器 URL,并将 DNS 记录指向可公开访问的集群。其余配置保持一致。
您将安装 cert-manager、创建 Let's Encrypt 的 ClusterIssuer、部署带有 Ingress 的示例应用,并观察证书自动签发和存储过程。
步骤 1:安装 cert-manager
cert-manager 现通过 OCI Helm 图表从 quay.io/jetstack 发布。--set crds.enabled=true 标志会随图表自动安装 CRD:
helm upgrade cert-manager oci://quay.io/jetstack/charts/cert-manager \
--install \
--create-namespace \
--namespace cert-manager \
--set crds.enabled=true \
--version v1.17.0 \
--wait还需安装 nginx Ingress 控制器(cert-manager 通过它路由 HTTP-01 挑战)。controller.service.type=ClusterIP 是针对 kind 的特殊配置:默认的 LoadBalancer Service 在 kind 中无法获取 EXTERNAL-IP(因为没有云负载均衡器),会导致 --wait 挂起。真实集群中可移除此覆盖并保留 LoadBalancer:
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 \
--set controller.service.type=ClusterIP \
--wait确认所有组件运行正常:
kubectl get pods -n cert-manager
kubectl get pods -n ingress-nginxNAME READY STATUS RESTARTS AGE
cert-manager-76f84784c8-r4fx4 1/1 Running 0 6m45s
cert-manager-cainjector-66fbf49587-gv25n 1/1 Running 0 6m45s
cert-manager-webhook-577fddf86-l5wj4 1/1 Running 0 6m45s
NAME READY STATUS RESTARTS AGE
ingress-nginx-controller-6c7cd85885-h7zgx 1/1 Running 0 3m34skind 特定注意事项 — 立即删除 nginx admission webhook。**在 kind 中,nginx admission webhook 使用自签名证书,而 Kubernetes API 服务器无法验证。首次创建 Ingress 资源时会报错
failed calling webhook "validate.nginx.ingress.kubernetes.io": ... x509: certificate signed by unknown authority。提前删除 webhook 可避免后续问题:
kubectl delete validatingwebhookconfiguration ingress-nginx-admission步骤 2:安装 Pebble
Pebble 是由 JupyterHub 维护的本地 ACME 测试服务器,配套部署的 pebble-coredns 用于 ACME 验证期间的域名解析:
helm install pebble pebble \
--repo https://jupyterhub.github.io/helm-chart/ \
--namespace pebble \
--create-namespace \
--wait确认两个 Pod 运行正常:
kubectl get pods -n pebbleNAME READY STATUS RESTARTS AGE
pebble-8d8d49d64-lz8ck 1/1 Running 0 36s
pebble-coredns-7fb5c7cbf4-4jw9h 1/1 Running 0 36s步骤 3:配置虚假域名的 DNS
我们将为 echo.pebble.local 签发证书。该域名不存在于真实 DNS 中,因此需提前配置两个独立的 DNS 解析器:
| 解析器 | 由...使用 | 需要实现的功能 | | --- | --- | --- | | pebble-coredns(pebble 命名空间) | Pebble 本身,在发起 HTTP-01 验证请求时 | 将 echo.pebble.local 解析到 ingress-nginx ClusterIP | | 集群 CoreDNS(kube-system) | cert-manager 的 HTTP-01 预检 | 将 pebble.local 查询转发到 pebble-coredns |
若遗漏任一配置,订单(Order)会因 DNS 解析失败进入 invalid 状态。
首先获取所需 IP 地址:
NGINX_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \
-o jsonpath='{.spec.clusterIP}')
PEBBLE_DNS_IP=$(kubectl get svc pebble-coredns -n pebble \
-o jsonpath='{.spec.clusterIP}')
echo "NGINX_IP=$NGINX_IP PEBBLE_DNS_IP=$PEBBLE_DNS_IP"修补pebble-coredns以使用入口控制器的IP响应*.pebble.local。CoreDNS的template插件在整块配置压缩为单行时解析不可靠,因此需要应用真正的多行ConfigMap:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: pebble-coredns
namespace: pebble
data:
Corefile: |
.:8053 {
errors
health
ready
template ANY ANY pebble.local {
answer "{{ .Name }} 60 IN A ${NGINX_IP}"
}
forward . /etc/resolv.conf
cache 2
reload
}
EOF
kubectl rollout restart deploy/pebble-coredns -n pebble
kubectl rollout status deploy/pebble-coredns -n pebble验证响应是否正确:
kubectl run dnstest --rm -it --restart=Never --image=busybox -- \
nslookup echo.pebble.local ${PEBBLE_DNS_IP}响应中应显示Address: <NGINX_IP>。如果出现SERVFAIL,检查kubectl logs -n pebble deploy/pebble-coredns——类似not a TTL: "}"的解析错误表示模板块又被压缩为单行。
修补集群CoreDNS以便cert-manager的自检能解析相同名称。添加将pebble.local转发到pebble-coredns的存根区域:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
pebble.local:53 {
forward . ${PEBBLE_DNS_IP}
}
EOF
kubectl rollout restart deploy/coredns -n kube-system
kubectl rollout status deploy/coredns -n kube-system验证集群解析器现在能否响应echo.pebble.local(无需指定服务器——将使用默认的kube-dns):
kubectl run dnstest --rm -it --restart=Never --image=busybox -- \
nslookup echo.pebble.local响应中应同时出现Server: 10.96.0.10和Address: <NGINX_IP>。
第4步:获取Pebble CA并创建ClusterIssuer
Pebble使用存储在pebble ConfigMap的root-cert.pem中的自签名根证书签发证书。cert-manager需要信任该CA才能与Pebble的ACME目录通信,因此需将CA以base64编码的caBundle形式传递到ClusterIssuer:
kubectl get configmap pebble -n pebble \
-o jsonpath='{.data.root-cert\.pem}' > pebble-ca.crt
head -1 pebble-ca.crt # 应显示-----BEGIN CERTIFICATE-----
CA_BUNDLE=$(base64 -i pebble-ca.crt | tr -d '\n')
echo "CA_BUNDLE长度: ${#CA_BUNDLE}" # 约1600字符,连续一行通过heredoc创建ClusterIssuer——${CA_BUNDLE}环境变量会在kubectl读取前被替换到YAML中:
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: pebble
spec:
acme:
server: https://pebble.pebble.svc.cluster.local/dir
email: test@example.com
privateKeySecretRef:
name: pebble-account-key
caBundle: ${CA_BUNDLE}
solvers:
- http01:
ingress:
ingressClassName: nginx
EOF检查签发器状态:
kubectl get clusterissuer pebbleNAME READY AGE
pebble True 5s如果READY保持False,最常见的两个原因是caBundle格式错误(确认是无换行的单行base64)或Pebble在cert-manager命名空间不可达。检查可达性:
kubectl run test-curl --rm -it --restart=Never \
--image=curlimages/curl:latest \
--namespace cert-manager -- \
curl -k https://pebble.pebble.svc.cluster.local/dir若返回JSON则说明Pebble可达。
第5步:部署示例应用
# echo-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: echo
template:
metadata:
labels:
app: echo
spec:
containers:
- name: echo
image: ealen/echo-server:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: echo
namespace: default
spec:
selector:
app: echo
ports:
- port: 80
targetPort: 80kubectl apply -f echo-app.yaml验证资源是否正常启动:
kubectl get deploy,pod,svc -n defaultNAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/echo 1/1 1 1 32s
NAME READY STATUS RESTARTS AGE
pod/echo-5665fbcfdd-mbgxj 1/1 Running 0 36s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/echo ClusterIP 10.96.103.114 <none> 80/TCP 40s
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 32m第6步:创建带TLS的Ingress
cert-manager.io/cluster-issuer: pebble注解会指示cert-manager自动为该Ingress创建Certificate资源,使用我们刚创建的签发器。主机名echo.pebble.local无需外部解析——我们在第3步已配置两个DNS解析器。
echo-ingress.yaml
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: echo namespace: default annotations: cert-manager.io/cluster-issuer: pebble spec: ingressClassName: nginx tls:
- hosts:
- echo.pebble.local
secretName: echo-tls # cert-manager 将创建此 Secret rules:
- host: echo.pebble.local
http: paths:
- path: /
pathType: Prefix backend: service: name: echo port: number: 80
kubectl apply -f echo-ingress.yaml 步骤7:监视证书的签发过程
# 监控证书资源(当 Ready=True 时按 Ctrl+C 退出)
kubectl get certificate echo-tls -n default -w NAME READY SECRET AGE
echo-tls False echo-tls 5s
echo-tls True echo-tls 28s 当 READY 变为 True 时,证书已签发并存储在 echo-tls Secret 中。在健康的集群中,整个流程(CertificateRequest → Order → Challenge → solver pod → Secret)可在一分钟内完成:
kubectl get certificate,certificaterequest,order,challenge -n default NAME READY SECRET AGE
certificate.cert-manager.io/echo-tls True echo-tls 81s
NAME APPROVED DENIED READY ISSUER AGE
certificaterequest.cert-manager.io/echo-tls-1 True True pebble 81s
NAME STATE AGE
order.acme.cert-manager.io/echo-tls-1-1824732543 valid 81s (Challenge 资源在 Order 完成后会被自动删除,因此此时 kubectl get challenge -n default 通常显示为空——这是成功而非失败的标志。)
如果 READY 超过一分钟仍为 False,请参考本节末尾的故障排查提示。
检查签发的证书以确认 Pebble 已签名:
kubectl get secret echo-tls -n default -o jsonpath='{.data.tls\.crt}' | \
base64 -d | openssl x509 -noout -issuer -subject -dates issuer=CN=Pebble Intermediate CA 05478c
subject=
notBefore=May 17 19:09:22 2026 GMT
notAfter=Aug 15 19:09:21 2026 GMT 颁发者是 Pebble 的中间 CA,证明 ACME 流程端到端正常工作。证书有效期为 90 天,cert-manager 会在第 60 天自动续期。
从集群内部通过 HTTPS 访问 Ingress 确认所有组件已正确连接:
kubectl run curltest --rm -it --restart=Never --image=curlimages/curl -- \
curl -sk https://echo.pebble.local/ 回显服务器应返回一个 JSON 数据块——注意其中的 "x-forwarded-proto":"https" 字段,这证明请求是通过 TLS 经过 nginx 转发的。
如果证书始终无法就绪的故障排查:
kubectl describe order -n default—— 检查事件中的 "DNS problem" 或 "Connection refused" 错误。kubectl logs -n pebble deploy/pebble --tail=50—— Pebble 日志会记录验证期间尝试访问的具体 URL 及错误信息。- 如果 Order 卡在 pending 状态且无事件:cert-manager 尚未完成协调。等待 30 秒。
- 如果 Order 状态为
invalid:步骤3中的 DNS 层配置有误。重新运行两次nslookup检查。 - 如果 Ingress 应用本身因 x509 webhook 错误失败:您跳过了步骤1中的
kubectl delete validatingwebhookconfiguration ingress-nginx-admission步骤。
步骤8:切换到 Let's Encrypt 测试环境(真实公网域名)
Pebble 验证了本地流程的可行性。现在切换到指向公网集群的真实可访问域名。此时无需步骤3的 DNS 操作——真实域名会被所有 DNS 解析器正常识别。
首先使用 Let's Encrypt 测试环境。它与生产环境使用相同的 ACME 协议,但速率限制更宽松,避免测试失败导致账号被封:
# clusterissuer-staging.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: your-email@example.com
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- http01:
ingress:
ingressClassName: nginx kubectl apply -f clusterissuer-staging.yaml
# 将 Ingress 指向测试环境和真实域名,强制重新签发证书
kubectl annotate ingress echo \
cert-manager.io/cluster-issuer=letsencrypt-staging --overwrite -n default
kubectl delete secret echo-tls -n default 新证书的颁发者会显示类似 (STAGING) Let's Encrypt 的标识。
步骤9:切换到 Let's Encrypt 生产环境
测试环境验证成功后,重复上述流程使用生产环境。唯一区别是服务器地址:
# clusterissuer-prod.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-email@example.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
ingressClassName: nginx kubectl apply -f clusterissuer-prod.yaml
kubectl annotate ingress echo \
cert-manager.io/cluster-issuer=letsencrypt-prod --overwrite -n default
kubectl delete secret echo-tls -n default cert-manager 会检测到缺失的 Secret 并立即向生产环境的 Let's Encrypt 请求受浏览器信任的证书。
cert-manager 会检测到缺失的 Secret 并立即触发使用生产颁发者的新证书请求。
如何通过 DNS-01 挑战获取通配符证书
DNS-01 挑战适用于集群无法公开访问(如内部集群、离线环境、通过 VPN 访问的 staging 命名空间)或需要通配符证书(覆盖所有子域名)的场景。
DNS-01 要求 cert-manager 能够在 DNS 提供商处创建和删除 TXT 记录。cert-manager 内置支持 Route53、Cloud DNS、Cloudflare、Azure DNS 等服务商。
以下是使用 AWS Route53 的 DNS-01 ClusterIssuer 示例:
# clusterissuer-dns01.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns01
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-email@example.com
privateKeySecretRef:
name: letsencrypt-dns01-account-key
solvers:
- dns01:
route53:
region: us-east-1
# 生产环境建议使用 IRSA(IAM Roles for Service Accounts)
# 而非静态凭证
hostedZoneID: YOUR_HOSTED_ZONE_ID使用该颁发机构的通配符 Certificate 配置:
# wildcard-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: default
spec:
secretName: wildcard-example-com-tls
issuerRef:
name: letsencrypt-dns01
kind: ClusterIssuer
commonName: "*.example.com"
dnsNames:
- "*.example.com"
- "example.com" # 同时覆盖根域名
duration: 2160h # 90天
renewBefore: 720h # 提前30天续签生成的 Secret wildcard-example-com-tls 可被 default 命名空间中的任意 Ingress 引用。所有子域名(如 api.example.com、dashboard.example.com、staging.example.com)都将通过自动轮换的同一证书受保护。
若使用 Cloudflare 而非 Route53,solver 配置如下:
solvers:
- dns01:
cloudflare:
email: your-email@example.com
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token示例 2 — 配置内部 CA 实现服务间 TLS
Let's Encrypt 证书适合公开服务,但内部服务(如 gRPC 微服务调用、Web 应用访问数据库)无需公共信任。我们需要集群信任的 CA,并为不存在公共 DNS 记录的服务名称签发证书。
cert-manager 的 CA 颁发机构功能可满足此需求。创建根 CA 后,通过该 CA 签发内部服务证书。所有信任根 CA 的服务都将信任其签发的所有证书。
步骤1:创建自签名 ClusterIssuer
自签名颁发机构生成的证书由证书本身签名,作为创建根 CA 的初始步骤:
# selfsigned-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned
spec:
selfSigned: {}kubectl apply -f selfsigned-issuer.yaml步骤2:创建根 CA 证书
使用自签名颁发机构生成 CA 证书,isCA: true 标识其可签发其他证书:
# internal-ca.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-ca
namespace: cert-manager # 存储在 cert-manager 命名空间
spec:
isCA: true
commonName: internal-ca
secretName: internal-ca-secret
duration: 87600h # 根 CA 有效期10年
renewBefore: 720h
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: selfsigned
kind: ClusterIssuerkubectl apply -f internal-ca.yaml
kubectl get certificate internal-ca -n cert-managerNAME READY SECRET AGE
internal-ca True internal-ca-secret 8s步骤3:创建基于根 CA 的 ClusterIssuer
创建使用刚生成的根 CA Secret 的 ClusterIssuer,用于签发内部服务证书:
# internal-ca-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: internal-ca
spec:
ca:
secretName: internal-ca-secret # 引用 cert-manager 命名空间中的 Secretkubectl apply -f internal-ca-issuer.yaml
kubectl get clusterissuer internal-caNAME READY AGE
internal-ca True 5s步骤4:为内部服务签发证书
为 gRPC 服务签发证书,使用 Kubernetes 内部 DNS 名称(如 <service>.<namespace>.svc.cluster.local):
# payments-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: payments-tls
namespace: production
spec:
secretName: payments-tls-secret
issuerRef:
name: internal-ca
kind: ClusterIssuer
commonName: payments.production.svc.cluster.local
dnsNames:
- payments.production.svc.cluster.local
- payments.production.svc
- payments
duration: 2160h # 90天
renewBefore: 360h # 提前15天续签kubectl create namespace production
kubectl apply -f payments-cert.yaml
kubectl get certificate payments-tls -n productionNAME READY SECRET AGE
payments-tls True payments-tls-secret 6sSecret payments-tls-secret 包含 tls.crt、tls.key 和 ca.crt。将其挂载到应用 Pod:
# 在 Deployment 配置中
volumes:
- name: tls
secret:
secretName: payments-tls-secret
containers:
- name: payments
volumeMounts:
- name: tls
mountPath: /etc/tls
readOnly: true您的应用程序通过读取 /etc/tls/tls.crt 和 /etc/tls/tls.key 配置 TLS。需要信任该应用的其他服务则读取 /etc/tls/ca.crt。
第 6 步:通过 trust-manager 分发 CA 证书包
使用自定义 CA 的问题是每个服务都需要知道它的存在。cert-manager 的配套工具 trust-manager 通过将 CA 证书包以 ConfigMap 形式分发到每个命名空间来解决这一问题:
helm upgrade trust-manager oci://quay.io/jetstack/charts/trust-manager \
--install \
--namespace cert-manager \
--wait创建一个 Bundle 资源,从 internal-ca-secret 中获取 CA 证书并集群范围内分发:
# ca-bundle.yaml
apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
name: internal-ca-bundle
spec:
sources:
- secret:
name: internal-ca-secret
key: ca.crt
target:
configMap:
key: ca-bundle.crt
namespaceSelector:
matchLabels:
# 分发到带有此标签的所有命名空间
kubernetes.io/metadata.name: productionkubectl apply -f ca-bundle.yaml几秒钟后,所有匹配的命名空间都会有一个名为 internal-ca-bundle 的 ConfigMap,其中包含 CA 证书。应用程序可通过挂载此 ConfigMap 自动信任内部签发的证书,无需逐个服务配置。
第 7 步:验证证书链
# 提取 CA 证书和服务证书
kubectl get secret payments-tls-secret -n production \
-o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt
kubectl get secret payments-tls-secret -n production \
-o jsonpath='{.data.tls\.crt}' | base64 -d > payments.crt
# 验证证书是否由 CA 签名
openssl verify -CAfile ca.crt payments.crtpayments.crt: OK证书轮换机制
证书轮换是证书管理中最容易导致生产集群故障的部分。cert-manager 虽然能自动处理,但理解其机制有助于调优和排查问题。
cert-manager 监控所有管理的 Certificate 资源,并检查 Secret 中证书的过期时间。当剩余有效期低于 renewBefore 阈值时,cert-manager 触发续签。默认 renewBefore 是证书总有效期的 1/3 —— 例如 90 天证书会在第 60 天开始续签。
续签会创建新的 CertificateRequest,完成完整的签发流程,并原地更新 Secret。新证书会原子性替换旧证书。使用文件挂载并监听变更的现代 Web 服务器和 gRPC 框架无需重启即可获取新证书。
# 查看当前轮换状态
kubectl describe certificate echo-tls -n default在输出中查找以下字段:
Status:
Not After: 2024-06-18T10:00:00Z
Not Before: 2024-03-20T10:00:00Z
Renewal Time: 2024-05-18T10:00:00Z # cert-manager 开始续签的时间
Conditions:
Type: Ready
Status: True
Message: Certificate is up to date and has not expired如果续签失败(例如 HTTP-01 挑战无法完成),cert-manager 会以指数退避策略重试。现有证书会持续生效直到实际过期,为问题调试留出时间。
实时查看续签事件:
kubectl get events -n default --field-selector reason=Issued
kubectl get events -n default --field-selector reason=Failed正确设置 `renewBefore`: 对于面向公网的服务,90 天证书设置 30 天缓冲期是合理的。对于短生命周期的内部证书(24 小时有效期),应设置 8 小时的 renewBefore,确保即使首次尝试失败也能在过期前完成轮换。切勿将 renewBefore 设为超过证书有效期的一半 —— cert-manager 会立即尝试轮换刚签发的证书。
清理
# 删除演示资源
kubectl delete ingress echo -n default
kubectl delete service echo -n default
kubectl delete deployment echo -n default
kubectl delete secret echo-tls -n default
kubectl delete certificate payments-tls -n production
kubectl delete namespace production
# 卸载 cert-manager 和 trust-manager
helm uninstall trust-manager -n cert-manager
helm uninstall cert-manager -n cert-manager
kubectl delete namespace cert-manager
# 删除 ClusterIssuers
kubectl delete clusterissuer letsencrypt-staging letsencrypt-prod \
internal-ca selfsigned 2>/dev/null总结
Kubernetes 将 TLS 配置完全交由用户自行处理。本文档演示了公有端和私有端的完整责任范畴。
在公有端,您通过当前的 OCI Helm 图表安装了 cert-manager,创建了基于 Let's Encrypt 的 ClusterIssuer,并观察了 cert-manager 完成 ACME HTTP-01 挑战的完整流程 —— 从创建临时验证 Pod 到将有效证书存储到 Kubernetes Secret。您还看到如何通过一行注解切换测试与生产环境,以及 cert-manager 如何在证书过期前自动续签。
在私有端,您通过自签名签发器创建了私有 CA,基于该 CA 创建了 ClusterIssuer,并为仅存在于集群内部的服务名称签发证书。通过 trust-manager 集群范围内分发 CA 证书包,使服务无需逐个配置即可相互信任。您还通过 openssl 验证了证书链,确保生产部署前的正确性。
理解证书轮换机制是区分能自信管理 TLS 团队和因证书过期半夜被叫醒团队的关键。cert-manager 自动化了续签流程,但 renewBefore 参数是您的安全余量 —— 正确设置它并学会解读续签状态至关重要。
所有 YAML 清单和 Helm 参数均可在 DevOps-Cloud-Projects GitHub 仓库 中找到。
免费学习编程。freeCodeCamp 的开源课程已帮助超过 40,000 人找到开发岗位。立即开始