初试云原生?用腾讯云Serverless(SCF)搭建Typecho博客

经历千辛万苦,我终于安置好了个人站。

开整前的胡扯

以前个人站在腾讯云的孟买轻量应用服务器上,访问速度感人。后来就将他合并进主站所在的北京应用服务器,但在国内的服务器提供网站服务都需要备案。个人站因为域名原因没法备案,所以大部分地区都会被屏蔽。后来我也动过租一个香港节点服务器的念头,但是看到恐怖的单价,我这个穷学生只得望而却步。

这可是起步价

前几天和朋友聊到了ipfs,和他分享了imalan的静态博客生成器。闲聊之余,我也对Vercel的静态网站托管服务起了兴趣。在简单了解后我得知Vercel可以托管PHP网站,便尝试将自己的typecho博客部署上去。然而在构建时生成的api.php超过了最大限制(50MB),我也只好作罢……

当然不可能。Vercel勾起了我对Serverless的好奇。经过一番研究后,我选择在腾讯云的SCF部署一个Typecho博客。

开整准备

  1. typecho本体(目前最新版本为1.2.1,经体验似乎没出现与1.2.0插件/主题不兼容的情况)
  2. Apache/Nginx(十分建议,不过非必选,用于在本地进行修改后的预览以及功能测试)

开整

部署前的准备

首先在腾讯云新建一个TDSQL-C数据库。建立数据库的详细教程不再给出,这里仅记录本例子的配置。实例形态选择Serverless,地域选择自己之后准备部署博客的地域(这里以广州为例),新建一个私有网络或使用默认,数据库版本选择MySQL8.0。算力配置和自动暂停用默认配置,默认字符集为UTF8MB4,表名大小写不敏感。

创建完成后进入集群详情,开启外网地址。

登录刚才创建的数据库,新建一个数据库,字符集使用utf8mb4,名称随意,如图本示例使用“example”。

新建数据库

如果你的typecho是从其他服务器迁移的(根目录下有config.inc.php),则可跳过第一次运行生成config.inc.php的步骤,直接转到创建scf_boostrap处。

将获得的Typecho本体部署到本地的Apache或Nginx上,进行第一次运行。关于如何将Typecho部署在web服务器上,网上教程漫天飞,本文不再赘述。

在第一次部署后,访问localhost会自动跳转到install.php,引导用户进行初始化。

初始化配置阶段,我们要将刚刚新建的数据库地址,端口(在高级选项里),数据库名,用户名和密码填进去。数据库适配器选择Pdo驱动Mysql适配器,数据库前缀自行设置,这里展示本例子的配置。

配置数据库

然后设置管理员账户后,typecho的初次设置就完成了,此时访问localhost,则会正常进入首页如图。

平平无奇的主页

此时再检查typecho的项目结构,会发现根目录下多了一个文件:config.inc.php。打开该文件,可以看到刚才的配置信息都已记录到该文件中,此处展示本示例的config.inc.php

代码语言:php
复制
<?php
// site root path
define('__TYPECHO_ROOT_DIR__', dirname(__FILE__));

// plugin directory (relative path)
define('TYPECHO_PLUGIN_DIR', '/usr/plugins');

// theme directory (relative path)
define('TYPECHO_THEME_DIR', '/usr/themes');

// admin directory (relative path)
define('TYPECHO_ADMIN_DIR', '/admin/');

// register autoload
require_once TYPECHO_ROOT_DIR . '/var/Typecho/Common.php';

// init
\Typecho\Common::init();

// config db
$db = new \Typecho\Db('Pdo_Mysql', 'typecho_');
$db->addServer(array (
'host' => '', //你的数据库地址
'port' => 22957, //数据库端口
'user' => 'root', //数据库用户名称
'password' => '
', //数据库用户密码
'charset' => 'utf8mb4', //数据库字符集
'database' => 'example', //数据库名称
'engine' => 'InnoDB',
'sslCa' => '',
'sslVerify' => true,
), \Typecho\Db::READ | \Typecho\Db::WRITE);
\Typecho\Db::set($db);

为了让他可以部署到SCF,我们要加入scf_bootstrap文件。这里先以SCF文档中展示的最简单的启动文件为例:

代码语言:txt
复制
#!/bin/bash
/var/lang/php7/bin/php -c /var/runtime/php7 -S 0.0.0.0:9000 index.php

