Linux

fc-list

将字体放在 $HOME/.fonts 目录下即可.

Linux程序的标准编译安装流程如下, 通用于绝大多数由C和C++编写的软件, 在LFS中被大量使用.

./configure # 配置编译选项
make # 编译
make install # 将编译结果安装至系统

旧的Linux内核所使用的防火墙, 由于许多Linux生态还未迁移至nftables, 因此仍然在生产中使用.

iptables常用选项:
-A, --append 将规则添加到链的末端
-C, --check 在链中查找符合要求的规则
-D, --delete 在链中删除指定的规则
-F, --flush 删除链中的所有规则
-I, --insert 将规则插入到链的指定位置
-N, --new-chain 创建一个新的链
-v, --verbose 详细信息, 该选项可以显示系统在各个链上处理的数据量
-X, --delete-chain 删除整个链

iptables -vnL 查看当前iptables详细配置
iptables --list --line-numbbers 列出规则的行号

  1. 1.
    raw PREROUTING
  2. 2.
    mangle PREROUTING
  3. 3.
    nat PREROUTING
  4. 4.
    mangle INPUT
  5. 5.
    filter INPUT
  6. 6.
    nat INPUT
  7. 7.
    raw OUTPUT
  8. 8.
    mangle OUTPUT
  9. 9.
    nat OUTPUT
  10. 10.
    filter OUTPUT
  11. 11.
    mangle POSTROUTING
  12. 12.
    nat POSTROUTING

一个table由多个chain组成.

Linux默认带有4个table.

最常用的表, 用于决定谁能进出网络.
iptables命令在不指定表的情况下, 默认使用此表.

带有以下链:

  • INPUT 服务器的入站流量/传入连接
  • OUTPUT 服务器的出站流量/传出连接
  • FORWARD 通过服务器路由的流量, 除非此计算机是路由器, 否则一般不会用到该链

用于实现NAT网络地址转换, 用于将数据包路由至无法直接访问的子网.

带有以下链:

  • PREROUTING 发生在INPUT/FORWARD之前
  • OUTPUT 服务器的出站流量, 与filter的output相同
  • POSTROUTING 发生在OUTPUT/FORWARD之后

用于调整数据包的IP报头.

带有以下链:

  • PREROUTING
  • POSTROUTING
  • OUTPUT
  • INPUT
  • FORWARD

带有以下链:

  • PREROUTING
  • OUTPUT

一个chain由多个rule组成.

一条语句, 用于定义系统转发数据包的规则(将数据包跳转至具体的target).

数据包符合rule时的行为.

  • ACCEPT 接受
  • DROP 丢弃, 对方不会收到错误, 例如对方的ping命令会因为收不到响应而超时
  • RETURN 发送回原始链以匹配其他规则
  • REJECT 拒绝, 对方会收到错误
iptables --append INPUT --in-interface lo --jump ACCEPT # 允许环回(localhost)入站流量
iptables --append OUTPUT --out-interface lo --jump ACCEPT # 允许环回(localhost)出站流量
iptables –-append INPUT –-protocol tcp ––dport 80 –-jump ACCEPT # 允许80端口的入站tcp流量(HTTP)
iptables –-append INPUT –-protocol tcp ––dport 443 –-jump ACCEPT # 允许80端口的入站tcp流量(HTTPS)
iptables --append INPUT --protocol tcp --dport 22 --jump ACCEPT # 允许22端口的入站tcp流量(SSH)
iptables --append OUTPUT --protocol tcp --sport 22 --jump ACCEPT # 允许22端口的出站tcp流量(SSH)
iptables --append INPUT --source <ip> --jump ACCEPT # 允许来自指定IP地址的入站流量
iptables --append INPUT --source <ip> --jump DROP # 丢弃来自指定IP地址的入站流量
iptables --append INPUT --source <ip>/<subnet-mask> --jump DROP #丢弃来自指定IP范围的入站流量
# 拒绝来自指定IP地址范围的入站流量
iptables --append INPUT --match iprange --src-range 192.168.0.1–192.168.0.255 --jump REJECT
iptables --list --line-numbbers # 列出规则的行号
iptables --delete INPUT <number> # 删除特定行

iptables --list | grep policy 查看链的默认行为
输出时链名后显示的policy表示该链在不匹配时采取的默认行为
例如 Chain INPUT (policy ACCEPT) 表示INPUT链的默认行为是ACCEPT
iptables --policy INPUT ACCEPT 将INPUT链的默认行为设置为ACCEPT

