0%

前言其一

本篇博客写作之时,本站的域名已经从 https://blog.goatman.me 切换到 https://blog.xdsoar.net,原因是为了将站点迁移到国内,只好更换了可以在国内备案的域名。原域名已经做了301重定向处理,不过还是建议看到这里的读者更新一下地址。

使用国内的对象存储服务来做静态站点托管有一个奇怪的矛盾。做静态站点托管,我需要有一个已备案的域名;要备案域名,我需要购买云主机;如果我购买了云主机,那么为什么我还需要对象存储做静态站点托管?

对象存储做静态站点是我特别喜欢的公有云服务,价格低廉又安全可靠,但为此单独买一台云主机着实让我受不了。直到最近发现阿里云购买函数服务包也可以享受代备案,此事才算了解。

前言其二

这个博客的最早的几篇博文,写的就是当初在s3上部署的流程,在那之后的几个实验项目我也都是按照当时博文里写的流程来做发布。不过最近几年里,我也在思考能不能更进一步,让整个流程以更顺畅的方式进行。CI工具已经打通了从代码到制品的全过程,代码到服务的最后一公里,或许就要靠IaC工具来实现了。

去年我花了不少时间研究aws的CloudFormation,老实说看的我云里雾里。折腾了半天也没搞明白,想了想这东西可能真的不适合个人使用。直到熟悉IaC的朋友给我推荐了Terraform才算找到了条可行的路子。

本身迁站是个很繁琐的事情,结合Terraform来做,本是可以省不少事的。不过实际使用下来,terraform与阿里云的衔接还是有不少坑,所以实际并没有那么顺利。好在这种事情折腾一次,后面的项目都可以复用,还算是比较便利。

关于Terraform

Terraform是HashiCorp创建的IaC工具,在IaC领域目前姑且算是较为成熟的产品。从我短暂的使用体验上来说,算不上优秀,只能说是够用的程度,当然这可能也和我使用的是阿里云的provider有关。因为阿里云的配套设施对比aws确实算不上成熟,一些配置项在控制台页面可以配置,但是在terraform中无法配置。我本来以为是provider没有实现,想着实在不行就提pr加一下,都拉了代码准备动手了,结果阿里云的API本身就没有提供这块的配置项……

因为目前我没有使用Terraform来管理aws,所以aws的provider姑且不评价。不过主观评估,这类第三方工具通常是很难做到完全覆盖第一方产品的功能的。

从动机上考虑,云服务厂商恐怕更希望提供独有的无可替代的服务。而目前除了aws s3成了对象存储的事实标准以外,各类云服务其实并没有特别统一的标准。因此即使Terraform声称支持众多云服务提供商,也不可能做到一次编写,到处运行的效果,想要把已有的aws上的服务,改一改provider就迁移到阿里云上,依旧是做不到的。

那么IaC的作用是什么呢?我个人认为是为基础设施提供更好的运维方式。特别是采用声明式的管理方式,可以确保基础设施的状态是受控的,可感知的。当然前提是能够完全通过IaC工具进行基础设施管理,使它成为单一事实来源。

Terraform的官方文档比较冗长,为了快速上手,我参考的是阿里云的文档)。下面我以本次博客部署为例,讲述使用Terraform管理基础设施的全过程。

开始之前

开始之前,需要做如下准备:

  • 安装Terraform命令行客户端
  • 创建阿里云用户对应的access key,并在RAM中赋权。
  • 购买一个域名

其中域名或许也可以通过Terraform创建,不过域名毕竟不像其他资源那么随意,所以我没有使用Terraform管理。第二步原则应该创建一个IaC专用的用户并设计权限范围,个人使用并没有那么讲究,我就直接使用了实名用户。

准备好之后就可以使用Terraform进行资源创建了。这里再啰嗦两句,使用Terraform的过程中会产生一些记录资源状态的文件(tfstate),又或者是创建的用户密钥文件,如果有团队协作的需求,使用对象存储是个好办法,别忘了注意访问权限。个人使用来说,放到一个私人的git仓库当然也是可以的,同样要注意不能放到公开仓库里

至于一些可复用的配置模板或者是分享给别人用的参考配置之类的,可能gist是个不错的选择。

配置对象存储与授权

首先厘清一个静态博客站点需要用到哪些云服务

  • 一个oss bucket用于存储静态站点的文件
  • 一个dns记录,作为博客域名
  • 一个用户,以及对应的RAM,用于CI部署制品到OSS
  • (可选)一个用于加速访问的CDN
  • (可选)一个SSL证书,支持https访问

前三步基础配置其实很简单,参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
provider "alicloud" {
alias = "hz-prod"
region = "cn-hangzhou"
}

resource "alicloud_oss_bucket" "bucket-blog-log" {
provider = alicloud.hz-prod
bucket = <bucket_name_for_log>
acl = "private"
force_destroy = true
}

resource "alicloud_oss_bucket" "bucket-blog" {
provider = alicloud.hz-prod

bucket = <bucket_name_for_blog>
acl = "public-read"
force_destroy = true

# 配置CDN加速
transfer_acceleration {
enabled = true
}
# 静态网站的默认首页和404页面
website {
index_document = "index.html"
error_document = "error.html"
}
# 访问日志的存储路径
logging {
target_bucket = alicloud_oss_bucket.bucket-blog-log.id
target_prefix = "log/"
}

# 防盗链设置
referer_config {
allow_empty = true
referers = ["http://<blog_url>","https://<blog_url>"]
}
}


resource "alicloud_ram_user" "blog_user" {
name = "blog_deploy"
display_name = "blog_deploy"
mobile = "86-18688866888"
comments = "devops user for blog"
force = true
}

resource "alicloud_ram_access_key" "ak" {
user_name = alicloud_ram_user.blog_user.name
secret_file = "<storage_path>/accesskey.txt"
# 保存AccessKey的文件名
}

resource "alicloud_ram_policy" "blog_policy" {
policy_name = "blog_oss_policy"
policy_document = <<EOF
{
"Statement": [
{
"Action": [
"oss:*"
],
"Effect": "Allow",
"Resource": [
"acs:oss:*:*:<bucket_name_for_blog>",
"acs:oss:*:*:<bucket_name_for_blog>/*",
"acs:oss:*:*:<bucket_name_for_log>",
"acs:oss:*:*:<bucket_name_for_log>/*"
]
}
],
"Version": "1"
}
EOF
description = "privilege for deploy to blog bucket"
force = true
}

resource "alicloud_ram_user_policy_attachment" "blog_attach" {
policy_name = alicloud_ram_policy.blog_policy.policy_name
policy_type = alicloud_ram_policy.blog_policy.type
user_name = alicloud_ram_user.blog_user.name
}

# 如果不需要使用独立cdn, 则将dns解析到oss专用的加速域名上
resource "alicloud_alidns_record" "blog_record" {
domain_name = "<domain>"
rr = "<sub_domain>"
type = "CNAME"
value = "${alicloud_oss_bucket.bucket-blog.id}.oss-accelerate.aliyuncs.com"
remark = "blog domain"
status = "ENABLE"
}

整个文件中需要使用者自己定义的参数都以<custom parameter>的方式标出,应该说还是挺通用的。如果想新建一个别的站点,只要把整个文件复制一份,并填写相应的参数就好了,然后一条terraform apply就可以创建出来。

不过很遗憾的是,做完上面这些步骤之后,还有两项设置,只能在控制台进行。分别是设置bucket可以访问的域名,以及设置子页面的首页,即访问aaa.com/bb/时展示aaa.com/bb/index.html的内容。由于这两项配置甚至连阿里云的API都没有提供,目前看来是很难实现了。我能想到的就是在hcl中声明,当bucket被创建的时候,打印一条消息,提醒需要手动执行的这两个步骤了……

除了这两项做不了的配置,整体过程应该说还是很顺畅的,配置完成后,本地也有了访问对象存储的密钥,先本地做一趟实验把命令跑通,再修改博客对应的流水线脚本,整个迁移过程就算完成了。

配置SSL证书

其实按上面的流程走完,OSS内建的加速方案也已经开启了,原则上就只有配置SSL证书这一件事情要单独做,偏偏这件事情做起来又不是那么方便。经过前面对OSS相关API的摸排,我确信虽然控制台上提供了配置SSL证书的步骤,但是这个过程无法通过Terraform完成。如果觉得麻烦,这步也可以在控制台完成。

