3
3
2019
14

使用 cgroups net_cls 来让 docker 走代理

我这里 docker hub 连不上或者连上了访问很慢,根本没法用。本来我常规代理的办法,要么是 proxychains,要么是用 iptables 代理特定的 IP 段。至于 docker 嘛,亚马逊的 IP 段那么多,它用到的域名我也不是很清楚,一点点加好麻烦。作为系统服务,用 proxychains 不仅得修改 systemd 服务配置,而且不知道会不会出什么幺蛾子。最近刚好在某个地方看到这一手,就试试啰。

其实用法很简单的。去 /sys/fs/cgroup/net_cls 下建立个目录,往 net_cls.classid 里写一个整数(支持十六进制的 0x 表示法),然后把 dockerd 的 pid 写到 cgroup.procs 里去。最后用 iptables 代理这部分流量即可。现在都用 443 端口啦,所以只要代理它便好,也避免影响了别的东西:

iptables -t nat -A OUTPUT -p tcp --dport 443 -m cgroup --cgroup 0x110001 -j REDIRECT --to-ports XXX

XXX 是 ss-redir 的端口啦。

注意不要把进程的 pid 往 tasks 文件里写。那里得写的是 task 的 id 而不是 process 的 id,也就是说(用内核的术语来说)是线程的 pid 而不是进程的 tgid(thread group id)。所以非要写 tasks 文件的话,得把 docker 所有的线程的 pid 都写进去才行。真是混乱呢……画个表格好了:

用户态 内核 相关系统调用
pid tgid getpid, kill
tid pid gettid, tgkill
process task group fork, clone without CLONE_THREAD
thread task clone with CLONE_THREAD

另外如果更新过内核的话,那句 iptables 有可能会找不到模块的。(所以更新内核之后还是重启一下以避免尴尬吧。)

Category: shell | Tags: iptables linux cgroups 网络 代理
3
2
2019
2

使用 cgroups 限制指定进程的内存使用

最近我的系统有这么个问题:在备份或者系统更新等高 I/O 负载的时候,系统整体性能下降严重,界面经常卡到动不了。经过分析发现此时比平常多了许多磁盘读操作。平常的时候磁盘读操作是很少的,会有大量的缓存命中,反倒是写操作一直都有(因为我本地搭了个监控系统)。啊对,分析用到的磁盘性能数据就是来自于这个监控系统。

所以原因很清楚了:备份和系统更新不仅造成了大量缓存未命中,还占用了本来存放着热数据的缓存,导致常规使用的缓存命中率也急速下降,结果我的机械硬盘就忙不过来了。

那么,要是能够限制这些操作占用的缓存,性能不就能好一点吗?那些新读进来的数据反正是短期内再也用不到了,缓存起来也只是浪费有限的内存空间啊。

研究了一下 /sys/fs/cgroup/memory/memory.stat,看起来 cgroups 内存限制是包含缓存部分的,于是就试试呗。正好 systemd 直接就能设置了:

$ sudo systemd-run -p MemoryMax=512M --scope pacman -Syu

本来我是设置的 256M 限制,结果发现 dkms 编译内核模块的时候超级慢,还用掉了不少 swap……于是分了 512M。效果还是不错的,常规操作偶尔还是卡一卡(毕竟还是有一些 I/O 操作),但比起不限制的时候要少很多。

要注意一点的是,不使用 cgroups v2 的话(Arch Linux 默认),这个命令不能加 --user 以在用户级的 systemd 下跑的。而使用 cgroups v2 的话,lxc 和 docker 都跑不了……

备份也是类似的,而且因为 rsync 自己用不到多少内存,这个效果更好:

$ systemd-run -p MemoryMax=256M --scope ./backup-my-system

终于又一次在半小时内完成了备份 QAQ 之前动不动就一两小时的。