自定义链可以用于复用一系列规则, 避免污染系统提供的链.

iptables --new-chain {{chain_name}} 创建自定义链
iptables --append INPUT --protocol tcp --dport {{port}} --jump {{chain_name}} 为INPUT链的特定tcp port应用自定义链的规则
iptables --flush {{chain_name}} 清空自定义链内的所有规则
iptables --delete-chain {{chain_name}} 删除自定义链(要求此链不被其他规则引用)

iptables规则会在重启后消失, 因此需要持久化.

iptables-save >iptables.rules # 将规则保存为文件
iptables-restore <iptables.rules # 载入规则文件

原理: 将出站流量转发至特定的程序(例如V2Ray的dokodemo-door端口).

# 删除nat表上的GLOBAL链(上一次运行时添加的链)
iptables --table nat --flush GLOBAL
iptables --table nat --delete-chain GLOBAL
# 在nat表上新建GLOBAL链
iptables --table nat --new-chain GLOBAL
# 忽略代理服务器地址
iptables --table nat --append GLOBAL --destination 代理服务器地址 --jump RETURN
# Ignore LANs and any other addresses you'd like to bypass the proxy
# See Wikipedia and RFC5735 for full list of reserved networks.
iptables --table nat --append GLOBAL --destination 0.0.0.0/8 --jump RETURN
iptables --table nat --append GLOBAL --destination 10.0.0.0/8 --jump RETURN
iptables --table nat --append GLOBAL --destination 127.0.0.0/8 --jump RETURN
iptables --table nat --append GLOBAL --destination 169.254.0.0/16 --jump RETURN
iptables --table nat --append GLOBAL --destination 172.16.0.0/12 --jump RETURN
iptables --table nat --append GLOBAL --destination 192.168.0.0/16 --jump RETURN
iptables --table nat --append GLOBAL --destination 224.0.0.0/4 --jump RETURN
iptables --table nat --append GLOBAL --destination 240.0.0.0/4 --jump RETURN
# 重定向tcp数据包到1080端口
iptables --table nat --append GLOBAL --protocol tcp --jump REDIRECT --to-ports 1080
# 将GLOBAL链添加到nat OUTPUT链上
iptables --table nat --append OUTPUT --protocol tcp --jump GLOBAL

由于缺乏一种有效的直接在本机用V2Ray over WS透明代理UDP流量的手段,
这导致透明代理无法直接用于解决DNS污染的问题.
cloudflaredh和dnscrypt-proxy经过测试都不能正常连接,
并且它们还存在与Systemd和NetworkManager不兼容的问题.

网络上现有的行之有效的关于V2Ray iptables透明代理的教程都是面向网关设备的,
因此如果想在Linux系统上解决DNS污染问题, 必须建立一个专门的透明代理网关,
这意味着在Linux上运行一个专门的虚拟机用于网关.

iptables的过滤依赖的是IP地址, 当透明代理使用的代理服务器会变更IP时,
会导致iptables无法为其创建例外规则, 进而导致网络断开.

在3.14内核中出现的新防火墙, 用于替代iptables, 有更强的性能和可配置性.

https://github.com/moby/moby/issues/26824

Ubuntu曾经两度将iptables迁移至nftables, 但最终都未能成功.
最近一次尝试是Ubuntu 20.10, 但实际的发行版中仍然在依赖iptables.

cat /etc/group
cat /etc/passwd
su - blackglory

在一些发行版里有此命令, 例如Ubuntu.

adduser blackglory

这种添加新用户的方式是最原始的, 需要手动设置很多内容, 因此不推荐.

useradd user # 添加用户
useradd -groups group1,group2 user # 添加用户并设置其用户组
useradd -D # 显示添加用户的默认配置(保存在/etc/default/useradd文件中)
useradd -D -s /bin/tsch # 将默认配置的登录shell修改为tsch (只是演示可以用此命令直接改默认值)
userdel user # 删除用户(默认情况下只会移除/etc/passwd文件中的用户信息)
userdel -r user # 删除用户, 并且删除其主目录和邮件目录

该命令用于修改/etc/passwd文件中的大部分字段, 请查看手册.

usermod -L user # 锁定用户, 使其无法登录
usermod -U user # 解除锁定
usermod -aG sudo blackglory # 将用户加到sudo组中
usermod -aG docker blackglory # 将用户加到docker组中
passwd # 交互式修改密码
passwd new_password # 修改当前用户密码
passwd username new_password # 修改指定用户密码(需要root权限)
passwd -e username new_password # 给用户设置一个简单的密码, 并要求其下次登录时修改密码

从文件中读取userid:passwd对为大量用户修改密码

chapasswd < users.txt

该命令可用于设置密码过期日期, 使临时用户届时无法登录.

$ ls -l
-rw-r--r-- 1 slynux slynux 2497 2010-02-28 11:22 bot.py
drwx-xr-x 2 slynux slynux 4096 2010-05-27 14:31 a.py
-rw-r--r-- 1 slynux slynux 539 2010-02-10 09:11 cl.pl

文件权限的第一列:

  • - 普通文件
  • d 目录
  • c 字符设备
  • b 块设备
  • l 符号链接
  • s 套接字
  • p 管道

剩余部分每3个字符为一组, 共分3组, 分别对应: 所有者权限, 用户组权限, 其他用户权限.

所有者权限有一个setuid/SUID(s)的特殊权限, 占用执行权限(x)的位置.
该权限允许其他用户以所有者的权限来执行该文件.

用户组权限有一个setgid/SGID(s)的特殊权限, 占用执行权限(x)的位置.
允许以该目录的拥有者所在组的权限来执行该文件.
如果该权限用于目录, 则目录中创建的新文件会以目录的用户组作为默认用户组.

执行权限还有u, g, o三种权限, 分别代表此权限跟所有者, 用户组, 其他用户的权限一致.

目录的读权限(r) 允许读取目录中文件和子目录的列表.
目录的写权限(w) 允许在目录中创建或删除文件或目录.
目录的执行权限(x) 指明是否可以访问目录中的文件和子目录.

目录有一个特殊权限粘滞位, 占用其他用户权限的执行权限(x)的位置.

带有粘滞位权限的目录, 被限制了只有目录的创建者能够删除目录中的文件, 用户组和其他用户即使有写权限也不能操作.

t 无执行权限(x)的粘滞位
T 有执行权限(x)的粘滞位.

假设需要设置权限: rwx rw- r--

chmod u=rwx g=rw o=r filename

u 指定所有者权限
g 指定用户组权限
o 指定其他用户权限

可以对用户、用户组和其他用户用 + 进行添加权限, 用 - 删除权限, 用 = 设定权限:

chmod o+x filename # 其他用户权限添加可执行权限
chmod o-x filename # 其他用户权限删除可执行权限
chmod a+x filename # 为每组权限添加可执行权限(a可省略)
chmod a=rw filename # 为所有用户将文件权限设置为rw(未包含的权限都会被设置为-)
chmod o+r,u+w filename * # 多个权限操作可用","隔开以一次执行
chmod a+t directory # 为目录添加粘滞位

也可以用八进制数字设置权限, 对应如下:

  • r-- 等于 4
  • -w- 等于 2
  • --x 等于 1

将权限相加即可得到权限的八进制表示, 例如 rwx 等于 7.

rwx rw- r-- 等于 7 6 4 等于 764

chmod 777 . -R # 递归设置当下目录下的所有文件和子目录的权限为777

只有root用户可以更改文件的所有者.

chown user.group filename # 用户与组之间用.分隔开, 也可以省略其中一边
chown user:group filename # 用户与组之间用:分隔开, 也可以省略其中一边
chown user.group . -R
chown user:group . -R

具有文件所有权的用户可以更改文件的用户组, 只要该用户同时是原用户组和新用户组的成员.

chgrp group file
id # 当前用户的UID和GID
id -u # 当前用户的UID
id -g # 当前用户的GID
id <username> # 指定用户的UID和GUI

iotop用来确定系统上的进程/线程处理IO的实时情况.

由Python开发的进程管理器, 在用户空间中运行.

由Node.js开发的进程管理器, 在用户空间中运行.
pm2可以生成systemd, upstart, launchd, rc.d的脚本.

pm2 start app.js 运行程序(也支持js以外的语言, 对脚本语言会根据文件后缀名决定解释器)
pm2 start web.js --name 'web-interface' 运行程序并命名
pm2 start app.js --wait-ready 等待程序发出ready信号(通过Node.js的 process.send('ready') 来通知pm2)
pm2 start app.js --no-autorestart 运行程序但不自动重启, 适用于运行一次性脚本

pm2 [list|ls|l|status] 列出所有运行中的程序
pm2 [list|ls|l|status] --sort [name|id|pid|memory|cpu|status|uptime][:asc|desc] 列出所有运行中的程序
pm2 jlist 以json格式输出list
pm2 prettylist 以格式化后的json格式输出list

pm2 stop <app_name|namespace|id|'all'|json_conf> 停止
pm2 restart <app_name|namespace|id|'all'|json_conf> 重启
NODE_ENV=production pm2 restart <app_name|namespace|id|'all'|json_conf> --update-env 重启同时更新环境变量
pm2 delete <app_name|namespace|id|'all'|json_conf> 删除

pm2 [show|describe] <id|app_name> 获取指定程序的更多细节

pm2 monit 打开监控终端

pm2 logs 查看pm2的日志
pm2 logs --json 输出json格式的日志
pm2 logs --format 输出格式化后的json日志
pm2 logs <app_name> 查看指定程序的日志
pm2 flush 冲洗掉所有日志
pm2 reset all 重置计数器(重启次数)

pm2 startup 让pm2在服务器重启时自动启动(不支持Windows)
pm2 unstartup 取消自动启动
pm2 save 保存当前pm2运行的进程, 以便能在重启时自动恢复.
pm2 resurrect 手动恢复pm2记录.

当Node.js的版本更新, 或pm2被重新安装时, 为了让pm2程序找到模块, 需要手动运行命令以更新路径:
pm2 update

pm2可以通过CLI触发正在运行的服务内设定的函数.

参考: https://pm2.keymetrics.io/docs/usage/process-actions/

pm2的声明式配置文件被称作生态系统, 可用来管理多个应用程序,
配置文件需要以 .config.js 结尾.

pm2 ecosystem 生成生态系统文件.
pm2 start ecosystem.config.js --only worker-app 只启动其中名为worker-app的程序
pm2 start ecosystem.config.js --only 'api-app,worker-app' 只启动其中多个具名程序

pm2 start process.json --env production 在启动时指定使用配置中 env_production 定义的环境变量

配置: https://pm2.keymetrics.io/docs/usage/application-declaration/

pm2的集群模式提供负载均衡, 支持零停机时间重载.

pm2 start api.js -i <processes> 还有特殊值 max, -1 (CPU数量减1).

pm2 reload all 零停机时间重载

pm2 scale app +3 缩放增加3个实例
pm2 scale app 2 缩放至2个实例

由容器运行时支持的进程管理器, 与其他进程管理器有很大区别, 并且很重(运行前需要构建镜像), 但最终目的是一样的.

在多数Linux发行版中流行的init系统, 同时也是服务进程管理器, 可以在系统级或用户空间中(通过 --user选项)运行服务.
通过在 /etc/systemd/system/ 下创建 .service 文件来定义服务.
systemd提供将服务按依赖顺序启动, 在失败时自动重启, 按模板启动多个实例的功能.

systemd的命令

  • systemctl: 控制systemd的用户接口.
  • journalctl: systemd的日志管理程序, 可按多种过滤方式查询日志.
ssh-keygen

密钥对会保存在~/.ssh目录下, 默认为 id_rsaid_rsa.pub.

使用ssh-copy-id工具(通常会包含在OpenSSH软件包中).

ssh-copy-id [email protected]_host

该命令会自动将公钥保存为 .ssh/authorized_keys, 从而允许用私钥登录.

注意: 以下代码会同时禁用root帐户的密码登录.

# 登录到服务器
# 编辑SSH配置文件
sudo vim /etc/ssh/sshd_config
# 找到PasswordAuthentication行, 将其设置为 PasswordAuthentication no
# 保存并关闭文件
# 重启ssh服务
sudo systemctl restart ssh

在线生成表达式: https://crontab.guru/

# 按照日期格式创建新的备份
0 0 * * * tar -zcf /backup/data$(date '+\%y-\%m-\%d').tar.gz /data
# 删除旧的备份文件
0 0 * * * find /backup/data* -mtime +5 -exec rm {} \;
fallocate -l 1G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
swapon -s # check swap
free -m # check swap
vim /etc/fstab
# 添加以下内容, 设置开机挂载swap文件
# /swapfile none swap sw 0 0
# 关闭swap
sudo swapoff -a
# 在/swapfile文件后追加1024MB的空间
sudo dd if=/dev/zero of=/swapfile bs=1M count=1024 oflag=append conv=notrunc
# 重新启用swap
sudo mkswap /swapfile
sudo swapon /swapfile
sysctl vm.swappiness=10
sysctl vm.vfs_cache_pressure=50
vim /etc/sysctl.conf
# 在文件尾部追加以下内容
# vm.swappiness=10
# vm.vfs_cache_pressure=50

参考: https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04

swapoff -a
# 没有输出则意味着成功
swapon -s
# 注意: 彻底关闭需要修改/etc/fstab删掉swap相关的部分.
# 检查是否支持zswap
cat /boot/config-`uname -r` | grep -i zswap
vim /etc/default/grub
# 写入以下配置(如果原本就有, 则需要修改):
# GRUB_CMDLINE_LINUX_DEFAULT="quiet splash zswap.enabled=1 zswap.compressor=lz4"
# 更新grub
update-grub
# 重启系统, 检查zswap是否开启
cat /sys/module/zswap/parameters/enabled
echo 'Asia/Shanghai' > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata

Ubuntu 20.04以上应该已经默认开启了BBR.

echo 'net.core.default_qdisc=fq' >> /etc/sysctl.conf
echo 'net.ipv4.tcp_congestion_control=bbr' >> /etc/sysctl.conf
sysctl -p
sysctl net.ipv4.tcp_available_congestion_control
# 应输出net.ipv4.tcp_available_congestion_control = bbr cubic reno
lsmod | grep bbr
# 应输出bbr

/boot Linux启动时的核心文件
/sbin 系统级程序, 只有root能够使用
/bin root和普通用户都能使用的系统自带命令/程序

/root 系统管理员的主目录
/home/* 普通用户的主目录

/usr 全称为Unix system resources, 用于一般程序(类似于Windows的Program Files)

/usr/local 被用作make命令的软件安装路径, 也是最常见的软件安装路径
/opt 用于存放专有/闭源软件/外部二进制程序, 较少被使用
/var 存放经常改变的数据, 例如系统日志

系统中的POSIX共享内存段(由内存虚拟出来的tmpfs文件系统).

有以下几种用途:

  • 高性能处理临时文件.
  • 在多个程序之间共享内存, 通常是为了实现IPC.

du --max-depth 1 --human-readable | sort --human-numeric-sort

ncdu

https://github.com/Byron/dua-cli

umount /dev/sda2 # 卸载分区
fsck /dev/sda2 # 检查分区
fsck -vnf /dev/sda2
fsck -y /dev/sdb

用于修复无法在操作系统中卸载的分区, 例如已经作为 / 挂载的分区.

touch /forcefsck # 建立forcefsck文件, 让系统在启动时执行fsck
reboot

rsync根据源路径最后是否带有斜杠, 来决定具体文件使用的目标路径.

  • 不带有斜杠时, 会在目标路径下创建一个与源相同的目录.
    例如 rsync a b, 源目录 a 下的文件的目标路径将是 b/a/....
  • 带有斜杠时, 会直接使用目标路径.
    例如 rsync a/ b, 目录 a 下的文件的目标路径将是 b/....
# 这会在remote维护一个与本地data一样的/mnt/blockstorage/data, 多余的文件会被删除
rsync -azvhP --delete data remote:/mnt/blockstorage
# 这会在本地维护一个与远程/mnt/blockstorage一样的data目录, 多余的文件会被删除
rsync -azvhP --delete remote:/mnt/blockstorage data
# 如果需要在远程登录时自动包含密码, 则需要使用sshpass:
sshpass -p password rsync -azvhP --delete [email protected]:/mnt/blockstorage data

如果在使用rsync时带上 --checksum, 则会启用校验和检查.
然而, rsync的比较策略设计得特别愚蠢, 导致即使本地与远程文件大小不同, 仍然会计算文件的校验和,
这使得校验和比较的效率变得非常低下.

https://stackoverflow.com/questions/17286290/rsync-checksum-only-for-same-size-files

两个进程可以同时打开一个文件, 但会分别得到两个不同的文件描述符, 文件描述符读写的偏移值是独立的, 写入会彼此覆盖.

为了避免这种情况, 需要给文件加锁.

建议锁是由程序自行遵守锁定约定实现的锁, 如果有不遵守约定的程序, 则锁机制会被破坏.

通过创建"锁定文件"对文件/目录进行锁定的方式,
其原子性是通过创建文件(open, O_CREAT, O_EXCL)来保证的:
两个程序同时创建锁文件, 只有其中一方能够创建成功, 另一方会报错EEXIST(错误号17)失败.

锁定文件是一个空文件, 仅仅作为指示器而存在.

flock只能用于锁定整个文件, 支持共享锁和排他锁.
锁的持有者是系统级的"文件打开表", 父进程退出时, 锁会被子进程继承.
flock需要程序自主询问文件是否上锁, 锁获取支持阻塞和非阻塞两种形式.

强制锁会在系统调用层级阻止不持有锁的进程访问文件.

锁定只对文件的一部分生效, 而不是锁住整个文件, 通常用于大文件的锁定.
锁的持有者是进程, 只要进程退出, 就会释放锁.
只支持排他锁.

lockf是fctnl的封装.

一种类似管道的进程间通信(IPC)机制, 属于半双工管道.

Linux内核将所有内存都视作虚拟内存, swap和物理内存对程序来说除性能外没有区别.

一种用逻辑卷技术, 可以将多个硬盘并成一个逻辑卷, 动态调整其空间.
类似于Windows的"存储空间".

参考: https://www.xmodulo.com/use-lvm-linux.html

硬盘分区被格式化为物理卷.

物理卷的组成单元.

逻辑卷的集合.

类似于无LVM情况下的磁盘上的标准分区, 可以通过加入或移除PE来调整其大小.

fdisk /dev/sdb # 打开需要操作的硬盘
n # 新建
p # 新建主分区
# 填写分区配置
t # 改变分区类型
# 选择分区
8e # 使用LVM分区的Hex code
w # 保存修改并退出fdisk
fdisk --list # 列出分区, 刚刚创建的分区System列应该显示为Linux LVM
pvdisplay
# or
pvs
pvcreate /dev/sdbX
pvremove /dev/sdbX
vgdisplay
# or
vidisplay 卷组名
# or
vgs
vgcreate 卷组名 /dev/sdb1 /dev/sdb2 /dev/sdb3 # 将sdb1, sdb2, sdb3加入卷组

扩展卷组是当需要往已经存在的卷组里添加额外的新物理卷时进行的操作.

vgextend 卷组名 /dev/sdc1
vgremove 卷组名
lvdisplay
# or
lvdisplay /dev/卷组名/逻辑卷名
# or
lvs
lvcreate --size 容量 --name 逻辑卷名 卷组名
ummount /mnt/lvm # 卸载逻辑卷相关的文件系统
lvremove /dev/卷组名/逻辑卷名 # 删除逻辑卷
mkfs.ext4 /dev/卷组名/逻辑卷名 # 格式化为ext4文件系统
mkdir /mnt/lvm
mount /dev/卷组名/逻辑卷名 /mnt/lvm # 挂载文件系统
lvresize --size +容量 /dev/卷组名/逻辑卷名 # 设置卷的大小
resize2fs /dev/卷组名/逻辑卷名 # 更新文件系统(ext4)大小

注意, 收缩会丢失掉磁盘尾部扇区的数据.

umount /mnt/lvm # 卸载逻辑卷相关的文件系统
e2fsck -f /dev/卷组名/逻辑卷名 # 检查磁盘错误
resize2fs /dev/卷组名/逻辑卷名 新容量 # 更新文件系统(ext4)大小
lvresize --size -容量 /dev/卷组名/逻辑卷名 # 减少逻辑卷大小

快照容量指的是可容纳的差异部分的大小, 如果快照容量满了, 快照就会转入无效状态, 不能继续使用.
挂载快照后, 由于启用CoW, 逻辑卷会大量增加性能开销, 因此建议备份完快照后就卸载.

创建快照将消耗逻辑卷组的剩余空间, 因此需要预留用于创建快照的空间.

# 创建快照, 此命令后被快照的LV的变化会写入一个专门的逻辑卷里(这个逻辑卷对用户来说是透明的)
lvcreate --snapshot --name 快照名 --size 快照容量 /dev/卷组名/逻辑卷名
# 挂载快照用于备份(快照不包含新写入的内容, 除非用户自己写入内容或快照失效, 否则总是一致的)
mount /dev/卷组名/快照名 /mnt/snapshot
umount /mnt/snapshot # 卸载快照
dd if=/dev/卷组名/快照名 | gzip > snap.gz
vim /etc/lvm/lvm.conf
# 编辑 snapshot_autoextend_threshold 至小于100, 快照在容量达到此百分比阈值时扩容(100为禁用)
# 注: 自动扩容的阈值如果太高, 在出现大量差异时, 可能还未来得及扩容就失效了.
# 编辑 snapshot_autoextend_percent, 该属性决定快照每次扩容的百分比
lvextend --size +1G /dev/卷组名/快照名 # 为快照添加1G容量

这是最正统的快照恢复方法方法, 对内核版本和lvm2包的版本有要求, 但现今的所有机器都能满足.

# 首先应该卸载源逻辑卷相关的文件系统, 如果不卸载卷, 则合并会被推迟, 直到卷被卸载.
# 在被推迟的合并完成前, 之后同一个卷的快照合并请求都会失败.
unmount /mnt/lvm
lvconvert --merge /dev/卷组名/快照名
tar -czf 打包文件 /mnt/snapshot # 创建打包文件
tar -xf 打包文件 /mnt/lvm # 释放打包文件

这种方法的缺点在于会复制每一个块的位置, 速度很慢.

umount /mnt/lvm # 卸载源逻辑卷相关的文件系统
dd if=/dev/卷组名/快照名 of=/dev/卷组名/逻辑卷名
umount /mnt/snapshot # 卸载快照
lvremove /dev/卷组名/快照名 # 删除快照

现代文件系统, 支持很多先进特性, 充满优点.
一旦Btrfs被认为是稳定的, 就可以淘汰掉LVM+ext4的组合.

Btrfs可以从ext3/4就地转换而来.

仍然有一些关于Btrfs损坏/不稳定的报告, 这使得它无法在生产环境中使用.

2021年:

  • https://www.reddit.com/r/linux/comments/kieqyu/warning_linux_510_has_a_500_to_2000_btrfs/

CoW文件系统在运行虚拟机或数据库这样的高频写入应用时会产生大量碎片, 导致性能表现很差.
目前似乎只有ZFS在这方面的表现较好.

此外, 在不同的Linux内核上, Btrfs的性能表现不稳定:
Linux 5.8

  • https://www.phoronix.com/scan.php?page=article&item=linux-58-filesystems&num=2
    Linux 5.14
  • https://www.phoronix.com/scan.php?page=news_item&px=Linux-5.14-File-Systems

Linux使用 /etc/resolv.conf 配置的DNS服务器.

使用systemd的系统可能会用systemd-resolved托管 /etc/resolv.conf (system-resolved作为systemd组件是可选的),
此时相关配置需要在 /etc/systemd/resolved.conf 里编辑.

systemd-resolve --status 验证当前的配置(等价于 resolvectl status).
resolvectl query {hostname} 来查询DNS.

  • 切换DNS服务器的行为具有记忆性, 而不是严格按照顺序尝试:
    https://github.com/systemd/systemd/issues/5755
  • 与Docker不兼容:
    Docker环境在发现宿主机的 /etc/resolv.conf 使用本地DNS服务器时会自动回退到Google的DNS服务器, 这会破坏需要本地DNS服务器的用例.
    解决方法:
    弃用systemd-resolved.
    但如果DNS服务器本身就是本地的(例如IP地址是192.168.xxx.xxx), 则即使弃用systemd-resolved也没有意义.

Ubuntu在处理systemd-resolved时有一个bug,
该bug导致 /etc/resolv.conf 被错误地指向 /run/systemd/resolve/stub-resolve.conf 而不是 /run/systemd/resolve/resolve.conf.

修复: 手动将其重新指向为正确的配置文件.

glibc的getaddinfo有很奇怪的DNS查询行为:

  • 即使在不具备IPv6网络的情况下, 它也会查找IPv6的AAAA记录.
  • DNS查询会并行查找A和AAAA记录, 只有当两条记录都返回时, 才视作查询完毕.
    这种行为导致如果IPv6的AAAA记录没有及时回应, 则会造成5秒左右的延迟.
    从而使得错误的DNS服务器实现或防火墙配置会拖累整个操作系统的网络访问速度.
    可以在 /etc/resolv.conf 里用 options single-request-reopen 设置只允许单个解析请求来解决此问题,
    但真正的解决方案应该是修复DNS服务器或防火墙中的错误.