火山引擎全站加速证书自动更新脚本实践
背景
火山云全站加速配置SSL证书,手动上传并绑定证书的过程虽然简单,但多域名重复操作既繁琐又容易出错。于是决定写一个自动化脚本,利用火山引擎 CLI 工具(ve)完成证书上传与绑定的全流程。
工具选型与思路
火山引擎提供了统一的命令行工具 ve,支持 CDN、DCDN 等多个服务。一开始查看了 DCDN 相关的命令:
ve dcdn CreateCertBind --help
发现 CreateCertBind 需要一个已存在于证书中心的 CertId,而不能直接传递证书文件内容。这意味着必须先将证书上传到火山引擎的证书中心,拿到 CertId 后再绑定到 DCDN 域名。
庆幸的是,ve cdn AddCertificate 命令可以将证书上传至火山引擎证书中心(设置 Source 为 volc_cert_center),并且返回的 CertId 可以在 DCDN 中直接使用。这样一来,流程就清晰了:
- 读取本地证书文件(fullchain.pem 和 privkey.pem)
- 调用
AddCertificate上传,获取CertId(若证书已存在则复用) - 调用
CreateCertBind将证书绑定到 DCDN 域名
遇到的两个坑
1. 通配符域名不被 DCDN 接受
一开始脚本中的 DomainNames 写成了 ["example.com", "*.example.com"],执行后报错:
DomainNotExist: Domain Name [*.example.com] does not exist
通过 ve dcdn ListDomainConfig 查看实际已添加的加速域名,发现 DCDN 控制台并不接受 *.example.com 这样的通配符形式。实际存在的域名是 example.com、sub.example.com 以及一个以点开头的特殊域名 .example.com(可能是历史原因或内部标识)。因此最终使用的域名列表需要与实际配置完全一致。
2. 错误响应的解析路径不一致
ve cdn AddCertificate 的错误信息位于 .ResponseMetadata.Error,而 ve dcdn CreateCertBind 的错误信息却直接在根节点 .Error 中。最初脚本只检查了 .ResponseMetadata.Error,导致 CreateCertBind 明明失败了,脚本却输出“绑定成功”。修正后同时检查两个可能的路径。
最终脚本
以下脚本实现了自动上传证书并绑定到 DCDN 域名。使用时请根据实际情况修改证书存放目录、日志目录以及目标域名列表。
#!/bin/bash
#set -xv
log_date() {
date '+[%Y/%m/%d %H:%M:%S]'
}
# 配置 - 请按实际修改
CERT_STORE_DIR="/path/to/your/certificates"
LOG_DIR="/path/to/log"
mkdir -p "$LOG_DIR"
exec > >(tee -a "$LOG_DIR/cert.log") 2>&1
echo "$(log_date) 脚本开始执行"
# 加载证书内容
cert_content=$(cat "$CERT_STORE_DIR/fullchain.pem")
key_content=$(cat "$CERT_STORE_DIR/privkey.pem")
echo "$(log_date) 证书内容长度: ${#cert_content}"
echo "$(log_date) 私钥内容长度: ${#key_content}"
# 1. 上传证书到火山引擎证书中心
upload_json_request() {
jq -n \
--arg cert "$cert_content" \
--arg key "$key_content" \
'{
"Source": "volc_cert_center",
"CertType": "server_cert",
"Certificate": $cert,
"PrivateKey": $key
}'
}
echo "$(log_date) 正在上传证书到火山引擎证书中心..."
response=$(ve cdn AddCertificate --body "$(upload_json_request)")
if echo "$response" | jq -e '.ResponseMetadata.Error' > /dev/null 2>&1; then
error_code=$(echo "$response" | jq -r '.ResponseMetadata.Error.Code')
if [ "$error_code" = "InvalidParameter.Certificate.Duplicated" ]; then
# 证书已存在时从错误消息中提取已有证书ID
cert_id=$(echo "$response" | jq -r '.ResponseMetadata.Error.Message | capture("ID为 (?<id>[^。]+)。") | .id')
echo "$(log_date) 证书已存在,使用已有证书ID: $cert_id"
else
error_msg=$(echo "$response" | jq -r '.ResponseMetadata.Error.Message')
echo "$(log_date) 上传失败: $error_msg"
exit 1
fi
else
request_id=$(echo "$response" | jq -r '.ResponseMetadata.RequestId')
cert_id=$(echo "$response" | jq -r '.Result.CertId')
echo "$(log_date) 上传成功,RequestId: $request_id"
echo "$(log_date) 证书ID: $cert_id"
fi
# 2. 绑定证书到 DCDN 域名(按实际域名列表修改)
deploy_json_request() {
jq -n \
--arg cert_id "$cert_id" \
'{
"CertSource": "volc",
"CertId": $cert_id,
"DomainNames": ["example.com", "sub.example.com", ".example.com"]
}'
}
echo "$(log_date) 正在将证书绑定到全站加速域名..."
response=$(ve dcdn CreateCertBind --body "$(deploy_json_request)")
# 增强错误检测:同时检查根级 .Error 和 .ResponseMetadata.Error
if echo "$response" | jq -e '.Error' > /dev/null 2>&1 || echo "$response" | jq -e '.ResponseMetadata.Error' > /dev/null 2>&1; then
error_msg=$(echo "$response" | jq -r '.Error.Message // .ResponseMetadata.Error.Message')
echo "$(log_date) 绑定失败: $error_msg"
exit 1
else
request_id=$(echo "$response" | jq -r '.ResponseMetadata.RequestId // "unknown"')
echo "$(log_date) 绑定成功,RequestId: $request_id"
exit 0
fi
执行效果
脚本执行时会输出详细日志:
[2026/05/19 14:38:06] 脚本开始执行
[2026/05/19 14:38:06] 证书内容长度: 2864
[2026/05/19 14:38:06] 私钥内容长度: 226
[2026/05/19 14:38:06] 正在上传证书到火山引擎证书中心...
[2026/05/19 14:38:11] 上传成功,RequestId: xxx
[2026/05/19 14:38:11] 证书ID: cert-xxxx
[2026/05/19 14:38:11] 正在将证书绑定到全站加速域名...
[2026/05/19 14:38:15] 绑定成功,RequestId: yyy
如果证书已经存在,会直接复用原有证书 ID,避免重复上传。
总结
通过结合 ve cdn AddCertificate 和 ve dcdn CreateCertBind,可以高效地完成火山引擎全站加速的证书自动更新。关键点在于:
- 证书中心是统一的,CDN 上传的证书可供 DCDN 使用
- DCDN 的
DomainNames必须精确匹配控制台中已添加的加速域名(不支持通配符形式的域名) - 不同命令的错误响应结构可能不同,需要编写健壮的解析逻辑
上述脚本配合1Panel执行,即可在证书更新后自动部署,告别手动操作的烦恼。
评论功能已关闭