美文网首页
🚀 自制K8s泛域名证书自动管理方案

🚀 自制K8s泛域名证书自动管理方案

作者: 子弹冲冲 | 来源:发表于2025-04-25 15:11 被阅读0次

对比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紧密集成
  • 离线/隔离环境:需要最小化外部依赖
  • 学习与控制:希望完全理解和控制证书管理流程

✅ 功能需求

  1. 通过 certbot 结合阿里云域名管理实现自动生成泛域名证书
  2. 将获取到的证书自动存储为 Kubernetes Secret 资源
  3. 定时执行证书检查与更新
  4. 智能检测证书状态,仅在必要时更新 Kubernetes Secret

🏗️ 解决方案描述

整体解决方案包含以下组件:

  1. 🐳 自定义 Docker 镜像:包含 certbot、阿里云 DNS 插件和 kubectl 工具
  2. 💾 持久化存储:用于存储 Let's Encrypt 账户信息和证书
  3. ⏱️ Kubernetes CronJob:定期运行,检查证书状态并按需更新
  4. 🔑 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 行尾。

🛡️ 安全考虑

  1. 最小权限原则:ServiceAccount 仅有管理 Secret 的必要权限
  2. 敏感信息保护:阿里云 API 密钥存储在 Kubernetes Secret 中
  3. 证书私钥保护:证书数据存储在持久卷中,并且只有特定 Pod 可以访问

🔄 维护与更新

维护任务 建议频率 说明
检查 CronJob 状态 每周 确保定时任务正常运行
更新 Docker 镜像 每季度 包含最新的 certbot 和安全补丁
Let's Encrypt 账户备份 每月 备份/etc/letsencrypt目录
密钥轮换 每年 更新阿里云 API 凭证

🎯 结论

本解决方案提供了一个轻量级精确可控高效的自动化流程,用于在 Kubernetes 集群中管理泛域名 SSL 证书。与 cert-manager 等大型解决方案相比,我们的方案更为简洁、资源占用更少,同时保持了完全的功能性和安全性。

通过结合 certbot、阿里云 DNS 验证和 Kubernetes CronJob,实现了证书的自动申请、续期和部署,无需人工干预。这种方法确保了集群中的 TLS 证书始终有效,同时遵循最小权限原则和安全最佳实践。

最终成果: 一个简单、可靠、节省资源的证书管理解决方案,特别适合那些寻求精简架构和直接控制的团队。

相关文章

网友评论

      本文标题:🚀 自制K8s泛域名证书自动管理方案

      本文链接:https://www.haomeiwen.com/subject/tfsobjtx.html