前言
直到今天我见过很多网站还是倾向使用独立的服务器部署自己的网站。但是在云服务更加完善的今天,已经有更好的选择。本文将介绍使用阿里云的OSS+CDN部署自己的前端页面,以及加速静态资源。
直接使用阿里云的OSS+CDN的方案有几大好处:
- 成本低廉。OSS+CDN部署自己的网站每个月的花费远比自己买ECS服务器部署网站花费要少得多
- 大幅降低运维成本。直接使用现成的云服务了,无需花精力去维护ECS了。
- 极高的可用性。无论阿里云的OSS还是CDN,都已经做好了高可用性,几乎可以保证网站始终可访问
- 极高的访问速度。ECS带宽毕竟有限的,高带宽意味着超高的费用。但是用OSS+CDN这种天然分布式的架构部署网站很轻松的解决了带宽问题,极大地提升了用户的访问体验。
部署准备
- 备案过的域名
- 足够的余额(流量不大的话100元余额都够用好几个月,比ECS便宜得多)
阿里云CDN需要绑定自己的域名,国内要求必须备案,所以务必先备案自己的域名。如果这个域名在阿里云购买的更好,几乎可以做到不用手改域名解析记录,阿里云会自动处理。
OSS和CDN都是后付费的服务,因此需要保证账户有足够的余额。价格方案: https://cn.aliyun.com/price/product#/oss/detail
注意: 网站链接需要特别注意,阿里云OSS的网站托管是兼容Angular的路由的,也就是根目录只有一个
index.html
,其他目录的访问都应该rewrite到/index.html
。所以如果你的静态资源不是通过Angular build出来的,不应该使用/location/
这种路径页面跳转,应该使用/location/index.html
这种路径,否则访问/location/
将显示/index.html
中的内容。具体的细则请参考阿里云的官方文档: 配置静态网站托管
部署步骤
开通OSS与CDN服务
这两个服务都是按量付费的,开通是不需要费用的。
OSS创建bucket
每个独立的bucket可以当做一个独立的网盘对待,要求bucket name全局唯一,不能重名。创建一个前端托管的bucket参考配置如下:
配置bucket
bucket创建之后,默认的bucket配置是不具有静态网站托管的功能的,因此需要做一些配置。参考配置如下:
启用CDN加速
OSS目前的策略限制了不能通过OSS自己的域名直接打开index.html
,会变成下载。因此无论是否使用CDN,都必须绑定自己的域名。由于启用CDN之后会加速OSS,并且回源流量半价,还是挺划算的,因此建议启用CDN。启用CDN的办法如下:
稍等片刻之后,等待CDN配置之后,转到CDN控制台就可以看到这个域名了:
此时基本配置就已经结束了,之后你可以上传你的静态网站到这个bucket的根目录下了。不过在此之前可以做一些CDN的优化配置
CDN的一些优化配置(可选)
CDN支持定制HTTP响应头的功能,一个比较常见的设置是直接在http头中添加浏览器缓存的头(cache-control
),这个功能可以在缓存配置
这里直接配置缓存规则:
目前全站https已经是趋势了而且免费的证书服务目前也比较多了。阿里云CDN就支持申请DigiCert免费证书,并且自动续期。下面的HTTP/2
也建议启用:
上面的DigiCert免费证书申请需要特别注意,如果你的主机头是www
,必须将空主机头@
也CNAME
也解析到CDN上才能申请成功。
此外,性能优化个人建议全部勾选,可以减少部分带宽:
上传/更新网站
到此为止OSS+CDN的部分已经配置完毕了,只要把静态页面上传到OSS上就搞定了。建议通过oss客户端上传,可以支持批量拖拽上传,而不是在OSS控制台上一个一个上传。
这里我推荐阿里云官方的开源项目oss-browser,因为这个客户端可以跨平台,并且支持记录AK,使用上很方便。
使用前需要开启账户的AccessKey,或者专门创建一个低权限的子账户管理OSS。
每次更新网站的时候,可能需要手工刷新下CDN(尽管OSS配置中有个自动刷新CDN的功能,但我发现使用客户端是上传的时候无效。疑似通过API上传的文件不会触发CDN刷新)。进入CDN的控制台,点击刷新,输入一下要刷新的URL路径即可:
这里分享一下我个人的deploy.sh
脚本,拼接阿里云接口参数的函数部分改动自acem.sh这个项目,在此之上做了一些扩充,感谢贡献者:
#!/bin/bash -e
_urlencode() {
# urlencode <string>
old_lc_collate=$LC_COLLATE
LC_COLLATE=C
length="${#1}"
for (( i = 0; i < length; i++ )); do
local c="${1:i:1}"
case $c in
[a-zA-Z0-9.~_-]) printf "$c" ;;
*) printf '%%%02X' "'$c" ;;
esac
done
LC_COLLATE=$old_lc_collate
}
_ali_nonce() {
date +"%s%N"
}
_timestamp() {
date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
}
_ali_urlencode() {
_str="$1"
_str_len=${#_str}
_u_i=1
while [ "$_u_i" -le "$_str_len" ]; do
_str_c="$(printf "%s" "$_str" | cut -c "$_u_i")"
case $_str_c in [a-zA-Z0-9.~_-])
printf "%s" "$_str_c"
;;
*)
printf "%%%02X" "'$_str_c"
;;
esac
_u_i="$(($_u_i + 1))"
done
}
_ali_signature() {
sorted_query=$(printf "%s" "${query}" | tr '&' '\n' | sort | paste -s -d '&')
string_to_sign=$(printf "%s" "GET&%2F&$(_ali_urlencode "${sorted_query}")")
signature=$(printf "%s" "${string_to_sign}" | openssl sha1 -binary -hmac "${ACCESS_KEY_SECRET}&" | base64)
_ali_urlencode "${signature}"
}
aliyun_request_builder() {
query="Format=json&AccessKeyId=${ACCESS_KEY_ID}&SignatureMethod=HMAC-SHA1&SignatureVersion=1.0&Version=${version}"
query="${query}&SignatureNonce=$(_ali_nonce)&Timestamp=$(_timestamp)"
for q in "$@"; do
query="${query}&${q%%=*}=$(_ali_urlencode "${q#*=}")"
done
query="${query}&Signature=$(_ali_signature "${query}")"
echo "${query}"
}
aliyun_rest() {
query="${1}"
curl -fs "${aliyun_endpoint}?${query}"
}
aliyun_cdn_refresh() {
ACCESS_KEY_ID="${CDN_ACCESS_KEY_ID}"
ACCESS_KEY_SECRET="${CDN_ACCESS_KEY_SECRET}"
if [ -z "${ACCESS_KEY_ID}" ] && [ -z "${ACCESS_KEY_SECRTE}" ]; then
echo "请设置CDN_ACCESS_KEY_ID,CDN_ACCESS_KEY_SECRET环境变量"
exit 1
fi
aliyun_endpoint="https://cdn.aliyuncs.com/"
version="2014-11-11"
request_query="$(aliyun_request_builder \
Action=RefreshObjectCaches \
"ObjectPath=${1}" \
ObjectType=File)"
aliyun_rest "${request_query}"
}
_oss_upload_one_file() {
file="${1}"
if [ -z "${OSS_ACCESS_KEY_ID}" ] && [ -z "${OSS_ACCESS_KEY_SECRET}" ] && [ -z "${OSS_BUCKET}" ]; then
echo "请设置OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET, OSS_BUCKET三个环境变量"
exit 1
fi
upload_path="${OSS_BASE_PATH:-/}"
host="${OSS_BUCKET}.oss-cn-hangzhou.aliyuncs.com"
Date="$(LC_ALL=C TZ=GMT date +'%a, %d %b %Y %T %Z')"
Content_MD5=`openssl md5 -binary < "${file}" | base64`
extension="${file##*.}"
case "${extension,,}" in
js)
Content_Type=application/javascript
;;
css)
Content_Type=text/css
;;
json)
Content_Type=application/json
;;
woff)
Content_Type=font/woff
;;
woff2)
Content_Type=font/woff2
;;
*)
Content_Type=$(file -b --mime-type "${file}")
;;
esac
storage_path="${upload_path}${file}"
CanonicalizedResource="/${OSS_BUCKET}${storage_path}"
SignString="PUT\n${Content_MD5}\n${Content_Type}\n${Date}\n${CanonicalizedResource}"
Signature=`echo -ne "$SignString" | openssl sha1 -binary -hmac "${OSS_ACCESS_KEY_SECRET}" | base64`
Authorization="OSS ${OSS_ACCESS_KEY_ID}:${Signature}"
echo "Uploading '${file}' to bucket: '${OSS_BUCKET}' path: '${storage_path}', content-type: '${Content_Type}'"
curl -XPUT -sfLkT "${file}" \
-H "Content-Type: ${Content_Type}" \
-H "Date: ${Date}" -H "Content-Md5: ${Content_MD5}" \
-H "Authorization: ${Authorization}" "https://${host}${storage_path}"
}
export -f _oss_upload_one_file
oss_upload() {
src="${1}"
cd "${src}"
find -type f -printf "%P\0" | xargs -0 -I{} --no-run-if-empty -P10 bash -ec "_oss_upload_one_file {}"
}
main() {
oss_upload "${1}"
if [ -n "${CDN_URL}" ]; then
echo "Refreshing CDN: '${CDN_URL}'"
aliyun_cdn_refresh "${CDN_URL}"
fi
}
main
AK信息配置到环境变量中:
export OSS_ACCESS_KEY_ID=xxxxxxx
export OSS_ACCESS_KEY_SECRET=xxxxxxx
export OSS_BUCKET=your-bucket
# 如果没有启用阿里云的CDN,下面三个环境变量可以不赋值
export CDN_URL=https://www.domain.com/ # 阿里云的CDN刷新地址要求路径最后的/不能省略
export CDN_ACCESS_KEY_ID=xxxxxxx
export CDN_ACCESS_KEY_SECRET=xxxxxxx
./deploy.sh /path/to/website/
注意点:
- 使用的oss endpoint为杭州节点,其他节点可以自行修改
host="${OSS_BUCKET}.oss-cn-hangzhou.aliyuncs.com"
这一行 - 考虑到OSS和CDN可能会分成两个子账户管理,因此将OSS和CDN的账户AK信息分成两组环境变量存储
- 如果
CDN_URL
不存在,则不会在上传后自动刷新CDN路径 - 刷新CDN方案使用的刷新类型是
URL
,上述配置只刷新了/
这一个访问地址。因此期望整个网站使用CDN友好(即每次更新资源之后,文件名或引用路径会改变,比如Angular或Vue的产品环境编译)