这一环节的另一复杂性在于,如果不想用阿里云的免费证书,使用诸如acme.sh之类的工具申请的免费证书只有3个月的有效期,为了能让证书自动更新,那么还需要定时作业来更新证书。简而言之,不是很有必要,而且很复杂,如果真的想干,那么就接着往下吧……

首先是再创建一个用户,并赋予更新dns record权限,用于申请证书。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
provider "alicloud" {
alias = "hz-prod"
region = "cn-hangzhou"
}


resource "alicloud_ram_user" "ssl_user" {
name = "sslUser"
display_name = "sslUser"
mobile = "86-18688866888"
comments = "for get ssl cert"
force = true
}

resource "alicloud_ram_access_key" "ak" {
user_name = alicloud_ram_user.ssl_user.name
secret_file = "accesskey.txt" # 保存AccessKey的文件名
}

resource "alicloud_ram_policy" "ssl_policy" {
policy_name = "ssl_policy"
policy_document = <<EOF
{
"Statement": [
{
"Action": "alidns:*",
"Resource": "*",
"Effect": "Allow"
}
],
"Version": "1"
}
EOF
description = "privilege for ssl"
force = true
}

resource "alicloud_ram_user_policy_attachment" "ssl_attach" {
policy_name = alicloud_ram_policy.ssl_policy.policy_name
policy_type = alicloud_ram_policy.ssl_policy.type
user_name = alicloud_ram_user.ssl_user.name
}

上面的配置里没有一个变量,属于拿来即用的简单配置。不过这里我分配的权限偏大了一点,给予了账户下DNS解析服务的所有权限,追求权限分配最小化的话可以更细化一点。

关于acme.sh的使用这里就不展开叙述了,网上相关资料很多,用acme.sh alidns做关键词就能搜到很多,比如这篇),你会发现这篇文章里占了较大篇幅的用户权限配置部分,在用了Terraform以后可以完全被上面的配置实现,有没有感受到一点IaC带来的便利?

这样就拿到了免费的证书,可以开始正式配置了。

由于CDN的配置中可以指定,所以可以通过重新配置CDN并指定SSL证书实现https。如果要进行以下配置,记得先在之前配置bucket的文件中,去掉关于dns记录的配置,并再次terraform apply,当然如果你想使用一个不一样的域名用于CDN的话,也是可以的。具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
provider "alicloud" {
alias = "hz-prod"
region = "cn-hangzhou"
}

resource "alicloud_cdn_domain_new" "domain" {
domain_name = "<cdn_blog_domain>"
cdn_type = "web"
scope = "global"
sources {
content = "<oss_bucket_url>"
type = "oss"
}
certificate_config {
server_certificate = <ssl_cert>
private_key = <ssl_key>
cert_name = "terra_ssl_cert"
cert_type = "upload"
}
}

resource "alicloud_alidns_record" "blog_record" {
domain_name = "<domain>"
rr = "<sub_domain>"
type = "CNAME"
value = "${alicloud_cdn_domain_new.domain.cname}"
remark = "blog domain"
status = "ENABLE"
}

这里有一个小细节,这个参数理论上可以从上一个关于oss bucket的配置中拿到。不过这个CDN的配置我单独拆了一个文件,原因是这个CDN的实现并不理想。如果按照上面的配置做一次terraform apply是没有问题,但是如果要做修改的话(其实相当于destroy再create),失败的可能性相当大。这主要是因为CDN这个资源的一致性较差,destroy后需要等待一段时间后才能使用重新创建。

而因为SSL证书有效期的问题,每隔几个月,就要手动destroy,等待一段时间后再apply。当然因为整个流程其实就是几个命令的事情,加一个cron任务,自动执行也不是多难的事情,倒也还算可以接受。另一个瑕疵点在于,删除了dns记录和cdn后,一段时间内这个域名就无法访问了。从可用性角度考虑,更新时采用AB两个CDN实例进行蓝绿切换会更合适。不过我这只是一个简单的博客站点,就不额外考虑这么多了。

后续

前面写了好几篇关于家用服务器(home server)的内容,但其实公有云也有很多实用又实惠的用法。但是记录过程要一页一页的截图,一些实验性质的操作做完再重复一遍也很麻烦,有了IaC工具以后确实方便了很多。后续我会把这些有趣的尝试记录下来,也算是一个系列吧。

背景

我有一个服役超过7年的android平板Sony z3 tablet compact,上面运行着号称android核心竞争力的应用:ehviewer。

长久以来,我都有每天登录ehviewer刷新一下订阅并下载新的漫画本子的习惯。之前我就有想过这个流程能不能优化,一方面每天手动刷新下载比较繁琐,并且若是不及时下载,一些资源在上传之后又会被删除。此外由于ehentai的资源限制,ehviewer下载原图并不方便,通常下载的还是重采样后的缩放图片,在平板上阅览虽然影响不大,若是作为仓鼠收藏则有点过意不去。

而且在长年使用之后,ehviewer中的下载列表变的很长,在翻阅上也有一些不便,大量漫画的管理并不是ehviewer的核心功能,基础的检索、收藏功能也是依托ehentai提供的api实现的。ehentai在功能上虽然没什么可以挑剔的,不过也曾面临过关站风险,而且部分版权物也会被删除,如果有办法做本地管理作为替代和补足,自然也更好。

订阅下载

使用到的软件:RSSHub、qBittorrent

本来以为整套流程都需要自己处理,结果调研的时候发现RSSHub+qBittorrent就可以把整个流程完美的解耦后串联了起来。

ehentai原站提供的RSS功能比较单一,无法满足个性化订阅的需要,不过有了RSSHub就比较方便了,这里主要用到的是RSSHub中ehentai的搜索路由。路由参考RSSHub官网的说明,格式为 /ehentai/search/:params?/:page?/:routeParams?。可以先在ehentai网站上搜索一次,复制网址后面的搜索参数。这里额外补充一点,其中页码参数page是从0开始而非1。以及因为要用于后续下载使用,所以还需要把获取种子地址参数打开。

例如我会订阅所有评分为5分的汉化漫画和同人志,最后的路由为:

/ehentai/search/f_cats=1017&f_search=language%3AChinese&advsearch=1&f_sname=on&f_stags=on&f_sr=on&f_srdd=5/0/bittorrent=1

qBittorrent在server端需要额外安装qBittorrent-nox来提供web管理功能,稍微需要注意,qBittorrent-nox的版本不能太低,否则是没有rss订阅功能的。把订阅地址扔到qBittorrent的RSS订阅里,配置好自动下载,整个流程就算完成了。本来还想着对于没有提供种子的漫画需要额外处理一下走http下载,搜了一下近半年的汉化漫画都提供种子文件,决定暂时先不折腾这块了。

当然这样一来下载的漫画就不方便在ehviewer里看了,需要额外的阅读工具,这部分在下面详述。

额外的EX

部分资源只在里站exhentai有,所以最好还是配置从里站下载。不过这比表站就要多几步折腾。

RSSHub本身支持检索exhentai,只需要在环境变量中添加EH_IPB_MEMBER_ID, EH_IPB_PASS_HASH, EH_SK, EH_IGNEOUS这四个参数,就会自动改成从里站爬取信息。这里有一个小坑,添加上述变量后,访问ehentai和exhentai,会采用用户的个性化配置,可能会对网页元素造成影响,影响爬取。可以在网站的个人设置中,添加一个默认的profile供RSSHub使用。

另外一个麻烦的问题是,exhentai只能在登录后访问,所以输出的exhentai的种子文件地址,在qBittorrent中是无法下载的。搜了一圈qBittorrent好像也没有提供在下载种子文件的时候配置自定义header这么偏门的功能。

RSSHub ehentai路由的作者也许在其他场景下碰到了这个问题,因此提供了一个EH_IMG_PROXY参数,用于替换生成的RSS文本中图像的链接为代理服务器地址,从而在RSS阅读器中可以看到exhentai上的封面,不过他可能没想过把这个参数应用在种子下载地址上……

这个思路倒是可行,在代理服务中手动配置Cookie到header中,应该可以解决exhentai无法访问的问题,遂尝试在nginx上添加一项代理配置

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name exhentai.<HOMELAB_DOMAIN>;
location / {
proxy_pass https://exhentai.org;
proxy_set_header Host exhentai.org;
proxy_ssl_server_name on;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie <EXHENTAI_COOKIE>;
}
}

测试了一下使用代理地址下载种子文件,没有问题,看来方法可行。

理论上可以把server_name直接配置成exhentai.org,然后在hosts或者自定义的dns中把这个域名指向homelab的内网ip,这样不需要修改RSS输出结果就能让qBittorrent通过反向代理下载到种子文件。不过exhentai的下载地址使用了https,虽然不清楚qBittorrent和ehentai在保障https通信安全上做的如何,使用自签证书或许可以绕过去,不过我也不太想干这种左右互搏的事情。

所以为了让qBittorrent订阅到的地址指向代理服务器,本来最后一步应该是给RSSHub提个PR,把代理地址应用到替换种子地址上,不过这样多少要费些时日。临时性的又想了个法子,反正是要做反向代理的,索性在nginx的反向代理里加一项配置,把RSSHub的响应体中的exhentai域名直接替换成代理的域名。修改的配置也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
server_name rsshub.<HOMELAB_DOMAIN>;
location / {
proxy_pass http://<HOMELAB_DOMAIN>:<RSSHUB_PORT>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
sub_filter_once off;
sub_filter_types application/xml;
sub_filter "https://exhentai.org/" "http://exhentai.<HOMELAB_DOMAIN>/";
}
}

管理阅览

我之前试过用calibre来做管理,calibre也有从ehentai[获取元数据的插件](yingziwu/doujinshi_metadata_plugins: the calibre metadata plugins for doujinshi (github.com)),把ehviewer上的漫画导进去之后,先编辑好基础的名称和作者信息,然后用插件从e站获取余下的tag等信息。整个管理流程是没有问题,不过体验也说不上来有多好,另外calibre基本只能解决管理的问题,阅览的需求需要另外想办法。

本以为没有更好的方案就只能把这两步分开,结果意外发现Lanraragi用起来还不错。在管理功能上,比起calibre更适合做漫画和同人志的管理,也有插件从ehentai获取元数据。

此外在阅览方面也做的不错,网页端的功能算不上很完善但是够用,而且非常难得的有tachiyomi插件,移动端的体验也比较好。

因为Lanraragi和tachiyomi插件都是最近才开始用,暂时还没有什么大问题,后续折腾的时候碰到什么问题再单独更新吧。因为元数据都在本地,后期即使要迁移到其他管理方案上,也具有一定可行性。

唯一比较麻烦的是这个应用的后端是Perl写的,看起来有点头疼,希望后续不会有需要钻进去改代码的时候。

使用中的一个小插曲,下载的漫画我是放在nas上通过smb挂载到运行Lanraragi的虚拟机上,本想着数据库也直接存在nas上方便备份,结果应用一启动硬盘吵的像破锣似的,但是又没有大量读写。我本以为是因为频繁需要扫描文件变更来做更新,感叹以后漫画本子只能存ssd了,后来看了日志发现是因为smb权限问题导致Redis AOF持久化失败了疯狂重试导致的,算是给我刚服役不久的两块机械盘留了条活路。

这篇说一下homelab相关的内网访问配置,本来想写一些资源管理的内容,提纲列到一半发现存在一些依赖,只好先把网络配置的部分说了。

基本需求

在硬件部署完成以后,剩下的步骤其实都可以通过web端进行,而我大部分捣腾这台机器的时间也不是在家里,这样一来就需要能通过外部网络环境访问到家里的局域网。

出于安全考虑,需要尽可能少的暴露端口到公网上。此外,国内的家用宽带常规是不允许提供互联网服务的,暴露homelab上的web服务端口也可能给自己带来不必要的麻烦。

方案设计

动态域名解析

远程访问需要解决的第一个问题是获取家里宽带的ip。家用宽带的IP每次拨号都会发生变化,且存在固定时间间隔强制断开重新拨号的情况,从便利性上来说这一步也是必要的。不过方案也是现成的,很多路由器或者nas系统都提供了动态域名解析功能,一般是使用品牌商提供的子域名用于解析,直接使用即可。

不过因为我有几个托管在aws上的域名,就索性定义了一个二级域名,用一个定时脚本结合aws-cli工具来做更新,脚本是网上找的,因为aws查询IP的服务器地址是在国外,调用时走代理路线导致拿到代理的IP,于是换用了国内的ip查询服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/bin/bash

#Variable Declaration - Change These
HOSTED_ZONE_ID=<HOST_ZONE_ID>
NAME=<DOMAIN_NAME>
TYPE="A"
TTL=60

