对比cert-manager,更轻、更快、更可控的阿里云DNS验证SSL证书管理解决方案
本文档详细介绍了如何使用 certbot 结合阿里云 DNS 验证方式,在 Kubernetes 环境中自动化申请、更新和管理泛域名 SSL 证书的完整解决方案。
📋 为什么不使用 cert-manager?
cert-manager 是 Kubernetes 生态系统中流行的证书管理工具,但在某些场景下,我们的自定义解决方案可能更适合:
优势对比
| 特性 | 自定义 certbot 解决方案 | cert-manager |
|---|---|---|
| 资源占用 | 🟢 极低(仅在需要时运行) | 🟡 持续运行多个控制器 |
| 复杂度 | 🟢 低(单一脚本实现核心逻辑) | 🟡 高(多种CRD、控制器和组件) |
| 可定制性 | 🟢 高(完全控制证书获取和部署流程) | 🟡 受限于内置API |
| 故障排查 | 🟢 简单(标准bash脚本和kubectl命令) | 🟡 需深入了解其内部工作机制 |
| 依赖性 | 🟢 最小化(仅依赖certbot和kubectl) | 🟡 引入多个控制器和CRD |
| 学习曲线 | 🟢 平缓(基础Shell脚本和K8s概念) | 🟡 陡峭(需理解多种自定义资源) |
| 适用场景 | 🟢 少量证书管理、离线或边缘环境 | 🟢 大规模证书管理 |
何时选择此解决方案?
- 资源受限环境:如边缘计算、小型集群或IoT场景
- 简单直接需求:仅需管理少量关键证书
- 特殊集成需求:需要与特定云提供商API紧密集成
- 离线/隔离环境:需要最小化外部依赖
- 学习与控制:希望完全理解和控制证书管理流程
✅ 功能需求
- 通过 certbot 结合阿里云域名管理实现自动生成泛域名证书
- 将获取到的证书自动存储为 Kubernetes Secret 资源
- 定时执行证书检查与更新
- 智能检测证书状态,仅在必要时更新 Kubernetes Secret
🏗️ 解决方案描述
整体解决方案包含以下组件:
- 🐳 自定义 Docker 镜像:包含 certbot、阿里云 DNS 插件和 kubectl 工具
- 💾 持久化存储:用于存储 Let's Encrypt 账户信息和证书
- ⏱️ Kubernetes CronJob:定期运行,检查证书状态并按需更新
- 🔑 RBAC 权限控制:确保证书管理 Pod 只有必要的权限
🚀 实现步骤
1. 创建自定义 Docker 镜像
Dockerfile
FROM certbot/certbot:latest
# 安装kubectl和必要工具
RUN apk add --no-cache curl bash jq python3 py3-pip coreutils && \
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && \
chmod +x kubectl && \
mv kubectl /usr/local/bin/
# 安装阿里云DNS验证插件
RUN pip install certbot-dns-aliyun
# 添加运行脚本
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
entrypoint.sh 脚本
#!/bin/bash
set -e
# 从环境变量获取配置参数,同时提供默认值
DOMAIN="${DOMAIN:-*.example.com}"
EMAIL="${EMAIL:-your-email@example.com}"
SECRET_NAME="${SECRET_NAME:-wildcard-tls-cert}"
NAMESPACE="${NAMESPACE:-default}"
RENEWAL_DAYS="${RENEWAL_DAYS:-30}"
echo "配置参数:"
echo "域名: $DOMAIN"
echo "邮箱: $EMAIL"
echo "Secret名称: $SECRET_NAME"
echo "命名空间: $NAMESPACE"
echo "续期天数: $RENEWAL_DAYS"
# 确保环境变量存在
if [ -z "$ALICLOUD_ACCESS_KEY" ] || [ -z "$ALICLOUD_SECRET_KEY" ]; then
echo "错误: 阿里云访问密钥未提供"
exit 1
fi
# 创建阿里云凭证配置文件
mkdir -p /etc/letsencrypt/
cat > /etc/letsencrypt/aliyun.ini << EOF
dns_aliyun_access_key = $ALICLOUD_ACCESS_KEY
dns_aliyun_access_key_secret = $ALICLOUD_SECRET_KEY
EOF
chmod 600 /etc/letsencrypt/aliyun.ini
# 证书日期检查函数
check_cert_renewal_needed() {
# 检查证书是否存在
if ! kubectl get secret $SECRET_NAME -n $NAMESPACE &>/dev/null; then
echo "Secret不存在,需要创建新证书"
return 0
fi
# 获取Secret中的证书
local cert_data=$(kubectl get secret $SECRET_NAME -n $NAMESPACE -o jsonpath='{.data.tls\.crt}' | base64 -d)
if [ -z "$cert_data" ]; then
echo "Secret中没有有效证书数据,需要创建新证书"
return 0
fi
# 检查证书过期日期
local expiry_date=$(echo "$cert_data" | openssl x509 -noout -enddate | cut -d= -f2)
local expiry_epoch=$(date -d "$expiry_date" +%s)
local current_epoch=$(date +%s)
local days_secs=$((RENEWAL_DAYS * 24 * 60 * 60))
if [ $((expiry_epoch - current_epoch)) -lt $days_secs ]; then
echo "证书将在${RENEWAL_DAYS}天内过期,需要续期"
return 0
else
echo "证书仍然有效,不需要续期"
return 1
fi
}
# 运行certbot获取或续期证书
get_or_renew_cert() {
base_domain=$(echo $DOMAIN | sed 's/\*\.//')
certbot certonly \
--authenticator dns-aliyun \
--dns-aliyun-credentials /etc/letsencrypt/aliyun.ini \
--agree-tos \
--non-interactive \
--email $EMAIL \
-d "$DOMAIN,$base_domain"
echo "证书已成功获取或续期"
}
# 更新K8s Secret
update_k8s_secret() {
echo "正在更新Kubernetes Secret..."
base_domain=$(echo $DOMAIN | sed 's/\*\.//')
CERT_PATH="/etc/letsencrypt/live/$base_domain"
# 检查证书文件是否存在
if [ ! -f "$CERT_PATH/fullchain.pem" ] || [ ! -f "$CERT_PATH/privkey.pem" ]; then
echo "错误: 证书文件不存在"
return 1
fi
# 创建或更新Secret
kubectl create secret tls $SECRET_NAME \
--cert="$CERT_PATH/fullchain.pem" \
--key="$CERT_PATH/privkey.pem" \
--namespace=$NAMESPACE \
--dry-run=client -o yaml | kubectl apply -f -
echo "Secret '$SECRET_NAME' 已更新"
}
# 主逻辑
main() {
echo "开始检查证书状态: $(date)"
if check_cert_renewal_needed; then
get_or_renew_cert
update_k8s_secret
echo "证书处理完成: $(date)"
else
echo "无需处理证书: $(date)"
fi
}
# 执行主逻辑
main
2. 构建并推送 Docker 镜像
# 构建镜像
docker build -t your-registry/certbot-aliyun:v1.0.0 .
# 推送到镜像仓库
docker push your-registry/certbot-aliyun:v1.0.0
💡 提示:您可以将镜像推送到私有仓库以提高安全性,尤其是因为此镜像将有权限管理敏感的TLS证书。
3. 创建 Kubernetes 资源
创建命名空间
kubectl create namespace cert-aliyun
创建阿里云 API 凭证 Secret
apiVersion: v1
kind: Secret
metadata:
name: aliyun-dns-credentials
namespace: cert-aliyun
type: Opaque
data:
access-key: <base64编码的阿里云AccessKey>
secret-key: <base64编码的阿里云SecretKey>
创建持久化存储
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: letsencrypt-data
namespace: cert-aliyun
spec:
accessModes:
- ReadWriteOnce
storageClassName: "your-storage-class" # 根据集群环境指定
resources:
requests:
storage: 1Gi
创建 RBAC 权限(集群级别)
如果需要在多个命名空间管理证书,可以使用以下替代配置:
apiVersion: v1
kind: ServiceAccount
metadata:
name: certbot-sa
namespace: cert-aliyun
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secret-manager
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: certbot-secret-manager
subjects:
- kind: ServiceAccount
name: certbot-sa
namespace: cert-aliyun
roleRef:
kind: ClusterRole
name: secret-manager
apiGroup: rbac.authorization.k8s.io
创建 CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: certbot-wildcard-renewal
namespace: cert-aliyun
spec:
schedule: "0 0 1 * *" # 每月1日凌晨执行
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
serviceAccountName: certbot-sa
containers:
- name: certbot
image: your-registry/certbot-aliyun:v1.0.0
env:
- name: DOMAIN
value: "*.example.com" # 您的泛域名
- name: EMAIL
value: "your-email@example.com" # 您的邮箱
- name: SECRET_NAME
value: "wildcard-tls-cert" # 要创建的Secret名称
- name: NAMESPACE
value: "default" # 要创建Secret的命名空间
- name: RENEWAL_DAYS
value: "30" # 证书到期前多少天续期
- name: ALICLOUD_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aliyun-dns-credentials
key: access-key
- name: ALICLOUD_SECRET_KEY
valueFrom:
secretKeyRef:
name: aliyun-dns-credentials
key: secret-key
volumeMounts:
- name: letsencrypt
mountPath: /etc/letsencrypt
volumes:
- name: letsencrypt
persistentVolumeClaim:
claimName: letsencrypt-data
restartPolicy: OnFailure
🧪 手动测试
您可以使用以下命令手动触发证书更新任务:
kubectl create job --from=cronjob/certbot-wildcard-renewal manual-cert-renewal -n cert-aliyun
也可以使用 Docker 命令直接运行容器:
Linux/macOS:
docker run --rm \
-e DOMAIN="*.example.com" \
-e EMAIL="your-email@example.com" \
-e SECRET_NAME="wildcard-tls-cert" \
-e NAMESPACE="default" \
-e RENEWAL_DAYS="30" \
-e ALICLOUD_ACCESS_KEY="your-access-key" \
-e ALICLOUD_SECRET_KEY="your-secret-key" \
-v /path/to/letsencrypt:/etc/letsencrypt \
-v $HOME/.kube/config:/root/.kube/config \
your-registry/certbot-aliyun:v1.0.0
Windows (CMD):
docker run --rm ^
-e DOMAIN="*.example.com" ^
-e EMAIL="your-email@example.com" ^
-e SECRET_NAME="wildcard-tls-cert" ^
-e NAMESPACE="default" ^
-e RENEWAL_DAYS="30" ^
-e ALICLOUD_ACCESS_KEY="your-access-key" ^
-e ALICLOUD_SECRET_KEY="your-secret-key" ^
-v C:\path\to\letsencrypt:/etc/letsencrypt ^
-v C:\Users\your-username\.kube\config:/root/.kube/config ^
your-registry/certbot-aliyun:v1.0.0
🔒 证书使用
创建的证书将作为 TLS Secret 存储在 Kubernetes 中,可以在 Ingress 或其他 TLS 终结资源中使用:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
namespace: default
spec:
tls:
- hosts:
- example.com
- "*.example.com"
secretName: wildcard-tls-cert
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
⚠️ 故障排除
1. 证书颁发失败
检查 DNS 验证是否成功:
kubectl logs job/certbot-wildcard-renewal-<timestamp> -n cert-aliyun
确保阿里云 API 密钥有正确的域名管理权限。
2. Secret 创建失败
检查 ServiceAccount 权限:
kubectl auth can-i create secrets --as=system:serviceaccount:cert-aliyun:certbot-sa -n default
3. 日期解析错误
如果遇到日期解析错误,确保容器镜像中已安装 coreutils 包。
4. 文件格式问题:
如果提示 entrypoint.sh 不存在,文件可能是DOS/Windows格式(CRLF行尾),而不是Unix格式(LF行尾),需要确保 LF 行尾。
🛡️ 安全考虑
- 最小权限原则:ServiceAccount 仅有管理 Secret 的必要权限
- 敏感信息保护:阿里云 API 密钥存储在 Kubernetes Secret 中
- 证书私钥保护:证书数据存储在持久卷中,并且只有特定 Pod 可以访问
🔄 维护与更新
| 维护任务 | 建议频率 | 说明 |
|---|---|---|
| 检查 CronJob 状态 | 每周 | 确保定时任务正常运行 |
| 更新 Docker 镜像 | 每季度 | 包含最新的 certbot 和安全补丁 |
| Let's Encrypt 账户备份 | 每月 | 备份/etc/letsencrypt目录 |
| 密钥轮换 | 每年 | 更新阿里云 API 凭证 |
🎯 结论
本解决方案提供了一个轻量级、精确可控且高效的自动化流程,用于在 Kubernetes 集群中管理泛域名 SSL 证书。与 cert-manager 等大型解决方案相比,我们的方案更为简洁、资源占用更少,同时保持了完全的功能性和安全性。
通过结合 certbot、阿里云 DNS 验证和 Kubernetes CronJob,实现了证书的自动申请、续期和部署,无需人工干预。这种方法确保了集群中的 TLS 证书始终有效,同时遵循最小权限原则和安全最佳实践。
最终成果: 一个简单、可靠、节省资源的证书管理解决方案,特别适合那些寻求精简架构和直接控制的团队。













网友评论