nginx的sub_filter调试笔记

本网站不会再更新,请访问新地址

前言

nginx的配置一直是我个人比较头疼的地方,几乎是我的小小心理阴影,很多配置方式能够使用但是很少能配置的好。也就是因为这份不自信,因此很多时候即使不是nginx原因,我也容易陷在调试nginx上,也从一定角度形成了恶性循环。今天记录一下最近调试”sub_filter”命令的笔记。

需求调研

由于目标应用已经是容器的方式发布了,我对其一些更新就准备在nginx上动手。有这样一个需求,希望首页的某张图片更换为另外一张,而不该动代码。

在解决问题的这天之前的晚上,我花了很晚的时间研究了一下cloudflare的workers能不能实现这个效果,希望网页在引用的图片不是静态图片而是一个worker,这样在图片被送到客户端之前还可以写一些逻辑。这个方案最终失败了,因为希望“首页”和“应用内部页面”引用同一个地址的时候返回不同图片,这样的需求需要根据referer字段判断,但是这个字段只保留了域名没有引用图片的path。这个方案就宣告失败了。

这样可行的方案就剩下了一个,使用nginx来替换最终首页显示的字符串。

问题重现

sub_filter需要原始网页不能压缩,不然压缩过之后就无法替换,这是使用这个命令比较重要的一点。实际如果想要使用这个命令需要安装对应的模块,幸运的是默认的nginx docker镜像中已经安装了对应的模块。然后,在实际配置中碰到了非常诡异的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
location /login {
proxy_pass http://app;
proxy_set_header Host $http_host;
proxy_set_header Referer $http_referer;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Accept-Encoding "";
sub_filter 'A.png' 'B.png';
sub_filter_once off;
sub_filter_types *;
}

location / {
proxy_pass http://app;
proxy_set_header Host $http_host;
proxy_set_header Referer $http_referer;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Accept-Encoding "";
sub_filter 'A.png' 'C.png'; # 1
sub_filter_once off; # 2
sub_filter_types *; # 3
}

以上配置方式在实际网站中出现了非常诡异的一点,本来不需要的1-3行发挥了真正的替代作用。而匹配到的/login根本没有发挥作用。这就非常诡异了。访问/login也没得时候,页面上本来应该存在的A.png被替换为了C.png,而不是B.png。

其实sub_filter开始完全不发挥作用,让我一度以为这个命令没有恰当运行(也是因为nginx不自信),可是非常偶然的出现了1-3这三行生效的情况,这就非常有意思了。到底是什么原因呢?

谜题解答

答案其实不是在nginx,nginx正确的发挥了应该发挥的作用。问题在于应用本身,应用是“js运行时”渲染的。对于/login路径,返回的仅仅是js文件的引用,没有任何内容。真正获取内容的网址是/api/info,这样实际上数据”A.png”这个字符串所在的数据流匹配的当然就是location /规则,也是因此出现的诡异的一幕。

发现问题的关键在于:不要只看浏览器的source页面,这个页面实际是渲染后的页面,查看network选项卡,可以看到实际下载的原始信息和原始请求,这里可以获得最关键的信息。

小结

这个事情困扰了我一天多时间,整个一天多不太开心,但是最后还是合理解决了,“百折不挠”还是很有必要的,另外, 在这个过程中咨询了一写人,学会获取帮助也是很有必要的。

《迷宫饭》Delicious-in-Dungeon

本网站不会再更新,请访问新地址

前言

《迷宫饭》,好久没有时间看一部漫画了。好多漫画都是虎头蛇尾,能有时间看看漫画感觉还挺幸运的。

49话的故事挺棒的

关于矮人过去的故事演绎成了一个小的推理故事,读者和大部分人物根据叙述接受了第一重最容易理解的解答,而看似神经大条的男主,居然扮演起了不断思考的角色。(当然最终解答是变形菇……也是太水了)

漫画下载

平时总是很懒,都是临时想到下载,这次找到个新的途径,其实用alist可以挂载baidu和aliyun,然后把百度的东西转到阿里云里,这样就能中间过渡一下,基本上一个晚上的时间,就能完成转移,这样就不用和百度浪费时间了。感觉花费在homlab建设上的时间非常值得,好像种地一样。

很多彩蛋

“巨怪”来自于半身人的语言,指的是人类!好棒的解读。

巨魔的由来

关于欲望

看到第11卷的时候,迷宫中关于欲望的讨论似乎非常的深刻。“迷宫满足人的愿望并以欲望为食”,感觉在映射梦魔的小章节。

关于寿命

关于寿命的思考,再次出现了,长寿是诅咒的命题似乎贯穿始终。这个地方似乎和芙利莲接上了。

1000年的寿命

关于食物

“差异性”的重要,这个类似于我之前的表述“所有人不可能在一张桌子上吃饭”,感谢作者有了更好的表述。

每个人喜欢的食物不同

关于主题

登场的所有怪物都是魔物,只有迷宫核心是恶魔,非常开心这部作品把恶魔放到了非常棒的位置。恶魔的不死不灭、无限力量、以欲望为食,正好又补充了我对恶魔学的感性认知。恶魔是概念而不是具体的东西,作者的故事内核很棒。

总结

非常好看的漫画,中间很有趣。最后的主题:“吃是生者的特权”,主题凸显的也非常好。

常用的云工具笔记

前言

虽然有了homelab,不过有些时候还是需要云工具解决的,比如,在cf上的workers用于处理openai的转发,比如需要在线上传一些东西呀,等等这里备忘记录一下。

rclone工具与R2云存储

众所周知,cloudflare真是个神仙网站,利用R2建立好存储桶,可以很容易的把内容存储起来,可以作为一个非常好的文件中转站。虽然目前找到了利用cos+zfile+homelab+cf_tunnel的组合来获得自己的私有专属网盘,这个组合可以使用网页上传文件,然后生成链接,方便目标客户端下载,解决了非常多的问题。

不过有个非常重要的需求一直没有解决。就是远程备份。或者说,如何仅仅使用命令行就服务器上的文件上传到云存储上(目前仅仅能完成下载)。另一方面,我现在使用的cyberduck,FE file explorer pro,感觉是用来使用去还是应该考虑尝试命令行工具。(当然cyberduck也是一直没有配置好r2)。

刚好借这次机会配置一下,首先创建好r2上的存储桶,然后设置域名,保证文件可以外部访问。其实至此,一个可以上传小文件(300MB以下)的服务器已经完成了,当然,cloudflare的界面不能说很好用。

安装和配置rclone

就安装来说,还是比较简单的。

1
2
3
brew install rclone #mac
sudo apt install rclone #ubuntu
sudo dnf install rclone #rocky

配置起来可以参考官方教程。理解了原理,是非常好配置的。

1
2
3
4
5
6
7
[r2]
type = s3
provider = Cloudflare
access_key_id = <access_key_id>
secret_access_key = <secret_access_key>
endpoint = https://<endpoint_string>.r2.cloudflarestorage.com
acl = private
1
rclone copy file.png r2:bucket_name

rclone非常好用,特别是可以上传文件夹以及删除服务器文件夹。不过也有小问题,r2如果配置一个指定bucket的token基本上什么也做不了,不知道这个bug怎么解决。也许,cloudflare就是这样的功能吧,如果需要隔离也许应该注册个小号。

cyberduck配置就比较奇葩了,在配置r2的时候,有几个注意点:

  1. 输入endpoint地址的时候一定不要把”https://“头带上,它会“友好”的给把协议改掉,这会造成添加失败;
  2. 账号的token也要是具有最高管理权限,另外,endpoint只能是根目录;
  3. cyberduck在使用的时候有三个标签“当前连接”,“书签库”,“历史连接”,配置好连接后,在当前连接页面添加可以把当前连接添加到书签中了;(添加甚至需要快捷键,或者右键菜单)
  4. cyberduck很多连接方式,输入的方式也是非常古怪,很多地址细节尽量使用粘贴的方式完成;
  5. 如此复杂的ui结果每次只能访问一个数据源,不能数据源间同步,cyberduck确实挺奇怪的。

以上最让我在意的其实是cyberduck的ui逻辑,有种匪夷所思的感觉,以至于我用了很久cyberduck才搞明白。(感觉快成了cyberduck吐槽了)

阿里云盘

考虑到很多视频资料的下载,其实阿里云盘也是很棒的中专资源,可惜群晖的套件里不支持阿里云盘的直接同步,虽然查到相关教程可以使用rclone映射,不过考虑到nas闲着也是闲着,准备部署一个alist把阿里云映射为webdav。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3.3'
services:
alist:
image: 'xhofe/alist:latest'
container_name: alist
volumes:
- '/etc/alist:/opt/alist/data'
ports:
- '5244:5244'
environment:
- PUID=0
- PGID=0
- UMASK=022
restart: unless-stopped

很容易就可以启动一个服务,然后用sudo docker logs <container-id>获取用户名密码,配置好存储源就可以访问了~
使用http://id:port/dav和用户名密码就可以访问webdav服务了!真的是非常方便啊。

学习tmux

多终端操作的机会感觉非常多啊,看来还是应该好好学习一下终端使用,nohup太原始了,虽然可以学习screen,不过既然要学了,不如学新的,这样我就找到了tmux。

1
2
tmux new -s hello
sessions should be nested with care, unset $TMUX to force

简单上手就出问题了,果然要理解他的理念模型。真的使用起来之后其实就是简单的几条命令就可以完成,教程也非常多。

1
2
3
4
5
6
7
tmux kill-server # 杀死所有进程
tmux ls # 列出一个进程
tmux new -s session_name #新建会话
tmux a -t session_name #进入会话 attach
tmux detach #分离会话,也就是让会话进入后台
ctrl b d #上一命令快捷键(毕竟有时候进程在运行)
exit #显式的杀死进程

也许最需要记忆的快捷键是C-b ?,这样可以看到帮助菜单。

很多实用命令简单就可以查到

原始的帮助文档非常有帮助,比如C-b s这个命令,很多中文文档写的是列出会话,感觉是tmux ls的快捷键,可是按照应用内置的文档效果是“Choose a session from a list”,实际使用的效果来看,应用的帮助更加贴切。当然也有不方便的地方,对于mac而言,触控板的操作变成了“上下”,无法滚动浏览信息了。更加复杂的设置也有参考帮助,不过这里感觉可以点到为止,tmux暂时可以作为后台运行的工具就好了,更多的设置未来用到可以再考虑。当然,对于滚动的问题,也许我应该更加熟悉一下more命令也许更好。

vs的终端管理感觉就很够用了

随着假期开始的充电——搭建自己的homelab

前言

假期屯了不少书,准备给自己一个充实的假期。好多东西想学习,airflow、superset、scrapy等,另外还想学习rocky linux,还有之前的kvm和pve的内容也想巩固。为了想法落地考虑利用时间搭建自己的homelab。

好多书,几乎每本都有惊喜的内容

另外,我终于有机会把这个学期学的内容进行实践了,目前的规划是给旧笔记本安装fedora,给小mini主机安装pve。新的小mini主机就作为服务器使用了。

fedora的安装还是很简单的,调整一下AHCI就能找到硬盘了。可是即使如此我也安装了两遍。问题出在密码上,新安装之后密码无法进入,第二次安装的时候注意到了原来是输入密码的时候输入法是大写状态。DELL电脑的win系统开机就会风扇狂转,现在安装了fedora之后,变得特别安静(下载软件的时候还是会有一点点声音)。

然后就是重新搭建基础

家里的旧电脑有点多了,显示器只有一台(多了桌子也放不了),要让我的mac重新能支持双屏幕估计要等到下次升级了。经过多次思考后决定让mini主机安装PVE:
第一点,如果我用fedora服务器,每次还要安装sshd和vnc,考虑到这麻烦情况,还不如直接用http控制电脑;
第二点,考虑到要做的本地项目组件众多已经有点复杂了,越早熟悉虚拟化平台能够越早启动项目。
第三点,如果可以把远程开发环境搞好,以后就有了坚实的homelab基础了。

安装PVE

长时间的调研准备,开始工作居然拖了这么久。这里要说一下,全新的mini主机自带win11 pro,什么都还没有安装开机风扇就想个不停,虽然我现在不能明确问题所在,不过,这个情况让我对win依然是不太有好感。

先不要考虑ip地址的设置,一把点安装。PVE安装时间意外漫长,检查了一下,居然卡在了“create LVs 3%”,没有想到一开始就这么不顺利。也有提示说不用管它,这是正常现象,果然多等了一会儿,后面安装就非常快了。安装成功了,然后就是要考虑ip地址接入的问题了,找根网线把机器接入到网络中,修改interface文件和hosts文件,把ip地址设置到静态。

1
2
3
4
vi /etc/network/interfaces
vi /etc/hosts
systemctl restart networking
reboot

PVE安装好了,这样就可以随便折腾了

安装操作系统的时候意外顺利。

rocky安装好了

一天下午的时间,我就安装了pve,fedora,rocky linux,感觉是挺充实的,并且由于之前阅读的关系,kvm的书籍让我重新理解了网络的模型,可以在配置上更加得心应手。

vmbr0这个网桥因为在kvm的书里看过,所以现在完全不虚

1
2
3
4
sudo dnf install qemu-guest-agent
sudo systemctl start qemu-guest-agent
sudo systemctl enable qemu-guest-agent
sudo systemctl status qemu-guest-agent

1.pve中开启agent,2.安装agent,3.重启->以上流程完成就可以启动guest-agent了

创建好base镜像,系统配置乱了随时回来。

修正系统时间

既然要折腾,就肯定有小修小补,比如,发现时间错误了,要改一下时间。pve的时间是错误的,而虚拟机的时间是正确的(date -R)。先尝试用hwclock解决了,一个有意思的现象,根据命令行修改时间之后,我的登录直接退出了!应该是系统根据这个时间判断登录时间的,非常透明和“干净”的感觉!查了一些文档,果然如果条件允许的话还是试试ntp服务吧。

安装fedora服务器

把dell xps改装成了小型服务器,合上盖子不休眠真的很容易配置。

1
2
3
4
sudo vi /etc/systemd/logind.conf
change #HandleLidSwitch=suspend
to HandleLidSwitch=ignore
sudo systemctl restart systemd-logind.service

然后就是配置虚拟机和插件:

1
2
3
dnf install cockpit-navigator cockpit-machines
dnf install qemu-kvm libvirt libvirt-daemon virt-install virt-manager libvirt-dbus
sudo dnf install podman

一个小型的服务器也准备好了,至此我的书架上有了linux、macos、win三个小型的环境,另外还有个pve主机可以一步一步继续学习了。

安装airflow

开始安装体验,官方的文档有很详细的安装教程,不过我还是想先从podman开始。

1
2
3
4
5
6
7
8
9
dnf install podman
podman pull apache/airflow
podman run -d -p 8080:8080 airflow
#安装并启用端口
sudo firewall-cmd --list-all
sudo firewall-cmd --zone=public --list-ports
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --list-ports

并没有成功,podman ps发现容器退出了,使用podman logs -l返回的内容居然是“airflow command error”,不得不说,非常诡异。最后尝试使用helloworld测试一下podman是否装好。

1
2
podman run hello-world
podman logs -l

看到输出的一瞬间我明白了。airflow的错误输出正是由于logs命令运行正常,是我没有对airflow做正确的配置。这个错误虽然很小,但是也非常有趣,容器内部和外部环境形成了“隔层”,但是内部程序是不知道的,他努力的发出声音也是因为没有配置好,但是这个声音的渠道被我听到了,是不是就构成了一种“穿透”。形式上就是,我输出podman日志的预期看到了容器的日志,从而担心整个podman环境出现了什么问题。我觉得这也是奎因第二类问题的一种体现。

如果用容器运行airflow不是最默认的方式肯定是有原因的,尝试没有配置好的话,就按照最默认的方式来吧

干净的系统居然没有pip,需要自己安装python -m ensurepip --default-pip

根据默认教程轻松就做好了

基础硬件设施解决之后发现,其实很多开源软件直接安装即可,容器化并不能让每个问题都简单,甚至有时在不熟悉的情况下会复杂化。

安装superset

这次学老实了,先跟着教程走。superset感觉是个“纯”的python包,如果考虑后续使用,也许可以借鉴一下jupyterhub的安装经验。

配置防火墙启动:superset run -h 0.0.0.0 -p 8088 --with-threads --reload --debugger

启动之后发现无法登录,依然是仔细看日志,然后在社区找到处理方案,这里提一下,这个社区的处理方案,提问人提问的方式太棒了非常详细。最终采用了两个配置建议(包括“TALISMAN_ENABLED”),最终见到了欢迎页面。中间一度很多错误,让我转向了docker部署,不过最终还是解决了。(原因是忘记运行superset init命令了)

意外的顺利,一个下午装好了,想想我当初学习tableau真是浪费时间

1
2
3
4
5
6
7
8
9
[Unit]
Description=Superset Application

[Service]
User=peter
ExecStart=/usr/bin/bash /home/peter/superset/superset_run.sh

[Install]
WantedBy=multi-user.target
1
2
3
4
source /home/peter/venv/bin/activate
export FLASK_APP=superset
export SUPERSET_CONFIG_PATH=/home/peter/superset/superset_config.py
superset run -h 0.0.0.0 -p 8088 --with-threads --reload --debugger

最后就注册成服务,可以随意使用了。其中有个点,如果User配置为root,账号密码不可用。这样让人非常在意账号密码存在哪里?还是说执行初始化的时候和用户关联了?

果然,破案了

配置casdoor

嗯,突然配置这个东西,确实有些跳跃。不过类似于游戏技能树一样,这个模块配置好可以很大程度上解决登录问题。
首先,还是看看rocky上怎么安装docker吧,毕竟,podman还是有些问题,生产环境中docker用的更多一点。
经过测试,casdoor确实可以在内网部署https服务,使用acme.sh申请证书。

1
2
sudo docker compose restart casdoor
sudo docker compose restart casdoor_proxy

配置这个重点是要生成证书,这样要学习一下acme.sh了。curl https://get.acme.sh | sh -s [email protected]
acme.sh结合cloudflare可以很容易的生成证书。主要都是证书的安装细节和注册脚本了。根据需要要安装cron:sudo dnf install crontabs

无论如何,安装sso的服务端都还是简单的,下一步就是考虑如何把用户端配置好,配置流程如下:

  1. SSO_CLIENT_ID和SSO_CLIENT_SERCRET是在SSO服务端生成的;
  2. 应用的实际生成地址需要提供给SSO作为callback;
  3. 使用命令行注入ca.cer的位置;export NODE_EXTRA_CA_CERTS=/real/path/ca.cer;
  4. 把sso中应用公钥的信息提供给应用。

注册到这里就出错了,node无法识别le的证书,本来node就没有le的证书,这里更换zerossl证书试试(之后依然要重新注入)。
最后还是选择了注册zerossl的账号,至此,我终于了解了证书的运作机制,并了解了acme.sh工具的使用。可以参考的教程还是很多的.

经过一通操作,至少在自己的实验环境搞定了。然后把app和sso都准备一份部署工具,等完全部署好,就可以了。

其中使用一个fetch函数可以验证证书有没有配置好。

1
2
3
4
5
fetch('https://door.tcub.site')
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.error(error));
//node test.js

node当然需要专门安装:

1
2
dnf module list nodejs
dnf module -y install nodejs:20/common

以casdoor为中心,就能很大程度上解决用户管理的问题了。

不过,casdoor的角色配置也是有技巧的,有两个问题要解决:

  1. 如何限制用户的权限范围,比如只能登录指定应用;
  2. 如果防止用户登录casdoor本身这个系统;
  3. 如何使应用登录无缝化;

实际使用起来,还是有些注意事项的:

  1. 如果防止应用用户和系统用户混乱(主要是昂志用户登录cas系统,这种情况下,需要建立新的组织,应用需要重新配置);
  2. 新建组织的情况下,只有组织中有应用的情况才能添加用户成功;

修改好了应用信息之后,需要重新发布一下就可以了。docker build -t video:v7 .

troubleshoot

早起发现无法正常生成域名的证书,“明明昨天还是正常的呀”然后一看昨天的证书其实也是不正常的。却少必要的公共证书。经过一个小时的波折,冷静了下来,法相原来是dns_cf的key的问题。之前的key只是访问一个域名的(或者至少zone_id只能指向一个域名)。
zone_id可以在dns管理对应域名的信息页面找到。令牌的话,在个人信息(右上角)里:My Profile->API Tokens->use DNS:edit_template就可以获得了。

troubleshoot 2

系统可能需要安装ca证书才能访问目标服务器,主要是使用update-ca-certificates命令。不同系统安装方式不一样,这里记录一个ubuntu的例子。对于php74容器运行的php网站,需要进入容器更新证书才能实现sso通信。

1
2
3
4
docker exec -i container_id bash #进入容器
ls -l /etc/ssl/certs/ #查看所有的证书所在目录,可以把证书拷贝到这个目录
vim /etc/ca-certificates.conf #把证书名字添加进去
sudo update-ca-certificates #更新证书

开始配置homelab

假期开始,终于有足够的时间配置属于我的homelab了,服务器也就位了。借助pve和cloudflare尝试构建homelab。之前我一直担心,cf的tunnel是不是不够快?现在想到我的homelab我突然明白了,如果我自己用就没有这个问题了。首先试试authentik

首先,尝试用podman部署一下authentik。sudo dnf install podman安装podman。然后就是一系列命令:

1
2
3
4
5
sudo dnf install python3
sudo dnf install python3-pip
pip3 install podman-compose
sudo dnf install -y epel-release
sudo dnf install -y pwgen

使用podman失败了,但是不清楚可能的原因,所以,还是老老实实装docker吧。

1
2
3
4
5
6
7
sudo dnf remove docker docker-client docker-client-latest docker-common docker-latest \
docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl start docker
sudo systemctl enable docker.service

根据教程进行安装到是很快,利用cf的tunnel也很容易把服务发布出去,不过证书和配置应用搞不好就没有那么简单了。毕竟这个compose文件都没有看太懂。
自己的小homelab运行一两天就会发现进程号到了几十、几百万,估计还是哪里有泄漏。果然很多事情是自己实践之后才能发现的。

有了自己的homelab之后发现vscode更加好用了,然后再次意识到,还是使用证书访问更加方便。

1
2
3
ssh-keygen -t rsa -b 4096
ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected] //for linux client
type $env:USERPROFILE\.ssh\id_rsa.pub | ssh [email protected] "mkdir .ssh && cat >> .ssh/authorized_keys" // for win

node网站镜像化

一个简单的node网站,是用的时候确实有问题的情况,直接发布镜像会方便一些,但是网站在使用了prisma的orm之后就需要在发布前建立数据库。建立数据库可能会用到的一些脚本如下:

1
npx prisma db push

ubuntu系统的磁盘管理

ubuntu默认安装的情况下会使用lvm,当然lvm不是问题,但是他会预留太多空间,导致默认的硬盘很容易就满了。这个时候就可以通过调整磁盘获得空间。

1
2
3
4
sudo df -Th
sudo vgdisplay # or sudo vgs
sudo lvdisplay
sudo lvextend -l +100%FREE -r /dev/ubuntu-vg/ubuntu-lv

总体来说,lvm还是提高了整体的灵活性。

建立首页

终于来到了首页阶段,到目前为止,homelab的服务和连接已经非常多了,如果能有一个好的首页就好了,先动起来,随手找到了一个首页项目,先把各种连接汇总吧。经过考虑zfile和authentik也许可以发布出去,其他的服务暂时还是放到家庭内网里比较好,必要的时候可以通过蒲公英访问。最终效果很好~可以非常方便的整理常用的访问链接了。

小结

随着第一天来到办公室,我也意识到假期结束了~买的很多书都没有看~不过我的homelab建设倒是很成功了。有个让我欣慰的点,我看到remix.run框架来到了2.7了,非常快的速度!感觉2.0还在昨天,新的开始加油~

《虚拟化KVM极速入门》

封面

系统学习开源虚拟化技术

前言

虽然琐碎的工作很多,但是,还是要加强学习保持充电为未来做准备。依然是非常朴实的书,就是讲解日常操作,顺利读完可以补充非常多的基础知识。想到很多时候,不管是运维还是开发,更多的是有条理的组织起基础工作,从这个角度考虑读这本书挺好的。

笔记

QUME这个东西,10年前就听到同学介绍过,当时如果好好学,直接就在linux体系里学习是不是会有很好的收获呢?也不一定。libvirt,virtIO、virt-manager等这些关键词也真实的的了解到了,并且特别好的一点,书里用cockpit做例子,非常实用。

读书真的是学习的捷径,只要能静下心来好好读好书,其实就可以节省非常多自己试错的成本。今天有个好消息,用于支持虚拟机学习的内存到货了,希望内存安装可以顺利。

实验环境开始

开始了新的学习,首先是文具的就位。

购买了新的内存,这样我就可以开出足够的虚拟机了

借助教程网友建议,准备开启windows的虚拟化选项,给自己建立一个简单的实验环境。

要关闭windows本身的内核保护

实际处理的时候:关闭所有的hyper-V服务项目、取消所有的虚拟机功能、关闭windows本身的内核保护,这样就可以嵌套虚拟机开始学习了。

非常暖心的提示,我碰巧使用了用户名peter,结果操作系统提示我“能力越大责任越大”

操作记录

默认安装的服务器版本,没有gui,即使是软件包中也没有包含gui的选项,思考了一下,也许可以尝试不用GUI,就用fedora39锻炼一下也未尝不可。第一个问题,先执行一下dnf update。发现速度很慢……果然第一项还是设置更新源吗?实际运行发现安装软件包非常快速!然后解决联网的问题,我突然灵光一动,保证外部可以访问,也保证可以联网,只要配置两块网卡就可以了,这样还能熟悉一下网络配置。

cockpit管理服务器开始

创建嵌套虚拟机~

一步一步完善cockpit的使用,首先是安装文件管理器,这样上传下载文件能方便许多,文件管理器

sudo dnf install cockpit-navigator
这样就能进行文件管理了,利用文件管理可以上传ISO镜像,这样就可以安装操作系统了。

一开始就碰到了权限问题,NFS默认是无法写入,只读的。查了一些资料,no_root_squash等适当的配置选项可以解决问题,不过这样感觉不安全,后来看来的教程提到可以将文件夹other属性打开,这个思路更加合适。虽然可以用终端解决,但是想了一下,还是尽量学习一下OMV吧。

进入这里设置权限

把权限设置正确之后就可以使用NFS了。

快速开始第二本书

东凑点时间,西凑点时间,虽然断断续续,但是最终还是把第一本入门看完了,开始了第二本的阅读。

开始第二本书,高阶教程

这次阅读之所以这么快也是因为实操实验进行的少。收获也是非常大的,入门教程里把虚拟机的虚拟网络解释的非常详细。

这本书更多的是利用间隙去看,其中很多内容,corosync、pacemaker等这些高阶项目确实让人受益匪浅,了解了非常多之前不知道的知识,比如stonith设备。比如集群的高可用本质就是让虚拟机在物理机器上飘移,再比如,virsh居然有如此的配置参数,而配置方式就是简单的XML文件,再比如,NUMA的vcpu和cpu的位置居然是可以指定的,等等。
实体书可以留下来,这样后续如果有用到相关的内容可以快速参考。

小结

kvm这本书真的是开卷有益的典型,如果早点知道这些内容,也许我就会更早采用kvm技术栈。可惜,一开始准备的内存其实并没有发挥什么作用。

操作笔记

尝试安装了ubuntu,然后意识到了ubuntu默认的磁盘管理已经到了lvm了,刚好学习一下。这里是操作参考

《Proxmox VE 超融合集群实践真传》读书笔记

前言

利用工作的间隙(也可以说是工作需要),读了这本书。先说结论:是一本非常棒的书。市面上关于pve的中文书籍并不多,这位作者能把书写完并出版真的是非常不易。

书的封面非常干净,有技术风格

如果是过去的我,可能并不会喜欢这个书的风格,因为“深度”不够,比如:作者拿出来一个小节讲解虚拟机的创建,销毁,这些基础操作过于简单而浪费篇幅了,相对应的“深刻”的技术原理和规律却并不多。不过,这次的阅读中,我却感觉意外顺畅,变化最多的应该是我的心态。因为我能明显感觉到作者实战经验丰富,他愿意把自己的经验整理分享出来,我通过静静地阅读就能获得这些知识,真的是非常棒的。

生活中,我发现我个人缺少的表达和倾听的能力,我如果能够静静地慢慢地做我喜欢的和擅长的事情,大概率是能做好,一定概率可以做的非常之好,静静地看书能让我在长跑路上不断进步,像我父亲说的:“更高更快更强”。静静读完这本书,然后挑战数理逻辑的学些,我觉得未来肯定会感谢现在的刻苦用功。

一些阅读感受

非常喜欢第七章的例子,只有一台机器,合理利用达到了非常好的效果,并且对关键的HAPROXY给出了详细配置。特别棒的阅读体验是,在文章的最后,用列表的方式给出实际效果的时候,居然不经意间透露了这个创业公司的项目:自动售货机。感觉是非常新奇的体验阅读体验,仿佛“推理小说”一把,在最后告诉你,这些“IT系统”实际在应用中支持了什么样的工作。

作者在行文中还会明显记录一下当时他的想法,甚至是弯路。我突然看到了非常熟悉的感觉,这不就是我自己写的文档吗?也就是说,因为我自己的开始不断输出了,所以,我再吸收的时候读的更加顺畅了!!!果然,坚持做正确的事情就是有回报。这也更加坚定了让我多写、多输出(无论是公开的输出,还是私下的写作笔记),让自己获得正反馈。

下单新书

看完了PVE果然开卷有益,让我了解了一片新的天地,趁热打铁,我又下单了关于KVM的书籍。同时利用手边的电脑快速开启各种实验,先体验体验OpenMediaVault的功能。

《数理逻辑--证明及其限度》读书笔记

前言

我要读《数理逻辑–证明及其限度》。写下这篇文章的时候我还在看龙骨,想不到我自己可以同时开两本书的阅读。契机就是看到了lean语言,读到了一些科普读物。尝试要在图书馆借一本书,不过,最终借到的教材不太好,最终选择了买二手书。

我希望这本书也能读完,因为我考虑形式证明如果能用lean这样的语言实现,那么是不是可以再区块链这类协议中应用呢?AI应用即然不准确,是不是把逻辑模块添加进去能加强整体性能呢?现在各种AI编辑软件其实都不好用,解决不了问题,我是否能做出来一个特定领域解决极致的问题呢?等等、等等想法,希望我在读完这本书后能获得一部分解答,或者获得更多的问题。

进度缓慢

备忘参考资料

Lean 4 定理证明
让我心心念非常感兴趣的编程语言。

《折断的龙骨》读书笔记

前言

一个相对清闲了下午,约了友人晚上吃饭,友人下班略微晚,有个小间隙,又开了一本书。契机也是非常神奇,晚上看up主解说《向日葵不开的夏天》的时候,他说明设定系作品的时候同时举例子《死了七次的男人》和《折断的龙骨》。然后,让我想到了,如果看下一本的话,就看看龙骨吧。

这次如果有什么不同的的话,就是我要好好的记录读书笔记,你看到的这篇文章就是我在开读的时候记录下来的。

序章

我非常喜欢这个序章!仿佛找到了古早的感觉,简洁的介绍了人物和框架,并且奠定了基调。如果有什么感触的话,那就是看到了当初我看《金田一一事件薄》的感觉,我最喜欢的那句开头。“河边一具尸体,无论如何也查不出身份,当时我们不可能知道,就是在这里开始了悲剧的序幕”。

开篇小插曲

看到了奇怪的名字

第一个笑点出现了,这个名字,如果说这是中国人,那就是“巴嘎推理”展开了。

跟班登场

我特别想吐槽你,这名字奇怪吗?那个法克儿才奇怪吧!!文档到这里应该没有剧透,后续,我就尽量不放原文了。

“法尔克在一瞬间就认定了我的身份。这样的人我还是第一次见。刚刚他提到逻辑,莫非是亚里士多德的信徒?”莫名其妙的与我在看的另一本书联动了。

沉浸阅读直到结束

看完了。对,从刚刚那行字到现在,我发烧了三天,然后恢复了两天。是的,在发烧的过程中,我断断续续的看完了折断的龙骨。大概率的原因是因为和友人吃饭的时候吹了凉风,风寒发烧。

和《死7》感觉一样流畅,感觉是非常好的作品了。如果说逻辑的体现也非常正统,在临近书结尾的部分,登场人物慷慨陈词,完成剧情的高潮,并把所有的伏线都收回来。嗯,推理小说实至名归。

当时看《死7》的时候半夜不睡觉偷着看。相比于《死7》,这本书的阅读是不是能算更健康一点呢?不好说。至少很长一段时间内,我不准备因为任何不必要的事情牺牲睡眠了,发烧生病,让我觉得应该好好保重身体。

涉及剧透的一些笔记

其中有个魔法师,是穆斯林,作者用语言误会(《两个人的距离概算》里有类似的手法)和宗教习惯构建的解答,其中让我特别惊喜的是,阿拉伯人是使用的魔法是希腊的巨人。这似乎正是映射了某些历史事实,文艺复兴的源头,阿拉伯的贡献等等,都被浓缩在小小的细节设定里,非常有意思。

尾声

个人其实并不太喜欢这部作品,看完之后,我又想起了《连峰之间是否放晴》,还是更加喜欢这类能引起我共鸣的作品。

部署一个支持小组开展工作的jupyter环境

前言

python统一环境从来都是一个复杂的问题,特别是要多人合作的时候(甚至两个人合作要统一环境也经常出问题)。环境配置不一致很容易打击团队的积极性,让本来应该聚焦到具体问题解决上的注意力被分散。晚上有很多教程,都是只是讲某个技术细节的配置,很少有完整的方案。本教程详细介绍一下如何配置一个帮助管理员或者小组领导带领小队完成数据分析任务的jupyterhub环境,包括环境安装、用户配置等诸多细节。

内网穿透

是的,从内网穿透开始讲,太多的教程都会忽略服务最终如何访问到的问题,好像所有人都有了很好的网络环境,实际上的情况往往很复杂,很多新手可能就被“卡”在这里。当然,本教程也忽略了操作系统的安装这个步骤,尽量做到取舍有度。

假设你已经有了一台可以上网的ubuntu主机,本教程是基于一台NAT模式的虚拟Ubuntu进行的。登录tailscale的官网,找到对应的安装命令。

我用github账号登录的,其实这类服务一般都非常易用,就当游戏玩能学到不少东西

curl -fsSL https://tailscale.com/install.sh | sh
相关的详细教程可以在官网查到https://tailscale.com/download/linux,官网贴心的附带了分步骤安装教程。

如果“网络好”,一条命令,输入密码就能完成安装了

其实只是需要下载一个25MB的包,因为网络的原因,这个步骤我失败了很多次。原因是tailscale的服务器在境外。如果能有一个安装了科学透明代理的网络环境,这类服务安装起来会简单很多,等后续的教程中我在补这部分吧。

使用命令启动tailscale,然后访问地址进行验证就成功了

启动的命令是: tailscale up

这里有个小提示:命令行给出的地址,并不需要一定要“本机”登录,实际上我用我的笔记本电脑访问了图上的地址,完成了验证。

下一步我在macbook上安装tailscale终端,就可以进入虚拟的内网,访问服务器了。跨区原因不能在应用商店安装,用brew安装也很方便。

1
2
3
4
5
6
brew install tailscale #安装tailscale
sudo tailscaled install-system-daemon #启动为系统服务
#sudo tailscaled uninstall-system-daemon #这个命令应该是注销系统服务
tailscale up
tailscale ip #查看ip地址
tailscale status #查看设备情况

启动服务就可以把终端和服务器放在一个内网里了,这样可以随时访问到NAT中的虚拟服务器了。
我的教程这里相当于一个例子,如果你使用的是不同设备,官网有详细的教程,链接在这里安装终端的链接

本教程重点在于全面细节,为了能够随时访问到该机器,自然还要在机器上安装sshd服务,命令如下:

1
2
sudo apt install ssh
sudo systemctl ssh

然后,使用你自己习惯的ssh终端,通过tailscale服务的内网地址,就可以访问到服务器了。
这里有个个人碰到的小提示:我在tailscale使用中,有时明明服务在线,但是却访问不到主机,此时我发现使用ipv6就可以轻松访问到主机了。

安装conda

第一时间找到官方教程总是不会错的Miniconda,根据教程可以完成安装。

官方的教程如下:

1
2
3
4
mkdir -p ~/miniconda3
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3
rm -rf ~/miniconda3/miniconda.sh

以下教程均是按照官方脚本执行之后配置。不过,这里强烈建议不要完全按照官方教程来做,原因见后面章节。如果你能把教程全部看完之后再操作,建议把minicoda安装在/opt/miniconda/。

安装好之后,记得运行~/miniconda3/bin/conda init bash这个命令把miniconda加入到环境变量里,然后重新登录一下就激活了conda的base环境了。

安装jupyterhub

虽然也有非常方便的tljh安装方式。实际落地的话,下载tljh特别考验网络速度,完全没有conda来的快捷,另外使用conda安装更好定制,所以,这里推荐用conda来安装jupyterhub。

1
2
3
4
5
6
7
8
9
10
11
12
conda create -n python310 python=3.10 #创建基础环境
conda create -n jupyterhub --clone python310 #根据基础环境创建jupyterhub环境
#这里提供一些可能会用到的管理命令
#conda env remove --name jupyterhub #适当的时候可以删除环境
#conda env list #现实当前的环境列表
conda activate jupyterhub #激活环境
#conda search -c conda-forge nodejs --info #找到允许的版本列表
conda install nodejs #安装node
npm install -g configurable-http-proxy #这个可以安装代理服务
python3 -m pip install jupyterhub jupyter notebook #这个可以顺利安装notebook
#如果网速过于慢,可以试试国内的源
#pip install jupyterhub jupyter notebook -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

经过上面的操作,jupyterhub已经装好了,可以尝试启动jupyterhub了。

1
2
3
4
mkdir ~/jupyterhub #创建用来放置jupyterhub的目录
cd ~/jupyterhub/
jupyterhub --generate-config #会生成配置文件模版
jupyterhub -f jupyterhub_config.py #运行jupyterhub,这个命令一般也用来测试jupyterhub文件

如果一切正常访问http://ip:8000这个地址就可以看到jupyterhub界面了,然后就是把jupyterhub配置的可用。
这里特别提示一下,jupyterhub默认使用的是linux系统本身的用户账户系统,用户名和口令就是linux系统本身的用户名和口令,新建用户也是同理。

配置jupyterhub

配置jupyterhub也是很重要的一环,基本而言就是围绕配置文件进行;不过在那之前可以考虑先把内核配置好。

1
2
3
4
5
pip3 install ipykernel
python3 -m ipykernel install --name=jupyterhub --display-name "dev_default"
#jupyter kernelspec list #列出kernel
#jupyter kernelspec uninstall jupyterhub #卸载kernel
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas # 安装一个包看看虚拟环境中的包是否在kernel中生效

两个心得:

  1. 因为使用jupyterhub环境安装的jupyterhub(怎么读着那么绕……),所有jupyterhub再后来的子用户就默认都具有jupyterhub这个虚拟环境,安装kernel至少在这里并不是必须的(更绕了),但这里还是提一下,如果想给所有用户都配置可选的kernel意外的困难,毕竟jupyterhub就装在jupyterhub虚拟环境下(……);
  2. pip -i参数可以加快安装速度,另外也不用额外的更改源,意外的好用。

直接成功,这样就可以随意调整kernel的环境了

1
2
3
4
5

