【阿里云原生应用】使用阿里云FC函数计算完成阿里云CDN目录刷新

原创 同时被 3 个专栏收录
13 篇文章 0 订阅
1 篇文章 0 订阅
1 篇文章 0 订阅

需求背景

我负责的项目是一个前后端分离项目。最近将前端静态页面通过阿里云OSS静态网站托管功能进行部署,并通过云效流水线功能对NodeJS项目进行打包发布。
同时,将页面接入阿里云的CDN,从而最大限度地加速C端用户的访问速度。

关于OSS静态网站托管

通过OSS的静态网页托管功能,我们可以很方便地发布我们自己的静态页面,而无需关注运维层面的诸多问题。
同时,在使用静态网站托管时,有个默认首页的配置。简单来说,假设我网站文件为index.html。在未配置静态首页的情况下,访问http://www.aaa.com时,会返回404,我们需要手动访问http://www.aaa.com/index.html才能展示首页;而如果配置了静态首页为index.html时,访问http://www.aaa.com则会直接展示首页。

但是,默认首页结合了CDN之后,却出现了一些问题

OSS静态网站托管之CDN缓存刷新问题

原本阿里云很贴心的为OSS静态网站托管+CDN组合提供了一个CDN 缓存自动刷新功能,即在特定的文件操作后,OSS会自动通知阿里云CDN做缓存刷新。(详情参看帮助文档

但是在进行相应配置后,开发同事向我反馈,上传新版本后,浏览器端未更新。

经过排查发现,当我们访问http://www.aaa.com/index.html时,文件的确已经更新了。但是我们的入口是http://www.aaa.com/,此时从header中可知访问的文件更新时间还是上传之前版本文件的时间。

查阅文档后发现,文档中有如下描述:

由生命周期触发的对象过期(Expire)、类型转换(TransitionStorageClass)操作不再支持CDN缓存刷新。使用CDN缓存自动刷新时有如下注意事项:

  • CDN缓存自动刷新功能提交的刷新URL为CNAME/ObjectName,不会刷新带请求参数的URL(图片处理、视频截帧等)。例如Bucket绑定的加速域名为example.com,当您更新Bucket根目录的a.jpg文件,则访问example/a.jpg可以获取最新文件;访问example.com/a.jpg?x-oss-process=image/w_100可能获取的还是旧文件。
  • CDN缓存自动刷新功能不保证一定能成功提交刷新任务,也不保证刷新任务提交的及时性。
  • CDN缓存自动刷新功能仅支持少量文件的更新提交刷新任务。如果有大量文件的更新操作,可能会触发流控丢弃部分刷新任务。

简而言之:OSS中的CDN缓存自动刷新功能刷新的是File,而不是Directory(二者的区别可以参看阿里云CDN文档刷新和预热资源,后面也会着重用到)

所以,要解决我们的问题,有2种思路:

  • 将入口改为http://www.aaa.com/index.html
  • 手动调用CDN的Directory刷新

本文中,讨论第二种思路

开始着手解决

CDN-刷新预热

通过刷新功能,您可以删除CDN节点上已经缓存的资源,并强制CDN节点回源站获取最新资源,适用于源站资源更新和发布、违规资源清理、域名配置变更等;通过预热功能,您可以在业务高峰前预先将热门资源缓存到CDN节点,降低源站压力提升用户体验。

CDN预热刷新官方文档
CDN刷新预热
简单地说,CDN给我们提供了一个入口,可以在版本更新后,通知CDN及时刷新缓存。而接下来,我们要做到自动调用,就需要找到它对应的API/SDK

预热刷新API

经过查询,我们所需要的API接口为RefreshObjectCaches(刷新节点上的文件内容)
用我一位同事的话来说:阿里云对开发者太友好了,阿里云要统治世界了
的确是这样,接口文档页面直接给出了多语言的Demo
多语言Demo

SDK技术选型

这种脚本类型的功能,自然使用Python最为合适啦!
下面是Demo中给出的完整代码

# -*- coding: utf-8 -*-
# This file is auto-generated, don't edit it. Thanks.
import sys

from typing import List

from alibabacloud_cdn20180510.client import Client as Cdn20180510Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_cdn20180510 import models as cdn_20180510_models


class Sample:
    def __init__(self):
        pass

    @staticmethod
    def create_client(
        access_key_id: str,
        access_key_secret: str,
    ) -> Cdn20180510Client:
        """
        使用AK&SK初始化账号Client
        @param access_key_id:
        @param access_key_secret:
        @return: Client
        @throws Exception
        """
        config = open_api_models.Config(
            # 您的AccessKey ID,
            access_key_id=access_key_id,
            # 您的AccessKey Secret,
            access_key_secret=access_key_secret
        )
        # 访问的域名
        config.endpoint = 'cdn.aliyuncs.com'
        return Cdn20180510Client(config)

    @staticmethod
    def main(
        args: List[str],
    ) -> None:
        client = Sample.create_client('accessKeyId', 'accessKeySecret')
        refresh_object_caches_request = cdn_20180510_models.RefreshObjectCachesRequest(
            object_path='http://www.aaa.com/',
            object_type='Directory'
        )
        # 复制代码运行请自行打印 API 的返回值
        client.refresh_object_caches(refresh_object_caches_request)

    @staticmethod
    async def main_async(
        args: List[str],
    ) -> None:
        client = Sample.create_client('accessKeyId', 'accessKeySecret')
        refresh_object_caches_request = cdn_20180510_models.RefreshObjectCachesRequest(
            object_path='http://www.aaa.com/',
            object_type='Directory'
        )
        # 复制代码运行请自行打印 API 的返回值
        await client.refresh_object_caches_async(refresh_object_caches_request)


if __name__ == '__main__':
    Sample.main(sys.argv[1:])

其实在用到FC函数计算功能之前,我还考虑使用云效流水线的自定义步骤的功能,但是不知道为什么本地调试成功,远端无法运行。这部分如果成功了我将单独写一篇文章说明

基础流程

由于我们使用云效的发布功能,在云效发布完成后,有一个Webhook通知功能,如下图
WEBHOOK
所以,我们从站点打包发布到自动化更新CDN缓存的基础流程如下

NodeJS打包
发布到OSS
WebHook通知FC函数计算接口
FC函数计算代码调用SDK通知刷新CDN

FC函数计算

函数计算文档中,对该产品的描述如下:

函数计算是事件驱动的全托管计算服务。使用函数计算,您无需采购与管理服务器等基础设施,只需编写并上传代码。函数计算为您准备好计算资源,弹性地、可靠地运行任务,并提供日志查询、性能监控和报警等功能。
借助函数计算,您可以快速构建任何类型的应用和服务,并且只需为任务实际消耗的资源付费。

创建函数

创建函数的基础功能,请参照官方文档操作

Python HTTP函数

函数计算的函数类型分为2种

而根据我们基础流程的设计,要求我们需要使用HTTP函数
具体介绍如下:

函数计算系统会将用户的请求,包括Method、Path、Body、Query及Headers和函数计算系统生成的Common Headers转发给HTTP Server。您可以平滑迁移已有的HTTP Web应用。更详细的介绍,请参见简介。

具体到Python,请参看Python HTTP函数

Python Runtime中函数的签名遵循WSGI(Python Web Server Gateway Interface)规范。您可以使用WSGI规范对请求进行处理。

主代码

根据上面SDK提供的DEMO,结合HTTP函数文档,可用的Python代码如下

# 用于CDN刷新节点上的文件内容,主程序入口参数依次为:AK,SK,PATH,TYPE
# 详见接口文档:https://help.aliyun.com/document_detail/91164.html
import sys
import os
import logging
import urllib.parse
from Tea.core import TeaCore

from alibabacloud_cdn20180510.client import Client as Cdn20180510Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_cdn20180510 import models as cdn_20180510_models
from alibabacloud_tea_console.client import Client as ConsoleClient
from alibabacloud_tea_util.client import Client as UtilClient

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'
)  # logging.basicConfig函数对日志的输出格式及方式做相关配置


