要非常感谢 memos 项目这是我第一个 sponsor 的 GitHub 项目,没有它我也就无从谈自己的 Memos 服务这件事。在动手搭建之前我打算先思考一个问题:为什么我这么看重为自己搭建 Memos Service?
帮助自己了解人肉上线一个生产可用的服务需要些什么技术?如果我自己运营一家一人公司的话哪些技术活是我必须掌握的?
现在有很多非常成熟的服务可以使用,为什么还需要一个完全由自己掌控的 Memos Service?
搭建自己的 Memos Service 这个事情本质上还是跟“我想要什么?”这个命题相关,现阶段我自己的一些思考和想法是这样的:
下面开始正文部分,会详细介绍我是如何部署 Memos Service 的。
部署之前需要购买一台服务器,出于性价比考虑,个人使用的情况,我只尝试过 AWS 的实例 ec2.t3.micro
和阿里云的实例 ecs.t5-lc1m1.small
(为了避免推销电话的骚扰阿里云我用的是 alibabacloud.com),为什么这么选型,以下是一个简单的评估方法:
云服务器的带宽,指的是出网带宽,用户发起请求,服务器发送数据给终端时,会占用这一部分的带宽。假如云服务器的带宽是 1M,最大的传输速度是 128kb/s,当用户浏览网站的时候,云服务器向用户发送数据,传输速度就是 128kb/s,1M=1024/8=128kb/s
。这个传输速度,看起来很慢,但实际上很多时候是够用的。我们浏览的网页,大多由文字和图片组成,一个汉字才 2 个字节,图片经过压缩,通常也在几十 kb 左右。只要页面内容不是特别多的话,1M 带宽的速度,跟 5M 带宽的打开速度没有什么差别。当然,这只是算同一时间,只有一个用户在访问网站的情况。如果网站同时有两个用户在浏览内容,理论上每个用户只能分到 60 多 kb,如果 10 个用户同时浏览,每个人只有 12.8kb/s 的速度,网站打开速度就会非常慢。那么,1M 的带宽到底能承受多少人在线呢?根据用户每秒请求数据量的大小估算,接口类的用户,每秒请求 10 次,每次数据量是 50 个汉字( 100 字节),1M 的带宽可以承载的用户数量为 128*1000/10/100=128 个用户
。用户需接收图片,假设每秒下载一次图片大小为 10kb,那么可以同时承载 12.8 人。如果是个人博客网站,一篇文章 1000 字,还会配 2 张图,那么这一篇文章大小在 100kb 左右,相当于每秒可以接收一个用户的访问。1 秒可以接收一个用户,相当于每小时能接收 3600 个用户,一天就是 86400 名。当然,这个只是理论数据,用户的访问不可能那么均匀,也不可能每秒请求一次。
另外还有一个非常经验型的判断方法,一般来说日均两三千 IP 以下的网站,1M 的带宽就够用了。另一种是观测云服务提供商的监控后台,出网带宽经常处于 128kb/s 峰值时,说明需要升级带宽了。
根据上述的判断方法 ECS 实例选型 1C1G 1M 完全够用了即 ec2.t3.micro
和 ecs.t5-lc1m1.small
完全满足要求。
在 Linux 上部署 Memos Service 容器服务本身没有什么特殊的,具备基本的 Linux 的知识就可以了,这里主要是参考了 memos 提供的指导文档:Self-Hosting Memos,简言之就是通过 docker 启动 Memos 容器:
# choose one of them to startup Memos container.
# 1. startup with docker-run cmdline
docker run -d \
--init \
--name memos \
--publish 5230:5230 \
--volume ~/.memos/:/var/opt/memos \
ghcr.io/usememos/memos:latest
# 2. startup with docker-compose cmdline
# docker-compose.yml
## version: "3.0"
## services:
## memos:
## image: ghcr.io/usememos/memos:latest
## container_name: memos
## volumes:
## - ~/.memos/:/var/opt/memos
## ports:
## - 5230:5230
docker-compose up -d
为了让 Memos 服务能够公网访问需要进行域名配置,这涉及到多个组件的配置包括:Nginx、HTTPS、CNAME。
完成云实例的购买之后可以在域名管理中添加对应的域名配置,我为 Memos 服务配置了一个子域名,如果你的 aws 提供了 public DNS 则可以配置一个 CNAME 指向该 public DNS,如果你的 ecs 提供了 public IP 则可以配置一个 A 指向该 public IP。
Memos 容器服务启动之后需要一个反向代理才能与域名连接,所以配置 Nginx 充当反向代理:
server {
server_name your-domain-name.com;
location / {
proxy_pass http://localhost:5230;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
配置 HTTPS 是通过 Let's Encrypt 申请 SSL证书来完成的:
注意在申请之前需要确保域名 CNAME 配置已经指向你的服务器。
Memos Services 存储可以根据存储数据类型的不同大致分为两类:
其中,文本数据以及管理相关的元数据 Memos 默认使用 SQLite 存储,如果有可用的 MySQL 服务也可以用 MySQL 替代 SQLite(出于对数据库服务的信任,如果使用了 MySQL 下文提及的数据备份可以不用配置)。非文本数据的主要考虑使用 OSS 对象存储用来加速访问,例如 Cloudflare R2,AWS S3,Aliyun OSS 等。
启动 memos 容器实例需要增加 MySQL 的配置参数:
--driver mysql
--dsn dbuser:dbpass@tcp(dbhost)/dbname
这样容器部署启动命令如下:
docker run -d \
--name memos \
-p 5230:5230 \
-v ~/.memos/:/var/opt/memos \
ghcr.io/usememos/memos:latest \
--driver mysql \
--dsn 'root:password@tcp(localhost)/memos_prod'
同时还可以考虑通过环境变量管理 MySQL 相关参数:
export MEMOS_DRIVER=mysql
export MEMOS_DSN=root:password@tcp(localhost)/memos_prod
docker run -d \
--name memos \
-p 5230:5230 \
-v ~/.memos/:/var/opt/memos \
ghcr.io/usememos/memos:latest \
--driver ${MEMOS_DRIVER} \
--dsn ${MEMOS_DSN}
对于存量的 SQLite 数据可以利用 Memos 自带的迁移命令将数据迁移到 MySQL:
/usr/local/bin/memos \
--driver mysql \
--dsn 'dbuser:dbpass@tcp(dbhost)/dbname' \
copydb --from sqlite://path_to_your_memos_prod.db
我采用的是 Cloudflare R2 作为非文本数据的存储,主要是因为 R2 每个月都有免费额度,目前对我个人使用来说是够用了。当然在进行 Cloudflare R2 的配置之前需要注册一个 Cloudflare 账号。
Memos 最重要其实是记录的数据,所以需要定期备份数据,无论是用 SQLite 还是用 MySQL 都应该养成定期备份的服务。我使用 Github Repo 进行自动备份,这种备份方法有一个问题就是受到 git LFS 的限制即最多 2GB 大小的备份文件,具体参考 About Git Large File Storage 说明。由于 Memos 大多是文本数据,所以 2GB 大致可以保存 10 亿汉字,目前来看足够用了。
为了能够自动备份到 GitHub 上,我自己写了一个脚本工具做自动化 commit 提交:
# memos data directory is ~/.memos/
tar -czf memos-archive.tar.gz ~/.memos/memos_prod.db
message="backup in `date '+%Y%m%d%H%M'`"
git add -A .
git commit -s -m "${message}"
git push
关于 GitHub Repo 配置的事情可以参考文档 Connecting to GitHub with SSH 。
为 Memos 备份配置定时任务,这样系统就自动执行备份程序:
/etc/crontab
中增加自动备份定时任务,例如每天凌晨3点进行备份;# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
# You can also override PATH, but by default, newer versions inherit it from the environment
#PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
# memos backup
0 03 * * * root sh /root/memos-backup/backup.sh
除了直接访问 Memos 的方式之外,我还在子域名中定制了一个只读的 Memos 用于进行公共展示。
<iframe
id="tweets"
title="talky talky"
src="https://memos.edony.ink/"
style="height: 100%; width: 100%;"
onload="resizeIframe(this)"
>
</iframe>
在 code injection 中插入 resizeIframe 的实现代码用于自动修改 iframe 的大小:
function resizeIframe(obj) {
obj.style.height = this.document.documentElement.scrollHeight + 'px';
}
支持 Memo 嵌入 Ghost 就完成了,具体效果可以参考:
Memos 推荐的 iOS 客户端 Moe Memos 解决移动端使用的问题,我单独为 iOS 客户端生成了一个专用的 Access Token 出于安全考虑定期轮转,具体如下所示:
iOS 效果如下:
Memos 还支持 Style 和 Script 自定义配置,CSS Style 我还没有做过定制,我之前在配置 R2 的时候碰到 Cross-Origin Resource Sharing (CORS) 的问题(其实是配置错误,正确配置是没有问题的),我自己通过 Script 自动替换 Memos 中的 R2 object access URL,这个我在 Memos 的 issue 中有分享。主要就是设置的 Additional script 中添加如下代码,代码比较丑了解一下意思就行🤪:
function refresher() {
setTimeout(function () {
for (item of document.getElementsByClassName('mt-2')) {
for (img of item.getElementsByTagName('img')) {
if (img.src.startsWith('https://${cloudflare R2 bucket URL}')) continue;
img.setAttribute('src', 'https://${cloudflare R2 bucket URL}'' + img.src.split('/')[img.src.split('/').length - 1]);
}
}
}, 1500)
}
setInterval(function () {
refresher();
}, 30000)
根据 Memos 的 Telegram bot 的指导文档配置了机器人自动转发 memo 消息,由于我自己还在为博客维护一个私有的 Channel,希望能够将 memos 自动转发到 Channel 里面,于是我在 Memos 代码中做了如下定制,具体可以参考我的代码https://github.com/edonyzpc/memos/blob/main/api/v1/memo.go#L424(由于是个人定制所以 Channel ID 直接写死,代码 Review 的时候轻喷🤣)。效果如下: