人人都是Serverless架构师之传统内容管理系统改造实战二[踩坑实践]

简介: 容管理系统是很常见的一种web应用场景,可以用到个人独立站,企业官网展示等场景,具有很高的实用价值,一个标准的内容管理系统主要由三个部分组成 主站展示部分、后台管理系统、API接口服务,本篇文章会以一个已有内容管理系统的Serverless架构重构展开,介绍改造的基本思路,改造细节,以及性能优化业务可观测设计等。涉及大家关心的Serverless生产遇到的一些问题,比如数据库、日志、动静态分离、调试、维护、灰度方案等。最真实的展现Serverless架构的实施落地细节。

本次改造的项目题材取自GiiBee CMS
Serverless架构改造的仓库地址

项目改造后预览地址 SSR方案地址SSG方案地址


1. 前置准备

本次Serverless重构需要的各组件服务依赖于阿里云的基础设施,比如网关入口会使用阿里云API 网关,函数服务使用阿里云函数计算FC, 日志和网站静态资源的存储使用文件存储NAS,而站点的前端源码托管于对象存储OSS,数据库部分则可以使用云数据库RDS MySQL版(原项目使用mysql)

部署和构建工具则使用ServerlessDevs

2. 项目测试

改造开始前需要先在本地验证一下GiiBee CMS的可用性,安装依赖,配置好数据库然后启动即可,测试效果如下

证实原项目可用,接下来是分模块的代码改造和配置编写工作

3. Server端改造

GiiBee CMS 核心的部分是server端,采用NestJS,编写的NodeJS 后端API服务,是管理后台和展示前台的前置依赖,所以从它下手开始是最合理的。

3.1 配置信息环境变量化

为了兼容函数计算服务的运行时环境,需要将源码中涉及配置的部分抽离出来,比如数据库配置,日志地址配置,静态资源访问路径配置等,这么做是为了更加灵活的实现自动化集成和构建流程,同时安全性也更好一些。

相应的例子如下


3.2 构建流程配置文件s.yaml编写

全配置部分请参考源码

3.2.1 定义全局变量

主要是将部署的region信息,函数计算的服务名,函数名以及静态资源地址,日志路径等配置到全局方便复用

vars:
  region: cn-hangzhou
  fc: 
    serviceName: 'modern-app-new'
    functionName: 'modern-app-new'
    triggers: 'modern-web-api'
    staticPath: '/mnt/auto/modern-app-new/public'
    logPath: '/mnt/auto/modern-app-new/logs/application.log'
  oss:
    bucketName: 'hanxie-modernweb-registery'
    bucketObject: 'admin'
    bucketObject2: 'portal'

3.2.2 API 服务的核心配置

重点关注构建的前置依赖,利用 ServerlessDevs的actions配置实现前置动作,诸如安装依赖,开启部分云服务能力

 actions:
      post-deploy: # 在deploy之后运行
      - plugin: keep-warm-fc
        args:
          url: http://modern-app-new.modern-app-new.xxxxxxxx.cn-hangzhou.fc.devsapp.net
    # actions:
    #   post-deploy:
    #     - component: fc nas upload -r ./server/logs /mnt/auto/modern-app-new
    #     - component: fc nas upload -r ./server/public /mnt/auto/modern-app-new

 

这里注释的部分是做了nas 的路径映射,因为映射关系执行一次后会永久生效,因此这里执行完毕后可以注释掉,未注释部分是使用keep-warm-fc来对部署的函数服务进行性能优化,这在后面优化篇章单独介绍

3.2.3 其他关键配置

对应源码部分的环境变量设置是要在s.yaml里面体现的,好的做法是使用.env的环境配置文件(该文件不必上传代码仓库,仅做本地使用),然后利用S工具的文件解析能力进行读取,尽量避免安全信息的泄露,有兴趣的同学可以去查看ServerlessDevs相关的介绍文档

