三月份的时候收到阿里云的促销邮件,内容是关于他们的函数计算服务的,从中我了解到阿里云的函数计算可以使用自定义runtime来实现函数;即只要能够监听端口提供服务的内容都可以函数化,当时我就开了个脑洞,能不能把博客也函数化,这样uptime有保障,也不用再单独弄台服务器来跑服务,降低了成本还省心。
为此我去做了点功课,并进行了一番尝试,成功将博客搬了上去,虽然博客的响应速度下降了,不过本来就是没什么人看也就无所谓了(,最近终于有点空,来整理一下函数化的笔记。
先挖个坑,慢慢更……
序:认识函数
什么是函数计算?
阿里云的网站上是这么说的:函数计算是事件驱动的全托管计算服务,无需采购与管理服务器等基础设施,只需编写并上传代码,函数计算就为您准备好计算资源,并弹性地、可靠地运行任务,同时提供日志查询、性能监控和报警等功能。
举个例子来说,如果我们需要从零开始实现一个网络服务,一般来说是先去购买一台VPS,然后根据服务的需求编写一个server,并让这个server去监听某个端口,将该端口暴露到公网然后提供服务。这其中就会有很多问题,首先,VPS的供应商是否可靠,网络是否健壮,会不会偶尔宕机、断网?其次,如果你配置server的时候,犯了低级错误(例如ssh弱口令,没有改默认端口,暴露了危险服务等等),会不会留下安全隐患等等……最后,购买VPS需要多高的配置,如何在性能和成本间做好平衡?
函数计算的出现解决了一些痛点,首先,管理者不再需要去关心VPS本身的可靠性,函数计算是动态部署的而不是局限于某台物理机器,你几乎永远遇不上宕机和网络问题;其次,函数计算只暴露网络服务,并且函数本身也是运行在类似docker的隔离、虚拟环境中的,通过比较合理地安排资源,安全性非常高,几乎不用担心受到攻击(除非你网络服务出了大漏洞爆个shell或者能让别人直接操作你数据库然后删库跑路);最后,函数计算能够弹性伸缩,不用担心买的配置不够,而且函数的计费是按照运算时长×资源大小收费的(数据库、网络、存储等周围服务当然是另外计费的),不用不收费,对于中低负载的应用来说这种计费方式几乎就代表了最低的成本。
你说得这么好,要怎么用呢?
以golang举个例子吧(虽然阿里云不支持直接用golang写函数):
func handler(w http.ResponseWriter, req *http.Request) {
// 处理req请求
// 将response写到w里
// return
}
你只需要实现一个类似的handler函数,把对应代码贴上去,选择运算资源大小(最大内存)并发布,你的服务就跑起来了。
??????就这?
当然,说起来是很简单,实际上函数写起来还是很麻烦的,首先,上述这种最简单的方式需要服务商提供支持,比如阿里云目前只支持使用Node.js、Python、PHP、Java、C#直接进行开发,当然,上述函数也不是随便写的,你要参考函数计算的文档来了解req中会有哪些重要的参数,需要以什么格式生成response等等,如何同周围服务进行交互(比如函数需要访问云数据库等等)同样也需要查阅文档。如果你顺手的语言不包含这几种,或者你嫌弃Python的运行效率觉得它会harm你的成本,那么阿里云还提供了CustomRuntime的方式,这也是我使用的方式;这种方式提供了最大的灵活性,你只需要提供一个脚本或者二进制,在运行后能有应用监听9000
端口并提供服务,即可作为一个函数提供服务。
阿里云的CustomRuntime
阿里云的CustomRuntime逻辑大致如下:
- 首先你需要准备一个压缩包,压缩包内包含自定义环境所需的全部内容(包括动态链接库等);
- 当一个新的函数”容器“被创建的时候,相当于将你准备的压缩包内的内容解压到了一个Debian9系统的
/code
目录下,然后启动器会以root
用户的身份运行/code/bootstrap
文件,这里的bootstrap
需要有x
权限,可以是一段bash脚本,也可以是二进制文件;启动器期望在运行bootstrap
后,在0.0.0.0:9000
端口或者0.0.0.0:${FC_SERVER_PORT}
有服务监听,这之后函数计算将会把所有的请求转发到该端口; - 虽然以
root
身份运行,但程序并没有文件系统的读写权限,只能读写/tmp
目录,并且限制总大小不超过512MB; - 程序运行时的内存占用不能超过创建函数时设置的大小,否则容器会被kill
我们可以发现,虽然我们可以放入任意的可执行程序作为bootstrap并提供服务,但是大部分程序都有动态链接库的依赖,而对应的运行环境中可能并没有对应依赖;对于该问题,我们可以提前把动态链接库打包,并使用环境变量提醒新的连接位置,或者使用funcraft来打包环境,这个我们后面再提。
函数化博客程序
程序选择
常见的博客程序有typecho
和wordpress
,后者虽然功能强大但是同样占用资源也较多,另外有一些静态博客程序例如hexo
,本身可以依赖GitHub Pages来进行部署,不需要如此大费周折。在这里我选择typecho
,一个是上手容易,另外一个是占用资源极低,从我的实践来看,使用typecho
并使用sqlite3
作为数据库,运行时内存占用一般不会超过90MB,为了保险起见,我的容器内存大小选择的是192MB,完全够用。
配套Server程序
首先PHP
肯定是跑不掉的,来个PHP-FPM
吧,web服务器的话可以选择nginx
,不过由于最初尝试的时候走了弯路,我最后选择的是caddy
,这玩意儿是golang编写的,应付中低负载完全足够,而且Go语言编译得到的二进制几乎没有外部依赖,可以放心丢上去。
梳理
梳理一下请求路径:
请求 -> CDN -> CDN缓存/回源函数caddy -> NAS存储博客本体/数据库
-> 动态部分回源函数 -> caddy -> php-fpm ↑
其中NAS将被挂载到函数容器上(对应操作可以通过funcraft或者控制台完成)作为一个可读写的块设备
Funcraft
错误的尝试
最初的时候,我尝试静态编译nginx
和php-fpm
,或者将它们的动态链接库打包和程序一起上传,经历了很多麻烦且未成功,直到我发现了阿里云自己的Funcraft工具,让你的CustomRuntime环境搭建变得超级方便
安装
请按照GitHub上的指示安装,并配置好区域和ACCESS ID/SECRET等
目录结构
你可以将任意一个目录变成你的函数环境,结构如下:
|
| - .fun
| | - nas
| | | - blabla
| |
| | - build
| | - blabla
|
| - bootstrap
| - template.yml
| - Funfile
| - something else
其中.fun
目录是通过fun nas
、fun build
等命令自动生成的,你可以使用fun nas
创建一个新的nas,或者将已有的nas与之关联,并将对应目录里的内容增量同步到阿里云NAS服务中;fun build
将根据Funfile
内的内容安装你需要的依赖并与标准镜像进行差分,替你管理好函数的依赖并打包成函数。template.yml
则包含了函数的全部配置信息,包括容器大小、NAS的挂载点、自定义函数地址等等。
Funfile
这里给出一个示例,关于如何使用Funfile直接安装php-fpm环境:
RUNTIME custom
RUN apt-get update
RUN apt-get -y install apt-transport-https lsb-release ca-certificates wget
RUN wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
RUN sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list'
RUN apt-get update
RUN fun-install apt-get install php7.2-fpm
RUN fun-install apt-get install php7.2-common
RUN fun-install apt-get install php7.2-gd
RUN fun-install apt-get install php7.2-json
RUN fun-install apt-get install php7.2-mbstring
RUN fun-install apt-get install php7.2-mysql
RUN fun-install apt-get install php7.2-opcache
RUN fun-install apt-get install php7.2-soap
RUN fun-install apt-get install php7.2-sqlite3
RUN fun-install apt-get install php7.2-xml
RUN fun-install apt-get install php7.2-zip
语法非常简单,几乎就是直接RUN fun-install
加上bash命令,通过fun build
之后,对应的环境将以差分的形式作为函数的一部分,并在之后被打包上传。
template.yml
给出一个template的示例,几乎不需要解释
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
BlogFC:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: A trivial blog.
Role: '{ROLE}'
VpcConfig:
VpcId: {VpcId}
VSwitchIds:
- {VSwitchId}
SecurityGroupId: {SecurityGroupId}
NasConfig:
UserId: 1000
GroupId: 1000
MountPoints:
- ServerAddr: '{ServerAddr}:/'
MountDir: /mnt/nas
InternetAccess: true
typecho:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: index.handler
Runtime: custom
CodeUri: './'
MemorySize: 192
Timeout: 10
InstanceConcurrency: 20
Events:
httpTrigger:
Type: HTTP
Properties:
AuthType: ANONYMOUS
Methods: [ 'HEAD', 'POST', 'PUT', 'GET', 'DELETE']
{CustomDomain}:
Type: 'Aliyun::Serverless::CustomDomain'
Properties:
Protocol: HTTP,HTTPS
RouteConfig:
Routes:
'/*':
ServiceName: BlogFC
FunctionName: typecho
里面具体各个项目的解释都可以在funcraft
的GitHub页面找到,并且这个template文件是可以通过控制台生成的。
我推荐先在网页上创建好VPC、安全组、NAS以及一个拥有listRAM
、fullVPC
、fullFC
、fullNAS
权限的RAM用户,并在函数控制面板创建一个自定义函数,在其配置页面将NAS手动挂载上去,然后下载配置文件(导出函数配置),你就得到了一个template.yml文件,直接用对应文件的内容来搭配funcraft
配置函数。
实际操作
上传博客程序
首先通过fun nas init
来初始化NAS,初始化完成后将博客程序和数据库文件放进.fun/nas
对应文件夹下,再通过fun nas sync
将文件上传
下载caddy
下载caddy并放到目录里
配置文件:
:{$FC_SERVER_PORT} {
proxy / localhost:65530 {
header_upstream Host capriccio.moe
header_upstream X-Real-IP {>X-Forwarded-For}
header_upstream X-Forwarded-For {>X-Forwarded-For}
}
}
:65530 {
root /mnt/nas/typecho
gzip
rewrite {
if {path} not_has admin
to {path} {path}/ /index.php
}
fastcgi / 127.0.0.1:65535 php {
root /mnt/nas/typecho
}
header /img Cache-Control "max-age=2678400"
header /usr/themes/handsome/usr/img Cache-Control "max-age=15552000"
header /usr/themes/handsome/assets Cache-Control "max-age=15552000"
}
我通过header_upstream的Host欺骗typecho程序让它以为域名是capriccio.moe,实际上函数的域名并不是这个,capriccio.moe是CDN的域名,如果不做这个trick会有很多毛病(rewrite部分实现伪静态;最后三行header是为了给静态资源加上Cache-Control头。
另外还有一点,typecho似乎默认会把{remote}当作用户的ip,导致所有的ip记录都是"::1",为了解决这个问题,需要在config.inc.php中增加如下内容(参考阿里云开发者社区):
/** 防止CDN造成无法获取客户真实IP地址 */
if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
{
$list = explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']);
$_SERVER['REMOTE_ADDR'] = $list[0];
}
bootstrap
这个抄的阿里云给的wordpress一元建站的,注意到我实际上把php和caddy的配置文件都放到了conf目录里,可以自行调整
#!/bin/bash
set +e
php-fpm7.2 -c /code/conf/php.ini -y /code/conf/fpm.conf
/code/caddy -quiet -conf /code/conf/caddy.conf
while true
do
php_fpm_server=`ps aux | grep php-fpm | grep -v grep`
if [ ! "$php_fpm_server" ]; then
echo "restart php-fpm ..."
php-fpm7.2 -c /code/conf/php.ini -y /code/conf/fpm.conf
fi
caddy_server=`ps aux | grep caddy | grep -v grep`
if [ ! "$caddy_server" ]; then
echo "restart caddy ..."
/code/caddy -quiet -conf /code/conf/caddy.conf
fi
sleep 10
done
另外fpm.conf我自己精简了下:
[global]
error_log = /dev/null
[www]
user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
listen = 127.0.0.1:65535
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
;php_admin_value[error_log] = /var/log/fpm-php.www.log
;php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 32M
部署
首先fun build
,该命令需要安装docker,经过一段时间build完成;
完成后fun deploy
,你将会看到一些提示,包括打包后的函数包大小,和部署后的更改项。
CDN等
在阿里云CDN控制台中可以直接添加回源目标为函数计算的加速域名,实际测试来看,CDN回源函数计算似乎走的是阿里云内网,不会再单独计费,nice!
由于我的博客域名没有备案(moe顶域也不能备),只能使用国际CDN,大陆访问速度其实不是很快,不过无所谓了(そんなのも関係ないですよね~)
成本
四月份博客支出为0.00元,这个月目前已经花了1分钱,原因是HTTPS请求数较多(
建议改成:零 元 建 站
大致就是以上这些步骤,如果有什么疑惑可以再参照一下阿里云的1元建站
One comment
陈澜憾:文章真不错https://www.renhehui.com/renhehui/2399.html