这学期刚刚进入大学,着急忙慌的博客也没怎么更新。要不是有一天突然发现中国移动ban掉了pengs.top,可能也不会腾出手来折腾。但是我做就要做绝,这一把顺带着给域名做了备案、更换了服务器(其实是砍预算)、以及最主要的——全站Docker化。

诶?WC!

一天我像往常一样下课走去食堂,心血来潮想看一眼过去这么久了pengs.top的访客情况如何,可是当我往浏览器里输入umami服务的地址后,竟然看到了Connection was reset 的恐怖字样。我立刻去看主域pengs.top 发现也是如此。能正常打开其他网站,我也没把锅往广电还有移动头上扣。到了食堂,我连上了校园网,再测试又正常访问。校园网是移动联通电信鹏博士教育网混合出口,我于是开始怀疑起中国移动,叫了几个人帮我测试都是连接已重置,这几乎是实锤了。

随后我又更细致地测试了一下,问题似乎只在北京移动的网络环境下出现。考虑到北京有自己的城域网,加上之前的作业让网站的访问量骤升了数百,我觉得倒有些合理,但这不能洗脱中国移动的罪恶。经控制变量法测试,北京移动阻断了二元组(pengs.top,ip),意味着如果使用ip裸连或者用cloudflare小黄云进行代理就好了。但是我不愿意浪费我这么好的iij线路走cloudflare,也不想配置运营商分流,于是就忍心让北京移动用户无法访问pengs.top了。

解决问题

毕竟pengs.top上运行着很多我自己日常要用的服务,不能在手机流量下正常使用总归不舒服,于是我想尽办法去解决这个问题。

更新:2025年12月24日发现中国移动还是ban了,没招了,我还是继续扯皮吧

备案

我想就算要刚中国移动,我好歹自己得是合法的,像原来那样海外主机+cloudflare域名托管+无备案简直容易被倒打一耙变成非法网站。我都成年了,不能这样没名没分的 于是又一次踏上了艰难的备案流程,不过好在比较顺利,这一次以自己的身份办的,中间没有卡壳。这里要感谢BUAASubnet的摩尔福斯提供的备案授权码!

于是,在2025年12月9日,pengs.top第二次拿到了ICP备案,这一次是京ICP备2025154912号-1

然后公安网备懒得做了额。

10086

打了好几次10086,态度很好,但是一点用都没有。

第一回打过去对面说登记一下,让技术人员联系我。

然后等了一两个礼拜杳无音讯。

中间我发现了互联网信息服务投诉平台,抱着试一试的心态我就给中国移动投诉了。某天上午9点,接到了回电。可是糟糕的是我是南京的手机号,导致工单被派发给了南京移动,10086告诉我查询没有异常。我告诉客服我在北京,她让我加区号010再打一下10086, 结果接线的还是南京的,客服尝试给我转接到北京移动,可是失败了。后来又尝试了一次,依旧如此,无奈之下我只能放弃。

壮士断腕,直接换云

几经周折我也是没辙了

这域名不能就这么废了啊,我还要作传家宝呢。看了眼原先vmiss服务器的到期时间,26年2月就到期了,我寻思着干脆换国内服务器得了,反正备案也已经有了,但是又被高昂的价格,小到离谱的带宽劝退了。其实也有很多新客福利,但是都只能优惠一两年,像我这种懒人,不想频繁重装整个一套服务 所以最终还是打消了这个念头。我又想直接续费vmiss了,但是某天突然想起来:

我是尊贵的大学生啊!

于是Github Azure的学生权益薅起来,这里有点很烦的是你航被美10043法案制裁,导致我到现在github的payment information还是被锁(如下图)

屏幕截图_20251223_220416

我真担心哪天把我大号给扬了,但发了工单也无人问津,唉。

不管怎么说,教育优惠拿到了,至少Github Copilot在手。这玩意儿是真好用啊,后面还会再讲到。

接着开Azure的学生也全是糟点。我自己的个人账号和原来薅E5订阅的管理员账号绑定了,E5订阅被吊销后我的个人账号就有了一些奇怪的租户问题,一度非常无解,而且要开工单需要登陆,但是我TM压根无法登陆。

于是后来我又专门注册了一个outlook账号和我的Github账号关联起来。但是验证学生资质又卡住了,我真的无语。Azure没有给我的edu邮箱发激活邮件,起初我以为是学校的邮件系统给我拦了,问了才知道也没拦截。所以我又开了个工单。

客服让我通过这个链接🔗进行验证,又要了我的学信网报告,折腾了半天总算学生订阅也激活了。

我以为可以咔咔开干了,结果发现我开不了机器,或者准确来说,开不到我想要的。

网上关于Azure VPS的测评不多。ping0.cc有一个VPS延迟的24小时监控 是我参考的主要依据,搜索azure结果大致如图:

屏幕截图_20251223_235726

综合来看,新加坡(Southeast Asia)的表现最好,其次是Japan West、Japan East、East Asia(香港)。

此处还有Azure官方的测试页面 可以测试延迟和速度。我使用的网络下,依然是新加坡表现最好,延迟不到100ms。测试下载速度却差距悬殊。新加坡跑满了20MB/s的带宽,其他的均在2MB/s以下。上传差距不大,均为10MB/s.不过我无法确定分别是通过什么运营商的网络连接的,学校网络出口有负载均衡。

所以比较一圈下来,发现新加坡的机子比较香。我就通过创建免费虚拟机的链接试图去创建,这个链接进去会自动应用适合免费范围的策略,且可选地区是齐全的。但是,就在要确定创建的时候,又报错了

thumbnail_Outlook-kynxsqg2

选的是East Asia而不是Southeast Asia其实是因为破防了,想着香港也行吧,结果发现新加坡,香港,日本都是清一色的这个报错。

一怒之下又开了工单,把材料交上去。就在对面要我继续补充细节的时候,我突然发现又可以开了,就稀里糊涂开了。估计免费机是有配额的。

看看新家园

配置与成本

SKU: Standard_B2ats_v2

CPU: EPYC™ 7763v 2核

内存: 1GB

硬盘: 64GB

网络: 有静态ipv4*1 无ipv6 每月免费流量为出站单向计算15GB 带宽未知,目前来看够用。

费用: 实际消耗0元。

Azure学生认证每一年有100刀的额度。其实感觉上还是有点紧,因为从9月开始Azure已经取消提供免费的动态ipv4地址了,使用ddns的路子行不通了,只能消耗100刀的额度租静态ipv4地址了。

静态ipv4地址的价格是$0.0036/小时,一年约32$,假设我每个月再有5$消耗在流量上,那么根据 带宽-定价 最高收费档(因为实际上我只有免费的15GB流量,而非前100GB免费)$0.12/GB 那么我每个月只能超出15GB再用40GB左右,这是相当少的。后续我可能通过观察流量消耗情况套个CDN啥的。

注:关于如何查看路由首选项,请看Microsoft的这篇教程,如果你是照着我说的做的,那么应该得到是通过Microsoft网络。

总结来看,除了流量减少了以外,其他方面属于是升配降费(降为0)

旧机子怎么办?

其实一开始想搭建一个节点上网的,可是目前手上还不缺梯子。而且对于一台陪伴pengs.top走过了三年的机子,终归有点舍不得最终将它作为节点,把这个ip糟蹋了。毕竟我一直以6.6折的价格用了这么久,还找老板免费加了10GB硬盘……可能就静静地等到它过期被释放掉吧。

建设新服

升级系统

Azure在免费区域里提供的系统映像最新好像只有Debian 11,对于我这种archlinux用户来说还是太古早了。通过将apt源依次换成Debian 12与Debian 13的同时执行apt updateapt upgrade可以顺利地完成升级。

启动bbr与性能优化

毕竟是生产环境,不太敢用网上那种魔改版BBR/锐速的一键脚本,linux内核早就内置bbr了,不过启用前需要先启用tcp_bbr内核模块。

使用以下命令可以立刻启用tcp_bbr模块并在下次开机时也自动激活:

1
2
modprobe tcp_bbr
echo "tcp_bbr" > /etc/modules-load.d/tcp-bbr.conf

注意,到这里为止,我们只是加载了内核模块,并没有真正改变拥塞控制算法

我把以下文件写入了 /etc/sysctl.d/99-network-optim.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
net.ipv4.ip_local_port_range = 30000 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_max_tw_buckets = 20000
net.ipv4.tcp_max_syn_backlog = 2048
net.core.netdev_max_backlog = 1000
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_sack = 1

# 设置TCP拥塞控制为BBR
net.ipv4.tcp_congestion_control = bbr
# BBR依赖fq队列调度器(必须配置)
net.core.default_qdisc = fq

这里面参数的大小有待考量,不一定科学,我查看了ArchWiki对于sysctl配置的建议,结合我小内存的现实情况给出了这些参数。

此外还有一些系统优化的配置在/etc/sysctl.d/99-system-optim.conf,同样不保证科学

1
2
3
4
5
6
7
8
9
10
11
vm.dirty_ratio = 20
vm.dirty_background_ratio = 5
vm.dirty_writeback_centisecs = 1000
vm.swappiness = 20
vm.vfs_cache_pressure = 60
vm.min_free_kbytes = 65536
vm.oom_kill_allocating_task = 1
vm.panic_on_oom = 0
fs.file-max = 500000
fs.noatime = 1
kernel.sched_autogroup_enabled = 0

文件写入完成后可以通过sysctl --system 加载这些配置使之生效。

服务统计

我需要知道哪些服务需要迁移。这其实又是一个浩大的探勘工作,毕竟vmiss的服务器已经年久失修了,基础设施像是一个神话,数据库的账号密码我也不记得了,可能硬编码在某处,反正能跑()

我运行了以下命令查看了端口开放情况:

1
ss -tulnp

接着又看了一下 已经以docker形式运行的服务

1
docker ps

接着还对照了DNS解析列表以及/etc/nginx/sites-enabled 终于梳理出了十余个站点/服务,清理了若干已经废弃的dns记录。

因为怕服务器遭到攻击,这些服务的细节不能公开。

Dockerize!

这里的Dockerize指将原先基于源码/二进制可执行文件/系统级软件包运行服务转变为统一由一个Docker Compose进行编排的过程,与Github上的Dockerize项目无关

为什么要这么做?

光是把杂乱的各项服务梳理出来已经很麻烦了,为什么还要费劲全部Docker化呢?其实是出于安全性,可迁移性与易管理性的考虑做出的大胆的决定。

  1. 安全性

已经有很多人听说了Next.js的CVE-2025-55182这个漏洞了吧。CVSS 10.0 满分意味着 攻击者只要可以使用网络访问受害者,无需任何权限,无需用户交互,即可自动化地完成攻击并完全控制系统

很不幸原来的服务器也中招了,因为umami访客统计使用了next.js.

幸运的是umami被我部署在了docker中,环境不完整,攻击者释放的木马未能成功执行。发现这个问题也是因为docker ps看到umami一直在restart.

通过重新拉取umami的docker映像,木马很容易就被解决了,同时更新了版本。这让我对Docker的安全性大加赞赏。

  1. 可迁移性

大白话是我可以随时拎包跑路,更方便地在多家服务商之间切换。蹭新客福利,虽然只有几年,到期再换就是了

这是因为利用docker的目录映射,我把所有的配置文件,数据库等等统一映射到了一个目录下,如果需要迁移,只要用rsync就能把数据与配置一股脑传走 ,在新机子上使用docker compose up就把服务都拉起来了。

事实上,在从vmiss迁移到azure的过程中,我就是现在本地docker环境中一遍遍测试,确保没有问题之后才把整个目录rsync到服务器上的,整个过程都很顺利,只要在hosts里把所有pengs.top的条目指向本地就好了。

所有文件都在一个目录下也让我备份的时候不再需要备份整个系统,而只要像这样拉取一个目录就好了,检修的时候信息密度也更高:

1
sudo rsync --partial -avz --delete --info=progress2 root@pengs.top:<服务器上的目录> <本地位置>
  1. 易管理性

通过docker ps我可以看到各服务是否在线,资源占用如何。我可以更方便地升级映像使服务版本更新。

通过把服务全部写入docker-compose.yml,我可以清楚地看到我到底跑了多少服务(没错,因为之前看到有好玩的东西就喜欢拿过来跑,结果过一段时间我自己都忘了),它们的配置文件在哪里,端口映射关系如何。

执行

对于没有多少云原生技术基础的我而言,让我从0开始编排服务难如登天,好在有Github Copilot了, 真是帮了我大忙。我主要的任务是清楚地描述我需要什么,当它限于某一处问题时从宏观的角度定位问题的根源。因为不便透露服务细节,我就记录一些踩过的坑还有学到的要点吧。

  1. Docker开机自启动服务

可以使用以下systemd服务单元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=Custom Docker Compose Services
Requires=docker.service
After=docker.service network.target network-online.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/docker-services
ExecStart=/usr/bin/docker compose -f <docker-compose.yml位置> up
ExecStop=/usr/bin/docker compose -f <docker-compose.yml位置> down
Restart=always
RestartSec=5
TimeoutStartSec=600

[Install]
WantedBy=multi-user.target
  1. Docker对于端口以及路径的映射

<宿主机路径/端口>:<容器内路径/端口>

  1. 如果要用rsync拉取或推整个docker目录,先docker compose down,要不然很容易导致postgresql等数据库数据出错

  2. Docker中使用Traefik报错 client version 1.24 is too old. Minimum supported API version is 1.44

解决方案:

编辑/usr/lib/systemd/system/docker.service

[Service]下增加一行

1
Environment=DOCKER_MIN_API_VERSION=1.24
  1. 如何使用Traefik通过Cloudflare DNS验证申请泛域名证书并dump出证书pem?

参考我的Traefik配置:

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
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
env_file: .env
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"

- "--certificatesresolvers.cf.acme.dnschallenge=true"
- "--certificatesresolvers.cf.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.cf.acme.email=${TRAEFIK_ACME_EMAIL}"
- "--certificatesresolvers.cf.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.cf.acme.dnschallenge.resolvers=1.1.1.1:53"

- "--entrypoints.websecure.http.tls.certresolver=cf"
- "--entrypoints.websecure.http.tls.domains[0].main=${TRAEFIK_TLS_DOMAIN_MAIN}"
- "--entrypoints.websecure.http.tls.domains[0].sans=${TRAEFIK_TLS_DOMAIN_SANS}"

- "--serversTransport.insecureSkipVerify=true"
ports:
- "${TRAEFIK_HTTP_PORT}:80"
- "${TRAEFIK_HTTPS_PORT}:443"
environment:
- "CLOUDFLARE_DNS_API_TOKEN=${CF_DNS_API_TOKEN}"
- DOCKER_API_VERSION=${DOCKER_API_VERSION}
volumes:
- "./traefik/letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`<traefik面板地址>`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.users=${TRAEFIK_DASHBOARD_USER}:${TRAEFIK_DASHBOARD_PASSWORD}"

这会把acme返回的结果放在./traefik/letsencrypt下,随后使用ldez/traefik-certs-dumper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

cert_dumper:
image: ldez/traefik-certs-dumper:v2.8.3
container_name: cert_dumper
restart: unless-stopped
entrypoint:
- sh
- -c
- |
apk add jq
while ! [ -e /letsencrypt/acme.json ] || ! [ -s /letsencrypt/acme.json ]; do
sleep 1
done
traefik-certs-dumper file --version v2 --watch --source /letsencrypt/acme.json --dest /letsencrypt/live --domain-subdir --crt-name "fullchain" --crt-ext ".pem" --key-name "privkey" --key-ext ".pem"
volumes:
- "./traefik/letsencrypt:/letsencrypt"
networks:
- proxy

然后你就会在./traefik/letsencrypt/live下得到熟悉的fullchain.pemprivkey.pem.

原谅我已经记不得太多排障的细节了,先写到这里吧。

新服务推出

本次服务器迁移带来了一些新的服务,简单介绍一下:

  • list.pengs.top : 原先alist的翻版,但是改用openlist了 存储后端从最早的E5订阅的Onedrive for Business换到了阿里云盘,在阿里云盘限制了第三方API后,现在转向了E3订阅的Onedrive for Business,不用担心容量不够。

  • paste.pengs.top : 我托管的开源的pastebin,可以上传一些小文件,支持阅后即焚,加密选项使用AES-256在浏览器加解密,我无法获知内容

  • nezha.pengs.top : 服务监控,服务器掉线了来戳我。

祝大家圣诞节快乐

2025年12月25日

{·B·}