这时的项目结构应如下:

代码语言:txt
复制
.
├─admin/
├─install/
├─usr/
├─var/
├─config.inc.php
├─index.php
├─install.php
├─LISENCE.txt
└─scf-boostrap

这样我们就做好了部署前的准备。

部署函数

进入serverless-函数服务,点击新建,选择“从头开始”。函数类型为Web函数,函数名称自己起。

地域选择与数据库相同的地域,运行环境为Php 7.4,时区为Asia/Shanghai(北京时间)。

将准备好的Typecho文件夹上传。这里展示本示例的配置。

选择国内地域的话,自定义域名需要备案

进入高级配置,描述按自己喜好修改,启动命令不用管,腾讯云会优先使用项目文件中的scf_bootstrap文件。

内存初始化超时时间按自己需求修改,执行超时时间建议不要低于五秒。

在网络配置中启用私有网络,选择和数据库相同的私有网络和子网。

完成以上配置后点击完成,函数就会开始部署。部署完成后进入触发管理。点击“创建触发器”,点击提交,就可以得到公网的访问路径。点击后出现Typecho主页,初步配置就完成了。

设置数据库从内网访问

在上一步中的网络配置中正确设置了私有网路,那么我们就可以通过内网访问数据库,提升访问速度和安全性。

进入函数管理,点击函数代码,打开config.inc.php,修改其中有关数据库的内容。将数据库地址改为其内网地址,端口改为3306,其余内容不需要变动,点击部署即可。

config.inc.php

部署成功后,点击下方的访问路径,可以正常访问,则表示配置成功,记得将数据库的外网地址关闭。

配置handler.php

此时我们点击Typecho主页的登录,会发现仍然会跳转到主页,无法正常访问后台。我们需要修改逻辑,使得可以访问其他PHP文件。

这里我们可以借鉴 Serverless Cloud Framework WordPress 组件,参考其handle.php的内容。以下直接给出解决步骤:

src目录下新建handle.php,并输入以下内容:

代码语言:txt
复制
<?php
error_reporting(0);

$extension_map = array(
"css" => "text/css",
"js" => "application/javascript",
"png" => "image/png",
"jpeg" => "image/jpeg",
"jpg" => "application/x-jpg",
"svg" => "image/svg+xml",
"gif" => "image/gif",
"pdf" => "application/pdf",
"mp4" => "video/mpeg4",
"bmp" => "application/x-bmp",
"c4t" => "application/x-c4t",
"img" => "application/x-img",
"m2v" => "video/x-mpeg",
"mp2v" => "video/mpeg",
"mpeg" => "video/mpg",
"ppt" => "application/x-ppt",
"rm" => "application/vnd.rn-realmedia",
"swf" => "video/mpeg4",
"tif" => "image/tiff",
"tiff" => "image/tiff",
"ttf" => "application/x-font-ttf",
"woff" => "application/x-font-woff",
"vcf" => "text/x-vcard",
"wav" => "audio/wav",
"wma" => "audio/x-ms-wma",
"wmv" => "video/x-ms-wmv",
"apk" => "application/vnd.android.package-archive",
"m1v" => "video/x-mpeg",
"m3u" => "audio/mpegurl",
"mp2" => "audio/mp2",
"mp3" => "audio/mp3",
"mpa" => "video/x-mpg",
"mpe" => "video/x-mpeg",
"mpg" => "video/mpg",
"mpv2" => "video/mpeg",
"rmvb" => "application/vnd.rn-realmedia-vbr",
"torrent" => "application/x-bittorrent",
);

