DTeam 技术日志

Doer、Delivery、Dream

使用阿里云OSS+CDN部署前端页面与加速静态资源

冯宇 Posted at — Dec 15, 2018 阅读

前言

直到今天我见过很多网站还是倾向使用独立的服务器部署自己的网站。但是在云服务更加完善的今天,已经有更好的选择。本文将介绍使用阿里云的 OSS+CDN 部署自己的前端页面,以及加速静态资源。

直接使用阿里云的 OSS+CDN 的方案有几大好处:

部署准备

阿里云 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 参考配置如下:

image.png

配置 bucket

bucket 创建之后,默认的 bucket 配置是不具有静态网站托管的功能的,因此需要做一些配置。参考配置如下:

image.png

启用 CDN 加速

OSS 目前的策略限制了不能通过 OSS 自己的域名直接打开index.html,会变成下载。因此无论是否使用 CDN,都必须绑定自己的域名。由于启用 CDN 之后会加速 OSS,并且回源流量半价,还是挺划算的,因此建议启用 CDN。启用 CDN 的办法如下:

image.png

image.png

稍等片刻之后,等待 CDN 配置之后,转到 CDN 控制台就可以看到这个域名了:

image.png

此时基本配置就已经结束了,之后你可以上传你的静态网站到这个 bucket 的根目录下了。不过在此之前可以做一些 CDN 的优化配置

CDN 的一些优化配置(可选)

CDN 支持定制 HTTP 响应头的功能,一个比较常见的设置是直接在 http 头中添加浏览器缓存的头(cache-control),这个功能可以在缓存配置这里直接配置缓存规则:

image.png

目前全站 https 已经是趋势了而且免费的证书服务目前也比较多了。阿里云 CDN 就支持申请 DigiCert 免费证书,并且自动续期。下面的HTTP/2也建议启用:

image.png

image.png

上面的 DigiCert 免费证书申请需要特别注意,如果你的主机头是www,必须将空主机头@CNAME也解析到 CDN 上才能申请成功。

此外,性能优化个人建议全部勾选,可以减少部分带宽:

image.png

image.png

上传/更新网站

到此为止 OSS+CDN 的部分已经配置完毕了,只要把静态页面上传到 OSS 上就搞定了。建议通过 oss 客户端上传,可以支持批量拖拽上传,而不是在 OSS 控制台上一个一个上传。

这里我推荐阿里云官方的开源项目oss-browser,因为这个客户端可以跨平台,并且支持记录 AK,使用上很方便。

使用前需要开启账户的 AccessKey,或者专门创建一个低权限的子账户管理 OSS。

image.png

每次更新网站的时候,可能需要手工刷新下 CDN(尽管 OSS 配置中有个自动刷新 CDN 的功能,但我发现使用客户端是上传的时候无效。疑似通过 API 上传的文件不会触发 CDN 刷新)。进入 CDN 的控制台,点击刷新,输入一下要刷新的 URL 路径即可:

image.png

这里分享一下我个人的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/

注意点:


相关文章