class DirFlush:
    def __init__(self):
        pass

    @staticmethod
    def create_client(
            access_key_id: str,
            access_key_secret: str,
    ) -> Cdn20180510Client:
        """
        使用AK&SK初始化账号Client
        @param access_key_id:
        @param access_key_secret:
        @return: Client
        @throws Exception
        """
        config = open_api_models.Config(
            # 您的AccessKey ID,
            access_key_id=access_key_id,
            # 您的AccessKey Secret,
            access_key_secret=access_key_secret
        )
        # 访问的域名
        config.endpoint = 'cdn.aliyuncs.com'
        return Cdn20180510Client(config)

    @staticmethod
    def main(
            access_key_id: str,
            access_key_secret: str,
            object_path: str,
            object_type: str = 'File',
    ):
        """
        刷新/预热CDN方法
        :param access_key_id: ak 必填
        :param access_key_secret: sk 必填
        :param object_path: 目录,多个目录之间使用换行符分割 必填
        :param object_type: 刷新类型。取值:File:文件。 Directory:目录。
        :return:
        """
        client = DirFlush.create_client(access_key_id, access_key_secret)
        refresh_object_caches_request = cdn_20180510_models.RefreshObjectCachesRequest(
            object_path=object_path,
            object_type=object_type
        )
        resp = client.refresh_object_caches(refresh_object_caches_request)
        resp_str = UtilClient.to_jsonstring(TeaCore.to_map(resp))
        ConsoleClient.log(resp_str)
        return resp_str

    @staticmethod
    async def main_async(
            access_key_id: str,
            access_key_secret: str,
            object_path: str,
            object_type: str = 'File',
    ) -> None:
        """
        刷新/预热CDN方法(异步)
        :param access_key_id: ak 必填
        :param access_key_secret: sk 必填
        :param object_path: 目录,多个目录之间使用换行符分割 必填
        :param object_type: 刷新类型。取值:File:文件。 Directory:目录。
        :return:
        """
        client = DirFlush.create_client(access_key_id, access_key_secret)
        refresh_object_caches_request = cdn_20180510_models.RefreshObjectCachesRequest(
            object_path=object_path,
            object_type=object_type
        )
        resp = await client.refresh_object_caches_async(refresh_object_caches_request)
        ConsoleClient.log(UtilClient.to_jsonstring(TeaCore.to_map(resp)))


