当前位置: 首页 > news >正文

certbot+shell+阿里云api+k8s实现自动化更新SSL证书

certbot + shell + 阿里云 API + k8s 实现自动化更新 SSL 证书

背景

当业务所使用的域名越来越多时,使用 certbot renew 生成证书后,还需手动上传到阿里云控制台,并修改 Ingress 的注解,整个过程非常繁琐且重复。

对于一个成熟的运维工程师来说,这显然不够优雅。

话不多说,直接开搞。

参考项目:https://github.com/justjavac/certbot-dns-aliyun


1. 安装 certbot 和 jq
yum -y install certbot jq
2.创建 RAM 用户用于 API 调用

权限策略要求

  • 添加 DNS 解析记录
  • 上传证书到阿里云证书服务(CAS)

推荐策略(JSON)

{"Version": "1","Statement": [{"Effect": "Allow","Action": "alidns:AddDomainRecord","Resource": "*"},{"Effect": "Allow","Action": "yundun-cert:UploadUserCertificate","Resource": "*"}]
}🔐 安全性建议:为策略添加来源 IP 限制(将 1.1.1.1 替换为你的服务器公网 IP):{"Version": "1","Statement": [{"Effect": "Allow","Action": "alidns:AddDomainRecord","Resource": "*"},{"Effect": "Allow","Action": "yundun-cert:UploadUserCertificate","Resource": "*","Condition": {"IpAddress": {"acs:SourceIp": ["1.1.1.1"]}}}]
}
3. 安装阿里云 CLI 工具
/bin/bash -c "$(curl -fsSL https://aliyuncli.alicdn.com/install.sh)"

配置凭证

aliyun configure --profile akProfileConfiguring profile 'akProfile' in '' authenticate mode...
Access Key Id []: 在这里输入 Access Key
Access Key Secret []: 在这里输入 Secret
Default Region Id []: cn-hangzhou
Default Output Format [json]: json
Default Language [zh|en] en:
Saving profile[akProfile] ...Done.
4. 安装 certbot-dns-aliyun 插件
wget https://cdn.jsdelivr.net/gh/justjavac/certbot-dns-aliyun@main/alidns.sh

移动并赋予执行权限

mv alidns.sh /usr/local/bin/
chmod +x /usr/local/bin/alidns.sh
ln -s /usr/local/bin/alidns.sh /usr/local/bin/alidns

💡 可选:为防止误删 DNS 记录,可注释掉脚本中删除记录的代码。

5. 测试插件是否能正常申请证书
certbot certonly \-d *.example.com \--manual \--preferred-challenges dns \--manual-auth-hook "alidns" \--email admin@example.com \--agree-tos \--non-interactive \--no-eff-email \--dry-run

✅ 测试成功后,去掉 --dry-run 即可正式申请。