function:
        name: ${vars.fc.functionName}
        description: Native recording handler
        timeout: 3000
        memorySize: 1024
        runtime: custom
        environmentVariables:
          NODE_ENV: production
          dbHost: 
          dbPort: 3306
          dbPassword: 
          dbUserName: 
          staticPath: ${vars.fc.staticPath}
          logPath: ${vars.fc.logPath}
        codeUri: ./server
        caPort: 3000


3.3 编写启动入口文件

为了将服务部署到函数计算FC上并且尽可能避免源码的改动,我们采用函数计算Customer Runtime的部署策略,该策略需要我们编写一个bootstrap启动文件,该文件存放执行启动的nodejs指令,比如这里启动指令如下

#!/usr/bin/env bash
export PORT=3000
node dist/main.js


暴露的端口要和源码中的启动端口一致,然后使用node指令运行编译后的服务文件即可。

4. Admin管理后台改造

4.1 本地调试

服务端改造完毕后,可以用来做管理后台的测试,在.env.development文件中设置 VUE_APP_BAE_HOST,把他改成前端Servere部署服务的返回值,然后启动 npm run dev测试效果

# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = '/api'
# base host http://localhosat//3000/ => http://modern-app-new.modern-app-new.xxxx.cn-hangzhou.fc.devsapp.net/
VUE_APP_BASE_HOST = 'http://localhost:3000/'


之后就是对内容详细测试并且修正了。

4.2 部署配置

管理后台是个标准的SPA应用,所以单纯将构件好的静态资源放置到OSS上即可,相关配置如下,利用ServerlessDevs的oss组件可以减少很多上传OSS的步骤,甚至他可以帮你获得一个自定义域名,来独立访问配置到的静态站点

www-admin: # 静态资源
component: oss
props:
region: ${vars.region}
bucket: ${vars.oss.bucketName} # OSS bucket 自动生成
subDir: ${vars.oss.bucketObject}
acl: public-read # 读写权限
codeUri: ./admin/dist # 指定本地要上传目录文件地址
website: # OSS 静态网站配置
index: index.html # 默认首页
error: 404.html # 默认 404 页
subDirType: redirect # 子目录首页 404 规则
# customDomains: # OSS 绑定域名
#   - domainName: auto
      #     protocol: HTTP


5. Portal改造

相比管理后台SPA型只需要做静态托管而言,Portal考虑的点会更多一些,比如对性能的追求,对SEO的追求以及安全性等,他是站点的门面,站长获客的重要渠道。Portal是使用NuxtJS开发他支持 SSR(服务端渲染), SPA(单页面应用),SSG(静态站点渲染 JAMStack的核心点之一),均衡考虑灵活、性能等本次采用SSR的渲染方式。

5.1 渲染配置项

这部分是在我们开发完毕进行构建的时候的设置选项,配置项在nuxt.config.js中, 我们需要修改 target值,从static改成server,即从静态渲染SSG变为服务端渲染SSR,具体说明参考官方文档

target: "server",


接下来是设置服务端的访问地址,SSR渲染的时候会先拿到服务端的数据再做页面生成,相应设置如下

http: {
  // debug: true,
  baseURL: "http://modern-app-new.modern-app-new.xxxxxxxxxx.cn-hangzhou.fc.devsapp.net/" // Used as fallback if no runtime config is provided
},


5.2 编写启动入口文件

SSR本质上是在服务端运行的,需要部署到函数计算服务上,因此跟上面的API部署类似,需编写启动文件bootstrap,内容也非常简单,是一个npm的启动指令,这里也是利用的框架的启动指令项。

#!/usr/bin/env bash
export PORT=3001
npm start 


5.3 编写构建配置

相应的s.yaml部分配置如下:

ssr-portal:
    component: fc
    actions:
      post-deploy: # 在deploy之后运行
      - plugin: keep-warm-fc
        args:
          url: http://modern-app-portal.modern-app-portal.xxxxxxxxx.cn-hangzhou.fc.devsapp.net
    # actions:
    #   post-deploy:
    #     - component: fc nas upload -r ./server/logs /mnt/auto/modern-app-new
    #     - component: fc nas upload -r ./server/public /mnt/auto/modern-app-new
    props:
      region: ${vars.region}
      service:
        name: modern-app-portal
        description: Aliyun RAM Role
        internetAccess: true
        nasConfig: auto
      function:
        name: modern-app-portal
        description: Native recording handler
        timeout: 3000
        memorySize: 1024
        runtime: custom
        codeUri: ./web
        caPort: 3001
      triggers:
        - name: modern-app-portal
          type: http
          config:
            authType: anonymous
            methods:
              - GET
              - POST
              - PUT
              - DELETE
              - HEAD
              - OPTIONS
      customDomains:
        - domainName: auto
          protocol: HTTP
          routeConfigs:
            - path: /*
              serviceName: modern-app-portal
              functionName:  modern-app-portal

API 网关配置

当三个部分分别配置及部署好之后,就是剩下最后的整合部分了,我们再回顾一下之前的访问架构方案

可以看出API 网关需要设置3个透传路由1.访问管理员的路由,2访问api服务的路由,3访问首页portal的路由

接下来登录API网关的控制台界面,创建一个分组具体操作步骤:分组管理->创建分组->选择默认共享实例->填写分组名->点击确定。

分组创建好之后进行API创建,操作步骤如下:分组管理->列表详情->API列表->创建API

上面是我创建好的api,比之前预想的多了一个路由,是因为本次文件上传服务也单独做了路径。另外有个点需要注意的是,本次api设置的后端服务类型都是使用了 HTTP(s)服务而非直接用函数计算

是因为调试过程中发现,GiiBee CMSAPI服务端使用了jwt验证,从API网关到函数计算会重新修改header内容导致服务测无法读取到用户的标识进而验证失败(API网关触发函数的时候会写入标识来让对方识别,属于产品集成的问题),所以就用HTTP(s)的方式替代。关于API网关的配置大家可以查看官方文档,这里不再赘述。

值得注意的是API 网关自带了丰富的插件,可以实现诸如IP访问控制、流量控制、JWT认证等能力,你无需写一行代码就可以拥有相当完善的站点安全效果,这点是非常赞的。

5.4 DNS及域名绑定

API 网关 分组上会自带了公网二级域名,不过这个域名本身有很多的限制,此外机器生成的域名也不便记忆,所以我们还是需要绑定自己的域名,你可以化十几块钱在阿里云域名注册一个自定义的域名,之后在阿里云DNS控制台进行对网关API公网域名的解析,比如笔者选取了abc.serverless-developer.com进行对API网关的CNAME解析

DNS设置解析后再回到API网关的控制台界面进行一次域名绑定即可

最后是通过自定义域名进行访问测试以及对细节进行联调了。

6. 总结

本次实践操作的内容比较多,包括源码改造,部署配置,以及云产品的操作等,这么操作下来给人的感觉还是挺沮丧的,Serverless的那些好并不容易得到。的确是这样,如果你的项目非常简单比如仅仅是拿来做展示的站点,是完全没必要搞这么复杂的。但是如果你想做一些更具商业价值的创新项目是可以考虑这套方案的,因为他在灵活扩展性,安全性以及性能体验上都会有着很好的优势,是可以支持你的创新从一个IDEA一直到IPO的,这套技术本身也会保持比较持久的活力(实际上属于前端的技术一点没有发生改变,只不过增加了部署运维态的能力),未来5-10年都可以不用改变。此外改善开发者体验也是我们的小伙伴一直在做的事情,以上的方案完全可以利用ServerlessDevs沉淀成一个标准的应用模板,你可以像使用任意开发脚手架一样使用它,对你的业务进行开发,调试,部署,上线,运维,至于是不是我说的这样得需要大家自己亲自验证了。

作者介绍
目录

相关产品

  • 函数计算