request_uri = explode(&#34;?&#34;, _SERVER['REQUEST_URI']);
local_file_path = _SERVER['DOCUMENT_ROOT'] . urldecode($request_uri[0]);
remote_file_path = &#34;/mnt/&#34; . urldecode(request_uri[0]);

if ( $local_file_path == FILE ) {
http_response_code(400);
echo 'Sorry';
exit();
}

$db_mode = "";
$db_mode = getenv("DB_MODE");
$cdb_st = microtime(true);
split = explode(&#34;.&#34;, local_file_path);
extension = end(split);
mapped_type = extension_map[$extension];

if ( mapped_type &amp;&amp; file_exists( remote_file_path ) ) {

header(&#34;Content-Type: {$mapped_type}&#34;);
$file_size=filesize($remote_file_path);
header(&#34;Accept-Length:$file_size&#34;);
$fp=fopen($remote_file_path,&#34;r&#34;);
$buffer=1024;
$file_count=0;
while(!feof($fp)&amp;&amp;($file_size-$file_count&gt;0)){
    $file_data=fread($fp,$buffer);
    $file_count+=$buffer;
    echo $file_data;
}
fclose($fp);

} elseif ( mapped_type &amp;&amp; file_exists( local_file_path ) ) {

header(&#34;Content-Type: {$mapped_type}&#34;);
$file_size=filesize($local_file_path);
header(&#34;Accept-Length:$file_size&#34;);
$fp=fopen($local_file_path,&#34;r&#34;);
$buffer=1024;
$file_count=0;
while(!feof($fp)&amp;&amp;($file_size-$file_count&gt;0)){
    $file_data=fread($fp,$buffer);
    $file_count+=$buffer;
    echo $file_data;
}
fclose($fp);

} elseif ( extension == &#34;php&#34; &amp;&amp; file_exists( local_file_path ) ) {

$cdb_et = microtime(true);
$cdb_time = ($cdb_et - $cdb_st) * 1000 . &#39;ms&#39;;
header(&#34;X-cdb-time:{$cdb_time}&#34;);
header(&#39;Cache-Control:no-cache,must-revalidate&#39;);
header(&#39;Pragma:no-cache&#39;);
header(&#34;Expires:0&#34;);
header(&#34;X-ExecFile: {$local_file_path}&#34;);
require( $local_file_path );

} elseif ( substr(local_file_path, -1) == &#34;/&#34; &amp;&amp; file_exists( local_file_path . "index.php" ) ) {
$cdb_et = microtime(true);
cdb_time = (cdb_et - $cdb_st) * 1000 . 'ms';
header("X-cdb-time:{$cdb_time}");
header('Cache-Control:no-cache,must-revalidate');
header('Pragma:no-cache');
header("Expires:0");
exec_file_path = local_file_path . "index.php";
header("X-ExecFile: {$exec_file_path}");
require( $exec_file_path );
} else {
$cdb_et = microtime(true);
cdb_time = (cdb_et - $cdb_st) * 1000 . 'ms';
header("X-cdb-time:{$cdb_time}");
header('Cache-Control:no-cache,must-revalidate');
header('Pragma:no-cache');
header("Expires:0");
$exec_file_path = dirname(FILE) . '/index.php';
header("X-ExecFile: {$exec_file_path}");
require( $exec_file_path );
}

然后修改scf_bootstrap文件:

代码语言:txt
复制
#!/bin/bash
/var/lang/php7/bin/php -c /var/runtime/php7 -S 0.0.0.0:9000 handle.php

此时的项目结构应当如下:

代码语言:txt
复制
.
├─admin/
├─install/
├─usr/
├─var/
├─config.inc.php
├─handle.php
├─index.php
├─install.php
├─LISENCE.txt
└─scf-boostrap

完成后将其部署,就可以正常登录和访问后台了。

自定义域名

如果需要自定义域名,则需将触发器升级为API网关标准版。

进入触发管理,点击”升级至API网关标准版“。

如图,没啥好说的

点击你的API服务名,进入API网关控制台。进入自定义域名,将自己的域名进行配置,详细步骤不再展示。

补充内容

  1. 需要新增插件或主题时,只需将其上传到src/usr下的对应目录即可。
  2. 部署在SCF的Typecho将无法正常使用上传功能。这里给出两个方法解决:

一,将需要上传的图片/文件保存在图床,然后在博文中引用。

二,使用COS插件,填补博客的上传功能。我使用的是:腾讯云对象存储(COS)插件。设置时需关闭“在本地保存”功能。

整完后的胡扯

说实话,作为一介穷学生,以前几乎是没接触过云原生的,顶多用用docker。这次也算是摸到了云原生的一点边,确实是次十分新奇的体验。本文是咱第一次写这种技术教程,主要动机是“阿里云都有部署typecho的教程和模板,腾讯云居然一点内容都没有”,遂在折腾完后扯了这么一篇。没什么技术含量,甚至很多步骤都是“手把手”级别,希望能帮到其他初试serverless的人。如果本文有错漏或是可以改进之处,也希望得到各位大佬斧正,十分感谢。