6. 上传证书 + 动态更新脚本(UploadUserCertificate.sh)
#!/bin/bash
set -euo pipefail# ========================
# 参数处理
# ========================
DOMAIN=${1:-}
if [ -z "$DOMAIN" ]; thencat << EOF
❌ 用法: $0 <domain>示例:$0 '*.test.example.com'        # 更新所有子域名$0 webapp.example.com     # 更新单个域名支持通配符格式: *.example.com
EOFexit 1
fiecho "[`date`] 🔄 开始刷新证书: $DOMAIN"# ========================
# 确定证书目录(支持泛域名)
# ========================
CERT_DIR=""if [[ "$DOMAIN" == \*.* ]]; then# 是泛域名,例如: *.example.comBASE_DOMAIN="${DOMAIN#\*.}"  # 提取 test.mall.example.comecho "[`date`] 🔍 查找泛域名证书: 尝试以下路径..."for candidate in \"/etc/letsencrypt/live/_wildcard.${BASE_DOMAIN}" \"/etc/letsencrypt/live/wildcard.${BASE_DOMAIN}" \"/etc/letsencrypt/live/${BASE_DOMAIN}" \"/etc/letsencrypt/live/${DOMAIN//\*/wildcard}" \"/etc/letsencrypt/live/${DOMAIN}"; doecho "  → 检查: $candidate"if [[ -f "$candidate/fullchain.pem" ]] && [[ -f "$candidate/privkey.pem" ]]; thenCERT_DIR="$candidate"echo "  ✅ 找到!"breakfidoneelse# 单域名CERT_DIR="/etc/letsencrypt/live/$DOMAIN"
fiif [ ! -f "$CERT_DIR/fullchain.pem" ] || [ ! -f "$CERT_DIR/privkey.pem" ]; thenecho "❌ 证书文件不存在: $CERT_DIR"echo "   请检查 Certbot 是否已正确签发该域名证书。"exit 1
fi# ========================
# 上传证书到阿里云 CAS
# ========================
CERT_NAME="cert-${DOMAIN//\*/wildcard}-$(date +%Y%m%d%H%M)"
echo "[`date`] 📤 正在上传证书到阿里云 CAS: $CERT_NAME"# ========================
# 加载证书内容并去除换行
# ========================
CERT_CONTENT=$(cat "$CERT_DIR/fullchain.pem")
KEY_CONTENT=$(cat "$CERT_DIR/privkey.pem")response=$(aliyun cas UploadUserCertificate \--region cn-hangzhou \--Name="$CERT_NAME" \--Cert="$CERT_CONTENT" \--Key="$KEY_CONTENT")NEW_CERT_ID=$(echo "$response" | jq -r '.ResourceId')
if [ -z "$NEW_CERT_ID" ] || [ "$NEW_CERT_ID" = "null" ]; thenecho "❌ 上传失败,未返回 CertId"echo "💡 响应: $response"exit 1
fiecho "[`date`] ✅ 证书上传成功,Cert ID: $NEW_CERT_ID"# ========================
# 查找所有匹配 DOMAIN 的 Ingress
# ========================
echo "[`date`] 🔍 正在扫描所有命名空间中的 Ingress..."# 获取所有 Ingress: namespace/name=host1 host2 ...
mapfile -t INGRESS_HOSTS < <(
kubectl get ingress -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}={.spec.rules[*].host}{"\n"}{end}'
)MATCHED_INGRESSES=()for record in "${INGRESS_HOSTS[@]}"; do# 跳过空行[[ -z "$record" ]] && continue# 分割为 INGRESS_FULLNAME 和 HOSTS_STRINGRESS_FULLNAME="${record%%=*}"HOSTS_STR="${record#*=}"# 去除首尾空白HOSTS_STR="$(echo "$HOSTS_STR" | xargs)"# 如果没有 hosts,跳过[[ -z "$HOSTS_STR" ]] && continue# 将 hosts 字符串转为数组IFS=' ' read -r -a HOSTS <<< "$HOSTS_STR"# 检查是否有任意 host 匹配 DOMAINMATCH=false
for host in "${HOSTS[@]}"; do[[ -z "$host" ]] && continue# 情况1: 精确匹配if [[ "$host" == "$DOMAIN" ]]; thenMATCH=truebreakfi# 情况2: 通配符匹配 *.test.example.comif [[ "$DOMAIN" == \*.* ]]; thensuffix="${DOMAIN#\*.}"  # 如 test.example.com# 必须是以 .$suffix 结尾if [[ "$host" != *".$suffix" ]]; thencontinuefi# 提取前缀部分:如 xxl-job.test → 剩下 "xxl-job"prefix="${host%.$suffix}"# 检查 prefix 中是否还包含 "."# 如果包含,说明是 admin.cms.test → prefix=admin.cms → 多层,跳过if [[ "$prefix" == *.* ]]; thenecho "[`date`] 🔇 跳过多层子域: $host (不匹配 $DOMAIN)" >&2continuefi# 合法的一级子域,匹配成功MATCH=truebreakfidone# 如果匹配,记录该 Ingressif [[ "$MATCH" == true ]]; thenNS="${INGRESS_FULLNAME%%/*}"NAME="${INGRESS_FULLNAME##*/}"MATCHED_INGRESSES+=("$NS:$NAME")echo "[`date`] ✅ 匹配到: $NAME ($NS) -> $host"fi
done# ========================
# 统计并更新
# ========================
TOTAL_MATCHED=${#MATCHED_INGRESSES[@]}
if [ $TOTAL_MATCHED -eq 0 ]; thenecho "[`date`] ⚠️  没有找到匹配 '$DOMAIN' 的 Ingress"echo "   请确认域名拼写或 Ingress 配置。"exit 0
fiecho "[`date`] 🛠️  准备更新 $TOTAL_MATCHED 个 Ingress 的证书..."for item in "${MATCHED_INGRESSES[@]}"; doNS="${item%:*}"NAME="${item#*:}"echo "[`date`] 📌 更新 Ingress: $NAME ($NS)"kubectl patch ingress "$NAME" -n "$NS" \-p "{\"metadata\":{\"annotations\":{\"alb.ingress.kubernetes.io/cert-id\":\"$NEW_CERT_ID\"}}}" \|| echo "⚠️  更新失败或无变化: $NAME ($NS)"
doneecho "[`date`] 🚀 证书更新完成!共更新 $TOTAL_MATCHED 个 Ingress"
echo "[`date`] 💡 提示: ALB Controller 会自动同步证书,通常 1-2 分钟生效"
7. 执行脚本 + 验证

正式申请证书并部署

certbot certonly \-d api.example.com \--manual \--preferred-challenges dns \--manual-auth-hook "alidns" \--email y***@163.com \--agree-tos \--non-interactive \--no-eff-email \--deploy-hook "/root/UploadUserCertificate.sh api.example.com"

证书续期

certbot renew \--cert-name api.example.com \--manual \--preferred-challenges dns \--manual-auth-hook "alidns" \--email y****@163.com \--agree-tos \--non-interactive \--no-eff-email \--deploy-hook "/root/UploadUserCertificate.sh api.example.com"

定时续期

将续期命令加入 crontab

http://www.dtcms.com/a/544687.html

相关文章:

  • Linux小课堂: 系统核心技能与应用总结与进阶指南
  • 前端vue项目在vscode使用插件部署到服服务器的方法
  • 使用Labelimg进行图像标注
  • 【计算机软件资格考试】软考案例分析题及解析模拟题10
  • IoTDA应用侧app开发403报错解决方案
  • 3.1 Lua代码中的元表与元方法
  • Rust——多重借用的冲突解决方案:驾驭Rust借用检查器的艺术
  • kaggle比赛与常用的dash board 3lc
  • 适配器模式:让不兼容的接口协同工作
  • Neo4j中导入.owl数据
  • 应急救援 “眼观六路”:SA/NSA 双模覆盖,偏远灾区也能实时传视频
  • 站长工具短链接生成网站中队人物介绍怎么做
  • 【Spring Boot + Spring Security】从入门到源码精通:藏经阁权限设计与过滤器链深度解析
  • 《嵌入式硬件(十七):基于IMX6ULL的温度传感器LM75a操作》
  • 用 Go 手搓一个内网 DNS 服务器:从此告别 IP 地址,用域名畅游家庭网络!
  • Rust async/await 语法糖的展开原理:从表象到本质
  • Rust 零拷贝技术:从所有权到系统调用的性能优化之道
  • 浪潮服务器装linux系统步骤
  • 视频网站服务器带宽需要多少?视频网站服务器配置要求
  • 《嵌入式硬件(十八):基于IMX6ULL的ADC操作》
  • 注册网站发财的富豪北京公司如何做网站
  • 仓颉语言异常捕获机制深度解析
  • 基于SAP.NET Core Web APP(MVC)的医疗记录管理系统完整开发指南
  • 咖啡网站建设设计规划书wordpress修改首页网址导航
  • C#WPF UI路由事件:事件冒泡与隧道机制
  • 神经网络时序预测融合宏观变量的ETF动态止盈系统设计与实现
  • 分布式Session会话实现方案
  • Java创建【线程池】的方法
  • 相机直播,HDMI线怎么选择
  • 做外贸哪些国外网站可以推广上海中学地址