#get current IP address
#IP=$(curl http://checkip.amazonaws.com/)
res=$(curl myip.ipip.net)
res_sub=${res: 6}
IP=${res_sub%% *}
#validate IP address (makes sure Route 53 doesn't get updated with a malformed payload)
if [[ ! $IP =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
exit 1
fi

#get current

/usr/local/bin/aws route53 list-resource-record-sets --hosted-zone-id $HOSTED_ZONE_ID | \
jq -r '.ResourceRecordSets[] | select (.Name == "'"$NAME"'") | select (.Type == "'"$TYPE"'") | .ResourceRecords[0].Value' > /tmp/current_route53_value

cat /tmp/current_route53_value

#check if IP is different from Route 53
if grep -Fxq "$IP" /tmp/current_route53_value; then
echo "IP Has Not Changed, Exiting"
exit 1
fi


echo "IP Changed, Updating Records"

#prepare route 53 payload
cat > /tmp/route53_changes.json << EOF
{
"Comment":"Updated From DDNS Shell Script",
"Changes":[
{
"Action":"UPSERT",
"ResourceRecordSet":{
"ResourceRecords":[
{
"Value":"$IP"
}
],
"Name":"$NAME",
"Type":"$TYPE",
"TTL":$TTL
}
}
]
}
EOF

#update records
/usr/local/bin/aws route53 change-resource-record-sets --hosted-zone-id $HOSTED_ZONE_ID --change-batch file:///tmp/route53_changes.json

代理服务

这里的实现方式和科学上网相似,只要把服务端部署在家里的homelab上即可。部署VPN也是可以的,不过我个人感觉这种方式有点重,安全性上来说我觉得应该相差不大,都算的上是经过考验的。

最终暴露在公网上的端口只有两个,一个是http代理服务端口,另一个是方便登录维护的ssh端口。当然这两者都建议映射到五位数以上的端口上,不是说这样就能确保安全,至少能减少一些无聊的嗅探也算节约一些资源吧……

安全性上,ssh服务建议关闭密码登录,或者是使用Fail2Ban这类软件防止恶意爆破。

以上方案适用于家庭网络至少具有公网IP的情况。如果没有的话,则需要使用一些内网穿透的方案,像是frp、ngrok之类。不过这类方案需要一台vps转发,有公网IP的情况下我就不折腾这个浪费流量了。

另外我也准备了zerotier这条备用路线以备不时之需。据说因为是基于udp打洞的方式,在网络质量上由于运营商qos的缘故要比tcp差一些,我实测下来在跨网络运营商的情况下确实差一些,暂时只做备用。

反向代理

这一步不是必要的,不过个人觉得做了以后可以一定程度上优化访问体验。

由于在同一台服务器上部署了大量web服务,访问时通过端口区分,所以浏览器上会有很多类似192.168.x.x:xxxx这样的地址。一方面不方便自己记忆,另外似乎浏览器的密码管理对这种情况处理的也不太好。因此我加了一步用nginx作为反向的代理,根据不同的域名代理不同的后端服务。因为走的是内网代理路线,这个nginx是不需要暴露端口到公网的,也不存在安全性的问题。

nginx的配置比较简单,为了便于维护,核心的nginx配置我只做了基本的日志配置和引入配置目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;
server_tokens off;

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
gzip on;

include /etc/nginx/mime.types;
default_type application/octet-stream;

# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
}

每个服务的反向代理配置分拆为单独的配置文件,内容也很简单,以qbittorrent为例

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
server_name <qbit_domain>;
location / {
proxy_pass http://<qbit_domain>:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

这里需要解决一个小问题就是将各个服务配置的域名都指向homelab使用的局域网ip。域名配置有两种做法,可以在hosts文件或者路由器的dns配置里按需配置,将域名指向homelab使用的局域网ip。这么做比较灵活,域名想配成什么都行。

另一个做法是使用公开域名做泛域名解析。比如配置一条A记录为*.home.xxxx.com指向192.168.1.100。这个方案的好处是只要配一次就好了,不需要逐个设备配置,也不需要每加一个服务就逐个设备改一遍hosts。缺点就是域名设置上没那么灵活了。

需要注意一点,我在用openwrt做软路由的时候使用了dnsmasq服务,结果发现域名解析无法指向私网地址,解决办法是在防火墙设置中打开IP动态伪装。

如果代理路线不能保证通信安全的话,还可以在nginx上配置ssl证书,通过https线路访问,来起到加密通信的作用。

前言

这个系列的第一篇先来说一说数据备份。毕竟很多人(包括我)组建nas的第一需求,就是用于数据备份,保护自己的数据安全。

我也是趁着这次重新组建的机会,重新整理了自己的数据备份需求。

开始之前

在规划一切方案之前,有一件我一直觉得很重要的事情,就是先要识别自己有哪些数据资产。这件事情并不简单,甚至可以说很难,使用过的设备越多,在互联网上留下的足迹越多,这些数据可能就越散乱。有些数据可能就在哪部电脑或者手机里装着,而有些则可能只存在于某个供应商提供的网盘里,甚至有些数据,服务商觉得并不属于你,你只是被授权访问了…

识别有效的数据资产是件很繁杂的事情,这里就不展开讲了。总之,在开始之前,我已经把所有我觉得需要做备份的数据都标识好了,这是一切的起点。

基本策略

数据备份有个被称为“3-2-1”的原则。即一份数据包含三份副本,保存在两种不同的介质上,并保持有一份数据存放在异地。

这个原则对于个人用户来说,最简单的实践方案是这样

  1. 在原始设备上(比如手机、电脑)存一份
  2. 定期将数据备份到移动硬盘或NAS
  3. 再存一份数据到云盘

我个人并不反感云存储,所以这个策略对于多数数据备份场景都是合适的。很多人会提到对于网盘,需要区分”同步“和”备份“。原因是”同步盘“无法防止误操作,如果本地误删除了某个重要文件,”同步盘”会在云端同步删除文件。

对于这一点,我个人觉得很难界定。一些同步网盘也会提供如历史版本、近期删除的文件等功能,来弥补“同步”的缺陷。但是回过头来说,很多“同步盘”都提供了像“文件随选”这样的功能,文件只在需要的时候才会下载到本地来,这时候就要注意了。

其实对于云盘,多数时候我并不担心数据丢失的风险,这本是供应商会考虑的问题。但是从近几年的情况来看,云盘或者说云存储的商业模式并不稳定,这反倒是需要担心的。

对我而言,还有另外一种情况需要关注,就是对于一些大文件(比如照片、视频),单一设备可能存不下的时候,可能本地只会在nas上留存了一份,这时候就需要有额外的方案了。

备份需求

当然,也不是所有数据我都会遵从“3-2-1”原则来处理,实际上我大概把数据分了三类。

  1. 第一类是不希望丢失的重要数据,会严格遵从“3-2-1”模式。
  2. 第二类是在一定程度上可容忍丢失的数据。比如手机、电脑如果故障坏了,能够完全恢复当然好,但是只要圈定的重要数据不丢,重新装一遍系统、应用,做一遍配置之类的,也不是不能接受。这类数据我会挑最简单的备份方案来执行,比如手机、平板就用厂商提供的云服务,实在恢复不了就算了。
  3. 第三类主要是网上下载的各类资源,这类数据定期记录一下目录信息就好,丢了的话再下一遍就好了。

商业上在容灾方案中,经常会提到RTO、RPO指标,通俗来讲,这两个分别指故障发生后,恢复需要花费的时间,以及,能够容忍的丢失数据时长。

对个人用户来说,前者通常是一个相对宽裕的值。假如我电脑坏了,需要买台新的,耽搁几天再正常不过,没什么不能接受的。

后者就需要自己好好评估了,如果觉得丢失一天数据都无所谓,那每天晚上做一次备份就足够了。如果丢失一两个小时的数据都不能接受,那可能就需要在使用的工具软件上做好方案,能够支持更及时的备份。

不过其实我提到这个,是我觉得需要结合这两个指标评估好自己能管理好的数据范围。比如我NAS上管理的数据如果有50TB,假如有一天硬盘全挂了,我需要重新买一套硬盘再把数据从网盘下回来,结果发现网速太慢,光是下载数据就要花6个月…这也是不能接受的。

工具与实施

明确了策略和需求以后,实际实施起来就没有多复杂。主要用的工具包括:

  • syncthing,用于实时同步文档,当然这里的同步并不是备份。
  • borgbackup,用于本地备份,同时兼顾增量备份、保留历史等需求。
  • rclone、BaiduPCS-Go,用于将文件备份到云盘。

其中syncthing会安装在nas和常用的电脑上,将变更的文件实时同步回来。

另外其他如相机上的照片、视频,或是电脑上录制的视频,定期拷到nas上以后,原设备上会删除,需要额外备份一份。这里我暂时没想到特别好的方案,只是刚好旧nas的硬盘(已经使用6~7年)并没有故障,所以就先拿来做备份副本使用,用borg backup制作备份副本的同时进行加密,也解决了上传云盘(主要是百度网盘)的安全问题。

云盘的备份我现在同时在用百度网盘和onedrive。前者单价更低,但是即使文件已经做了加密,我依旧担心会因为各种原因导致文件在云端不可用,因为office365赠送了tb级别的onedrive空间,所以姑且先拿来用。使用rclone可以直接将原始文件加密备份到onedrive上。不过因为onedrive的空间较小,而且分散在多个用户下,使用上还是有一些不便。另外云厂商的提供的对象存储也是可以考虑使用的,可靠性比云盘可能会略好一些,不过价格也略高,对可靠性要求高,数据量不是特别大的话可以考虑使用。

本地的备份前后零零散散大概花了一周左右做完,之后上传两个网盘则在半个月左右(总共4t左右数据)。随着直播行业的热捧,国内宽带的上行带宽略有好转,百兆以上宽带基本提供了30兆左右的上行速度,额外加钱也有套餐可以提升到百兆的上行带宽。另外也有临时的方案,电信提供了为期1天的加速包,可以临时将上行速度提升到百兆,在跑满的情况下,一天可以上传700G左右的文件,我在处理数据备份的这几天使用,很快就完成了全部文件的上传备份。

重构

这篇博客在两个月前写完后,我一直觉得有点不满意,内容上虽然已经把现在做完的东西都讲了一遍,感觉没有重点,而且对于homelab提供的软件服务部分,本身就是在一直迭代更替的,全都更新在一篇里面感觉也不合适。思来想去,决定重构一番,本篇内容只集中在系统搭建上,对于软件的部分,将分篇章逐篇论述。

底层系统

本着所有系统都虚拟化了方便管理备份的原则,这次底层采用的是esxi。不过这只是凭着过去几年的刻板印象选的,事前没有做过很完善的调研,事后想想,也许PVE会更合适。

gen10 plus的唯一的pcie卡槽我用来接了m2转接卡来使用ssd作为系统盘,esxi本身是否放在ssd上并不重要,这块盘更主要的作用是存储虚拟机系统。不过因为sata控制器计划是直通给上层的nas虚拟机使用,所以底层系统自然是需要有独立的介质存储的,能存在机器里面多少还是比插u盘方便一些。整个pcie槽位只用来接一块ssd其实有点浪费,理论上来说分拆成2~4个m2接口或者提供2个2.5G网口都是可行的。不过支持这些功能的转接卡价格会高很多,同时和机器本身也可能存在这样那样的适配性问题。因为暂时来说我的需求只要接一块ssd就够了,所以就姑且先只用单接口的转接卡就行。

由于这次的iLO与Gen8存在一些差别,底层系统的安装比起gen8稍微要波折一点,设置gen10plus的iLO配置需要先找一台显示器接上,在BIOS中设置好iLO使用共享网口的相关配置,配好以后就可以把机器插上网线塞到柜子里去了,后面的环节都可以通过iLO远程配置。

安装esxi的过程很快,底层系统安装完成后,后面的系统安装都可以在esxi的web端进行。

truenas(文件服务)

首先是安装truenas系统,在esxi中上传系统镜像并创建虚拟机,再把sata控制器直通给truenas即可。出于稳妥考虑,这里我安装的系统是truenas core,因为是基于freeBSD的系统,从便利性上来讲可能会差一些。如果需要额外的功能,就需要用其他虚拟机来支援了。

不过事后发现这么做带来的一个好处。由于sata控制器被直通给了truenas,整个truenas系统要做备份就变得比较困难,把整个存储系统做的比较简单,复杂的软件服务集中在独立的虚拟机上,单独对这些虚拟机做备份就比较简单了。

在使用上,这套系统只用于提供存储服务使用,存储池定义,用户ACL设置,设置smb、nfs、afp、webdav等文件服务,其余功能一概不用。这样一来,整套系统除了存储的数据以外,就只有配置信息了。只要定期备份配置(这些配置变动其实也是比较少的,所以备份的频次也会很低),就能防备系统发生故障后能够顺利恢复。

硬盘数据的备份,是个很复杂的方案,这个以后再具体展开讲。

ubuntu server(docker宿主)

因为除了基础的文件服务,通常我还会用到很多额外的软件服务,所以需要至少一套额外的虚拟机来充当软件服务器,之所以说是至少一套是因为部分软件如果限定了特定平台(比如windows),那么单独一套linux server是搞不定的,不过目前这种情况没有,所以我就先只装了一套linux系统,后面有需要再按需安装。

这里选什么系统倒是并不重要,大部分server软件都会以docker方式运行,所以并没有区别。选ubuntu一方面是延续原有的使用习惯,另外是考虑到部分软件或命令行工具会以原生方式安装,选个保有量大的版本很多时候会更方便(和买车一个道理)。

openwrt(软路由)

这个系统只是实验性质,我对软路由的主要需求是流量控管与监控以及透明代理。目前尝试下来效果还不太令人满意,就不展开讲了。只能说折腾网络设备确实是最麻烦的,一个没配置好导致断网就很麻烦。即便作为尝试,我已经是只拿这套系统做旁路由使用,但是在底层esxi部署好以后,大部分时间我都是在外面通过代理方式访问家里的局域网设备,所以一旦中断,可能这天都干不了什么了…

vinchin(系统备份)

因为ubuntu server上做了很多配置和安装的活计,此外除了server,我也还有其他几套用途的虚拟机需要备份。查了一下感觉vmware的备份方案都挺重的(这也是让我觉得一开始如果用的是PVE可能就没那么多事了)。相对而言vinchin(云祺)的这套方案还算不错。唯一的问题是付费模式主要面向企业用户,不太适合个人用户。好在有永久的免费授权可以一直使用,只是有三台虚拟机的限制。

结语

最后整理一下,系统整体结构图如下,还算清晰吧。

AIO.drawio

系统的搭建到这里基本告一段落了。可能还会再尝试一些软路由系统,或是出于软件需要装个windows server之类的小变更,也没必要展开说了。

后续的重点会放在软件服务的构建上,这些就逐篇展开再慢慢说吧。

起因

我客厅的电视柜边角上,放着一台HP Gen8,我把它用做网络存储,一直以来都工作的很好,也不算太突兀,很多来家里的客人都不觉得这是一台电脑,也许以为是音箱或是电视机顶盒之类的东西。作为一台有四个3.5寸硬盘位的电脑,我本来也觉得这个体积已经算是极限了,在迈入全固态存储时代前,我是没有办法把这样一台设备像ps5或是路由器一样塞到柜子里面的,直到去年我的Gen8电源开始出现问题,然后我在搜索后续替代品的时候看到了下一代产品Gen10 Plus。

同样的四盘位机箱,相比Gen8,仅仅少了光驱和用不了几次的硬盘快捷插拔前面板,但是高度却缩减了整整一半,真的变成了一台像路由器一样的设备,考虑到Gen8我已经使用了6年以上,升级换代的理由已经很充分了。

购买之前我也考虑过,如果Gen8再撑几年的话,理想的下一代NAS产品该是什么样子。我脑子里想到的两条我最关心的是:1、arm架构;2、纯SSD存储。

想到这两条并非是我的什么特殊偏好,而是因为我发现我对NAS产品最大的需求,就是体积,而实现这两点都可以让NAS产品的体积有比较显著的下降,从而在一个近乎“隐身”的状态下提供尽可能多的存储容量。

不过考虑到这两点短期内要实现确实不太现实,因此Gen10 Plus有理由成为在完全实现这以目标前的一代过渡产品。

当然,往更长远考虑的话,完全的云存储在未来也是可能的,只是这里的不确定性,更多取决于未来云存储是否能有合理的商业模式,以及作为基础的网络设施能到什么程度了,这就不展开说了。

购买

Gen10 Plus在国内并没有像Gen8当时那样有正常的购买渠道,对比了海淘和其他国内代购商家后,我选择了从computeruniverse网站海淘。这或许并不是一个正确的选择,因为去年的疫情原因,最终这台电脑在下单两个多月的时间之后才配送到了我手里。不过考虑到其他硬盘等配件我也是等到双十一才购买的,所以这段等待时间倒也不算是什么问题。

最终完整的采购部件包括这些东西

部件
Gen10 Plus Xeon E2224 16GB RAM
Gen10 Plus iLO5 kit共享卡
佳翼 NVMe转接卡 PCIE转M.2
西数 SN750 1TB
东芝 14TB MG08ACA14TE *2

硬盘理论上可以配满4块,不过我准备还是一步一步来,先用两块把原来的数据转移过来再说。大容量机械硬盘很多人吐槽噪音会比较大,因此先买两块也是出于先扫雷的想法。SSD用了Gen10 Plus唯一的PCIE插槽,是有一点浪费。一些扩展卡支持转接更多的SSD,或者是附带高速的网络接口。不过因为暂时没有需求,也就先用着了。

gen8_gen10p_compare

硬件组装

刚拿到机器的时候,我开机看了一下,意识到这次需要折腾的会比Gen8要少很多。原因是当时Gen8的风扇实在太吵了,于是花了不少功夫去魔改电源和风扇。

因此这次就是简单的把所有的零件插上,拧上螺丝就好了。唯一没想到的是硬盘的固定方式,虽然一开始知道不像前代Gen8那么简单,不过螺丝固定的方式还是有点超乎我的预期,在网上找了几篇评测才找到怎么固定的说明。

装完后整个扔到电视机柜里,没有额外研究怎么把前指示灯关掉,整体存在感还算比较低,就先不折腾了,至此算是开始正常服役了。

gen10p_deploy

系统搭建

上一代Gen8我使用的主机系统是Windows server2012,对于其他系统的需求则通过Hyper-V安装虚拟机解决。

我细想了一下过去几年里主机操作系统的选择是否给我带来了多少便利或是不便,从纯粹的网络存储设备角度上来说,Windows的频繁更新重启带来的只有不便,从应用软件的角度看,我也并不强依赖于Windows生态下的软件。唯一值得一提的可能是硬盘监控软件Disk Sentinel,它确实让我在使用过程中,特别是硬盘过保后超期服役期间,更加安心了。

这当然并不足以成为我把平台留在Windows的理由,最终我安装了Exsi作为宿主系统,一个Truenas虚拟机提供基础的存储服务,并把SATA控制器直通给这个虚拟机。对于其他软件的需求(由于Truenas并非linux系统,不支持docker,这类情况恐怕还不少),再通过其他虚拟机挂载网络存储后对外提供服务。

我并没有组建raid,Truenas和ZFS我了解不多,我也并不打算依靠raid来保障数据安全,对我来说,这两块硬盘仅仅就是两块硬盘而已。对于数据安全的问题,就留给下次说明吧。

AdventofCode这个网站是我在几年前浏览Hackernews时看到的。网站的作者在每年的十二月份,会在每天给出一道编程题,包含一二两部分问题,答对一题可以累积1颗星,直到12月25号圣诞节这天为止。通常一天的题目中,第一部分会简单一些,第二部分是在第一部分的基础上有一些延伸,当然通常难度差别不会很大。从网站给出的数据也可以看出来,只回答一道题的人数通常在1%~10%这个区间,只在最后几天里会有比较大的起伏,比如今年最后一天的题目,在答出第一题的基础上,只有一般人回答了第二题。

第一次看到这个网站的时候是在十二月中旬,做了几题之后,因为感觉落下太多就没有跟着做,之后的几年不是想起来太晚就是忘记了……直到今年,算是在十二月到来前就想起了这件事,于是记着在一号的时候开始跟着做。跟题之前我还特地挑去年比较难的题目试了一下,从难度上来说,都是属于可以做出来的程度。所以理论上今年跟完全程应该是可行的。

或许跟着做题才会发现这些 puzzle hunter 的狂热程度。前几天的题目都不算难,我提交上去的时间大概是在题目开放后20分钟左右,然后发现最快的选手2分钟就已经做完了……

于是第二天开始为了能做的更快一点,我就没有继续用传统的编程方式来写,而是用juypter notebook这种交互式的编程界面,能快速拿到响应的方式来做。不过从之后的经验来看,这种方式虽然在一些小题目上能快速的响应,但是面对复杂的问题时,反而是种负担。

再简单说说感想吧。在前面的一周半的时间里,整个解谜过程还算是比较轻松的,基本上属于上班的时候抽出一点时间就能做完。十二月中旬的时候出去旅游了一趟,落下了几天的题目没做,然后接下来又是出差,作息规律上受了一些影响,导致在后面的一段时间里,我花了不少时间来追赶进度,差不多每天追两三题的样子。

而从中旬开始,偏偏题目的难度也有所上升,属于比较耗时耗精力的事情了。于是整个过程就变成了不是饭后娱乐那么简单的事情。说实话追题的过程还是比较辛苦的,很多题目从阅读理解上就比较耗费精力,更别提完整做完了。诚然这些谜题都还属于可以解开的范畴,做到第19、20天的题目的时候,我评估了一下完整做完需要投入的时间精力,感觉并不算小,而相对来说十二月和接下来的一月事情都还比较多,于是决定终止继续追题。

总体来说,解谜的过程算的上是有趣。如果有心思思考的话,也确实可以磨练一下编程技巧,不知道是不是已经过了能够一门心思投入在一件事情上的年纪了。完整地跟完解谜的全程对于我而言都已经是接近奢侈的事情了,更别提进一步的反思总结了。

我姑且把今年的解谜过程记录下来,如果来年还有机会参与的话,不妨作为对比。

从去年搬家以来,我就保持着极简的局域网环境。光纤入户后,用客厅里的路由器拨号上网,除了和路由器放在一块的nas以外,其他所有设备,包括电视、电脑以及其他智能设备,都是通过wifi接入,结构上非常扁平,简单。而且过去的这一年多时间里也很少遇到实际问题,就一直这么用着。

虽然大问题没有,但是我也清楚这套结构是有很大的不足的。主要是受限于单路由器的信号限制,放在书房的主力电脑,连接到路由器的速度大概只有10~20MB/s。虽然在重启或关闭wifi重新打开后,速度可以短暂的恢复到60MB/s,但是之后又会降到不足一半的程度。我推测的原因是书房和客厅之间虽然没有实体墙堵着,但是书房的门如果关上了,对于信号敏感的5G频段的wifi还是会受到干扰,导致降速。除此之外,像笔记本电脑连着wifi备份也会碰到网络断开导致备份失败。因为我差不多一周才会把电脑带回来备份一次,备份失败多少还是有点影响的。

而促使我下决定进行调整的直接原因,则是偶然发现当电脑和nas之间进行大文件传输的时候,网络延迟已经会严重影响游戏上的延迟时间了。看起来内网的带宽已经比连接公网的带宽可能还小了,这确实太影响上网体验了。

这里简单说下网络环境,如图所示:

img

虽然各个房间之间与弱电箱都通过交换机布置了网线,但是遗憾的是每个房间都只有一条网线,包括客厅。因此如果要保持由客厅的路由器拨号的话,那么是没有第二条线能把信号回传给交换机,再与各个房间连接的。对于网络拓扑我也是个外行,大概了解到有两个办法可以解决,一个是客厅里加一个交换机,设置vlan;另一个是把弱电箱的交换机换成路由器。前者实施起来有一点门槛,后者则受限于弱电箱的格局,要加设备也比较困难。

在网上发帖求助之后,看到一个有意思的方案。就是把客厅的路由器升级成mesh wifi路由,并在每个需要较高带宽的房间里单独部署一个mesh路由。好处自然是部署简单,而且完全不受环境约束。当然mesh路由也支持无线回程,如果路由器之间还是通过网线连接的话,带宽和延迟保障会更好一点。但是这个方案最吸引我的地方还是在于可以全部使用无线的方式解决问题。某种意义上,这也算是以科技进步的方式,影响了房子的物理结构吧。

整个过程实施起来也很方便,我从京东上下单了两台小米的ax6000路由器作为试验。先用一台测试取代了现有的主路由进行测试。wifi的连接速率在60~70MB/s左右,和我原先的路由器在刚连接上wifi时差别并不大。接着再激活第二台路由器,并配置好与第一台路由器结成mesh网络,整个过程操作都比较简单,照着说明书操作即可。配对好以后把第二台路由器放到书房里并接好电源。这里我分别测试了电脑与路由器通过wifi的方式和网线的方式连接,两者相差不大,实测从nas上拷贝文件,速率在110MB/s左右。考虑到这可能已经是受限于nas硬盘的读取速度了,我觉得这个速率毫无疑问已经是让我满意的了。后续如果卧室或其他房间对于网速较高要求的话,也都可以考虑通过这种方式解决。

对于网络的稳定性我也在如此布置了一两周后又测试了几遍,速率依旧能稳定在110MB/s左右,没有像之前的路由器一样出现降速的情况,稳定性上来说,也算是满足了我的需求。

值得一提的是,由于路由器之间依旧是通过无线连接的,因此在位置的布置上就比较随意了。客厅的路由器依旧受限于与光猫之间的连接,因此我放在了电视机柜上,也还算比较隐蔽。书房里的路由器则被我放在书架的底层。我个人很喜欢这种把这类基础设施类设备摆放隐蔽的做法。毕竟我需要的只是网络信号,而不是在家里哪个显眼的位置摆放一个祭台,像供奉神灵一样把wifi信号请来。mesh路由对于布置位置的随意性也是相当好,只要能接上电,放在哪里都行。我想这类设备最好的做法可能是像电灯一样装在天花板上。不过眼下这样一台可以随意摆放的设备也已经能满足我的需求了,就不多折腾了。

起因

我第一次接触量化自我是在2010年的时候,读到李笑来先生的《把时间当做朋友》,里面提到的《奇特的一生》这本书,书中记录了柳比歇夫是如何几十年如一日地将自己的时间开销以数字化的方式记录下来的。当时出于好奇进行了实践,采用与柳比歇夫类似的方式,把时间开销详细地记录下来。

说实话这并不是一件很容易的事情,实践过后会发现很多问题。我自己总结来说,最大的问题在于这种记录方式对于精神的集中度要求会很高,在进行一些纯粹的工作时也许影响不大,但是在进行很多琐碎事情时,很容易让人举步维艰。

举例来说,如果一天的时间只投入在5~10件事情上,那记录的成本就很低,诸如投入一到两小时以后,回过头来记录,1小时35分,写博客。但是如果投入的事情很多,时间比较发散,没有计划性,那这件事情的成本就会很高,每5~10分钟就要回过头去想想自己刚才在做什么。

最终我的第一次尝试在实践了大约2周时间后终止了。因为在这个实践的过程中,自己反而被锁在了时间的牢笼里,每分每刻都在想着自己要做什么。一旦规划好了,就投入到目标的事情上去。等事情做完,又想着马上该接上的下一件事情是什么。

这次尝试终止以后,很长一段时间里我没再把这件事情当回事。对于一般的任务管理来说,采用GTD工具对我来说已经够用了。直到工作后的一些年份里,我开始意识到时间的开销管理开始成为一个问题,我觉得有必要像管理收支一样,对时间的开销进行记录。于是我又回到了量化自我的实践上来。

当然,实际上量化自我所收集的数据,并不仅限于时间的开销,我整理的方法也会包含其他各方面的数据。只不过对于时间的记录和分析,是我做这件事情的初衷。

收集数据

线上时间开销数据

在现在这个时代,人们花在线上,或者说屏幕前的时间真的越来越多了。这也使得这部分的时间统计变的非常的简单。

针对桌面端来说,有非常多的应用可以自动记录时间的开销。目前来说,在pc和mac上,我都会使用RescueTime这个应用。只要设置成开机自启动,这个应用就可以在后台静默地记录所有的时间花费。(除了桌面端以外,也支持android。)另外,除了记录,RescueTime提供的在线服务还可以查看时间开销的统计报告,其中比较有价值的部分在于,它还会自动根据活动的类型进行(积极/消极)分类。

RescueTime还提供了付费服务,会有更多的时间管理和数据管理上的功能。不过这部分我没有接触过,出于对数据自主性的考虑,我写了一个简单的程序,每月通过RescueTime开放的API接口,把自己的数据下载回本地,以csv文件存储,并保存在私有的代码仓库里。

它的缺点在于,本地运行的静默程序不包含任何数据交互的功能,所有的数据都是上传到服务器上,在web端才能查看。如果觉得个人私密数据不想上传,ActivityWatch是一个可以考虑的替代品,不过我没有具体用过,不好对其功能做具体评价。

另外如果使用的是纯粹的苹果生态圈,也可以考虑系统自带屏幕时间(ScreenTime)。缺点是对于第三方浏览器(chrome、edge、firefox),无法统计到具体网站,只有自带的safari是会将花费在浏览器上的时间,穿透到具体站点上。这一点对于通常使用定制app的移动端无所谓,但是对于浏览器使用较为频繁的桌面端还是有些影响。假以时日各大浏览器若是都能支持屏幕时间的话,倒是一个不错的方案。

另外,使用系统自带的屏幕时间,想要获取原始的数据,也是需要费一些手段的。这个项目似乎可以做到这一点,不过根据sof上的这个帖子的说法,可能还需要关闭SIP才行。一个更合乎常理的途径是通过这个地址直接向苹果索要数据,不过我尝试了两回向苹果索要数据,其中并不包含屏幕时间的数据。

对于移动端,在android上我也使用的是RescueTime。不过即使有精神洁癖不想用,问题也不大,android系统本身也会记录应用的使用时间,想必找个法子记录在本地或者是导出都是比较简单的。

至于iOS系统,和上面提到的mac一样,虽然有现成的系统功能会记录这些数据,但是却无法便利的拿到。因为我目前iOS设备使用并不多,所以在这件事情上并不是太关注。后续如果需要的话,我打算的做法是用一台旧iOS设备越狱,通过iCloud同步主设备的数据,然后从文件系统中拿到相关数据,以此来尽量避免对日常使用的影响。不过即使如此,通过iCloud同步的数据依旧存在因越狱引起泄露的风险。最好还是由苹果提供这些数据,毕竟这下数据本来就属于用户,希望以后苹果能完善这块数据的供给吧。

线上其他数据

数字世界的数据,除了上面这些常规的记录以外,另外一项补充的是截图。在Windows上我使用TimeSnapper,而在mac上则是之前提到过的一个自己编写的定时脚本,并不是多么特别的功能,不过作为对于数字记录的补充,有时候留有几张简单的截图,能够更好的理解自己在那个时候在做什么。

移动端似乎没有类似的软件,确实是一个遗憾。不过我想android端倒是存在自己编写程序进行记录的可能,有必要的话倒是什么时候可以试着写一个。

线下时间开销数据

线下的记录不像线上那么方便,可以自动记录,返璞归真的做法当然可以用纸笔。稍微便利一点则是可以用手机app。

android上我长期使用过的是TimeMeter,通常我会给事前就做好分类,设置成桌面快捷方式,在开始前点一下,结束的时候再点一下,就完成了基础的记录,需要有额外的信息则再添加一点备注。

ios上与之类似的app是TimeLogger,虽然iOS本身不支持设置快捷方式这种操作,不过这款app搭配apple watch使用的话,倒是可以做到不用解锁手机就能进行时间记录。而且某种意义上,这似乎也确实是手表应该具备的理所应当的功能。

运动与生理数据

在可穿戴设备丰富的今天,这些数据的收集也变成理所当然,甚至可能是量化自我的一大重点。我个人从最早的智能手环时代开始就有使用穿戴设备,到目前为止,华为的智能手表gt2在产品层面上是足够让我满意的。能收集的数据包括运动记录(我常用的是室内室外跑步、游泳、骑车、划船),睡眠时间,日常热量消耗,心率(并不是很准确)等等,应该算是覆盖全面了。而手表相对较好的续航能力,也使得我不用时常操心什么时候要给手表充电。这一点在我原来使用apple watch时是个麻烦事,频繁的充电使得我必须把充电时间选择晚上,但这么一来就会使得睡眠的记录变的不完整。

值得一提的是,华为提供的数据记录app运动健康本身并不支持数据导出,我不知道是出于封闭生态圈的考虑还是什么。当然也有一些hack的手段,比如github上的这个项目有提到。不过由于华为官方提供了用户自主索要数据的通道,我个人更倾向于采用这种合乎规矩的做法。唯一的缺点就是从提出申请到提供数据,通常需要等待一周左右。从我个人经验上来说,比向苹果索要数据要短一点。另外从两家公司的实际做法上来说,华为在个人数据的保护上似乎也做的更妥当一些。在提出申请时,会需要用户提供账号密码、手机验证、邮箱验证、身份信息验证等四道验证。在提供的数据包上,也会要求用户提供密码进行加密保存。

具体数据的索要方式是在app中按账户中心->隐私中心->获取您的数据副本。获取的数据相当齐全,不过由于是json格式,在作为分析数据前,通常还需要进行一些处理。

财务收支数据

我个人认为这是挺重要的一个数据,至少在财务自由之前,我还是关心钱赚了多少,花了多少的。

比较意外的是,我发现要做这个统计并不容易。举例来说,我能不能通过支付宝账单统计我花了多少钱呢?不能,因为支付宝上的钱有的来自银行卡,有的来自信用卡,有的来自花呗,有的来自余额宝,还有的钱我是不通过支付宝花出去的……而有时候通过支付宝的支出是偿还信用卡,有的是消费,有的是转账,这其中关系错综复杂,而更关键的问题,支付宝是不会记录这笔钱从哪来,花到哪去的……

最终为了终结这些混乱的资金流向,我对个人的资产账户做了一系列精简,包括关掉花呗,关掉不必要的信用卡,互联网金融产品等等。最终只保留一个存款账户作现金收支,以及一张信用卡作为信用消费使用。

这样一来,我只要简单的统计存款账户的资金流向,和信用卡账单,就可以统计到全部的收支情况。所幸这两项数据,在招行的专业版网银客户端上都可以比较容易的拿到。

最后的记录则是通过简单的数据清洗脚本,转成标准csv文件,在经过人工复核(主要是确认开销类别)后,再通过定制脚本,转化成beancount账本。

beancount是一个用于复式记账的领域专用语言,对于复式记账,这里就不展开介绍了,有兴趣可以自行了解一下。单纯的使用来说,并不需要太多的财务知识,使用beancount的好处,我个人认为就和使用markdown写作一样,数据的自主性较好,仅此而已。

当然在财务这块,我个人感觉做的还不是太好。就目前来说,我的记录效果也仅限于能够分析自己当前的净资产状况是怎么样的,过去几个月里自己的开销主要是什么。我个人更希望能够将数据用于对于未来开销预算的分析上,不过因为每个月都有些不同的开销和收入项目,感觉并不是那么容易。

分析与回顾

除了财务数据是单独使用fava这个专用于beancount的可视化工具,以及截图这种非常规格式数据以外,剩余的数据在收集完之后,都是按标准化格式进行保存,以便于分析。

这里我使用的分析工具是tableau,这是一个专用的BI领域工具,不过在数据分析领域也非常合适。相对于制作固定的可视化图表,使用BI工具在进行数据分析上更具有灵活性,可以快速制作图表,必要时可以针对特定数据进行定制分析。早先我也试过使用excel,主要缺点在于每个月制作新的excel文件需要花费一些精力,而tableau的dashboard在制作好以后,后续只要将新的数据文件存放在特定的目录下,就可以自动读取、展示。

缺点的话,一方面是软件本身的授权价格并不便宜,如果制作的图表或是仪表盘想要在移动端查看,那还需要单独部署 tableau server,server端的授权本身就是一笔不小的开支,而要支撑这种量级的软件部署,服务器的开销也并不小。而若是使用公开的tableau public则必须将数据公开才行。由于这类数据涉及过多私人数据,我个人并不太倾向于在公开网站上发布。

好在即使只在桌面端使用,对于我的分析场景来说,也已经足够了。我通常会每月进行一次数据收集和清洗,并进行分析和回顾,一方面看看有没有什么可以改进的地方,另一方面也是让自己重新感受一下,过去的一个月我都做了什么。

前言

大概两年前,vscode推出了以SSH为基础的远程开发插件,当时还只是略作了解,然后拿来作为远程文本编辑的一个备选项。相当于是ssh环境下vimemcas的一个替代品。作为GUI基础的文本编辑器,在访问未经配置远程环境下,相比使用全默认配置的vim还是要更好用一点,不过也就仅限于此了。

最近翻出来研究的主要原因,是自己的办公电脑配置慢慢跟不上开发需要。虽然4c16g的配置不算低,不过办公机需要开启大量的网页窗口、各类文档、IM软件等等,再加上IDE和大量依赖服务,而且本身所处的开发环境还需要运行额外的监控软件、防病毒软件等等,实际在开发过程中和代码编辑时还是能明显感受到拖慢的。测试一个完整的java项目编译,server端2分钟可以跑完,本地要跑上十几分钟,确实浪费了不少生命。于是一不做二不休,捣腾一下全给搬到server上,通过远程访问方式进行开发。

总结一下的话,远程开发带来的好处大概有这么几个

  • 更可控的机器配置,云端虚拟机可以比较方便的做在线扩容。目前我使用的配置是8c32g,如果有需要完全可以扩到32c256g甚至更高。
  • 便利的环境快照。基于虚拟机的开发环境,折腾前先做个快照,发现什么问题可以随时回退。或者机器更换,基于快照恢复也很方便。
  • 纯原生的linux环境。没啥好说的,可以完整使用linux的开发生态。使用docker镜像啥的也比较方便
  • 上下文环境保持。因为server端基本可以一直挂着不用关,不用烦恼每次重启后又要打开一大堆调试窗口或者是启动一堆背景服务。

另外还有一些可以算作远程开发的优势,不过在我的使用场景下不涉及的

  • 对客户端性能要求较低,具有一定移动开发友好度。比如在阿里云上申请台高配server,然后本地使用mba或者surface开发,也基本不会受限于cpu或内存等硬件桎梏。
  • 低成本的多设备切换。在公司开发到一半了,出差或回家了,用笔记本远程连上就可以继续工作。当然这点要看具体的网络环境了。

至于缺点也有一些,不过我觉得核心就是一条,就是vscode作为一个主打文本编辑器的轻量级开发环境,能否满足开发效率上的需求。

我个人感觉还是有点难度,当然其中也有一些使用习惯上的问题。比如对于版本管理,我个人更习惯intellij中提供的version control界面。基于命令行的tig或是vscode 的插件git lens,个人习惯上还是觉得差一点。

另外对于java项目来说,idea的代码补全(code complete)还是要比vscode更好一点。虽然现在新一代的基于AI模型的代码补全工具在很多时候确实也非常好用,不过短期内感觉还很难取代传统基于语言模型的代码补全。当然最致命的一点是,由于java ee项目的技术栈深度问题,诸如mavenspring bootjunitlombok等等,idea已经提供了很完备的插件生态来支持开发,而vscode在这方面则多还处于起步阶段。

总体而言,给人的感觉是,在使用IDE时,通常开发者不需要对其背后的运作原理有太深入的理解,只需要专注在自己的代码上就好了。而基于vscode的开发环境,则需要开发者对代码的编译、调试、打包等流程都需要有一定了解才能顺利进行。

如果上面说的这些都不成问题,那么搭建一个server端的开发环境还是非常实用的。

环境搭建

前置准备

这里要准备的东西倒不是整个开发环境必备的,倒不如说是作为开发环境,我个人习惯会准备的一些开发工具。

  1. zsh&oh my zsh

    习惯使用zsh,自然是第一时间会装上

  2. tmux

    基本是server环境下必装的,用于会话管理和终端窗口管理,也是减轻vscode本身的terminal在窗口管理上功能孱弱带来的影响。

  3. docker

  4. pet

    因为windows端我没有用类似dash这样的工具管理命令行snippet,因此选择在linux环境装了pet用来做日常命令的收集。

  5. tig

    虽然vscode本身也有git插件,不过纯粹的命令行交互式的git工具在很多时候还是很方便的。

  6. Ncat

    比较通用的网络工具,在远程环境下我拿来作为一个比较简单粗暴的转发工具

  7. Ant、maven、gradle、java、gcc、python等开发工具

说实话,如果有人能把这些开发环境统统装好打成镜像,我觉得还挺有用的。不过就算自己准备也不算太费事吧,linux环境下通过包管理工具通常都可以很便利的装好。(我使用的server环境是rhel7.4,因此使用的是yum进行包管理)。

vscode remote安装

本身remote插件的安装其实很简单,在vscode中打开extension然后搜索Remote SSH就能装上。需要额外一提的是,本身这个插件是作用于本地的vscode的。对于server端而言,还需要单独安装vscode-server。当然如果是处于互联网环境的话,那么第一次使用vscode连接到远程服务器的时候,就会自动去下载server端安装包。

不过如果你的server是处于非互联网环境下,那么注意这个安装过程虽然不能顺利进行,但是可以通过提前下载部署server包来跳过,具体可以参考这个帖子。**另外需要注意的是,vscode-server版本是要与vscode匹配的,因此如果vscode更新过了,那么也需要注意更新vscode-server**。

除此之外,基于remote模式,vscode只有界面渲染等与UI相关的部分是在客户端执行的,编辑器内的计算部分是在服务端完成。因此,想要使用vscode的插件,需要在server端安装才能正常使用。

除了基础的编辑、保存等功能外,remote模式提供了一个非常实用的功能就是端口映射。这是由于,因为编辑的代码都是实际存储在server端,编译和调试自然是在server端进行。那么比如我在开发一个前端项目时,写完代码想在界面上调试看看效果,那么自然是要npm start把网站跑起来。可是此时网站是启动在server上,虽然应用会提示说请通过localhost:xxxx来访问,但实际在本地自然是访问不到的。remote插件提供的端口映射功能,就是通过侦测远程端口的开启情况,将本地对应的端口映射到远程端口上,来达到本地访问远端服务,实际体验上就和在本地调试一样。

另外就是我在上面提到的装的ncat。这是因为某些代码需要访问本地客户端的一些服务,距离来说,由于某些原因,我没有在server端安装mysql数据库,此时server端访问本地的mysql就会访问不到。于是我干脆通过ncat --sh-exec "ncat <ip> <port>" -l <port> --keep-open把server上访问mysql的请求转发到本地。这当然不是必须的,不过在过渡期懒得折腾的时候倒也能省不少事。

IDEA&Rsync

虽然理论上采用这种方式,就可以把代码全部搬到server端进行开发。不过正如上面我提到的,使用vscode进行开发,在java ee这种特定场合下,效率上还是不如使用ide来的更方便快捷。于是我想到一个折中妥协的法子,就是在本地用ide开发后,将代码同步到服务端进行编译调试。实际体验中,采用rsync进行同步,其效率还是很高的。

对于单纯的代码同步,rsync命令如下

rsync -avz --filter=':- .gitignore' --delete -e "<ssh_path>" -r <source_dir> <user>@<server_id>:<target_dir>

除了第一次代码的全量同步以外,rsync在后续的同步中会自行进行增量对比,整体效率还是相当不错的。使用.gitignore文件则是把编译、构建过程中不必要的文件排除掉,某种程度上也能加快同步效率。

后记

这通折腾我大概在3月中旬的时候折腾完毕,把整套开发环境顺利搬到了服务器上,也顺利用了近一个月。

意料之外的是,想不到就在我折腾完没过两周,jetbrains就发布了projector产品,同样是通过远程渲染的方案,将基于swing的IDE工具搬到server上,而本地只需要通过浏览器或瘦客户端来访问。

可能未来的趋势,就是IDE慢慢云端化吧。我个人倒是还挺待见这种趋势的,有空的话再折腾一下部署projector,若是可以把idea也搬到服务器上,本地开发就可以彻底成为过去式了。