# To enable the initializer feature (https://help.aliyun.com/document_detail/158208.html)
# please implement the initializer function as below:
# def initializer(context):
#    logger = logging.getLogger()
#    logger.info('initializing')


def handler(environ, start_response):
    """
    Web调用入口
    遵循WSGI规范
    用于阿里云FC函数调用功能调用
    通过Get请求的requestParam分别写入如下参数
    path(刷新目录,如:http://www.baidu.com/)
    type(刷新类型,枚举:File:文件  Directory:目录。)
    :param environ: 通过requestParam分别写入path(刷新目录)
    :param start_response:
    :return: 返回执行结果
    """
    request_param = urllib.parse.parse_qs(environ['QUERY_STRING']) # 获取请求参数中的Param
    logging.info("requestParam:[%s]", request_param)
    ak = os.getenv("CDN_AK")
    sk = os.getenv("CDN_SK")
    path = request_param["path"][0]
    object_type = "File" if request_param.get("type") is None else request_param["type"][0]
    object_type = "File" if object_type != "Directory" and object_type != "File" \
        else object_type  # 用来为object_type赋予一个默认值
    logging.info("ak:[%s],path:[%s],type:[%s]", ak, path, object_type)
    try:
        result = DirFlush.main(ak, sk, path, object_type)
    except IOError as err:
        logging.error(err)
        raise err
    status = '200 OK'
    response_headers = [('Content-type', 'application/json')]
    start_response(status, response_headers)
    return [result.encode()]


if __name__ == '__main__':
    """
    主程序入口
    """
    if len(sys.argv[1:]) < 4:
        DirFlush.main(sys.argv[1], sys.argv[2], sys.argv[3])
    else:
        DirFlush.main(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])

环境变量

由于access_key_id和SK为敏感数据,为了尽量保证其安全,我们通过环境变量来获取。环境变量可以在函数设置中配置。环境变量如下:

  • CDN_AK:对应access_key_id
  • CDN_SK:access_key_secret

GitHub

在GitHub中有个独立项目,根目录下的server.py是一个http规范的调试脚本,本地调试可参照改脚本进行操作
Github项目aliyunApiTools

请求样例

我们通过下面2个Key放在Get请求的Request Param中,完成我们的请求

  • path:代表需要刷新的目录或文件
  • type: 代表刷新类型,如果不传则默认为File
curl --location --request GET 'http://127.0.0.1:8000?path=http://www.aaa.com/&type=Directory'

这里要注意的是,当需要刷新目录时,path应当以斜杠(/)结尾,否则会报错:

Tea.exceptions.TeaException: Error: InvalidObjectPath.Malformed code: 400, The specified ObjectPath is invalid. request id: 659EB0E6-CB0D-5141-9696-E92BE5DE2570 Response: {‘RequestId’: ‘659EB0E6-CB0D-5141-9696-E92BE5DE2570’, ‘HostId’: ‘cdn.aliyuncs.com’, ‘Code’: ‘InvalidObjectPath.Malformed’, ‘Message’: ‘The specified ObjectPath is invalid.’, ‘Recommend’: ‘https://error-center.aliyun.com/status/search?Keyword=InvalidObjectPath.Malformed&source=PopGw’}

Python运行环境

根据Python运行环境一文所示,函数计算环境所需的依赖包部分需要自己安装。而安装依赖的方式也有多种(参看Python运行环境中的“自定义模块”一节)。而我在这里使用pip包管理器进行依赖管理的方式。
关于当前类,我总结了下面几个环境是需要安装的,pip代码如下

pip3 install -t . alibabacloud_cdn20180510 --no-user
pip3 install -t . urllib --no-user
pip3 install -t . idna_ssl --no-user
pip3 install -t . alibabacloud_tea_console --no-user

部署

安装完环境后,打包整个目录并上传,即可在编辑页面进行部署了
代码编辑页面
注意,将函数设置中的入口函数更改一下
函数配置
建议使用自定义域名功能进行管理

部署完成后,即可调用了
调用成功

总结

  • 这次尝试帮我们打开一个新的思路:对于这种脚本化运行的自动化代码,可以尝试用函数计算功能实现,降低成本的同时也获得了较高的可用性
  • 当然,我们还有很多的方式,比如流水线里有个自定义步骤的功能
  • Github上我会维护更多的功能,有需要的伙伴可以关注一下
  • 1
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值