2025-03-06 11:59:28
在研究 cert-manager
使用 webhook
方式调用 dnspod
使用 DNS-01
方式签发 SSL 证书遇到问题,一直得到错误:
I0306 03:48:38.870605 1 controller.go:144] "syncing item" logger="cert-manager.controller"
I0306 03:48:38.870714 1 dns.go:118] "checking DNS propagation" logger="cert-manager.controller.Check" resource_name="test1-tsh1-frytea-com-1-3300738485-2689263791" resource_namespace="default" resource_kind="Challenge" resource_version="
v1" dnsName="test1.tsh1.frytea.com" type="DNS-01" resource_name="test1-tsh1-frytea-com-1-3300738485-2689263791" resource_namespace="default" resource_kind="Challenge" resource_version="v1" domain="test1.tsh1.frytea.com" nameservers=["223.5.5.5:53","8.8.8.8:53"]
I0306 03:48:38.879628 1 wait.go:94] "Updating FQDN" logger="cert-manager.controller" resource_name="test1-tsh1-frytea-com-1-3300738485-2689263791" resource_namespace="default" resource_kind="Challenge" resource_version="v1" dnsName="test
1.tsh1.frytea.com" type="DNS-01" fqdn="_acme-challenge.test1.tsh1.frytea.com." cname="tsh1.frytea.com."
I0306 03:48:38.897174 1 wait.go:145] "Looking up TXT records" logger="cert-manager.controller" resource_name="test1-tsh1-frytea-com-1-3300738485-2689263791" resource_namespace="default" resource_kind="Challenge" resource_version="v1" dns
Name="test1.tsh1.frytea.com" type="DNS-01" fqdn="tsh1.frytea.com."
E0306 03:48:38.897227 1 sync.go:208] "propagation check failed" err="DNS record for \"test1.tsh1.frytea.com\" not yet propagated" logger="cert-manager.controller" resource_name="test1-tsh1-frytea-com-1-3300738485-2689263791" resource_nam
espace="default" resource_kind="Challenge" resource_version="v1" dnsName="test1.tsh1.frytea.com" type="DNS-01"I0306 03:48:38.897688 1 controller.go:164] "finished processing work item" logger="cert-manager.controller"
我使用了以下资源:
在相关仓库找到这些 issue
发现,只要申请证书的域名能够匹配到 CNAME 记录,就会默认跟随,找不到正确的 TXT 记录,导致认证失败。
虽然 cert-manager
提供了这个参数 cnameStrategy: None
,能够在声明 ISSUE
时使用,但是似乎大部分实现的 webhook
都没有实现这个特性:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: dnspod
spec:
acme:
email: xxxx # 在证书过期的时候,会发邮件通知
preferredChain: ""
privateKeySecretRef:
name: example-com-letsencrypt-dev-key # 用于存储ACME帐户私钥的密钥名称
server: https://acme-staging-v02.api.letsencrypt.org/directory
#server: https://acme-v02.api.letsencrypt.org/directory # 生产
solvers:
- dns01:
cnameStrategy: None
webhook:
config:
secretId: xxxxxx
secretKeyRef:
key: secret-key
name: dnspod-secret
ttl: 600
groupName: acme.imroc.cc
solverName: dnspod
目前临时的解决办法,只能是 避免 cert-manager 托管域名能够解析到 CNAME 记录,等有空了研究一下 cert-manager
和 webhook
的实现方法,看能否解决这个问题。
2025-03-04 09:32:45
伪装允许只有私有 IP 地址的访客使用主机 IP 地址来访问网络,以处理传出流量。每个传出数据包都会被重写 iptables ,使其看起来来自主机,响应也会相应地被重写以路由到原始发件人。
auto lo
iface lo inet loopback
auto eno1
#real IP address
iface eno1 inet static
address 198.51.100.5/24
gateway 198.51.100.1
auto vmbr1
#private sub network
iface vmbr0 inet static
address 10.10.10.1/24
bridge-ports none
bridge-stp off
bridge-fd 0
post-up echo 1 > /proc/sys/net/ipv4/ip_forward
post-up iptables -t nat -A POSTROUTING -s '10.10.10.0/24' -o eno1 -j MASQUERADE
post-down iptables -t nat -D POSTROUTING -s '10.10.10.0/24' -o eno1 -j MASQUERADE
post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
其中 vmbr1
为 NAT 网桥,网桥 IP 为 10.10.10.0/24
,该网段流量会被转换为 eno1
网卡的 IP 发出,并在收到回复保温时转换为原始 IP,实现共享外部 IP 的目标。
重载网络配置使其生效:
ifreload -a
实测立刻可以生效,VM 中配置该段 IP 并将 10.10.10.1
作为网关即可,也可根据需要配置 dnsmasq
之类的实现 DHCP 自动分配 IP。
2025-02-27 23:36:16
docker-volume-dump.sh
#!/usr/bin/env bash
dump_dir=~/docker-volume-dump
if [ ! -d $dump_dir ]; then
mkdir -p $dump_dir
fi
for volume in $(docker volume ls -q); do
dump_file=$dump_dir/$volume.tar.gz
if [ -f $dump_file ]; then
( set -x; echo rm $dump_file; )
fi
echo "Dump docker volume \"$volume\" to \"$dump_file"\"
docker run --rm -v $volume:/from alpine sh -c "cd /from; tar -cf - ." | gzip > $dump_dir/$volume.tar.gz
done
docker-volume-restore.sh
#!/usr/bin/env bash
dump_dir=~/docker-volume-dump
for file in ~/docker-volume-dump/* ; do
volume=$(basename $file)
volume=${volume%%.*}
echo "$volume"
docker volume inspect $volume &>/dev/null
if [ $? -eq 0 ]; then
( set -x; docker volume rm $volume 1>/dev/null )
fi
( set -x; docker volume create $volume 1>/dev/null )
cat $file | docker run --rm -i -v $volume:/to alpine sh -c 'tar zxvf - -C /to'
done
2025-02-27 23:35:55
来源: ArchLinux 休眠到交换文件
Linux 使用交换分区来休眠,首先冻结所有进程并申请足够的交换内存(位于磁盘),把当前内存都存进去。 然后下次启动时,initramfs 会直接加载上次休眠时的内存状态,跳过内核的 init 过程。 因此首先需要有足够大的交换分区或交换文件;再把内核指向到休眠的分区上;最后再配置 initramfs 让它加载休眠的内存文件。 官方文档请参考 Power_management/Suspend_and_hibernate#Hibernation, 本文细述如何休眠到交换文件,并对其中一些概念和细节进行了解释。
在本文讨论的范围内, 挂起(suspend)是指冻结当前的进程,保留它们的内存,并把几乎除了内存之外的设备都断电。 休眠(hibernate)是指把挂起后的内存写入磁盘并完全关机。 锁定(lock)则只是显示一个模态的全屏软件输入正确的密码才能退出。
在 安装系统 前需要创建交换分区,现在的机器普遍内存较大不太需要交换分区来扩展内存空间, 而且磁盘一般使用读写快速但读写次数有限 SSD,因此我的交换分区也很小根本不够用来休眠。
你可以通过 swapiniss 来让你的交换分区只用于休眠。
所以我们用交换文件来替代交换分区,在创建交换文件之前首先需要知道系统休眠需要多大空间。 可以从 sysfs 来看查看:
cat /sys/power/image_size
可以参考 官方教程 来创建:
按照你需要的大小创建,bs * count 是最终文件大小
dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress
chmod 600 /swapfile
# 检查大小和权限
ls -l /swapfile
# 初始化交换文件并立即应用到系统
mkswap /swapfile
swapon /swapfile
# 在 /etc/fstab 中写入以下内容,交换文件会在重启后生效
/swapfile none swap defaults 0 0
重启后通过 swapon -s
来检查是否生效:
> sudo swapon -s
Filename Type Size Used Priority
/swapfile file 12582908 0 -2
我们需要设置 resume 和 resume_offset 两个内核参数,告诉内核在挂起时把内存写入到哪里。
filefrag -v /swapfile | awk '{ if($1=="0:"){print $4} }'
命令得到。可以通过 cat /proc/cmdline
来查看当前的内核参数,但是在哪里设置取决于你的 Boot Loader。 以 rEFInd 为例,打开 /boot/refind_linux.conf 写入 resume 和 resume_offset:
"Boot with standard options" "ro root=/dev/sda3 resume=/dev/sda3 resume_offset=3192832"
重启后用 journalctl
或 dmesg
来找到写入休眠镜像的日志:
Oct 19 13:57:56 harttle.arch.mac kernal: PM: Creating hibernation image:
Oct 19 13:57:56 harttle.arch.mac kernel: PM: Need to copy 596422 pages
Oct 19 13:57:56 harttle.arch.mac kernel: PM: Normal pages needed: 596422 + 1024, available pages: 1477067
如果看到这样的错误说明设置有误,请检查你交换文件所在分区和 filefrag 给出的偏移量:
Oct 19 13:57:56 harttle.arch.mac kernal: PM: Image not found (code -22)
initramfs 是由 Boot Loader 直接加载的一个早期的用户空间,其中已经加载了一些内核模块。 它会进行设备初始化、挂载文件系统、运行磁盘检查等工作,之后再交给内核的 init 过程。 加载休眠的内存也是它的工作,但 ArchLinux 默认并未开启,需要去 /etc/mkinitcpio.conf 中添加 resume 钩子:
HOOKS=(base udev resume autodetect modconf block filesystems keyboard fsck)
注意因为 resume 参数用到了磁盘设备名称,resume 需要写在 udev 之后。 然后重新编译 initramfs(就像更新内核时一样):
默认使用当前系统的内核,如果你现在位于启动盘的系统则需要指定宿主环境上的内核版本。
mkinitcpio -p linux
至此配置工作都完成了,通过 systemctl hibernate
来休眠,再按下电源键开机来检查休眠功能是否正常。
MacBook Pro 的显示器盖子开着默认会阻止挂起,可能会出现挂起屏幕变黑后立即结束挂起。 这是因为显示器盖子默认可以唤醒休眠,在 /proc/acpi/wakeup 中可以查看哪些设备可以唤醒,其中 LID0 是显示器盖子:
> cat /proc/acpi/wakeup
Device S-state Status Sysfs node
P0P2 S3 *disabled
EC S4 *disabled platform:PNP0C09:00
HDEF S3 *disabled pci:0000:00:1b.0
RP01 S3 *enabled pci:0000:00:1c.0
RP02 S3 *enabled pci:0000:00:1c.1
RP03 S3 *enabled pci:0000:00:1c.2
ARPT S4 *disabled pci:0000:03:00.0
RP05 S3 *enabled pci:0000:00:1c.4
RP06 S3 *enabled pci:0000:00:1c.5
XHC1 S3 *enabled pci:0000:00:14.0
ADP1 S4 *disabled platform:ACPI0003:00
LID0 S4 *enabled platform:PNP0C0D:00
我们可以 echo LID0 > /proc/acpi/wakeup
来更改它的状态,然后再试休眠。 我们希望它的状态默认就是 disabled,需要一个这样的 systemd 服务:
[Unit]
Description=Disable LID0 wakeup triggers in /proc/acpi/wakeup
[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo LID0 > /proc/acpi/wakeup"
ExecStop=/bin/sh -c "echo LID0 > /proc/acpi/wakeup"
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
把它添加到 /etc/systemd/system 并启用即可。
自动休眠和其他电源管理功能,由很多不同的软件和配置方式来实现。为避免混淆先介绍几个常见的软件:
systemd-logind:ArchLinux 的默认安装包含了 systemd,其中的 systemd-logind 是自动启用的。 它包含了一些非常简单的电源管理功能,比如按下电源按钮时关机、笔记本合上盖子时挂起。 因此 ArchLinux 装好之后就基本可以用了。
acpid:acpid 是一个比较基础的电源管理工具,工作方式是响应 ACPI 事件,做相应的处理比如关机还是休眠。注意 acpid 只是电源管理工具,ACPI 是设备配置接口跟它没关系。
dpms:dpms 是 xorg 提供的显示器电源管理服务,用来控制显示器关闭等动作。有 standby, suspend, off 等阶段,跟 systemd 事件一样需要有人来订阅(比如 xss-lock)才能执行具体操作。可以通过 xorg.conf 的 StandbyTime
, SuspendTime
, OffTime
等来配置,也可以在运行时用 xset s
来配置。
tlp:tlp 是一个比较无脑的电源管理工具,提供类似电池模式、电源模式、性能优先这样级别的配置。
xss-lock, xidle, xautolock:这些是 X11 下的工具用来在用户无操作时执行挂起等操作,有些还会监听 ACPI 事件,这样在 suspend 时屏幕也能锁定。
自动休眠和自动挂起需要桌面环境(DE)或者 X11 软件的支持。 如果你在用 Gnome 或 KDE 在控制面板中配置后,会把 idle 信息报告给 systemd-logind,后者接管具体操作。
如果你像 Harttle 一样没有桌面系统和登录管理器的话, 需要安装一个类似 xss-lock, xidle 这样的工具来靠 X11 事件计时, 然后调用 systemctl hibernate
或 systemctl syspend
。
合上盖子后 systemd-logind 的默认行为是挂起,可以在 /etc/systemd/logind.conf
中把它重新设置为休眠,或先挂起再休眠:
HandleLidSwitch=suspend-then-hibernate
挂起后休眠前的时间可以在 /etc/systemd/sleep.conf 中设置:
HibernateDelaySec=15min
除了合上盖子之外,其他场景也可以直接调用 systemctl suspend-then-hibernate
。
这件事情需要具体的软件来做,或者直接安装 tlp 并启动 tlp, tlp-sleep 两个 systemd 服务。 下面提供一个简单的 udev 规则,在电量小于等于 5% 时休眠:
SUBSYSTEM=="power_supply", ATTR{status}=="Discharging", ATTR{capacity}=="[0-5]", RUN+="/usr/bin/systemctl hibernate"
把它写入 /etc/udev/rules.d/99-lowbat.rules,重启即可生效。
2025-02-27 23:35:46
来源: Vim 下大小写敏感的搜索/替换
Vim 中的搜索默认是大小写敏感的,即搜索 vim
不会匹配到 Vim
。 这一点跟多数编辑器/IDE 都不同,因此 Vim 的默认设置其实很不顺手。 本文来分享一些个性化的配置方法,让 Vim 下的大小写敏感/不敏感用起来更加顺手。 比如当搜索词包含大写时应用大小写敏感搜索;其他情况应用大小写不敏感搜索。
以搜索词为 harttle 为例(省略了最后的回车):
/harttle\c
/harttle\C
s/harttle\c/Harttle
s/harttle\C/Harttle
:set ignorecase
:set noignorecase
:set smartcase
:set nosmartcase
正如在正则表达式有类似 i
这样的开关,Vim 也有特殊字符来控制大小写敏感。 在模式末尾加 \c
表示大小写不敏感,加 \C
表示大小写敏感。 例如:
" 大小写不敏感搜索,可以匹配:vim, Vim, VIM
/vim\c<CR>
" 大小写敏感搜索,只可以匹配:Vim
/Vim\C<CR>
" 把出现的所有 vim, Vim, VIM 等都替换为 Vim,在写文章时会经常会用到
:%s/vim\c/Vim/g
这一语法的优先级高于下文的 ignorecase
, smartcase
等选项, 所以比较万能,在远程机器上、别人的电脑上,一般用这个操作。
Vim 中的 ignorecase
用于设置大小写敏感,它将在所有搜索、替换命令中生效。 在 normal 模式中 :set ignorecase
设置为不敏感;:set noignorecase
设置为敏感。 ignorecase
属于选项变量,因此也可以通过 &
来设置,例如::let &ignorecase=1
。 把冒号去掉后可以直接放到 .vimrc 文件里持久生效。
更多 Vim 变量赋值和引用的细节,可参考这篇文章:Vim 中的变量赋值、引用与作用域。
开启 ignorecase
之后还可以把 smartcase
也打开(后者要求前者出于开启状态), Vim 会启用智能模式:
例如:
:set ignorecase
:set smartcase
" 大小写不敏感,可以匹配:vim, Vim, VIM
/vim<CR>
" 大小写敏感,只可以匹配 Vim
/Vim<CR>"
smartcase
只对输入的模式(pattern)生效,其他不需要输入 pattern 的搜索命令不生效。 比如 在 Vim 中优雅地查找和替换 中介绍过可以用 *
(向后),#
(向前),g*
(不切词)等命令来搜索光标所在的词搜索光标所在的词。 为了让它们好使,可以先按下 *
来搜索一次,然后按下 /
(向后)再按上箭头找到上次历史(这是一个具体的 pattern)再按回车搜索。同样地,按 ?
(向前)也可以。
“展开光标所在词”是存在 Vim 命令的,因此我们可以把 *
, #
映射掉来自动化上面的过程:
" respect to smartcase, expand the pattern
:nnoremap * /\<<C-R>=expand('<cword>')<CR>\><CR>
:nnoremap # ?\<<C-R>=expand('<cword>')<CR>\><CR>
这样下次按下 *
或 #
时,Vim 就会展开光标处的词,分别应用 /
或 ?
进行搜索。 这样当光标处的词有大写时就用大小写敏感搜索,全小写时就用大小写不敏感搜索。 <C-R>=
用来插入计算表达式并插入到命令里,类似我们在 使用 Vim 寄存器 中介绍的 <C-R>"
可以把匿名寄存器(上次拷贝、剪切、删除)的内容插入到命令里。
2025-02-27 23:35:38
来源: 对 tail -f 使用管道
最近发现 tail -f
时管道后面的程序都会被卡住,才发现 grep,sed,awk 不直接输出到 TTY 时都是带缓冲的。平时跟在 cat
后使用没问题是因为输入管道关闭触发了 flush。本文详细解释其中的坑,以及怎么让 sed, awk, grep 立即 flush。
TL;DR:grep 添加 --line-buffered
,sed 添加 -u
,awk 调 fflush()
。
管道 是 Linux/Unix 中进程间通信的一种方式,可以在命令间、进程间传递数据。比如下面的命令用来来打印所有文件不存在的异常。
cat log.txt | grep Error | grep ENOENT
由于 cat
命令会在读完文件后立即退出并关闭 STDOUT,grep 的缓冲会立即 flush,我们会在执行完上述命令后立即看到输出。但如果改成实时打印日志的 tail -f
则会看不到任何输出:
tail -f log.txt | grep Error | grep ENOENT
因为当 grep 的输出不是 TTY(终端) 时,会启用缓冲。输入关闭或缓冲区满时才输出。这个例子中第一个 grep 的输入 tail -f
一直没有关闭,因此缓冲一直不会输出,第二个 grep 也永远不会收到输入。 因此控制台不会有任何输出。
但如果反过来,grep 的输出是 TTY 时就不会缓冲。也就是说 tail -f log.txt | grep Error
(注意少了一个 grep)会正常地持续地输出。
那么 grep 会检查它输出到哪里?虽然理论上有悖于管道的设计,也不那么函数式。 难以想象我们有个函数,它的返回值竟然会取决于这个返回值下一步被用于做什么操作。 不仅是 grep,sed 也有类似的行为,这里不去更多地讨论设计,而是给几个有用的场景:
那么怎么判断标准输出的文件描述符呢?
[ -t 1 ]
来判断 stdout(文件描述符 1) 是否是 TTY。process.stdout.isTTY
来判断是否是 TTY。注意 [
是一个命令,-t
是它的参数,可以 man [
查看详情。
既然 tail -f
日志看不到输出是因为缓冲区没有 flush,那么缓冲区什么时候会被 flush 呢?有两种情况:
Stream.prototype.end()
调用)。但是 tail -f
的输出流永远不会结束,因为 -f
会永远 follow 文件 append。作为对比,cat 命令的输出流会在读到文件尾时结束。比如执行 cat log.txt | grep Error
会立即 flush 并退出。那么 grep 的缓冲区是多大呢?既然 tail 的输出不足以填满缓冲区,我们用输出足够多的 yes 命令:
yes Error ENOENT | grep Error | grep ENOENT
yes 命令用来不断地循环(死循环,直到被 Ctrl-C
)输出它的参数,因此缓冲很快会满。果然上面的命令我们可以看到大量的输出。
grep 提供了 --line-buffered
来按照行缓冲,也就是每写满一行 flush 一次:
--line-buffered
Force output to be line buffered. By default, output is line buffered when standard output is
a terminal and block buffered otherwise.
sed 可以用 --unbuffered
来禁用缓冲:
-u, --unbuffered
load minimal amounts of data from the input files and flush the output buffers more often
awk 作为一门完整的编程语言,需要调用 fflush()
方法来清空缓冲:
The built-in function fflush(expr) flushes any buffered output for the file or pipe expr.
因此前面的例子中给 grep 添加 --line-buffered
即可让它持续地输出:
tail -f log.txt | grep --line-buffered Error | grep ENOENT
注意第二个 grep 不需要添加 --line-buffered
,因为它的标准输出是 TTY,默认不会启用缓冲区。 下面是一个更完整的例子,从 log.txt 文件实时读日志,过滤包含 Error 的行,把 harttle 标记去掉,打印出第一列,再过滤得到 ENOENT
的行:
tail -f log.txt | grep --line-buffered Error | sed -u 's/harttle//' | awk '${print $1; fflush()}' | grep ENOENT