我也不知道为什么这个问题近期才出现,总之现在是缓解了。(接下来有空继续计划换 SSD 硬盘的事情~

Category: Linux | Tags: systemd linux cgroups
2
16
2019
4

在 Linux 下整理磁盘碎片

磁盘碎片其实有两种:文件碎了,和空闲空间碎了。使用 FIEMAP 命令可以获取到文件在磁盘(的逻辑地址上)的分布情况。也是 filefrag -v 命令输出的东西。比如我的 pacman.log 就很碎:

Filesystem type is: 58465342
File size of /var/log/pacman.log is 11052443 (2699 blocks of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..    2015:  170210423.. 170212438:   2016:
   1:     2016..    2017:  170567879.. 170567880:      2:  170212439:
   2:     2018..    2027:  170569969.. 170569978:     10:  170567881:
   3:     2028..    2030:  170574582.. 170574584:      3:  170569979:
   4:     2031..    2031:  170574631.. 170574631:      1:  170574585:
   5:     2032..    2033:  170592662.. 170592663:      2:  170574632:
....
 123:     2683..    2687:   56903805..  56903809:      5:   56906403:
 124:     2688..    2698:   56903011..  56903021:     11:   56903810: last,eof
/var/log/pacman.log: 125 extents found

整理的办法也很简单,复制一下,基本上就好了。只要剩余空间足够,小文件会变成一整块,大文件也是少数几块。如果非要弄一整块大的,比如我存放 pacman 数据库的那个小文件系统,可以用 fallocate -l 200M pacman.fs2 这样子的命令分配空间,然后把数据 dd 进去(cp 不行,因为它会先截断文件再写入,之前分配的空间就释放掉了)。

介绍完毕,重点来了:怎么找到那些被写得很碎很碎的文件呢?

对每个文件调用 filefrag 肯定太慢了,所以我写了个库和工具 fiemap-rs 直接调用 FIEMAP。它提供两个工具。一个是 fraghist,统计碎片数量分布直方图,用来了解一下某群文件有多碎。另一个是 fragmorethan,用来寻找碎到一定程度的文件。运行起来是这样子的:

/var/log:
# Number of samples = 712
# Min = 1
# Max = 297
#
# Mean = 11.338483146067423
# Standard deviation = 40.138129228003045
# Variance = 1611.0694179238724
#
# Each ∎ is a count of 13
#
  1 ..  31 [ 658 ]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
 31 ..  61 [  11 ]:
 61 ..  91 [   9 ]:
 91 .. 121 [  10 ]:
121 .. 151 [   6 ]:
151 .. 181 [   5 ]:
181 .. 211 [   3 ]:
211 .. 241 [   2 ]:
241 .. 271 [   3 ]:
271 .. 301 [   5 ]:
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 271
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 277
/var/log/journal/00000000000000000000000000000000/system.journal: 274
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 297
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 274

我系统上最碎的两群文件是 journal 日志和 python2-carbon 的数据文件。carbon 优化做得挺不好的,明明是预分配的固定大小文件啊,不知道怎么的就弄得很碎了。部分程序的日志(如 pacman、getmail)和火狐的 SQLite 数据库也挺碎的。后边这些我已经处理掉了所以示例输出只好用 journal 的啦。

找到想要整理的过碎的文件之后,复制一下就好啦:

for f in $(<list); do sudo cp -a $f $f.new; sudo mv $f.new $f; done

啊对了,工具的编译方法是,获取源码并安装 Rust 之后,在项目根目录里 cargo build --release 然后就可以在 target/release 下找到新鲜的可执行文件了~顺便说一下,这东西是支持 Android 的哦。

Category: Linux | Tags: linux 文件系统 Rust
2
7
2019
9

docker 里几个基本概念的简单类比

首先说明一下,这是一位 docker 新手对于 docker 的粗浅理解。如有不对还请谅解。我很早之前就尝试过使用 docker,然而由于术语的差异,导致我每次运行东西时都傻乎乎地创建了一个新的容器……现在感觉用法终于是弄对了,所以整理一下,将其类比到 Linux 上的普通软件的概念上。

image
相当于软件分发中的软件(安装)包
Dockerfile
跟 PKGBUILD 类似,是用于制作一个 image 的打包脚本。用 docker build -t name:tag . 就可以制作。
container(容器)
一个容器就像是一个安装好了的软件包。该软件已经准备好,随时可以运行了。
docker run
安装」指定的 image。也就是从 image 制作出容器来,顺带着进行首次运行。如果反复使用,会把同一个软件给安装多次。
docker start
就像是「运行」一个已经安装好的软件,容器跑起来了。之前容器的状态(文件的修改)也会生效。
docker ps
列出运行中或者已安装(带 -a 参数)的软件们。前者和 UNIX 命令 ps 类似,后者则没什么相似之处了。
docker exec
在正在运行的软件的环境内执行命令。有点类似于 ssh。
repository
跟 Linux 的包含众多软件的软件源并不一样。这个东西跟软件名类似,用于标识为特定功能的 image 集。发布出来的 repository 名的格式通常是 `owner/name`,跟 GitHub 差不多的。
tag
软件的版本,跟什么 lite、pro、beta 之类区分类似。它并不是用于分类的标签,也不是 git 中对于指定版本的不变的称呼。它更像是 git 的分支在某些情况下的作用,比如 latest tag 就跟 git 仓库的 master 分支一样,总是指向最新的版本。

我经过以上这样的映射之后,docker 理解起来就容易多了,行为也更符合预期。

Category: docker | Tags: linux docker
1
2
2019
2

解析 zxinc IPv6 数据库

很久以前,我就开始使用纯真 IP 数据库来进行简单的 IP 归属查询。离线数据库的好处在于快速、可依赖,我可以使用 ipmarkup 程序来快速地给文本中的所有 IP 地址注解。

然后随着 IPv6 的普及,我越来越多地需要查询 IPv6 地址了。每次 whois 或者找 ipip.net 实在是太没效率了,还暴露隐私。于是看到这个IP地址查询网站提供离线数据库下载之后,就也做了一个和 LinuxToy 介绍的这个程序类似的工具。

数据格式部分参考《纯真IP数据库格式详解》以及该网站自带的简略说明。

ipdb 程序库(带命令行)使用截图:

ipdb 命令行使用截图

用于在文本中标注 IP 的 ipmarkup 工具链接开头给了。另外有一个 cip 可以根据情况调用 ipdb 或者 QQWry。

ipdb 解析部分只依赖 Python 3.5+,下载及更新部分会依赖我同一仓库中的 myutils 库。

顺便我也优化了一下 QQWry 模块:一是没必要保留一个低效率的版本,二是既然用 mmap 了,就去掉了所有的 seek 操作,目前大概是线程安全的了。这种跳转来跳转去的二进制格式,就算不用 mmap,也可以用 pread 来操作,免得要维护个当前文件位置,还线程不安全。

Category: python | Tags: python IP地址
12
25
2018
7

Ant Design 彩蛋事件之我见

事情是这样的:一个叫「Ant Design」的大概有挺多人用的前端框架加入了一个彩蛋,在12月25日这天会更改按钮样式并修改提示文字为无意义的消息。

由于使用广泛,此事搞得比较严重,听说有程序员因此被开除,还有不少老板在跟甲方解释。事发之后,作者发布了道歉,并称「这完全是我个人的一意孤行且愚蠢的决定」。

确实很愚蠢,因为当事人似乎还没有意识到自己为什么「愚蠢」。在程序库中加入未预期的行为,是十分不负责任的表现。

库应当提供机制而非策略,并且具有良好定义的行为。软件中彩蛋这种东西由来已久,为什么这次影响这么大呢?其根本原因不在于它是开源软件,也不在于使用广泛,而是在于——它是库。库能不能提供彩蛋呢?是可以的,只要它是以 opt-in 的形式提供的,并且有文档明确其行为,使用方需要显式启用就没有任何问题。库的作者不需要知道圣诞节还可能在1月,也不需要知道代码是运行在哪个国家,他的职责应当是提供清晰明确的行为,而不是某天给你耍个花招。只有最终面向用户的应用才知道什么样的彩蛋对于它的用户是合适的,所以决定权在于应用。

至于那些在 issue 下边滥骂的人,真是给中国人丢脸唉。

PS: 关于「洋节」,shell909090 有篇文章《关于抵制洋节》。关于阿里巴巴,shell909090 还有篇《最近的阿里月饼事件》。

PPS: 每当国外发生这种令程序员们关注的事件的时候,Internet ArchiveArchive.is 上都有会很多的存档来保留历史,而国内发生这种事件,却并没有多少人去存档。大概有保存意思的人们也只是自私地本地截图保存了吧。

11
24
2018
8

通过 Cloudflare DNS 验证来申请 Let's Encrypt 证书

我本地的 MediaWiki 的证书过期啦,干脆申请个免费证书好了。之所以用 HTTPS,是因为 MediaWiki 不喜欢不加密的 HTTP,会登录不了……

在网上寻找时,发现 certbot 就有 Cloudflare 的插件呢!这下就方便了。首先 pacman -S certbot-dns-cloudflare 装上,然后把自己的 Cloudflare 凭证信息写到一个 ini 文件里:

dns_cloudflare_email = cloudflare@example.com
dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567

就这么两行。保存好,设置好权限,然后就可以申请证书了:

sudo certbot certonly -d 域名列表 --dns-cloudflare --dns-cloudflare-credentials 凭证ini文件路径

稍微回答几个问题,证书就在 /etc/letsencrypt/live 下准备好啦。在 nginx 里配置一下就好了。

然后还要加个 cron 任务来更新证书(dcron 格式):

@weekly         ID=cert-renew   certbot renew -q

这样就大功告成了。

Category: 网络 | Tags: HTTPS Cloudflare TLS
11
19
2018
2

正确地上传至 PyPI 并展示文档

数年过去了,今天经过多次尝试和询问,我终于成功让 nvchecker 的文档以富文件的形式展示在 PyPI 上了!

正确地打包上传命令是这样的:

rm -rf dist && python setup.py sdist && twine check dist/* && twine upload -s dist/*

如果 twine check 不通过,那么修正后重新执行。如果文档包含错误,PyPI 会以纯文本形式展示。

  • 听说要设置 long_description_content_type。好像 Markdown 才要设置的,rst 不用。
  • 听说不支持 # 开头的链接。实际上现在已经支持了。
  • 听说要用 twine,那就用吧。

原来我之前的文档一直没有正确地渲染,是因为有一个标题的「-」少了两个……

关于 PyPI 的资料是挺多的,但是细节不够详细,陈旧信息很多,唉。

Category: python | Tags: python
11
8
2018
8

与 Android 进行 WLAN Direct 连接

首先 iw list 看是否支持。如果支持,那就

iw dev wlan0 interface add p2p0 type __p2pdev

这样其实并不会多出一个叫 p2p0 的网络接口。iw dev 能看到多了个「Unnamed/non-netdev」设备。不执行这个也可以连接上 WLAN Direct,但是当前的 managed Wi-Fi 连接会断掉。执行之后再连接,managed 连接会持续,iw dev 里会有两个 Unnamed,不知道何故。另外这个 type __p2pdev 加上去了我就不知道怎么删除了。试了几个命令,结果搞得内核 oops 了……

然后是 wpa_supplicant 配置文件:

ctrl_interface=/run/wpa_supplicant_p2p
ap_scan=1

device_name=起一个名字
device_type=1-0050F204-1

driver_param=use_p2p_group_interface=1

wpa_supplicant 跑起来。注意这里的接口名还是那个 managed 接口的。

wpa_supplicant -i wlan0 -c p2p_config.conf

然后 wpa_cli 连过去操作:

wpa_cli -p /run/wpa_supplicant_p2p

首先用 p2p_find 开启搜索。这时候对端设备能够看到自己了。使用 p2p_connect 对端MAC pbc go_intent=0 连接,在对端接受连接即可。go_intent=0 是让对方作为 group owner,这样对端 Android 才会提供 DHCP 服务(否则要本地提供了)。

然后就可以给自己添加 IP 地址了。此时是可以用 dhcpcd 的,然而直接跑的话它会抢走默认路由,所以知道地址范围之后手动加一个好了:

ip a add 192.168.49.22/24 dev p2p-wlan0-1

Android 设备的地址是 192.168.49.1。

之后就可以用 adb connect 然后 scrcpy 了。

PS: Android 很喜欢四十几的 IP 段呢。USB 网络共享是 192.168.42.129/24,Wi-Fi 网络共享是 192.168.43.1/24,而 WLAN Direct 是 192.168.49.1/24。不知道蓝牙网络共享是多少呢。

PPS: scrcpy 在我的 XZ2C 上运行完美,但是在 MIUI 10 上需要去开发者选项里开启选项「USB 调试(安全设置)」,否则会是「只读模式」,只能看,所有交互操作无效。

参考资料

Category: 网络 | Tags: linux 网络 Android
10
11
2018
19

获得高精度环形镜子一枚

如图:

高精度环形镜子

这面镜子上边有好几百G数据呢,然而早已读不出来了。

镜子来源于九年前的西数移动硬盘。因为太老了所以坏掉了吧,反正是识别不了了。最近看到 YouTube 上有人各种折腾硬盘,群里又有人拆了好几面镜子出来,所以我把它找出来拆啦。这镜子比我那总也擦不干净的丽塔芙镜子清晰多了呢,就是小了点儿。

拆起来也不难。首先想办法把壳弄开。然后见着螺丝就旋下来,见着贴纸就撕掉。那覆盖一个螺丝孔的小圆片不用撕下来,直接螺丝刀往中间捅,然后把螺丝旋下来就好了。

HDD 拆解中

覆盖磁头的那块金属有两个螺丝,其中一个在洞里。它上边有块形状奇怪的磁铁,照片里吸了很多螺丝的就是了。磁头是直接翻个面,让重力把它拉下来就可以了,我之前以为还有螺丝之类的还弄了好久呢,结果手一翻它自己掉下来了。马达中间那个螺丝很不容易取下,因为整体会跟着转。我是用剪刀卡住它的两个洞才给旋下来的。剩下的手拆就可以了。

HDD 拆解完毕

Category: 硬件 | Tags: 硬件 硬盘 拆解

| Theme: Aeros 2.0 by TheBuckmaker.com