``` python
c.JupyterHub.admin_users = {'peter'}
c.Spawner.default_url = 'tree'
c.LocalAuthenticator.create_system_users = True

以上命令分别是设置默认界面、设置管理员、设置添加用户方式等等,这个方面可以参考文档,当然直接阅读jupyterhub --generate-config命令生成的模版学习如何配置也是非常棒的。学习的重点就是更改一些配置,然后用命令直接运行看看结果。很多问题是在使用中发现的,比如现在jupyterhub添加用户是会报错的。

添加用户报错

这个报错是我意料中的,原因自然是权限不足,目前普通账户无法创建用户。(意料中出错,然后修正,有点测试驱动的意味在里面呢)。为了解决以上问题,同时也是为了方便日常管理jupyterhub服务,把这个注册为系统服务。

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
which jupyterhub #这个命令确定Environment的目录

#这段脚本根据你自己的情况改一下再用
sudo sh -c "cat <<EOT > /etc/systemd/system/jupyterhub.service
[Unit]
Description=JupyterHub

[Service]
User=root
Environment=PATH=/home/peter/miniconda3/envs/jupyterhub/bin/:/usr/local/bin/:/usr/bin/:/usr/sbin/
ExecStart=/home/peter/miniconda3/envs/jupyterhub/bin/jupyterhub -f /home/peter/jupyterhub/jupyterhub_config.py

[Install]
WantedBy=multi-user.target
EOT"
#以上命令是以root用户安装conda为例子进行的

sudo systemctl daemon-reload
sudo systemctl start jupyterhub
sudo systemctl enable jupyterhub
sudo systemctl status jupyterhub
#sudo systemctl restart jupyterhub
#sudo systemctl disable jupyterhub
#sudo systemctl stop jupyterhub
#sudo journalctl -u jupyterhub -n 50 #这是查看日志的命令

这样整个jupyterhub就配置好了。再次尝试添加用户,成功添加。
用户添加成功
然后用管理员登录,给这个用户设置一个密码sudo useradd -m -s /bin/bash hubuser && sudo passwd hubuser,用户就可以登录了,然后我们就看到了错误。

是的,没有成功,这个教程也会展示一些失败的部分

这是说明一开始就配置错误了,conda被安装在了用户目录下,新添加的用户完全访问不到。等于是官方教程的mkdir -p ~/miniconda3这个命令开始就出错了,应该把miniconda安装在/opt里才比较合适,事到如今,只能尝试修复一下权限了。

1
2
sudo -u hubuser /home/peter/miniconda3/envs/jupyterhub/bin/jupyterhub-singleuser -h
chmod 755 ~/home/peter/

提示:如果有机会重新配置conda,一定要安装在/opt目录。

经历漫长的配置,“一般”用户可以关注开发了

虽然配置工作很辛苦,不过至少环境可以统一了,如果用户多一点(也不需要特别多,节约的精力依然可观),使用conda在对应虚拟环境下安装的包都可以让所有用户共享。对于某些的特定的共享文件,可以放在共享系统库目录中共享,python -c "import sys;print(sys.path)"命令输出的地址都可以放置共享文件。另外对于,需要使用ctypes.cdll.LoadLibrary函数加载的模块,系统会寻找LD_LIBRARY_PATH设置的目录,对于systemd,设置两个节点就可以了。

1
2
3
4
#这是配置例子,实际测试过并没有成功
[Service]
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
Environment="LD_LIBRARY_PATH=/opt/gams/"

是的,最终没有成功,jupyterhub貌似忽略了systemd的环境变量

需要在jupyterhub_config.py文件中配置保留哪些环境变量参考链接

1
c.Spawner.env_keep = ['LD_LIBRARY_PATH','PATH', 'PYTHONPATH', 'CONDA_ROOT', 'CONDA_DEFAULT_ENV', 'VIRTUAL_ENV', 'LANG', 'LC_ALL', 'JUPYTERHUB_SINGLEUSER_APP']

我们来捋一下环境变量的传递:首先系统运行bash的时候会加载用户自己和系统通用的环境变量->conda环境会激活部分环境变量->systemd的unit文件忽略系统变量,可以自己设置->jupyterhub要设置过滤哪些,默认继承的环境变量里没有LD_LIBRARY_PATH。感觉就是个环境变量的传递游戏。

环境变量最终“穿透”成功

用户目录配置

sudo mkdir share && chmod 777 share/首先建立一个目录供所有人共享使用。

建立起基本的目录结构
mkdir pub codes project

通过以下方式可讲目录的权限给到任意用户。

1
2
3
4
5
6
cd /var/share/project/
mkdir 01-notebooks
sudo su
chmod 777 01-notebooks
cd /home/hubuser/
ln -s /var/share/project/01-notebooks 01-notebooks

通过以上设置,就可以使用linux的权限管理机制来管理用户的文件了。

gams安装

这是安装文档

这个可以用来参考在虚拟环境中安装gams

安装gams其实比想的要简单,只要按照文档把gams解压,并配置好环境变量就可以了。

对于jupyterhub的情况,在systemd的注册脚本中,在Environment配置节点,把gams所在的目录添加进去,就可以完成配置了。本教程主打一个详细,看到无数教程都写如何配置,但是最重要的其实是怎么确定配置好了。这里提供gams的例子:

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
sh -c "cat <<EOT > ~/example.gams
Sets
i canning plants / seattle, san-diego /
j markets / new-york, chicago, topeka / ;

Parameters

a(i) capacity of plant i in cases
/ seattle 350
san-diego 600 /

b(j) demand at market j in cases
/ new-york 325
chicago 300
topeka 275 / ;

Table d(i,j) distance in thousands of miles
new-york chicago topeka
seattle 2.5 1.7 1.8
san-diego 2.5 1.8 1.4 ;

Scalar f freight in dollars per case per thousand miles /90/ ;

Parameter c(i,j) transport cost in thousands of dollars per case ;

c(i,j) = f * d(i,j) / 1000 ;

Variables
x(i,j) shipment quantities in cases
z total transportation costs in thousands of dollars ;

Positive Variable x ;

Equations
cost define objective function
supply(i) observe supply limit at plant i
demand(j) satisfy demand at market j ;

cost .. z =e= sum((i,j), c(i,j)*x(i,j)) ;

supply(i) .. sum(j, x(i,j)) =l= a(i) ;

demand(j) .. sum(i, x(i,j)) =g= b(j) ;

Model transport /all/ ;

Solve transport using lp minimizing z ;

Display x.l, x.m ;
EOT"

以上是官方例子的模型,在用python调用。

1
2
3
4
5
6
7
from gams import GamsWorkspace
import os
ws = GamsWorkspace()
job = ws.add_job_from_file(os.getcwd() + "/example.gams")
job.run()
for rec in job.out_db["x"]:
print(f"x({rec.key(0)},{rec.key(1)}): level={rec.level} marginal={rec.marginal}")

这就是配置好的效果~

小结

至此,一个小型的可多人共享的jupyterhub配置好了,团队需要共享的代码可以放到share目录中,权限根据linux系统的权限来管理。对于管理员来说,需要进行的操作无非就是如下几种:

  1. 创建用户,并设置密码以及忘记密码时重置密码
  2. 维护项目目录的文件内容
  3. 把目录挂载给指定用户,并设置适当的权限
  4. 删除用户的权限或者不允许其再看到对应的目录
  5. 给虚拟环境安装适当的软件包

以上就是管理员所有操作的枚举,只要管理员熟悉了以上操作,就可以让团队利用好jupyterhub顺利推进工作啦。

在ubuntu上部署一个php网站

前言

很久没有写教程了。
其实日常写的东西并不少,但是我的博客还是断更了,距离上次写内容一个月以上了,这一个月的生活变化、人际关系变化特别多,总的来说:一切在客观上更加顺利了。
写下这篇教程的时间,我有非常多的想法,但是要把想法落地落实,却又需要很多的时间和精力,本着尽量留下痕迹的方式,如果要写就尽量留下公开的痕迹吧,本着这样的思路写下这篇php网站的部署博客。

环境部署

第一步是安装ubuntu,有一个可以部署应用的系统。由于本文撰写的时间,我选择了ubuntu 22.04(友人推荐的长期支持版),至于ubuntu的安装这里不在赘述。

装好的干净的Ubuntu

设置swap

新的机器,最好设置一下swap防止内存不足,我的机器是2g,设置4g的交换空间应该就足够了。
相关脚本如下:

1
2
3
4
5
6
7
8
sudo swapon --show #查看是否设置了交换空间
sudo fallocate -l 2G swapfile #创建交换文件
# sudo dd if=/dev/zero of=/swapfile bs=1G count=2 #fallocate不可用时采用这个命令
sudo chmod 600 swapfile #设置文件权限
sudo mkswap swapfile #创建交换文件
sudo swapon swapfile #启用交换文件
echo "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab #添加内容到启动项中
free -h #检查是否成功

我的系统是使用虚拟机安装的,一开始就被设置好了交换分区了。

交换分区一开始就设置好了

安装docker

第一步,肯定是安装docker,虽然整个网站是使用的php,不过,数据库最好还是容器化比较简单,安装docker按照教程来就可以。Install Docker Engine on Ubuntu,这是安装教程链接,使用过多次,每次都很顺利。

卸载旧版本:这个命令是卸载旧版本用的,如果没有安装旧版本,可以不用这个。

1
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done

设置docker的库,设置好docker软件库,为下载做准备

1
2
3
4
5
6
7
8
9
10
11
12
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

安装docker,并进行测试安装效果

1
2
3
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

sudo docker run hello-world

安装openoffice

最好给openoffice单独建立一个目录,这样使用compose可以直接部署openoffice服务。
使用docker compose来部署openoffice服务

1
2
3
4
5
6
7
8
9
10
11
12
version: "3"

services:

onlyoffice:
container_name: oo7
image: onlyoffice/documentserver:7.4.1.1
ports:
- "9000:80"
restart: always
environment:
- JWT_SECRET=<yourkey>

当然可以使用命令解决以上问题:!!!注意,yourkey需要修改!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mkdir -p ~/workspace/onlyoffice
cat <<EOF | tee ~/workspace/onlyoffice/docker-compose.yaml
version: "3"

services:

onlyoffice:
container_name: oo7
image: onlyoffice/documentserver:7.4.1.1
ports:
- "9000:80"
restart: always
environment:
- JWT_SECRET=<yourkey>
EOF

sudo docker compose up -d #下载镜像,启动服务

如果想停止服务,可以使用一下命令 sudo docker compose down

http://localhost:9000,可以通过这个网址查看是不是部署成功了。

onlyoffice的镜像可能很大,可以把镜像提前准备好来节省实际部署时候的时间.

1
2
3
sudo docker images #这个命令查看镜像,找到要导出镜像的id
sudo docker save 0fdf2b4c26d3 > oo7.tar #导出镜像文件,其中参数就是镜像id
sudo docker load < oo7.tar #导入镜像

速度相当感人,如果不提前准备好镜像,一个小时就耗费在这个地方了

这里还有一个细节:下载好的镜像,可以放在一个公开的服务器上(比如放到对象存储上),这样用wget下载速度就快了。例子:wget https://www.example.com/directlink/bj/webs/oo7.tar

这里的细节其实很多,因为虚拟机是NAT模式联网,我通过web界面把文件上传到了服务器上。这时候发现如果我当初安装的server版,没有gui,那么这个工作还非常难以实现呢。虽然就算有GUI,也是因为我有自己的文件中转服务才能简单实现。

果然,一旦写起教程来,发现细节特别多

部署php环境

然后就是部署php环境的阶段,虽然按照正常流程是php、mysql、nginx这样依次安装,但是这样效率太低了,这里还是推荐用docker安装。使用docker-lnmp项目来进行php网站的配置,部署和后续迁移都会方便。

如果以dzzoffice项目的安装为例子,把源代码放到默认的www目录中,目录结构如下:
|____docker-lnmp
| |____nginx
| |____php74
| |____php81
| |____php73
| |____php72
| |____php71
| |____mysql
| |____php80
| |____php56
| |____redis
|____www
| |____static
| |____misc
| |____install
| |____user
| |____config
| |____dzz
| |____core
| |____data
| |____admin

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
find . -name "._*" -print ## 检查缓存文件
find . -name "._*" -delete ## 清除缓存
find ./ -mindepth 1 -maxdepth 2 -type d | sed -e 's;[^/]*/;|____;g;s;____|; |;g' 确认目录结构,如上图
cd docker-lnmp #来到环境目录
cp .env.example .env #创建配置文件
sed -i 's/NGINX_HOST_HTTP_PORT=.*/NGINX_HOST_HTTP_PORT=8086/' .env #替换其中的某些配置,比如,设置端口
docker network create backend --subnet=172.19.0.0/16 #关键步骤,为容器应用创建子网络
docker network ls | grep backend
docker compose up -d nginx php74 mysql #启动应用,这个命令会非常慢,可以尝试配置docker源的方式加速
# echo '{"registry-mirrors":["http://hub-mirror.c.163.com"]}' | sudo tee /etc/docker/daemon.json
# systemctl restart docker
# docker info #查看配置结果
# #在Dockerfile中的apt也可以配置更新源来加速,在apt-get update之前添加一行命令就可以了
# RUN sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list
# #sed格式如下
# sed '/^RUN apt-get update/ i\
# RUN sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list
# ' Dockerfile


docker compose restart nginx #修改配置后启动某些容器

sudo sh -c "cat <<'EOT' > ./docker-lnmp/nginx/conf.d/default.conf
server {
listen 80;
server_name localhost 127.0.0.1;

root /var/www;
index index.php index.html;

charset utf-8;
default_type text/html;

location / {
try_files \$uri \$uri/ /index.php\$is_args\$args;
}
include conf.d/fpm/php74-fpm;
}
EOT" #设置

上面的脚本细节特别坑,sh和cat会对 $ 解释两次,所以要用两次防止过滤的方式。

然后安装php网站会碰到以下错误:Host '172.19.0.74' is not allowed to connect to this MySQL server
参照这个解决思路可以通过更改访问权限来解决问题。

进入容器:docker compose exec mysql mysql -u root -p,
执行命令:

1
2
3
4
5
show databases;
use mysql;
select host,user from user; --查看用户权限配置
update user set host = '%' where user ='root'; --开放外部连接
flush privileges; --更新权限

然后就可以安装任意的php网站了,为了在网速“不快”的服务器上,可以利用docker save -o myimages.tar image1:tag1 image2:tag2 把多个镜像导出到一个文件,这样就可以整体搬家了。

bonus 配置部署包

网站在后期开发中可能需要添加扩展,扩展要通过composer安装。这样的情况目标机器上还是需要安装php,不同的系统不一样。rocky9.3参考这里

1
2
3
4
5
6
7
8
sudo dnf config-manager --set-enabled crb
sudo dnf install \
https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm \
https://dl.fedoraproject.org/pub/epel/epel-next-release-latest-9.noarch.rpm
sudo dnf install dnf-utils http://rpms.remirepo.net/enterprise/remi-release-9.rpm -y #key cmd line

dnf module list php
sudo dnf module enable php:remi-7.4 -y

当然还有个apt的例子。

1
2
3
4
5
sudo apt-get update
sudo apt -y install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt -y install php7.4

然后就是安装composer了,为了部署方便,安装在本地就可以。

1
2
3
4
5
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'e21205b207c3ff031906575712edab6f13eb0b361f2085f1f1237b7126d785e826a450292b6cfd1d64d92e6563bbde02') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php \
php -r "unlink('composer-setup.php');" \
php composer.phar require casdoor/casdoor-php-sdk #安装sdk的例子

新的问题配置到这里,虽然一切看似正常了,不过curl可以正常访问casdoor,可是php却提示缺少证书。这个时候才发现,证书是被配置在docker里的!因此,证书要暴露给docker……真的是复杂啊。

进入docker,参考这个文档可以临时更新证书。

1
2
sudo docker exec -it <container_id_or_name> bash
## update certificates

特别注意

如果修改了.env一定要重新rebuild,env的参数进入dockerfile是build时发生的。

1
2
docker compose down
docker compose up -d --build mysql nginx php74

新的问题

mysql可能会出现“Restarting (1) Less than a second ago”不断重启,这时可以用logs命令查看问题所在。

sudo docker logs container_id

具体例子dzzoffice部署

通过以上部署,dzzoffice网站就可以安装了,http://ip:port/install/index.php这个网址是固定安装目录。

先看到安装界面
检查扩展,php的扩展很多,安装程序先做检查

目录权限需要修正

chmod -R 777 config/ data/

填写数据库信息,这里会报错,参考教程修改数据库访问权限

安装过程非常迅速

设置管理员账户

网站运行起来了,然后就是设置,第一项是把onlyoffice配置好,在线浏览文章的功能是必要的。

`chmod 777 -R dzz/`给插件目录添加权限

onlyoffice要装

网盘也是必装

然后一个可以共享文件的小平台就建立好了。

Troubleshooting

使用文中的docker脚本,有时候再第一次配置错误的情况下,会产生“奇怪的跳转”,比如,访问http://127.0.0.1:8086会强制跳转到http://127.0.0.1,经过多次测试,发现这是“客户端”的某种“记忆”。解决方式也很简单,对于safari浏览器,可以把对应域名的缓存删除掉。对于Edge、chrome等浏览器,使用开发者模式,在”网络选项卡“上把”禁用缓存“勾选上,然后再访问一次这个网页就能把缓存遗忘了。

鸣谢

特别鸣谢我的妻子,她帮我写了一个小的手册文章,所以我有时间精力写下这篇教程。